Repository navigation
Optionally preserve logging scopes in buffered log records - #7807
evgenyfedorov2 wants to merge 1 commit into
Conversation
🎉 Good job! The coverage increased 🎉
Full code coverage report: https://dev.azure.com/dnceng-public/public/_build/results?buildId=1624548&view=codecoverage-tab |
🎉 Good job! The coverage increased 🎉
Full code coverage report: https://dev.azure.com/dnceng-public/public/_build/results?buildId=1625111&view=codecoverage-tab |
🎉 Good job! The coverage increased 🎉
Full code coverage report: https://dev.azure.com/dnceng-public/public/_build/results?buildId=1626028&view=codecoverage-tab |
🎉 Good job! The coverage increased 🎉
Full code coverage report: https://dev.azure.com/dnceng-public/public/_build/results?buildId=1626139&view=codecoverage-tab |
🎉 Good job! The coverage increased 🎉
Full code coverage report: https://dev.azure.com/dnceng-public/public/_build/results?buildId=1626234&view=codecoverage-tab |
Adds the experimental GlobalLogBufferingOptions.IncludeScopes and PerRequestLogBufferingOptions.IncludeScopes
options (EXTEXP0003). When an option is enabled, the buffer adds the name/value pairs of the logging scopes
that are active when a log record is buffered to the record's attributes. IBufferedLogger implementations
get them in BufferedLogRecord.Attributes, and other loggers get them in the state passed to ILogger.Log.
- Only scopes that implement IEnumerable<KeyValuePair<string, object?>> are added, without their
"{OriginalFormat}" pairs. The log record's own "{OriginalFormat}" stays the last attribute.
- Scope values are converted to strings and count toward the size limits.
- The buffers get the IExternalScopeProvider through constructor injection. AddGlobalBuffer registers one
with TryAddSingleton, and ExtendedLoggerFactory uses it too, so the factory and the buffers share it.
- Both options bind from configuration, and the delegate overload of AddPerIncomingRequestBuffer copies
IncludeScopes to the global buffer options.
Fixes #7781
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
808400a to
6d8e6a9
Compare
🎉 Good job! The coverage increased 🎉
Full code coverage report: https://dev.azure.com/dnceng-public/public/_build/results?buildId=1626352&view=codecoverage-tab |
Log buffering drops
ILogger.BeginScopedata today: buffered records are emitted without the scopes that were active when they were logged. This was raised in the #5635 review. This PR adds an opt-in way to keep that data by adding the scopes' name/value pairs to the attributes of buffered records.New API (experimental, EXTEXP0003)
GlobalLogBufferingOptions.IncludeScopesPerRequestLogBufferingOptions.IncludeScopesBoth default to
falseand bind from configuration. The buffers read them each time they buffer a record, so options reloads take effect. The name matchesOpenTelemetryLoggerOptions.IncludeScopesandConsoleFormatterOptions.IncludeScopes, which are also opt-in. I avoidedCaptureScopesbecauseLoggerFilterOptions.CaptureScopesis the factory-wide switch and defaults totrue. The delegate overload ofAddPerIncomingRequestBuffercopies the value to the global fallback buffer, the same way it already copiesRules.How it works
IncludeScopesis on,GlobalBufferandIncomingRequestLogBufferread the current scopes, includingActivityTrackingOptionsscopes, and add each scope's name/value pairs to the record's attributes.{OriginalFormat}, which stays the last attribute.ExtendedLoggerguarantees that since [Telemetry, Logging] Emit {OriginalFormat} as the last property of the log record #6111, and OpenTelemetry up to 1.15.0 only looks for the message template in the last attribute.IEnumerable<KeyValuePair<string, object?>>are added, without their{OriginalFormat}pairs. Other scopes, such as strings andDictionary<string, string>instances, are skipped. Console's JSON formatter and OpenTelemetry also treat only such scopes as name/value pairs.IBufferedLoggerimplementations get the pairs inBufferedLogRecord.Attributes. Other loggers get them in the state passed toILogger.Log.IExternalScopeProviderthey get through constructor injection.AddGlobalBufferregisters one withTryAddSingleton, andExtendedLoggerFactoryalready uses anIExternalScopeProviderfrom DI when there is one, so the factory and the buffers share it.Worth a careful look
BeginScopealways records into it, even whenIncludeScopesis off.ISupportExternalScope, this costs 256 bytes perBeginScope(measured on net10.0). The Console and OpenTelemetry providers implement it, so in typical apps the factory records scopes anyway.IExternalScopeProviderthat an app registers itself, or decide at startup, which needs a DI registration that returns null. Autofac throws on that.Testing
New tests cover:
{OriginalFormat}, with string scopes,Dictionary<string, string>scopes and the scopes'{OriginalFormat}pairs skipped{OriginalFormat}The config-binding tests and the per-request delegate overload tests are extended.
The full
Microsoft.Extensions.Telemetry.TestsandMicrosoft.AspNetCore.Diagnostics.Middleware.Testssuites pass on net8.0, net9.0, and net10.0.The learn.microsoft.com log buffering limitations page will need a follow-up in dotnet/docs.
Fixes: #7781
Microsoft Reviewers: Open in CodeFlow