Skip to content

Commit 8fdfda6

Browse files
authoredApr 2, 2024··
improve formatting of Runtime failures (#2461)
1 parent f456ba2 commit 8fdfda6

File tree

8 files changed

+109
-84
lines changed

8 files changed

+109
-84
lines changed
 

‎.changeset/little-seahorses-lie.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"effect": patch
3+
---
4+
5+
add Inspectable.toStringUnknown/stringifyCircular

‎.changeset/mean-nails-divide.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"effect": patch
3+
---
4+
5+
improve formatting of Runtime failures

‎.changeset/smooth-turtles-grin.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"effect": patch
3+
---
4+
5+
use Inspectable.toStringUnknown for absurd runtime errors

‎packages/effect/src/Inspectable.ts

+30
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,33 @@ export abstract class Class {
8282
return format(this.toJSON())
8383
}
8484
}
85+
86+
/**
87+
* @since 2.0.0
88+
*/
89+
export const toStringUnknown = (u: unknown, whitespace: number | string | undefined = 2): string => {
90+
try {
91+
return typeof u === "object" ? stringifyCircular(u, whitespace) : String(u)
92+
} catch (_) {
93+
return String(u)
94+
}
95+
}
96+
97+
/**
98+
* @since 2.0.0
99+
*/
100+
export const stringifyCircular = (obj: unknown, whitespace?: number | string | undefined): string => {
101+
let cache: Array<unknown> = []
102+
const retVal = JSON.stringify(
103+
obj,
104+
(_key, value) =>
105+
typeof value === "object" && value !== null
106+
? cache.includes(value)
107+
? undefined // circular reference
108+
: cache.push(value) && value
109+
: value,
110+
whitespace
111+
)
112+
;(cache as any) = undefined
113+
return retVal
114+
}

‎packages/effect/src/internal/fiberRuntime.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { dual, identity, pipe } from "../Function.js"
2222
import { globalValue } from "../GlobalValue.js"
2323
import * as HashMap from "../HashMap.js"
2424
import * as HashSet from "../HashSet.js"
25+
import * as Inspectable from "../Inspectable.js"
2526
import type { Logger } from "../Logger.js"
2627
import * as LogLevel from "../LogLevel.js"
2728
import type * as MetricLabel from "../MetricLabel.js"
@@ -125,7 +126,9 @@ const runtimeFiberVariance = {
125126

126127
const absurd = (_: never): never => {
127128
throw new Error(
128-
`BUG: FiberRuntime - ${JSON.stringify(_)} - please report an issue at https://github.com/Effect-TS/effect/issues`
129+
`BUG: FiberRuntime - ${
130+
Inspectable.toStringUnknown(_)
131+
} - please report an issue at https://github.com/Effect-TS/effect/issues`
129132
)
130133
}
131134

@@ -1419,7 +1422,7 @@ export const tracerLogger = globalValue(
14191422
return
14201423
}
14211424

1422-
const attributes = Object.fromEntries(HashMap.map(annotations, (value) => internalLogger.serializeUnknown(value)))
1425+
const attributes = Object.fromEntries(HashMap.map(annotations, Inspectable.toStringUnknown))
14231426
attributes["effect.fiberId"] = FiberId.threadName(fiberId)
14241427
attributes["effect.logLevel"] = logLevel.label
14251428

‎packages/effect/src/internal/logger.ts

+6-28
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { LazyArg } from "../Function.js"
22
import { constVoid, dual, pipe } from "../Function.js"
33
import * as HashMap from "../HashMap.js"
4+
import * as Inspectable from "../Inspectable.js"
45
import * as List from "../List.js"
56
import type * as Logger from "../Logger.js"
67
import type * as LogLevel from "../LogLevel.js"
@@ -167,7 +168,7 @@ export const stringLogger: Logger.Logger<unknown, string> = makeLogger<unknown,
167168
]
168169

169170
let output = outputArray.join(" ")
170-
const stringMessage = serializeUnknown(message)
171+
const stringMessage = Inspectable.toStringUnknown(message)
171172

172173
if (stringMessage.length > 0) {
173174
output = output + " message="
@@ -205,22 +206,14 @@ export const stringLogger: Logger.Logger<unknown, string> = makeLogger<unknown,
205206
}
206207
output = output + filterKeyName(key)
207208
output = output + "="
208-
output = appendQuoted(serializeUnknown(value), output)
209+
output = appendQuoted(Inspectable.toStringUnknown(value), output)
209210
}
210211
}
211212

212213
return output
213214
}
214215
)
215216

