fix(logging): use Symbol.for for externalTransports to survive jiti module isolation#12475
fix(logging): use Symbol.for for externalTransports to survive jiti module isolation#12475Yida-Dev wants to merge 1 commit intoopenclaw:mainfrom
Conversation
…odule isolation
Plugins loaded via jiti get their own module cache, so the module-level
`externalTransports` Set in logger.ts is a different instance in the
plugin context than in the main process. Any transport registered by a
plugin is silently lost when buildLogger() iterates the main Set.
Store the Set on `globalThis[Symbol.for("openclaw.logging.externalTransports")]`
so all module instances share the same collection, matching the pattern
already used by warning-filter.ts and plugins/runtime.ts.
Closes openclaw#12473
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| it("externalTransports is stored on globalThis via Symbol.for", () => { | ||
| // Importing logger.ts should have populated the global key. | ||
| const g = globalThis as typeof globalThis & { | ||
| [key: symbol]: Set<LogTransport> | undefined; | ||
| }; | ||
| expect(g[EXTERNAL_TRANSPORTS_KEY]).toBeInstanceOf(Set); |
There was a problem hiding this comment.
Order-dependent test setup
This test expects globalThis[Symbol.for(...)] to be populated by importing logger.ts, but this file only imports LogTransport as a type (erased at runtime), so ./logger.js may never run. That makes the assertion fail when this test runs in isolation or in a fresh worker. Consider doing a real await import("./logger.js") (or importing a runtime symbol) before reading g[EXTERNAL_TRANSPORTS_KEY] so initialization is deterministic.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/logging/logger-transport.test.ts
Line: 7:12
Comment:
**Order-dependent test setup**
This test expects `globalThis[Symbol.for(...)]` to be populated by importing `logger.ts`, but this file only imports `LogTransport` as a type (erased at runtime), so `./logger.js` may never run. That makes the assertion fail when this test runs in isolation or in a fresh worker. Consider doing a real `await import("./logger.js")` (or importing a runtime symbol) before reading `g[EXTERNAL_TRANSPORTS_KEY]` so initialization is deterministic.
How can I resolve this? If you propose a fix, please make it concise.| const g = globalThis as typeof globalThis & { | ||
| [key: symbol]: Set<LogTransport> | undefined; | ||
| }; | ||
| const set = g[EXTERNAL_TRANSPORTS_KEY]!; | ||
| const sizeBefore = set.size; |
There was a problem hiding this comment.
Dereference before initialization
In this test, const set = g[EXTERNAL_TRANSPORTS_KEY]!; happens before importing ./logger.js, so set can be undefined and the non-null assertion will crash. Import ./logger.js first (or otherwise ensure initialization) before reading the global key, so the test doesn’t depend on previous tests/order.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/logging/logger-transport.test.ts
Line: 18:22
Comment:
**Dereference before initialization**
In this test, `const set = g[EXTERNAL_TRANSPORTS_KEY]!;` happens *before* importing `./logger.js`, so `set` can be `undefined` and the non-null assertion will crash. Import `./logger.js` first (or otherwise ensure initialization) before reading the global key, so the test doesn’t depend on previous tests/order.
How can I resolve this? If you propose a fix, please make it concise.bfc1ccb to
f92900f
Compare
|
This pull request has been automatically marked as stale due to inactivity. |
|
Closing as AI-assisted stale-fix triage. Linked issue #12473 ("[Bug]: Plugin log transports lost due to module isolation in logging system") is currently CLOSED and was closed on 2026-02-13T15:11:52Z with state reason NOT_PLANNED. If the underlying bug is still reproducible on current main, please reopen this PR (or open a new focused fix PR) and reference both #12473 and #12475 for fast re-triage. |
|
Closed after AI-assisted stale-fix triage (closed issue duplicate/stale fix). |
The externalTransports Set in logger.ts is module-scoped, so plugin bundles that receive a separate copy of the module have their own isolated registry. registerLogTransport() from the OTel plugin may register on a different Set than the one the gateway logger reads. Fix: use globalThis.__openclawLogTransportState singleton, matching the pattern PR openclaw#12897 established for the diagnostic event bus. Related openclaw#12475
The externalTransports Set in logger.ts is module-scoped, so plugin bundles that receive a separate copy of the module have their own isolated registry. registerLogTransport() from the OTel plugin may register on a different Set than the one the gateway logger reads. Fix: use globalThis.__openclawLogTransportState singleton, matching the pattern PR openclaw#12897 established for the diagnostic event bus. Related openclaw#12475
Summary
Fixes #12473
Plugins loaded via jiti get their own module cache, so the module-level
externalTransportsSet inlogger.tsbecomes a separate instance in the plugin context. Any transport registered by a plugin viaregisterLogTransport()is silently lost whenbuildLogger()iterates the main process's Set.Root cause
Fix
Store the Set on
globalThis[Symbol.for("openclaw.logging.externalTransports")]so all module instances -- regardless of how they were loaded -- share the same collection.This matches the pattern already established in:
src/infra/warning-filter.ts(Symbol.for("openclaw.warning-filter"))src/plugins/runtime.ts(Symbol.for("openclaw.pluginRegistryState"))Files changed
src/logging/logger.tsnew Set()withglobalThis[Symbol.for()]gettersrc/logging/logger-transport.test.tsTest plan
externalTransportsis stored onglobalThisviaSymbol.forregisterLogTransportadds to and removes from the shared global SetGreptile Overview
Greptile Summary
This PR changes
src/logging/logger.tsto store theexternalTransportsSet onglobalThis[Symbol.for("openclaw.logging.externalTransports")]so transports registered from jiti-loaded plugin module instances are visible to the main process logger builder.It also adds
src/logging/logger-transport.test.tsto validate that the Set is shared viaSymbol.forand thatregisterLogTransport()mutates the shared Set (including unregister cleanup).Confidence Score: 4/5