Skip to content

Filter AWS SDK trace noise by default - #53

Merged
chris-peterson merged 2 commits into
mainfrom
filter-aws-sdk-trace-noise-by-default
Aug 10, 2026
Merged

chris-peterson merged 2 commits into
mainfrom
filter-aws-sdk-trace-noise-by-default

Conversation

@chris-peterson

@chris-peterson chris-peterson commented Aug 3, 2026 •

Copy link
Copy Markdown
Owner

Context

Spiffy.Monitoring.Aws installs a TraceListener into the AWS SDK's diagnostics pipeline, so anything the SDK reports shows up in the host application's structured logs as Component=AwsSdk Operation=Event. Everything the SDK emitted became a log event, minus a two-entry deny-list. This makes that filtering the library's job instead of every consumer's, and classifies each event from the severity the SDK actually reported rather than guessing from the message text. Closes #52.

The SDK reports a great deal at informational severity, and some of it repeats on every call while carrying nothing actionable. A production log query over one window returned 2.9M AwsSdk events, 94% of which were a single message restating the SDK's own user-agent string; a separate query put the request-signing canonicalization lines at 4.3M/day on their own. Consumers were each solving this by hand — a BeforeLogging callback hardcoding the same prefixes, or RemoveTraceListener, which runs too late to help because clients have already captured the listener.

Review guide

Critical path

Public surface

Tests

Ancillary

  • docs/setup.md:41 — the AWS provider had no docs section; this adds one
  • docs/setup.md:59 — SDK v3 hands the exception to the listener and v4 discards it, so Exception_* fields appear only on v3
  • tests/TestConsoleApp/Program.cs:84 — -- aws uses the defaults, -- aws unfiltered clears them, for eyeballing the delta against a live SDK

Mechanical — skim only

Approach & trade-offs

Deny-list, not allow-list. The SDK reports failures at informational severity as well as at Error — RetryHandler uses InfoFormat for … Attempting retry N of M. and Logger.Error for … Attempt N.. So an allow-list keyed on severity would have dropped the DynamoDB throttling notices, which is the signal most worth alerting on in the production sample. Filtering named noise keeps unrecognized messages flowing by default.

Kinds, not prefix strings. A consumer can't guess "Resolved DefaultConfigurationMode for RegionEndpoint", and the worst-served case was keeping one thing the defaults filter — previously that meant re-listing the other six. Remove(SdkNoise.UserAgentHeader) does it directly, and one kind can front several prefixes. Raw strings remain available via Add for noise SdkNoise can't name.

Prefix matching, not regex. Every message in the sample is distinguishable by its leading literal, and a prefix can't backtrack on a multi-megabyte-per-day path. The cost is that a message needing a mid-string match has to be handled by the consumer. One filter widened as a result: Description for table matched only alongside a loaded from SDK Cache suffix before, and now covers a description the SDK loaded as well as one it served from cache. Both are routine DynamoDB chatter, so the kind is TableDescription rather than TableDescriptionCache.

Severity replaces a text heuristic that had a real bug. The old classifier called IndexOf("exception", Math.Min(100, message.Length), …). The intent was to search the first 100 characters, but that parameter is a start index, so for any message shorter than 100 the search began at its own end and returned -1. The SDK's canonical error string is 76 characters, so it was logged at Info. (3d75010 added the Math.Min clamp to stop the original IndexOf(…, 100, …) from throwing on short messages; the clamp fixed the throw, not the semantics.)

Validation

  • Classified against a production log query — 2.9M AwsSdk events — observed: 95.7% filtered, with every error, Received error response, and ProvisionedThroughputExceededException throttling event retained
  • Anchored prefix query re-run against production — Message="Single encoded *" OR Message="Double encoded *" — observed: 4.3M events/day, confirming the canonicalization lines are the largest single class
  • Exercised against a live SDK v4 client — TestConsoleApp -- aws vs -- aws unfiltered — observed: 9 events vs 27, error classification intact

Two default kinds are not covered by the production evidence above: BufferResizing and TableDescription aren't reachable through the STS path the harness exercises, so they rest on reading the SDK source. DefaultConfigurationMode is carried over unchanged and I couldn't locate it in current SDK source at all — an unmatched prefix is inert, so it's left in place.

@chris-peterson chris-peterson self-assigned this Aug 3, 2026
The AWS SDK reports a great deal at informational severity, and some of
it repeats on every call while carrying nothing actionable. A production
log query returned 2.9M events over the sample window, of which 94% were
a single message restating the SDK's own user-agent string; a separate
query put the request-signing canonicalization lines at 4.3M/day on
their own. Consumers were each suppressing this by hand, either with a
BeforeLogging callback that hardcoded the same prefixes or with
RemoveTraceListener, which runs too late to help because clients have
already captured the listener. Requiring upstream code to filter noise
spiffy chose to emit is backwards.

Gate on the severity the SDK reports, and apply prefix matching only
where severity can't decide. Anything at Warning or above is always
logged; Information and Verbose are matched against a deny-list. The
list is a deny-list rather than an allow-list on purpose: the SDK
reports throttling and retry failures at Information, so an allow-list
would drop the signal worth alerting on. SuppressMessages lets an
application extend the defaults, replace them, or turn suppression off.

Severity arrives via TraceSource.TraceData, which is where both SDK
majors report -- v3 through InternalSystemDiagnosticsLogger, v4 through
DiagnosticAdaptorLogger. Overriding TraceData also replaces a
classification heuristic that never worked: it searched for "exception"
starting at Math.Min(100, length), treating the offset as a start index
rather than a count, so every message shorter than 100 characters was
searched from its own end. The SDK's canonical error string is 76
characters, which means it has been logged at Info all along.

Note that the two SDK majors differ in what reaches the listener. v3
passes the exception alongside the message, so it is broken out into
Exception_* fields; v4 accepts an exception and discards it before the
listener sees it, leaving only the message text.

This is a behavior change: log lines emitted today stop being emitted,
so anyone searching or alerting on them will see them disappear. The
public surface only grows, so Spiffy.Monitoring.Aws goes to 2.1.0.

Refs: #52
@chris-peterson
chris-peterson force-pushed the filter-aws-sdk-trace-noise-by-default branch from 7366f2e to dd729c4 Compare August 3, 2026 16:40
@chris-peterson
chris-peterson marked this pull request as ready for review August 3, 2026 16:45
@chris-peterson
chris-peterson force-pushed the filter-aws-sdk-trace-noise-by-default branch from c98fa97 to 87d787e Compare August 3, 2026 22:25
Review feedback on the filtering added in the previous commit, in three
parts.

The listener had two predicates, IsActionable(eventType) and
IsSuppressed(message), combined at the call site. IsActionable asserted a
spiffy-side judgment over the SDK's own severity taxonomy, and
IsSuppressed shadowed EventContext.IsSuppressed, which already exists and
suppresses an entire event rather than filtering one message. They are now
one IsNoise(eventType, message): severity short-circuits, prefixes decide
the rest.

The config took prefixes as strings, which a consumer could only discover
by reading spiffy's source. Nobody guesses "Resolved
DefaultConfigurationMode for RegionEndpoint". SdkNoise names the seven
kinds instead, and a kind can cover more than one prefix -- request
canonicalization is reported as both "Single encoded" and "Double
encoded", which strings forced callers to know as two separate things. A
string overload of Add remains for noise SdkNoise can't name, either
application-specific or from a newer SDK.

Keeping one thing the default filters was the case strings served worst:
it meant re-listing the other six. Remove(SdkNoise.UserAgentHeader) does
it directly. The property is plural -- NoiseFilters -- because
"NoiseFilter.Remove(UserAgentHeader)" scans as "remove the user-agent
noise", which is the opposite of what it does; removing one of several
filters reads the right way round. Only and None are gone, since Clear
plus Add says the same thing.

Five tests cover what the redesign added: Remove on its own, a kind whose
removal has to clear both its prefixes, and a guard that every SdkNoise
member maps to at least one prefix -- a member added without one would
filter nothing, and do it silently.

Moving to prefixes widened one filter without saying so: the code it
replaced matched "Description for table" only when the message also ended
"loaded from SDK Cache", so a description the SDK loaded rather than
served from cache used to reach the log. Filtering both is the intent --
either way it's routine DynamoDB chatter -- so the kind is TableDescription
rather than TableDescriptionCache, and the docs no longer say "cached".

Test parallelization is off assembly-wide, replacing the AwsListener
collection. The listener builds its EventContext from the ambient
Configuration, so the tests capture what it logs by installing a provider
into Configuration.Default. A collection only serializes its own
scenarios and xunit parallelizes across collections, so any test elsewhere
that disposes an EventContext constructed without an explicit
Configuration would publish into the AWS tests' capture list. Nothing in
the suite does that today, which is why it passed.

The null guards in Add and Remove are gone. Both Add overloads take
params, so Add(null) is ambiguous and won't compile without a cast: the
guards read as handling a call the overload set forbids. A null array is a
caller bug and now throws.

Riding along, unrelated to the filtering: the test and sample projects
target net10.0 instead of net8.0, so `dotnet test` runs on a machine with
only a current SDK installed. All three workflows move to 10.0.x, not just
CI -- publish and the api-compatibility matrix each restore or build the
whole solution, and an 8.0 SDK fails outright on a net10.0 project. The
five library projects keep their target frameworks: netstandard2.0 on
four, net6.0 on Prometheus.

Refs: #52
@chris-peterson
chris-peterson force-pushed the filter-aws-sdk-trace-noise-by-default branch from 87d787e to 5e95f8d Compare August 3, 2026 22:47
@chris-peterson
chris-peterson merged commit 6e518a3 into main Aug 10, 2026
41 checks passed
@chris-peterson
chris-peterson deleted the filter-aws-sdk-trace-noise-by-default branch August 24, 2026 17:35
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.

Filter AWS SDK trace noise by default

1 participant