216-
export const serializeUnknown = (u: unknown): string => {
217-
try {
218-
return typeof u === "object" ? jsonStringifyCircular(u) : String(u)
219-
} catch (_) {
220-
return String(u)
221-
}
222-
}
223-
224217
/** @internal */
225218
const escapeDoubleQuotes = (str: string) => `"${str.replace(/\\([\s\S])|(")/g, "\\$1$2")}"`
226219

@@ -242,7 +235,7 @@ export const logfmtLogger = makeLogger<unknown, string>(
242235
]
243236

244237
let output = outputArray.join(" ")
245-
const stringMessage = serializeUnknown(message)
238+
const stringMessage = Inspectable.toStringUnknown(message, 0)
246239

247240
if (stringMessage.length > 0) {
248241
output = output + " message="
@@ -280,7 +273,7 @@ export const logfmtLogger = makeLogger<unknown, string>(
280273
}
281274
output = output + filterKeyName(key)
282275
output = output + "="
283-
output = appendQuotedLogfmt(serializeUnknown(value), output)
276+
output = appendQuotedLogfmt(Inspectable.toStringUnknown(value, 0), output)
284277
}
285278
}
286279

@@ -340,23 +333,8 @@ export const structuredMessage = (u: unknown): unknown => {
340333
}
341334
}
342335

343-
const jsonStringifyCircular = (obj: unknown) => {
344-
let cache: Array<unknown> = []
345-
const retVal = JSON.stringify(
346-
obj,
347-
(_key, value) =>
348-
typeof value === "object" && value !== null
349-
? cache.includes(value)
350-
? undefined // circular reference
351-
: cache.push(value) && value
352-
: value
353-
)
354-
;(cache as any) = undefined
355-
return retVal
356-
}
357-
358336
/** @internal */
359-
export const jsonLogger = map(structuredLogger, jsonStringifyCircular)
337+
export const jsonLogger = map(structuredLogger, Inspectable.stringifyCircular)
360338

