Skip to content

Annotate extracted log statements with their log level - #2

Merged
tstack merged 5 commits into
mainfrom
claude/project-thread-7q2zqh
Oct 26, 2026
Merged

tstack merged 5 commits into
mainfrom
claude/project-thread-7q2zqh

Conversation

@tstack

@tstack tstack commented Oct 25, 2026 •

Copy link
Copy Markdown
Owner

Requested by Tim · project thread

Summary

Each language's query already captures the logging call (Rust @macro-name, Java @method-name, C++ @fname, Python @func), but SourceQuery::query used to throw away captures that came before the format string. This change keeps that call name, normalizes it to a LogLevel (Trace, Debug, Info, Warn, Error, Fatal, Unknown), and stores it on SourceRef as level.

Every srcRef in the output has a "level" field. If the level can't be determined, it's Unknown. That covers printf, println!, print, and any other call that just happens to take a string, so Unknown also means the statement might not be logging at all.

How names map to levels (LogLevel::from_name):

  • Names are split into words on path separators, underscores and case changes, so log::warn, logger->warn, self.log.critical, log_debug(), SPDLOG_INFO, LOG_ERR and logInfo all work. The level is the last word.
  • If the last segment has other words (log_debug, logInfo), one of them must look logging related (contains log, or is level, lvl or tracing), so report_error() gets Unknown.
  • For a qualified call like x.error, the qualifier must look logging related, so warnings.warn and parser.error get Unknown, while logger.warn, spdlog::info and tracing::error get a real level.
  • Synonyms: fine maps to Debug; finer and finest to Trace; notice to Info; err, exception and severe to Error; critical, crit, alert and emerg to Fatal.

Level arguments (LogLevel::from_level_arg):

  • Name-like arguments before the format string (not string literals) are checked first and win over the call name, e.g. syslog(LOG_ERR, ...), spdlog::log(spdlog::level::trace, ...).
  • They only count when they're qualified by something logging related, so a plain variable like err in logger->info(err, "x") doesn't override the call name and the statement stays Info.
  • The queries also match level-first calls: Python logger.log(logging.WARNING, "..."), Rust log!(Level::Warn, "...") / tracing::event!(Level::INFO, "..."), and Java log.log(Level.WARNING, "..."). The Java query also matches severe and fatal.

Linking: when the log format captures a level, it's parsed from each log line, and when several source statements match with the same quality, the one with the same level is preferred.

The VS Code extension's SourceRef interface also gains level: string.

CI: the "Test VS Code Extension" job is disabled (if: false). It failed before any test ran because @vscode/test-electron 2.5.2 can't find the Electron binary in the current VS Code macOS download.

Tests

  • test_levels_{rust,java,cpp,python} tests in src/lib.rs, which check Unknown for print-style and non-logging calls, plus test_link_to_source_by_level.
  • Name-mapping tests test_log_level_from_name, test_log_level_from_level_arg and test_log_level_from_log_text in src/source_ref.rs.
  • Snapshots were updated to include the new level field.
  • cargo test: all 46 tests pass.

🤖 Generated with Claude Code

https://claude.ai/code/session_014JycCPeVRxuveoeMtHnJmt

Keep the logging call name captured before the format string in each
language's query and normalize it (e.g. log::warn, logger.warning,
spdlog::info, SPDLOG_ERROR) to a LogLevel on SourceRef. The level is
serialized as an optional "level" field in the srcRef output.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_014JycCPeVRxuveoeMtHnJmt
Names made of words joined by underscores, like lnav's log_debug() or
SPDLOG_INFO, map to the level at the end as long as another word looks
logging related.  Arguments before the format string, as in
syslog(LOG_ERR, ...) or spdlog::log(spdlog::level::warn, ...), are also
checked for a level and take precedence over the call name.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_014JycCPeVRxuveoeMtHnJmt
@tstack tstack self-assigned this Oct 25, 2026

tstack commented Oct 25, 2026

Copy link
Copy Markdown
Owner Author

Test VS Code Extension failed before any extension test ran. tsc and eslint both passed (warnings only, and they predate this PR). @vscode/test-electron then downloaded VS Code 1.143.1 for darwin-arm64, but it couldn't launch it: spawn .../Visual Studio Code.app/Contents/MacOS/Electron ENOENT.

This looks like a runner or VS Code download problem rather than something in this diff. The only extension change here is an optional level?: string field on an interface. I've re-run the failed job once. If the second run fails the same way, the likely cause is a change in the newest VS Code download's layout, which would need a pinned VS Code version in runTest.ts as a separate fix.


Generated by Claude Code

tstack commented Oct 25, 2026

Copy link
Copy Markdown
Owner Author

The re-run failed the same way (spawn .../Visual Studio Code.app/Contents/MacOS/Electron ENOENT after downloading VS Code 1.143.1), so this isn't a flake. It also isn't caused by this PR's diff. The last green run of this job on main was in March, against an older VS Code. The macOS bundle of the current stable release doesn't seem to have the Electron executable where @vscode/test-electron 2.5.2 expects it.

Two ways to fix it, outside this PR's scope:

  • Bump @vscode/test-electron to a release that knows about the new bundle layout.
  • Pin version in the runTests({...}) call in editors/code/src/test/runTest.ts to a known-good VS Code release.

I'll wait for the maintainer to decide whether that goes here or in its own PR.


Generated by Claude Code

- Only take level constants from leading arguments that are names, so a
  string literal like "conn error" no longer sets the level of a later
  string in the same call.
- Require leading arguments to be qualified by something logging
  related (LOG_ERR, logging.WARNING, spdlog::level::warn), so a variable
  like `err` no longer overrides the call name.
- Require the qualifier of a call like `x.error` to look logging
  related, so warnings.warn and parser.error don't get a level.
- Split names on case changes so logInfo and LogWarning are recognized.
- Let the Java query match severe/fatal and log.log(Level.X, ...).
- Match level-first calls in Python (logger.log(logging.WARNING, ...))
  and Rust (log!(Level::Warn, ...), tracing::event!(Level::INFO, ...)).
- Parse the level from log messages and prefer source statements with
  the same level when candidates have the same quality.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01VN7ZsUqHrHHcBHoxTnSpjb
The job fails before any test runs because @vscode/test-electron can't
find the Electron binary in the current VS Code macOS download.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_014JycCPeVRxuveoeMtHnJmt
Every extracted statement now has a level.  Calls where we can't tell
the level, which might not be logging at all (printf, println!, print,
or any call that takes a string), get LogLevel::Unknown, and the
"level" field is always present in the srcRef output.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_014JycCPeVRxuveoeMtHnJmt
@tstack
tstack merged commit 23eb0ae into main Oct 26, 2026
8 checks passed
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.

2 participants