Fix: keep winston out of the published browser build by splitting the logger by platform - #683
Open
AmaadMartin wants to merge 6 commits into
Open
Fix: keep winston out of the published browser build by splitting the logger by platform#683AmaadMartin wants to merge 6 commits into
AmaadMartin wants to merge 6 commits into
Conversation
added 6 commits
August 5, 2026 09:14
The browser entry point reaches utils/logger.ts, which imported winston, so no browser bundler could resolve the published web build. utils/logger.ts now writes through console, and the winston logger moves to utils/logger_node.ts, which the Node entry point installs. Node output is unchanged. Part of #611
dist/web is transpile-only, so esbuild passes every import specifier through verbatim. Emitting index.ts and the *_node.ts modules would ship winston in the published browser artifact even though index_web.ts never imports them. Part of #611
…ools installNodeLogger() runs after index.ts evaluates its re-export graph, so a module that captured getLogger() at module scope kept the console logger for the process lifetime. setLogLevel() never reached those two tools and their Node output bypassed winston. The logger facade already forwards to the current logger, which is what the other 39 importers use. Part of #611
…pin the browser build's entry-point exclusion to src/index.ts Part of #611
…uild" The excluded modules were already inert: core/package.json maps exports for "." only, so a consumer cannot deep-import dist/web, and the browser field points at index_web.js, whose graph reaches neither index.ts nor logger_node.ts. Dropping entry points from a transpile-only build would also hide a future dangling import instead of failing the build. Part of #611
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Please ensure you have read the contribution guide before creating a pull request.
Link to Issue or Description of Change
Part of: Fix: remove orphaned fragment from BasePlugin.onToolErrorCallback JSDoc #611
Related: Fix: keep temp: state readable for the whole invocation in DatabaseSessionService (stacked on #132) #607
Problem:
core/src/utils/logger.tsimportswinston, and the browser entry point reaches that module throughcommon.ts. The published web build is transpile-only, so esbuild passes the specifier through unchanged.core/dist/web/utils/logger.jstherefore shipsimport * as winston from "winston", which no browser bundler can resolve.Solution:
utils/logger.tsnow writes throughconsole, and the winston logger moves unchanged into the newutils/logger_node.ts. The Node entry point installs it through the existingsetLoggerseam, so Node output stays the same and winston stays acoredependency. #617 proposed deleting winston and was rejected, so this PR does not touch the dependency. An esbuildaliascannot solve this, because esbuild rejectsaliaswithoutbundleand the published web build does not bundle, so theplatform === 'browser' && bundlegate and the rest ofcore/build.jsare left as they are.load_artifacts_tool.tsandvertex_ai_search_tool.tsheldconst logger = getLogger()at module scope.installNodeLogger()runs afterindex.tsevaluates its re-exports, so those two modules would have kept the console logger for the process lifetime, out of reach ofsetLogLevel(). Both now log through theloggerfacade, which the other 39 importers already use. That is the only call-site change here.Behaviour notes
resetLogger()now installs the console logger on Node as well. It is internal: neitherindex.tsnorcommon.tsexports it, and only core's own tests call it.utils/logger.jsgets the console logger, because the Node wiring lives inindex.ts. The packageexportsmap only maps".", so a package consumer cannot reach that path.dist/webstill containsutils/logger_node.js, and it still nameswinston. That module is unreachable:core/package.jsondeclaresexportsfor"."only, so a consumer cannot deep-import it, andbrowserpoints atindex_web.js, whose graph does not include it. Excluding it from the transpile-only build would hide the day a*_node.tsmodule does become reachable, so the build is left alone.logger.log(level, ...)on Node still throws inside winston, because it passes the numeric enum value as the level name. That behaviour is pre-existing and is preserved here;logger_node_test.tspins it. The console logger emits normally.common.tsis untouched and no symbol is added or removed.Overlapping work: I checked the open pull requests before starting. #614 makes the web target always bundle and moves Node-only exports out of
common.ts; it does not touchcore/src/utils/logger.ts, so this change is independent of it. Thenode:async_hookshalf of #611 ships separately, which is why this isPart ofand notCloses.Testing Plan
Please describe the tests that you ran to verify your changes. This is required for all PRs that are not small documentation or typo fixes.
Unit Tests:
[x] I have added or updated unit tests for my change.
[x] All unit tests pass locally.
npx vitest run --project unit:core core/test/utils/logger_test.ts core/test/utils/logger_node_test.ts core/test/utils/logger_node_wiring_test.ts core/test/tools/tool_logging_test.ts— 30 passed.logger_test.tsgains the consoleSimpleLoggercases and abrowser safetyblock. That block reads both sources:logger.tsmust contain noimport, andlogger_node.tsmust still importwinston, so the first assertion cannot be met by deleting the dependency.logger_node_test.ts(new) captures the Console transport output and checks the line format, the colorized level, level gating, and argument joining.logger_node_wiring_test.ts(new) imports@google/adkand asserts the entry point installed the winston logger.tool_logging_test.ts(new) drivesLoadArtifactsToolandVertexAiSearchToolagainst real in-memory session and artifact services, with no mocks. It asserts that their log records reach the current logger and thatsetLogLevel(LogLevel.ERROR)suppresses the missing-artifact warning.npx vitest run --project unit:core --project unit:dev— 2602 passed, 1 failed. The failure isdev/test/cli/cli_create_test.ts > should handle Vertex AI selection with gcloud defaults, which reads the local gcloud configuration. It fails the same way onmain, so it is unrelated to this change.npm run lint,npm run format:checkandnpm run docs:checkare clean.npm run ts:checkreports 281 errors on this branch and 281 onmain, with an identical per-file breakdown.Coverage:
core/src/utils/logger_node.tsis at 96.96% lines and 93.33% branches. The only uncovered lines are the early return inerror():LogLevel.ERRORis the highest level, sothis.logLevel > LogLevel.ERRORcan never be true. That guard is pre-existing code moved without modification, so I kept it rather than deleting it for the number. The new code incore/src/utils/logger.tsis fully covered.Mutation proof: each new test was run against the unfixed code.
core/src/utils/logger.tslogger_test.ts: 8 failures, includingbrowser safety > keeps the browser-reachable logger free of importsinstallNodeLogger()incore/src/index.tslogger_node_wiring_test.tsfails —expected SimpleLogger{ logLevel: 1 } to be an instance of WinstonLoggerconst logger = getLogger()in both toolstool_logging_test.tscases fail, e.g.expected "warn" to not be called at all, but actually been called 1 timesManual End-to-End (E2E) Tests:
Please provide instructions on how to manually test your changes, including any necessary setup or configuration.
The check that matters is what the browser entry point reaches. Bundling it lists every bare specifier a browser bundler would have to resolve:
On
mainthat list containswinston:On this branch it does not, and no module named
logger_nodeis among the 147 reachable inputs. The remainingnode:specifiers are the other half of #611 and #612, and are out of scope here.Node output is unchanged. The script below prints the same four lines, with the same colours and the same format, on
mainand on this branch:dist/esm,dist/cjsanddist/webstill mirrorcore/srcfile for file, as before.Checklist
[x] I have read the CONTRIBUTING.md document.
[x] I have performed a self-review of my own code.
[x] I have commented my code, particularly in hard-to-understand areas.
[x] I have added tests that prove my fix is effective or that my feature works.
[x] New and existing unit tests pass locally with my changes.