361339
/** @internal */
362340
const filterKeyName = (key: string) => key.replace(/[\s="]/g, "_")

‎packages/effect/src/internal/runtime.ts

+45-52
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import * as FiberId from "../FiberId.js"
88
import type * as FiberRef from "../FiberRef.js"
99
import * as FiberRefs from "../FiberRefs.js"
1010
import { dual, pipe } from "../Function.js"
11-
import { format, NodeInspectSymbol } from "../Inspectable.js"
11+
import * as Inspectable from "../Inspectable.js"
1212
import * as Option from "../Option.js"
1313
import { pipeArguments } from "../Pipeable.js"
1414
import * as Predicate from "../Predicate.js"
@@ -131,38 +131,22 @@ export const unsafeRunSync = <R>(runtime: Runtime.Runtime<R>) => <A, E>(effect:
131131
}
132132
}
133133

134+
class AsyncFiberExceptionImpl<A, E = never> extends Error implements Runtime.AsyncFiberException<A, E> {
135+
readonly _tag = "AsyncFiberException"
136+
constructor(readonly fiber: Fiber.RuntimeFiber<A, E>) {
137+
super(
138+
`Fiber #${fiber.id().id} cannot be be resolved synchronously, this is caused by using runSync on an effect that performs async work`
139+
)
140+
this.name = this._tag
141+
this.stack = this.message
142+
}
143+
}
144+
134145
const asyncFiberException = <A, E>(fiber: Fiber.RuntimeFiber<A, E>): Runtime.AsyncFiberException<A, E> => {
135146
const limit = Error.stackTraceLimit
136147
Error.stackTraceLimit = 0
137-
const error = (new Error()) as any
148+
const error = new AsyncFiberExceptionImpl(fiber)
138149
Error.stackTraceLimit = limit
139-
const message =
140-
`Fiber #${fiber.id().id} cannot be be resolved synchronously, this is caused by using runSync on an effect that performs async work`
141-
const _tag = "AsyncFiberException"
142-
Object.defineProperties(error, {
143-
_tag: {
144-
value: _tag
145-
},
146-
fiber: {
147-
value: fiber
148-
},
149-
message: {
150-
value: message
151-
},
152-
name: {
153-
value: _tag
154-
},
155-
toString: {
156-
get() {
157-
return () => message
158-
}
159-
},
160-
[NodeInspectSymbol]: {
161-
get() {
162-
return () => message
163-
}
164-
}
165-
})
166150
return error
167151
}
168152

@@ -177,37 +161,46 @@ export const FiberFailureCauseId: Runtime.FiberFailureCauseId = Symbol.for(
177161
"effect/Runtime/FiberFailure/Cause"
178162
) as any
179163

180-
type Mutable<A> = {
181-
-readonly [k in keyof A]: A[k]
182-
}
164+
class FiberFailureImpl extends Error implements Runtime.FiberFailure {
165+
readonly [FiberFailureId]: Runtime.FiberFailureId
166+
readonly [FiberFailureCauseId]: Cause.Cause<unknown>
167+
constructor(cause: Cause.Cause<unknown>) {
168+
super()
169+
170+
this[FiberFailureId] = FiberFailureId
171+
this[FiberFailureCauseId] = cause
172+
173+
const prettyErrors = InternalCause.prettyErrors(cause)
174+
if (prettyErrors.length > 0) {
175+
const head = prettyErrors[0]
176+
this.name = head.message.split(":")[0]
177+
this.message = head.message.substring(this.name.length + 2)
178+
this.stack = InternalCause.pretty(cause)
179+
}
183180

184-
/** @internal */
185-
export const fiberFailure = <E>(cause: Cause.Cause<E>): Runtime.FiberFailure => {
186-
const limit = Error.stackTraceLimit
187-
Error.stackTraceLimit = 0
188-
const error = (new Error()) as Mutable<Runtime.FiberFailure>
189-
Error.stackTraceLimit = limit
190-
const prettyErrors = InternalCause.prettyErrors(cause)
191-
if (prettyErrors.length > 0) {
192-
const head = prettyErrors[0]
193-
error.name = head.message.split(":")[0]
194-
error.message = head.message.substring(error.name.length + 2)
195-
error.stack = InternalCause.pretty(cause)
181+
this.name = `(FiberFailure) ${this.name}`
196182
}
197-
error[FiberFailureId] = FiberFailureId
198-
error[FiberFailureCauseId] = cause
199-
error.toJSON = () => {
183+
184+
toJSON(): unknown {
200185
return {
201186
_id: "FiberFailure",
202-
cause: cause.toJSON()
187+
cause: this[FiberFailureCauseId].toJSON()
203188
}
204189
}
205-
error.toString = () => {
206-
return format(error.toJSON())
190+
toString(): string {
191+
return "(FiberFailure) " + InternalCause.pretty(this[FiberFailureCauseId])
207192
}
208-
error[NodeInspectSymbol] = () => {
209-
return error.toJSON()
193+
[Inspectable.NodeInspectSymbol](): unknown {
194+
return this.toString()
210195
}
196+
}
197+
198+
/** @internal */
199+
export const fiberFailure = <E>(cause: Cause.Cause<E>): Runtime.FiberFailure => {
200+
const limit = Error.stackTraceLimit
201+
Error.stackTraceLimit = 0
202+
const error = new FiberFailureImpl(cause)
203+
Error.stackTraceLimit = limit
211204
return error
212205
}
213206

‎packages/effect/test/Logger.test.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@ describe("stringLogger", () => {
5353
})
5454

5555
expect(result).toEqual(
56-
`timestamp=${date.toJSON()} level=INFO fiber= message="My message" imma_span__=7ms just_a_key=just_a_value good_key="I am a good value" good_bool=true I_am_bad_key_name="{\\"coolValue\\":\\"cool value\\"}" good_number=123`
56+
`timestamp=${date.toJSON()} level=INFO fiber= message="My message" imma_span__=7ms just_a_key=just_a_value good_key="I am a good value" good_bool=true I_am_bad_key_name="{
57+
\\"coolValue\\": \\"cool value\\"
58+
}" good_number=123`
5759
)
5860
})
5961

@@ -81,7 +83,11 @@ describe("stringLogger", () => {
8183

8284
expect(result).toEqual(
8385
`timestamp=${date.toJSON()} level=INFO fiber= message="My
84-
message" imma_span__=7ms I_am_also_a_bad_key_name="{\\"return\\":\\"cool\\nvalue\\"}" good_key="{\\"returnWithSpace\\":\\"cool\\nvalue or not\\"}" good_key2="I am a good value
86+
message" imma_span__=7ms I_am_also_a_bad_key_name="{
87+
\\"return\\": \\"cool\\nvalue\\"
88+
}" good_key="{
89+
\\"returnWithSpace\\": \\"cool\\nvalue or not\\"
90+
}" good_key2="I am a good value
8591
with line breaks" good_key3="I_have=a"`
8692
)
8793
})

0 commit comments

Comments
 (0)
Please sign in to comment.