From 6684b4c27d77a7fcc7af2e261a450edf971b62b5 Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 12 Jul 2024 11:12:01 +1200 Subject: [PATCH] improve safari support for Logger.pretty (#3235) --- .changeset/moody-beers-unite.md | 5 ++ .changeset/stupid-readers-retire.md | 5 ++ .changeset/ten-points-drop.md | 5 ++ packages/effect/src/Console.ts | 4 +- packages/effect/src/internal/cause.ts | 10 ++- packages/effect/src/internal/logger.ts | 114 +++++++++++++++++++++---- 6 files changed, 122 insertions(+), 21 deletions(-) create mode 100644 .changeset/moody-beers-unite.md create mode 100644 .changeset/stupid-readers-retire.md create mode 100644 .changeset/ten-points-drop.md diff --git a/.changeset/moody-beers-unite.md b/.changeset/moody-beers-unite.md new file mode 100644 index 0000000000..44b3581b4b --- /dev/null +++ b/.changeset/moody-beers-unite.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +improve safari support for Logger.pretty diff --git a/.changeset/stupid-readers-retire.md b/.changeset/stupid-readers-retire.md new file mode 100644 index 0000000000..da30372e19 --- /dev/null +++ b/.changeset/stupid-readers-retire.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +fix span stack rendering when stack function returns undefined diff --git a/.changeset/ten-points-drop.md b/.changeset/ten-points-drop.md new file mode 100644 index 0000000000..d6737344bb --- /dev/null +++ b/.changeset/ten-points-drop.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +align UnsafeConsole group types with web apis diff --git a/packages/effect/src/Console.ts b/packages/effect/src/Console.ts index 40b6d516a6..82ac78b414 100644 --- a/packages/effect/src/Console.ts +++ b/packages/effect/src/Console.ts @@ -63,8 +63,8 @@ export interface UnsafeConsole { dir(item: any, options?: any): void dirxml(...args: ReadonlyArray): void error(...args: ReadonlyArray): void - group(label?: string | undefined): void - groupCollapsed(label?: string | undefined): void + group(...args: ReadonlyArray): void + groupCollapsed(...args: ReadonlyArray): void groupEnd(): void info(...args: ReadonlyArray): void log(...args: ReadonlyArray): void diff --git a/packages/effect/src/internal/cause.ts b/packages/effect/src/internal/cause.ts index 680a19e8c4..baabaf6a9d 100644 --- a/packages/effect/src/internal/cause.ts +++ b/packages/effect/src/internal/cause.ts @@ -1100,9 +1100,13 @@ const prettyErrorStack = (message: string, stack: string, span?: Span | undefine const stackFn = spanToTrace.get(current) if (typeof stackFn === "function") { const stack = stackFn() - const locationMatch = stack.match(locationRegex) - const location = locationMatch ? locationMatch[1] : stack.replace(/^at /, "") - out.push(` at ${current.name} (${location})`) + if (typeof stack === "string") { + const locationMatch = stack.match(locationRegex) + const location = locationMatch ? locationMatch[1] : stack.replace(/^at /, "") + out.push(` at ${current.name} (${location})`) + } else { + out.push(` at ${current.name}`) + } } else { out.push(` at ${current.name}`) } diff --git a/packages/effect/src/internal/logger.ts b/packages/effect/src/internal/logger.ts index 17fbd5330a..3fc82c55d9 100644 --- a/packages/effect/src/internal/logger.ts +++ b/packages/effect/src/internal/logger.ts @@ -400,6 +400,16 @@ const logLevelColors: Record> = Error: [colors.red], Fatal: [colors.bgBrightRed, colors.black] } +const logLevelStyle: Record = { + None: "", + All: "", + Trace: "color:gray", + Debug: "color:blue", + Info: "color:green", + Warning: "color:orange", + Error: "color:red", + Fatal: "background-color:red;color:white" +} const defaultDateFormat = (date: Date): string => `${date.getHours().toString().padStart(2, "0")}:${date.getMinutes().toString().padStart(2, "0")}:${ @@ -407,8 +417,8 @@ const defaultDateFormat = (date: Date): string => }.${date.getMilliseconds().toString().padStart(3, "0")}` const processStdoutIsTTY = typeof process === "object" && "stdout" in process && process.stdout.isTTY === true -const processIsBun = typeof process === "object" && "isBun" in process && process.isBun === true const hasWindow = typeof window === "object" +const isWorker = typeof self === "object" && self.constructor && self.constructor.name.includes("Worker") /** @internal */ export const prettyLogger = (options?: { @@ -418,21 +428,31 @@ export const prettyLogger = (options?: { readonly mode?: "browser" | "tty" | "auto" | undefined }) => { const mode_ = options?.mode ?? "auto" - const mode = mode_ === "auto" ? (hasWindow ? "browser" : "tty") : mode_ + const mode = mode_ === "auto" ? (hasWindow || isWorker ? "browser" : "tty") : mode_ const isBrowser = mode === "browser" const showColors = typeof options?.colors === "boolean" ? options.colors : processStdoutIsTTY || isBrowser - const color = showColors ? withColor : withColorNoop const formatDate = options?.formatDate ?? defaultDateFormat + return isBrowser + ? prettyLoggerBrowser({ colors: showColors, formatDate }) + : prettyLoggerTty({ colors: showColors, formatDate, stderr: options?.stderr === true }) +} +const prettyLoggerTty = (options: { + readonly colors: boolean + readonly stderr: boolean + readonly formatDate: (date: Date) => string +}) => { + const processIsBun = typeof process === "object" && "isBun" in process && process.isBun === true + const color = options.colors && processStdoutIsTTY ? withColor : withColorNoop return makeLogger( ({ annotations, cause, context, date, fiberId, logLevel, message: message_, spans }) => { const services = FiberRefs.getOrDefault(context, defaultServices.currentServices) const console = Context.get(services, consoleTag).unsafe - const log = options?.stderr === true ? console.error : console.log + const log = options.stderr === true ? console.error : console.log const message = Arr.ensure(message_) - let firstLine = color(`[${formatDate(date)}]`, colors.white) + let firstLine = color(`[${options.formatDate(date)}]`, colors.white) + ` ${color(logLevel.label, ...logLevelColors[logLevel._tag])}` + ` (${_fiberId.threadName(fiberId)})` @@ -454,18 +474,11 @@ export const prettyLogger = (options?: { } } - if (isBrowser) { - console.groupCollapsed(firstLine) - } else { - log(firstLine) - if (!processIsBun) console.group() - } + log(firstLine) + if (!processIsBun) console.group() + if (!Cause.isEmpty(cause)) { - if (isBrowser) { - console.error(Cause.pretty(cause, { renderErrorCause: true })) - } else { - log(Cause.pretty(cause, { renderErrorCause: true })) - } + log(Cause.pretty(cause, { renderErrorCause: true })) } if (messageIndex < message.length) { @@ -484,3 +497,72 @@ export const prettyLogger = (options?: { } ) } + +const prettyLoggerBrowser = (options: { + readonly colors: boolean + readonly formatDate: (date: Date) => string +}) => { + const color = options.colors ? "%c" : "" + return makeLogger( + ({ annotations, cause, context, date, fiberId, logLevel, message: message_, spans }) => { + const services = FiberRefs.getOrDefault(context, defaultServices.currentServices) + const console = Context.get(services, consoleTag).unsafe + const message = Arr.ensure(message_) + + let firstLine = `${color}[${options.formatDate(date)}]` + const firstParams = [] + if (options.colors) { + firstParams.push("color:gray") + } + firstLine += ` ${color}${logLevel.label}${color} (${_fiberId.threadName(fiberId)})` + if (options.colors) { + firstParams.push(logLevelStyle[logLevel._tag], "") + } + if (List.isCons(spans)) { + const now = date.getTime() + const render = renderLogSpanLogfmt(now) + for (const span of spans) { + firstLine += " " + render(span) + } + } + + firstLine += ":" + + let messageIndex = 0 + if (message.length > 0) { + const firstMaybeString = structuredMessage(message[0]) + if (typeof firstMaybeString === "string") { + firstLine += ` ${color}${firstMaybeString}` + if (options.colors) { + firstParams.push("color:blue") + } + messageIndex++ + } + } + + console.groupCollapsed(firstLine, ...firstParams) + + if (!Cause.isEmpty(cause)) { + console.error(Cause.pretty(cause, { renderErrorCause: true })) + } + + if (messageIndex < message.length) { + for (; messageIndex < message.length; messageIndex++) { + console.log(message[messageIndex]) + } + } + + if (HashMap.size(annotations) > 0) { + for (const [key, value] of annotations) { + if (options.colors) { + console.log(`%c${key}:`, "color:gray", value) + } else { + console.log(`${key}:`, value) + } + } + } + + console.groupEnd() + } + ) +}