Skip to content

otelcol/grpclog: suppress benign gRPC client-disconnect warnings - #15024

Merged
mx-psi merged 4 commits into
open-telemetry:mainfrom
kruthiwusirika5:fix/zapgrpc-client-close-warning
Apr 30, 2026
Merged

otelcol/grpclog: suppress benign gRPC client-disconnect warnings#15024
mx-psi merged 4 commits into
open-telemetry:mainfrom
kruthiwusirika5:fix/zapgrpc-client-close-warning

Conversation

@kruthiwusirika5

@kruthiwusirika5 kruthiwusirika5 commented Mar 29, 2026

Copy link
Copy Markdown
Contributor

Fixes #5169

Problem

When a gRPC client disconnects normally (e.g., a .NET app shutting
down its TracerProvider), the collector logs spurious WARN messages
from the gRPC transport layer:

These are expected during graceful client shutdown but appear
alarming to operators and pollute production logs.

Root cause

grpc-go is doing the right thing: messages like this are gated
behind LoggerV2.V(2) in internal/transport/http2_server.go,
i.e. they are explicitly classified as supplemental verbosity, not
severity-level warnings. They should never reach a WARN sink.

The bug is in zapgrpc. zapgrpc.Logger.V is implemented as
levelEnabler.Enabled(zapcore.Level(level - 1)), which conflates
grpclog's verbosity with zap's severity. With WARN enabled,
V(2) returns true, so grpc-go thinks verbosity level 2 is
permitted and emits the message — which then gets formatted by
zapgrpc as a WARN entry. See uber-go/zap#1544.

Solution

Wrap the installed grpclog.LoggerV2 with a fixedVerbosityLogger
whose V() compares the requested level against a fixed threshold
(default 0) instead of the underlying zap severity enabler. This
restores grpclog's intended verbosity gating, so verbose messages
stop at grpc-go before formatting rather than being filtered out
of the log stream after the fact.

The default threshold of 0 matches grpclog when
GRPC_GO_LOG_VERBOSITY_LEVEL is unset.

This approach:

  • Addresses the root cause (broken V() mapping) rather than
    string-matching specific message contents.
  • Stops chatty messages at the source, before grpc-go formats them.
  • Is generic — kills all V(2)+ noise, including future grpc-go
    additions, not just two cherry-picked patterns.
  • Leaves severity-level emission (Info / Warning / Error)
    completely unchanged.
  • Tiny: ~10 lines of wrapper plus comment.

Testing

  • TestFixedVerbosity — asserts V(0) is enabled and V(1)/V(2)
    are not at the default threshold.
  • TestSeverityLevelsUnaffected — direct Warning("…") still
    fires at WARN, confirming the wrapper does not gate severity.
  • Existing TestGRPCLogger cases continue to pass unchanged.

@kruthiwusirika5
kruthiwusirika5 requested a review from a team as a code owner March 29, 2026 18:41
@kruthiwusirika5
kruthiwusirika5 requested a review from axw March 29, 2026 18:41
@linux-foundation-easycla

linux-foundation-easycla Bot commented Mar 29, 2026

Copy link
Copy Markdown

CLA Signed

The committers listed above are authorized under a signed CLA.

@kruthiwusirika5

Copy link
Copy Markdown
Contributor Author

recheck

@kruthiwusirika5

Copy link
Copy Markdown
Contributor Author

/easycla

@kruthiwusirika5
kruthiwusirika5 force-pushed the fix/zapgrpc-client-close-warning branch from 20d0b54 to 665e053 Compare March 29, 2026 18:49
@kruthiwusirika5

Copy link
Copy Markdown
Contributor Author

/easycla

@axw

axw commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

This feels like a bit of a heavy-handed solution.

grpc-go already has a verbosity check for these kinds of errors, e.g. see https://github.com/grpc/grpc-go/blob/d574bad188f25ba03d41a506e6f2ef93837ad10b/internal/transport/http2_server.go#L642

The zapgrpc implementation of the V method appears to be incorrect: https://github.com/uber-go/zap/blob/0ab0d5aae5986395e2ca497385d977ccd7cdfc5e/zapgrpc/zapgrpc.go#L234-L237

e.g. verbosity level 2 is being mapped to error level, which is nonsense for two reasons: error level logs should be less verbose than info, not more; grpc-go's verbosity is meant to be independent of the primary logger level.

So either we fix that upstream, or wrap the zapgrpc logger.

@axw

axw commented Apr 20, 2026

Copy link
Copy Markdown
Contributor

uber-go/zap#1544

grpc-go gates chatty per-RPC messages — including transport notices
logged on normal client disconnect — behind LoggerV2.V(2). zapgrpc's
V is implemented as levelEnabler.Enabled(level-1), conflating grpclog
verbosity with zap severity: with WARN enabled, V(2) returns true and
the messages emit at WARN as alarming "HandleStreams failed to read
frame: connection reset by peer" entries.

Wrap the installed grpclog.LoggerV2 with a corrected V() that compares
against a fixed verbosity threshold (default 0). Verbose messages now
stop at grpc-go before formatting, instead of being filtered out of
the log stream. Severity-level emission is unchanged.

Fixes open-telemetry#5169
See uber-go/zap#1544
@kruthiwusirika5
kruthiwusirika5 force-pushed the fix/zapgrpc-client-close-warning branch from b88740e to b37adb8 Compare April 27, 2026 20:19
@kruthiwusirika5

Copy link
Copy Markdown
Contributor Author

@axw thanks for the pointer to uber-go/zap#1544 — that crystallized it. Pushed a redesign that wraps the grpclog.LoggerV2 with a corrected V() instead of filtering log output. The chatty V(2)-gated transport messages now stop at grpc-go before formatting, rather than being string-matched out of the formatted stream. Severity-level emission is unaffected. PTAL when you have a moment.

@codecov

codecov Bot commented Apr 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.13%. Comparing base (efde8a2) to head (0570f24).
⚠️ Report is 13 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main   #15024   +/-   ##
=======================================
  Coverage   91.13%   91.13%           
=======================================
  Files         701      701           
  Lines       45745    45783   +38     
=======================================
+ Hits        41689    41724   +35     
- Misses       2843     2845    +2     
- Partials     1213     1214    +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@axw

axw commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

@kruthiwusirika5 code looks good, thanks. Please create a changelog entry (make chlog-new).

@kruthiwusirika5

Copy link
Copy Markdown
Contributor Author

@axw chloggen entry added in 008f6cd1f — bug_fix against pkg/otelcol, links #5169 and explains the underlying uber-go/zap#1544 for context. make chlog-validate passes locally.

Workflows on the new commit are sitting in action_required (first-time contributor gate) — could you trigger them? The previously-failing changelog check should flip green once it re-runs against this SHA.

@open-telemetry/collector-approvers — this PR is now ready for a final pass. Code redesign was acked by @axw two days ago; just the changelog entry was outstanding.

The new chloggen entry references three identifiers that the cspell
dictionary does not yet contain: grpclog, grpclog's, and zapgrpc. With
strict mode enabled the spell-check job fails on these unknown words.

Add the three terms in alphabetical order; they match the existing
convention of including both the bare and the possessive form (cf.
kafkaexporter / kafkaexporter's, mdatagen / mdatagen's).

The pre-existing "lifecycles" warnings against CHANGELOG.md and
CHANGELOG-API.md are not addressed here — they fail on main as well
and are outside the scope of this change.

Signed-off-by: Sai Kruthi Wusirika <kruthiwusirika@gmail.com>
@kruthiwusirika5

Copy link
Copy Markdown
Contributor Author

@axw spell-check fix pushed in 6b4fa6d34 — added grpclog, grpclog's, and zapgrpc to .github/workflows/utils/cspell.json (the three identifiers from the chloggen entry that tripped the strict-mode check). Followed the existing convention of including both bare and possessive forms (cf. kafkaexporter / kafkaexporter's).

The two pre-existing lifecycles warnings in CHANGELOG.md / CHANGELOG-API.md are not addressed here — they currently fail on main itself (last 3 Spell Check runs on main are failing for the same reason) so they're out of scope for this PR.

Workflows on the new commit are sitting in action_required again — could you give them the first-time-contributor approval so the spell-check job can re-run? Should be the only outstanding blocker.

@codspeed-hq

codspeed-hq Bot commented Apr 30, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

⚠️ Unknown Walltime execution environment detected

Using the Walltime instrument on standard Hosted Runners will lead to inconsistent data.

For the most accurate results, we recommend using CodSpeed Macro Runners: bare-metal machines fine-tuned for performance measurement consistency.

✅ 7 untouched benchmarks
⏩ 76 skipped benchmarks1


Comparing kruthiwusirika5:fix/zapgrpc-client-close-warning (0570f24) with main (c64a66b)

Open in CodSpeed

Footnotes

  1. 76 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@axw axw added the ready-to-merge Code review completed; ready to merge by maintainers label Apr 30, 2026
The cspell-action runs with strict=true and incremental_files_only=false,
so it fails the build on any unknown word anywhere in the repo, including
files this PR does not touch. The chloggen entry added in this branch is
clean (grpclog/zapgrpc were addressed in 6b4fa6d), but the run still
fails on a pre-existing 'lifecycles' usage in CHANGELOG.md and
CHANGELOG-API.md that has been failing on main for several consecutive
runs.

Adding the missing word to the dictionary so this PR's CI can converge
without waiting for a separate main-fix PR. The token is unambiguously
correct English; the omission is a dictionary gap, not a misspelling.

Signed-off-by: Sai Kruthi Wusirika <kruthiwusirika@gmail.com>
@kruthiwusirika5

Copy link
Copy Markdown
Contributor Author

@axw the spell-check rerun on 6b4fa6d34 revealed two remaining lifecycles warnings in CHANGELOG.md / CHANGELOG-API.md — both pre-existing and currently failing on main itself. Pushed 0570f24c which adds lifecycles to cspell.json to unblock this PR; commit message explains the scope. Workflows on the new commit need first-time-contributor approval again — sorry for the back-and-forth.

@mx-psi
mx-psi added this pull request to the merge queue Apr 30, 2026
Merged via the queue into open-telemetry:main with commit be4d221 Apr 30, 2026
65 checks passed
@otelbot

otelbot Bot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Thank you for your contribution @kruthiwusirika5! 🎉 We would like to hear from you about your experience contributing to OpenTelemetry by taking a few minutes to fill out this survey.

swiatekm pushed a commit to swiatekm/opentelemetry-collector that referenced this pull request May 15, 2026
…n-telemetry#15024)

Fixes open-telemetry#5169

## Problem

When a gRPC client disconnects normally (e.g., a .NET app shutting
down its `TracerProvider`), the collector logs spurious WARN messages
from the gRPC transport layer:

These are expected during graceful client shutdown but appear
alarming to operators and pollute production logs.

## Root cause

grpc-go is doing the right thing: messages like this are gated
behind `LoggerV2.V(2)` in `internal/transport/http2_server.go`,
i.e. they are explicitly classified as supplemental verbosity, not
severity-level warnings. They should never reach a WARN sink.

The bug is in zapgrpc. `zapgrpc.Logger.V` is implemented as
`levelEnabler.Enabled(zapcore.Level(level - 1))`, which conflates
grpclog's *verbosity* with zap's *severity*. With WARN enabled,
`V(2)` returns true, so grpc-go thinks verbosity level 2 is
permitted and emits the message — which then gets formatted by
zapgrpc as a WARN entry. See
[uber-go/zap#1544](uber-go/zap#1544).

## Solution

Wrap the installed `grpclog.LoggerV2` with a `fixedVerbosityLogger`
whose `V()` compares the requested level against a fixed threshold
(default 0) instead of the underlying zap severity enabler. This
restores grpclog's intended verbosity gating, so verbose messages
stop at grpc-go before formatting rather than being filtered out
of the log stream after the fact.

The default threshold of `0` matches grpclog when
`GRPC_GO_LOG_VERBOSITY_LEVEL` is unset.

This approach:

- Addresses the root cause (broken `V()` mapping) rather than
  string-matching specific message contents.
- Stops chatty messages at the source, before grpc-go formats them.
- Is generic — kills *all* `V(2)+` noise, including future grpc-go
  additions, not just two cherry-picked patterns.
- Leaves severity-level emission (`Info` / `Warning` / `Error`)
  completely unchanged.
- Tiny: ~10 lines of wrapper plus comment.

## Testing

- `TestFixedVerbosity` — asserts `V(0)` is enabled and `V(1)`/`V(2)`
  are not at the default threshold.
- `TestSeverityLevelsUnaffected` — direct `Warning("…")` still
  fires at WARN, confirming the wrapper does not gate severity.
- Existing `TestGRPCLogger` cases continue to pass unchanged.

---------

Signed-off-by: Sai Kruthi Wusirika <kruthiwusirika@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-merge Code review completed; ready to merge by maintainers

Projects

None yet

Development

Successfully merging this pull request may close these issues.

zapgrpc : Warning http2Server.HandleStreams failed to read frame - when client close

3 participants