From 52634f156d30925d2bc36a61f089c3fc4b4da937 Mon Sep 17 00:00:00 2001 From: Johannes Schickling Date: Sun, 24 Aug 2025 12:01:45 +0200 Subject: [PATCH 1/3] fix(Logger): Support Cloudflare Workers by avoiding console.group when in workerd - Add workerd runtime detection using navigator.userAgent === 'Cloudflare-Workers' - Modify prettyLoggerTty to skip console.group/groupEnd in workerd (similar to Bun) - Modify prettyLoggerBrowser to use flat logging instead of console.groupCollapsed in workerd - Fixes missing log messages in Cloudflare Workers where console.group methods are no-ops Resolves #5398 --- packages/effect/src/internal/logger.ts | 68 +++++++++++++++++++------- 1 file changed, 49 insertions(+), 19 deletions(-) diff --git a/packages/effect/src/internal/logger.ts b/packages/effect/src/internal/logger.ts index 124b5e98f0f..55480e8a8ef 100644 --- a/packages/effect/src/internal/logger.ts +++ b/packages/effect/src/internal/logger.ts @@ -357,6 +357,8 @@ const prettyLoggerTty = (options: { readonly formatDate: (date: Date) => string }) => { const processIsBun = typeof process === "object" && "isBun" in process && process.isBun === true + const isWorkerd = typeof navigator !== "undefined" && navigator.userAgent === "Cloudflare-Workers" + const supportsConsoleGroup = !processIsBun && !isWorkerd const color = options.colors ? withColor : withColorNoop return makeLogger( ({ annotations, cause, context, date, fiberId, logLevel, message: message_, spans }) => { @@ -389,7 +391,7 @@ const prettyLoggerTty = (options: { } log(firstLine) - if (!processIsBun) console.group() + if (supportsConsoleGroup) console.group() if (!Cause.isEmpty(cause)) { log(Cause.pretty(cause, { renderErrorCause: true })) @@ -407,7 +409,7 @@ const prettyLoggerTty = (options: { } } - if (!processIsBun) console.groupEnd() + if (supportsConsoleGroup) console.groupEnd() } ) } @@ -417,6 +419,7 @@ const prettyLoggerBrowser = (options: { readonly formatDate: (date: Date) => string }) => { const color = options.colors ? "%c" : "" + const isWorkerd = typeof navigator !== "undefined" && navigator.userAgent === "Cloudflare-Workers" return makeLogger( ({ annotations, cause, context, date, fiberId, logLevel, message: message_, spans }) => { const services = FiberRefs.getOrDefault(context, defaultServices.currentServices) @@ -454,30 +457,57 @@ const prettyLoggerBrowser = (options: { } } - console.groupCollapsed(firstLine, ...firstParams) + if (isWorkerd) { + // In workerd, console.groupCollapsed is a no-op, so we use flat logging + console.log(firstLine, ...firstParams) - if (!Cause.isEmpty(cause)) { - console.error(Cause.pretty(cause, { renderErrorCause: true })) - } + if (!Cause.isEmpty(cause)) { + console.error(Cause.pretty(cause, { renderErrorCause: true })) + } - if (messageIndex < message.length) { - for (; messageIndex < message.length; messageIndex++) { - console.log(Inspectable.redact(message[messageIndex])) + if (messageIndex < message.length) { + for (; messageIndex < message.length; messageIndex++) { + console.log(Inspectable.redact(message[messageIndex])) + } } - } - if (HashMap.size(annotations) > 0) { - for (const [key, value] of annotations) { - const redacted = Inspectable.redact(value) - if (options.colors) { - console.log(`%c${key}:`, "color:gray", redacted) - } else { - console.log(`${key}:`, redacted) + if (HashMap.size(annotations) > 0) { + for (const [key, value] of annotations) { + const redacted = Inspectable.redact(value) + if (options.colors) { + console.log(`%c${key}:`, "color:gray", redacted) + } else { + console.log(`${key}:`, redacted) + } + } + } + } else { + // Standard browser behavior with grouping + 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(Inspectable.redact(message[messageIndex])) } } - } - console.groupEnd() + if (HashMap.size(annotations) > 0) { + for (const [key, value] of annotations) { + const redacted = Inspectable.redact(value) + if (options.colors) { + console.log(`%c${key}:`, "color:gray", redacted) + } else { + console.log(`${key}:`, redacted) + } + } + } + + console.groupEnd() + } } ) } From 8b957732c7e39aacdc6911c5e6aa939130b96ac2 Mon Sep 17 00:00:00 2001 From: Johannes Schickling Date: Sun, 24 Aug 2025 12:10:50 +0200 Subject: [PATCH 2/3] Improve workerd detection approach: treat as TTY instead of modifying browser logger - Detect workerd as TTY environment using shouldUseTTYMode logic - Route workerd to TTY logger which already has console.group detection - Keep browser logger unchanged for actual browser environments - Add changeset documenting the fix --- .changeset/workerd-logger-fix.md | 14 ++++++ packages/effect/src/internal/logger.ts | 66 ++++++++------------------ 2 files changed, 34 insertions(+), 46 deletions(-) create mode 100644 .changeset/workerd-logger-fix.md diff --git a/.changeset/workerd-logger-fix.md b/.changeset/workerd-logger-fix.md new file mode 100644 index 00000000000..fcfe8519571 --- /dev/null +++ b/.changeset/workerd-logger-fix.md @@ -0,0 +1,14 @@ +--- +"effect": patch +--- + +fix(Logger): Support Cloudflare Workers by treating workerd as TTY environment + +Fixes missing log messages in Cloudflare Workers where console.group methods are no-ops. + +The pretty logger now: +- Detects workerd runtime using `navigator.userAgent === 'Cloudflare-Workers'` +- Routes workerd to TTY logger mode instead of browser mode +- Disables console.group calls in workerd (similar to existing Bun handling) + +This ensures all log messages display correctly in Cloudflare Workers local development. \ No newline at end of file diff --git a/packages/effect/src/internal/logger.ts b/packages/effect/src/internal/logger.ts index 55480e8a8ef..52349a18c8e 100644 --- a/packages/effect/src/internal/logger.ts +++ b/packages/effect/src/internal/logger.ts @@ -332,7 +332,9 @@ const hasProcessStdout = typeof process === "object" && process.stdout !== null const processStdoutIsTTY = hasProcessStdout && process.stdout.isTTY === true +const isWorkerd = typeof navigator !== "undefined" && navigator.userAgent === "Cloudflare-Workers" const hasProcessStdoutOrDeno = hasProcessStdout || "Deno" in globalThis +const shouldUseTTYMode = hasProcessStdoutOrDeno || isWorkerd /** @internal */ export const prettyLogger = (options?: { @@ -342,7 +344,7 @@ export const prettyLogger = (options?: { readonly mode?: "browser" | "tty" | "auto" | undefined }) => { const mode_ = options?.mode ?? "auto" - const mode = mode_ === "auto" ? (hasProcessStdoutOrDeno ? "tty" : "browser") : mode_ + const mode = mode_ === "auto" ? (shouldUseTTYMode ? "tty" : "browser") : mode_ const isBrowser = mode === "browser" const showColors = typeof options?.colors === "boolean" ? options.colors : processStdoutIsTTY || isBrowser const formatDate = options?.formatDate ?? defaultDateFormat @@ -419,7 +421,6 @@ const prettyLoggerBrowser = (options: { readonly formatDate: (date: Date) => string }) => { const color = options.colors ? "%c" : "" - const isWorkerd = typeof navigator !== "undefined" && navigator.userAgent === "Cloudflare-Workers" return makeLogger( ({ annotations, cause, context, date, fiberId, logLevel, message: message_, spans }) => { const services = FiberRefs.getOrDefault(context, defaultServices.currentServices) @@ -457,57 +458,30 @@ const prettyLoggerBrowser = (options: { } } - if (isWorkerd) { - // In workerd, console.groupCollapsed is a no-op, so we use flat logging - console.log(firstLine, ...firstParams) - - if (!Cause.isEmpty(cause)) { - console.error(Cause.pretty(cause, { renderErrorCause: true })) - } - - if (messageIndex < message.length) { - for (; messageIndex < message.length; messageIndex++) { - console.log(Inspectable.redact(message[messageIndex])) - } - } + console.groupCollapsed(firstLine, ...firstParams) - if (HashMap.size(annotations) > 0) { - for (const [key, value] of annotations) { - const redacted = Inspectable.redact(value) - if (options.colors) { - console.log(`%c${key}:`, "color:gray", redacted) - } else { - console.log(`${key}:`, redacted) - } - } - } - } else { - // Standard browser behavior with grouping - console.groupCollapsed(firstLine, ...firstParams) - - if (!Cause.isEmpty(cause)) { - console.error(Cause.pretty(cause, { renderErrorCause: true })) - } + if (!Cause.isEmpty(cause)) { + console.error(Cause.pretty(cause, { renderErrorCause: true })) + } - if (messageIndex < message.length) { - for (; messageIndex < message.length; messageIndex++) { - console.log(Inspectable.redact(message[messageIndex])) - } + if (messageIndex < message.length) { + for (; messageIndex < message.length; messageIndex++) { + console.log(Inspectable.redact(message[messageIndex])) } + } - if (HashMap.size(annotations) > 0) { - for (const [key, value] of annotations) { - const redacted = Inspectable.redact(value) - if (options.colors) { - console.log(`%c${key}:`, "color:gray", redacted) - } else { - console.log(`${key}:`, redacted) - } + if (HashMap.size(annotations) > 0) { + for (const [key, value] of annotations) { + const redacted = Inspectable.redact(value) + if (options.colors) { + console.log(`%c${key}:`, "color:gray", redacted) + } else { + console.log(`${key}:`, redacted) } } - - console.groupEnd() } + + console.groupEnd() } ) } From 434e834cb3f2421298fe40753b285d3bc97e7a8c Mon Sep 17 00:00:00 2001 From: Johannes Schickling Date: Sun, 24 Aug 2025 12:12:58 +0200 Subject: [PATCH 3/3] Remove duplicate isWorkerd definition - Reuse module-level isWorkerd constant instead of redefining in prettyLoggerTty - Cleaner code with single source of truth for workerd detection --- packages/effect/src/internal/logger.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/effect/src/internal/logger.ts b/packages/effect/src/internal/logger.ts index 52349a18c8e..811f1f79c73 100644 --- a/packages/effect/src/internal/logger.ts +++ b/packages/effect/src/internal/logger.ts @@ -359,7 +359,6 @@ const prettyLoggerTty = (options: { readonly formatDate: (date: Date) => string }) => { const processIsBun = typeof process === "object" && "isBun" in process && process.isBun === true - const isWorkerd = typeof navigator !== "undefined" && navigator.userAgent === "Cloudflare-Workers" const supportsConsoleGroup = !processIsBun && !isWorkerd const color = options.colors ? withColor : withColorNoop return makeLogger(