Repository navigation
Filter AWS SDK trace noise by default - #53
Merged
Merged
Conversation
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
force-pushed
the
filter-aws-sdk-trace-noise-by-default
branch
from
August 3, 2026 16:40
7366f2e to
dd729c4
Compare
chris-peterson
marked this pull request as ready for review
August 3, 2026 16:45
chris-peterson
force-pushed
the
filter-aws-sdk-trace-noise-by-default
branch
from
August 3, 2026 22:25
c98fa97 to
87d787e
Compare
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
force-pushed
the
filter-aws-sdk-trace-noise-by-default
branch
from
August 3, 2026 22:47
87d787e to
5e95f8d
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.
Context
Spiffy.Monitoring.Awsinstalls aTraceListenerinto the AWS SDK's diagnostics pipeline, so anything the SDK reports shows up in the host application's structured logs asComponent=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
AwsSdkevents, 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 — aBeforeLoggingcallback hardcoding the same prefixes, orRemoveTraceListener, which runs too late to help because clients have already captured the listener.Review guide
Critical path
src/Spiffy.Monitoring.Aws/AwsProvider.cs:123— the whole filter decision: severity short-circuits, prefixes decide the rest,Warningand above are never filteredsrc/Spiffy.Monitoring.Aws/AwsProvider.cs:52— the severity is only available onTraceData;Write/WriteLinesee a flattened string, which is why the old code had to guessPublic surface
src/Spiffy.Monitoring.Aws/AwsConfigurationApi.cs:32—SdkNoisenames the seven kinds, so nothing is configured by magic stringsrc/Spiffy.Monitoring.Aws/AwsConfigurationApi.cs:60— kind → prefixes, ordered so the result is deterministic;RequestCanonicalizationcovers twosrc/Spiffy.Monitoring.Aws/AwsConfigurationApi.cs:91—Add/Remove/Clear, plus the string overload ofAddas the escape hatchTests
tests/UnitTests/AwsListenerTests.cs:189— the filtered cases use production message text verbatim, so a later prefix edit can't silently stop clearing this traffictests/UnitTests/AwsListenerTests.cs:211— the counterpart: throttling and failure notices arrive atInformationand must survivetests/UnitTests/AwsListenerTests.cs:332— guards the failure mode the enum introduces: a member added without a prefix mapping filters nothing, silentlytests/UnitTests/Properties/AssemblyInfo.cs:8— the listener builds itsEventContextfrom the ambientConfiguration, so capturing what it logs means installing a provider intoConfiguration.Default. Scoping that to a collection isn't enough, since xunit parallelizes across collectionsAncillary
docs/setup.md:41— the AWS provider had no docs section; this adds onedocs/setup.md:59— SDK v3 hands the exception to the listener and v4 discards it, soException_*fields appear only on v3tests/TestConsoleApp/Program.cs:84—-- awsuses the defaults,-- aws unfilteredclears them, for eyeballing the delta against a live SDKMechanical — skim only
src/Spiffy.Monitoring.Aws/Spiffy.Monitoring.Aws.csproj:15— 2.0.3 → 2.1.0tests/UnitTests/UnitTests.csproj:17andProperties/AssemblyInfo.cs— test wiring, soAwsEventis reachable from the suite.github/workflows/ci.yml:22— unrelated to the filtering, riding along: the three test/sample projects targetnet10.0sodotnet testruns on a machine with only a current SDK installed.publish.ymlandapi-compatibility.ymlmove to10.0.xas well, since each restores or builds the whole solution and an 8.0 SDK fails outright on anet10.0project. The five library projects keep their target frameworks —netstandard2.0on four,net6.0on PrometheusApproach & trade-offs
Deny-list, not allow-list. The SDK reports failures at informational severity as well as at
Error—RetryHandlerusesInfoFormatfor… Attempting retry N of M.andLogger.Errorfor… 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 viaAddfor noiseSdkNoisecan'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 tablematched only alongside aloaded from SDK Cachesuffix 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 isTableDescriptionrather thanTableDescriptionCache.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 atInfo. (3d75010added theMath.Minclamp to stop the originalIndexOf(…, 100, …)from throwing on short messages; the clamp fixed the throw, not the semantics.)Validation
AwsSdkevents — observed: 95.7% filtered, with every error,Received error response, andProvisionedThroughputExceededExceptionthrottling event retainedMessage="Single encoded *" OR Message="Double encoded *"— observed: 4.3M events/day, confirming the canonicalization lines are the largest single classTestConsoleApp -- awsvs-- aws unfiltered— observed: 9 events vs 27, error classification intactTwo default kinds are not covered by the production evidence above:
BufferResizingandTableDescriptionaren't reachable through the STS path the harness exercises, so they rest on reading the SDK source.DefaultConfigurationModeis 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.