Skip to content

Fix: pass a winston level name from SimpleLogger.log - #795

Open
AmaadMartin wants to merge 1 commit into
mainfrom
fix/logger-log-numeric-level-name
Open

Fix: pass a winston level name from SimpleLogger.log#795
AmaadMartin wants to merge 1 commit into
mainfrom
fix/logger-log-numeric-level-name

Conversation

@AmaadMartin

Copy link
Copy Markdown
Owner

Please ensure you have read the contribution guide before creating a pull request.

Link to Issue or Description of Change

  1. Link to an existing issue (if applicable):

None.

  1. Or, if no issue exists, describe the change:

Problem: SimpleLogger.log() passed level.toString() to winston. LogLevel is a numeric enum, so winston received '0'..'3', while its levels map is keyed by debug/info/warn/error. The format chain calls colorize(), which looks up the colour for the unknown level, finds undefined, and calls it. The Console write is synchronous, so the resulting TypeError propagates out of the public logger.log() call and into user code.

Solution: log() now reads the winston level name from an explicit LogLevel -> name table. The Readonly<Record<LogLevel, string>> annotation is exhaustive, so adding a fifth LogLevel member is a compile error at the table (verified: TS2741). I preferred that over LogLevel[level].toLowerCase(), which couples winston's registered level names to the TypeScript member names. The level gate, the levels map, the format chain and the four named methods are untouched.

Scope: this PR fixes the core logger only. dev/src/utils/logger.ts carries the same one-line bug, and #790 already fixes it with a behaviourally identical change. I did not duplicate that half. The two files are disjoint, so this PR branches from main rather than stacking on #790, and either can land first.

Collision check: I ran gh pr list --repo AmaadMartin/adk-js --state open --limit 400, then listed the files of all 400 open PRs and inspected every one touching a logger source. Six do. #790 and #672 are dev-only. #697 (stderr routing) and #431 (getLogLevel()) do not touch this line. #432 fixes it, but only inside a bundled logger refactor that is currently CONFLICTING. #683 splits the logger by platform and carries level.toString() across to logger_node.ts unfixed; if it lands first, this same table applies there.

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:

  • I have added or updated unit tests for my change.
  • All unit tests pass locally.

I appended a new top-level describe('SimpleLogger.log') to core/test/utils/logger_test.ts and changed no existing test. Five cases: an it.each over the four LogLevel members asserting the resolved winston level name, plus the level gate.

The tests capture records at winston.transports.Console.prototype.log. That is the only seam that works here: vitest replaces the global console, whose _stdout is not process.stdout, so spies on process.stdout.write and console.log both see nothing. The spy asserts on info[Symbol.for('level')], which is the name winston resolved, and it calls the transport callback so a second record is not stalled behind the first.

Proof the tests can fail. I restored this.logger.log(level.toString(), ...) and re-ran the file:

× SimpleLogger.log > writes a debug record under that winston level name
× SimpleLogger.log > writes a info record under that winston level name
× SimpleLogger.log > writes a warn record under that winston level name
× SimpleLogger.log > writes a error record under that winston level name
Tests  4 failed | 9 passed (13)
TypeError: colors[Colorizer.allColors[lookup]] is not a function

The fifth case, suppresses a record below the configured level, passes with and without the fix by design. It is the regression guard on the level gate this change must not disturb, so it is green either way rather than proof of the fix.

Coverage. Both lines this PR adds or changes are covered, and branch coverage is 100%. The uncovered lines the report names (83-112, 165-178) are the pre-existing debug/info/warn/error methods and the deprecated logger proxy, which this one file does not drive.

Manual End-to-End (E2E) Tests:

Build the workspace, then run this from the repo root so Node resolves @google/adk through the workspace link:

// repro.mjs
import {getLogger, LogLevel, setLogLevel} from '@google/adk';
setLogLevel(LogLevel.DEBUG);
getLogger().info('info() works');
getLogger().log(LogLevel.INFO, 'does log() appear?');

Before, the process exits 1:

INFO: [ADK] 2026-08-08T02:04:14.465Z info() works
[winston] Unknown logger level: 1
TypeError: colors[Colorizer.allColors[lookup]] is not a function
    at Colorizer.colorize (node_modules/logform/colorize.js:73:49)

After, it exits 0 and log() renders the same line as info():

INFO: [ADK] 2026-08-08T02:05:58.075Z info() works
INFO: [ADK] 2026-08-08T02:05:58.077Z does log() appear?

Driving all four levels emits four correctly coloured lines with nothing on stderr.

Local validation on the pushed commit:

  • npx vitest run --project unit:core core/test/utils/logger_test.ts — 13 passed.
  • npx vitest run --project unit:core — 2736 passed, 201 files, no regressions.
  • npm run build — passed.
  • npx eslint on both files — clean.
  • npx prettier --check on both files — clean.
  • npm run docs:check — passed.
  • bash scripts/check_license.sh — passed.

npm run ts:check fails, but it fails identically on main with this branch stashed. I diffed the two error lists and they are the same. The errors are in other core/test files, and the validation workflow does not run ts:check.

Checklist

  • I have read the CONTRIBUTING.md document.
  • I have performed a self-review of my own code.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have added tests that prove my fix is effective or that my feature works.
  • New and existing unit tests pass locally with my changes.

SimpleLogger.log handed winston level.toString(). LogLevel is a numeric
enum, so winston received '0'..'3' while its levels map is keyed by
debug/info/warn/error. The format chain calls colorize(), which looks up
the colour for an unknown level, finds undefined and calls it. The write
is synchronous, so the resulting TypeError propagates out of the public
logger.log() call.

Look the name up through an explicit LogLevel -> winston name table. The
Record<LogLevel, string> annotation is exhaustive over the enum, so a new
LogLevel member becomes a compile error at the table rather than a silent
return of this bug.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant