Conversation
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Record validation/fallback and coded UserLogMessage handling need correction.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Updates v2 parser output relaying from JSON compatibility logs to OTEL JSONL for cleaner, richer parity.
Changes:
- Filters spans and relays supported log records.
- Preserves severity, codes, progress formatting, and native styling.
- Adds unit coverage and a changelog entry.
File summaries
| File | Reviewed changes |
|---|---|
tests/unit/parser/test_v2.py |
Tests OTEL filtering, formatting, severity, and fallback behavior. |
core/dbt/parser/v2.py |
Implements OTEL parsing and event relaying. |
.changes/unreleased/Fixes-20260911-155947.yaml |
Documents the fix. |
Review details
Suppressed comments (1)
core/dbt/parser/v2.py:547
- The stated relay behavior groups
LogMessageandUserLogMessagetogether for restoring the v2code/code_nameprefix, but this condition only applies_prefix_log_message_codetoLogMessage. Any codedUserLogMessagewill therefore lose its[Name (dbt####)]text. Apply the same prefix helper to both message event types (it is a no-op when the attributes have no code).
if event_type == _EVENT_TYPE_LOG_MESSAGE:
- Files reviewed: 3/3 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
aiguofer
force-pushed
the
aiguofer/v2-parser-output-parity
branch
from
September 15, 2026 21:20
ff1f84a to
1d92bae
Compare
aiguofer
force-pushed
the
aiguofer/v2-parser-output-parity
branch
from
September 16, 2026 17:54
ef9cb98 to
c09ba12
Compare
aiguofer
added this pull request to stack #16339
September 16, 2026 18:02
mishamsk
previously approved these changes
Sep 16, 2026
tauhid621
approved these changes
Sep 18, 2026
… parity
The subprocess relay for `--use-v2-parser` shelled out with `--log-format
json` and forwarded every line as a flat, unstructured Note event,
producing duplicate banners, per-node span noise, and losing the
original fusion error/warning codes.
Switch to `--log-format otel` and parse the JSONL envelope (SpanStart/
SpanEnd/LogRecord). Spans are dropped unconditionally, which structurally
eliminates the span-derived noise without a hand-maintained denylist.
Only an allowlist of LogRecord event types is relayed: LogMessage and
UserLogMessage relay their body (with the fusion error/warning code
composed back into the message text), StdoutMessage/StderrMessage relay
as-is, and ProgressMessage is rendered v1-side to reproduce fusion's
"{action} {target} ({description})" formatting. Severity is mapped from
the OTLP severity_number onto dbt-core's EventLevel.
Unparseable or unrecognized lines now relay at INFO instead of WARN,
fixing a latent bug where a stray fusion stderr line could get promoted
to a raised EventCompilationError under --warn-error and abort the run.
Also forward --log-level-file off. The v2 parser defaults its file log to
{--project-dir}/logs/dbt.log, which is the same path dbt-core writes its
own file log to when --log-path isn't redirected, so both processes were
appending the same relayed events to one file. Everything the subprocess
emits already reaches dbt-core over stdout and lands in dbt.log through
dbt-core's own logger.
The `# type: ignore[arg-type]` only covered one of the codes mypy emits for int(object) depending on version, so it failed under mantle's mypy with call-overload. An isinstance narrow needs no ignore and keeps the relay identical between dbt-core and mantle.
The status line ('Finished 'parse' with N warnings and M errors') has no
LogRecord of its own: the v2 parser renders it in its console formatter
out of Invocation span-end attributes. The relay dropped every span
wholesale, so the line went missing from --use-v2-parser output.
Read the aggregate counts off the Invocation span end instead, and
re-render the line with dbt-core's own ui color helpers. Every other span
is still dropped.
These event types exist for console output only and are not intended to reach the otel stream, so allowlisting them for forward-compatibility was speculative. Restore the TODO about switching to the Python OTel decoding library once it is released. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tauhid621
force-pushed
the
aiguofer/v2-parser-output-parity
branch
from
September 18, 2026 16:56
e891d83 to
88fac75
Compare
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.
Stacked on #16281 (
rebrand-fusion-to-v2-parser). Review that one first; this PR's diff is only the commits stacked on top of it.Previous behavior
--use-v2-parsershells out to the v2 (Fusion) binary and relays its log lines. The relay invoked it with--log-format jsonand forwarded every line as a flatNoteevent, keeping only the message text and the level. Consequences:[Generic (dbt1000)]-style prefixes at render time; the json wire carries the code as a separate field, which the relay discarded.--use-colors.--warn-errorabort. Unrecognized lines were relayed atWARN. Under--warn-error, WARN events are promoted to a raisedEventCompilationErrormatched by event class name, so a single stray line of v2 stderr could abort an otherwise-successful parse.New behavior
Invoke with
--log-format oteland parse the JSONL envelope (SpanStart/SpanEnd/LogRecord).Spans are dropped, with one deliberate exception (below). This is the load-bearing change: in the otel schema only
LogRecordhas abodyfield — spans structurally do not — so consuming just the message-shaped records both fixes the body problem and removes the span-derived noise with no hand-maintained denylist. v1 also stops being coupled to v2's span-visibility logic, which is where the layer drift lives.Of the remaining log records, an allowlist is relayed:
LogMessage,UserLogMessagebody, with the v2 error/warning code composed back into the message text ([Generic (dbt1000)]: ...)ProgressMessage{action} {target} ({description})column formatting from the record's attributesSeverity comes from the OTLP
severity_number, mapped ontoEventLevelby band. WARN/ERROR styling uses v1's owndbt_common.uitag helpers rather than trying to be byte-identical to v2 — the output looks native to v1 while carrying the same information, and it stays plain text that platform log ingestion parses unchanged.Unparseable or unrecognized lines now relay at
INFO, fixing the--warn-errorabort above.The end-of-run status line
v2's
Finished 'parse' with 1 warning for target 'default' [1.2s]has noLogRecord: it exists only as a console-formatter rendering of theInvocationspan's end attributes. Dropping every span would have silently deleted it, so theInvocationspan end is the one span record the relay reads rather than discards, and the line is synthesized v1-side from its attributes.The counts come from v2's own metric aggregator (
metrics.total_warnings/total_errors), so they stay correct even for warnings whoseLogRecords this relay filtered out. Mirrored from v2'sformatters/invocation.rs: the success/warning/error phrasing and coloring (both counts red once any error is present), singular/plural labels, thefor target '...'clause, the duration tiers fromformatters/duration.rs, and theRESULT_LINE_OPT_OUT_COMMANDSset — so the relay withholds the line on exactly the commands (man,login) where v2 itself prints none.Two wire details worth noting, both covered by tests: pbjson renders proto
uint64as a JSON string, and it omits optional fields entirely when unset — so both"12"and a missing key have to be handled.The subprocess no longer writes its own
dbt.log--log-level-file offis now forwarded too. v2 defaults its file log to{--project-dir}/logs/dbt.log, and_build_argvforwards--project-dirbut not--log-path— so on any run that doesn't redirect the log path (a plain local CLI run), that resolves to the same file dbt-core writes its own file log to. Both processes were appending the same relayed events to it, with no shared locking, producing alogs/dbt.logwith every parser line doubled and the two writers' formats interleaved.Nothing is lost by turning it off: everything the subprocess emits already reaches dbt-core over stdout and lands in
dbt.logthrough dbt-core's own logger.Note this is fixed explicitly rather than relying on the format switch. The otel format happens to install no file-log layer (
config.rs,LogFormat::Otel => None), so the double-write would have disappeared on its own — but incidentally, and it would come back if a file renderer is added for otel or if a run passes--log-format-file.--log-level-file offshort-circuits before the writer is even constructed, so nologs/dir or empty file is created either.This is unrelated to the duplicate-log display in Studio/IDE. That is an ide-server realtime-buffer-vs-finalized-artifact overlap, fixed separately by dbt-labs/ide-server#1488; platform redirects dbt-core's log path per task and never ingests the subprocess's file, so this change neither causes nor fixes that symptom.
Notes
fusion-client-python'sscrub_message(); a v1 relay has no equivalent. Not a regression introduced here, and I have not confirmed such URLs can actually appear in relayed text, but it is worth a second opinion before this ships widely.Example outputs
CLI:

Studio:
