#12475: fix(logging): use Symbol.for for externalTransports to survive jiti module isolation
stale
Cluster:
Plugin Enhancements and Fixes
## Summary
Fixes #12473
Plugins loaded via jiti get their own module cache, so the module-level `externalTransports` Set in `logger.ts` becomes a **separate instance** in the plugin context. Any transport registered by a plugin via `registerLogTransport()` is silently lost when `buildLogger()` iterates the main process's Set.
### Root cause
```
Main process: import logger.ts -> externalTransports = new Set() (A)
Plugin (jiti): import logger.ts -> externalTransports = new Set() (B)
Plugin calls registerLogTransport(fn) -> adds to Set B
buildLogger() iterates Set A -> fn is missing
```
### 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
| File | Change |
|------|--------|
| `src/logging/logger.ts` | Replace `new Set()` with `globalThis[Symbol.for()]` getter |
| `src/logging/logger-transport.test.ts` | Verify shared Set and register/unregister lifecycle |
## Test plan
- [x] New test: `externalTransports` is stored on `globalThis` via `Symbol.for`
- [x] New test: `registerLogTransport` adds to and removes from the shared global Set
- [x] All 27 existing logging + diagnostics-otel tests pass
<!-- greptile_comment -->
<h2>Greptile Overview</h2>
<h3>Greptile Summary</h3>
This PR changes `src/logging/logger.ts` to store the `externalTransports` Set on `globalThis[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.ts` to validate that the Set is shared via `Symbol.for` and that `registerLogTransport()` mutates the shared Set (including unregister cleanup).
<h3>Confidence Score: 4/5</h3>
- Mostly safe to merge, but the newly added tests are order-dependent and can fail in isolation/sharded runs.
- The production change in logger.ts is small and follows established patterns (Symbol.for + globalThis). The only clear issues found are in the new test file, which assumes logger.js has already been executed and dereferences the global Set before ensuring initialization.
- src/logging/logger-transport.test.ts
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#22478: fix(diagnostics-otel): wire OTLP exporter to emit traffic to config...
by LuffySama-Dev · 2026-02-21
82.7%
#16865: fix(diagnostics-otel): share listeners/transports across module bun...
by leonnardo · 2026-02-15
76.8%
#11281: fix(logging): prevent subsystem loggers from bypassing file log lev...
by janckerchen · 2026-02-07
73.7%
#19353: fix(diagnostics-otel): fix cross-chunk module isolation breaking even…
by nez · 2026-02-17
73.4%
#13109: fix(plugins): inject globalThis.require for CJS interop in jiti-loa...
by mcaxtr · 2026-02-10
73.3%
#11305: fix(logging): remove redundant subsystem prefix from log output
by janckerchen · 2026-02-07
72.1%
#19611: fix: use local timezone in log file and console timestamps
by tag-assistant · 2026-02-18
72.0%
#14746: fix(hooks): use globalThis for handler registry to survive bundler ...
by openperf · 2026-02-12
71.9%
#13881: fix: Address Greptile feedback - test isolation and channel resolution
by trevorgordon981 · 2026-02-11
71.9%
#11153: refactor(hooks): replace console.warn/error with subsystem logger
by hclsys · 2026-02-07
71.2%