feat(serilog)!: the Sentry sink no longer initializes the SDK - #5573
Conversation
The Sentry sink for Serilog now only configures the sink. Sentry must be initialized separately (SentrySdk.Init, UseSentry, etc). - SentrySerilogOptions no longer derives from SentryOptions and only carries sink settings; InitializeSdk is removed - Remove the WriteTo.Sentry(string dsn, ...) overload - Rename ApplySerilogScopeToEvents() to UseSerilog(), make it idempotent - The sink logs a one-time diagnostic warning when UseSerilog() was not called on the options used to initialize Sentry Part of #5245 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…into feat/no-init-from-logging-5245
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## version7 #5573 +/- ##
===========================================
Coverage ? 74.73%
===========================================
Files ? 516
Lines ? 18866
Branches ? 3676
===========================================
Hits ? 14100
Misses ? 3892
Partials ? 874 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Nice @jamescrosswell! Assuming we agree with this direction (Logging extension only configures the sink and does not inits the SDK), I think it's looking pretty good!
Other than that, I believe we're good to merge this PR! |
The sample sets the DSN in code via UseSentry, so the commented-out Dsn entry is misleading. EnableTracing is declared on BindableSentryOptions but never applied, so setting it has no effect. Part of #5245 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Good call. Done in 0aada87 |
Emit can run concurrently, so the check-then-set on the warned flag could let more than one thread log the warning. Part of #5245 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
I'm guessing we're talking about the scenario where someone initialises both Serilog and the Sdk via configuration bindings (e.g. We could try to bind to the DSN value in the logging config... not because we actually want to use it but just to check if someone has erroneously configured it there and so that we can show them a warning? EDIT: did a bit of research on this...
Normally I'd be against adding a dependency for a single warning... however this is a fairly major change and most apps would have that particular dependency already anyway, so we could do it. Also @ric-oliv thoughts? |
That one's trickier... the call to initialise the SDK is made from the The only other mechanisms that I can think of are:
I think any of those is a fairly complex piece of work that would have lots of edge cases and testing (given that people can initialise both Sentry and Serilog either from code and/or from configuration bindings, we don't know the order of initialisation, init logic may be delegated to code from other external modules etc.). If we do want to tackle it, I think it's a separate issue/PR and probably safer to assume we won't get it done before v7. |
Sdk.Name should identify the integration that initialised the hub, which after this change can no longer be a logging integration. The sink identifies itself through the log origin (auto.log.serilog) instead. See #5497. Events are no longer stamped with sentry.dotnet.serilog, and structured logs no longer carry it as sentry.sdk.name; both now report the SDK that initialised Sentry. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Exactly!
How do you feel about keeping the SentrySinkExtension "Sentry" with the DSN as an obsolete overload that will throw an exception whenever bounded to? (instead of deleting it). I'll create a PR into this branch so you can have a look if it's a good approach. As a side-note: the PR description says that the config-bound dsn args "will no longer bind to a Sentry sink method", but they do bind, that's why the issue is silent. |
…on error Serilog configuration providers bind sink arguments by parameter name, so removing the dsn-first overload made them drop `dsn` silently: the sink still binds, Sentry is never initialized, and nothing is reported. Keeping the overload as an [Obsolete(error: true)] tombstone that throws makes both Serilog.Settings.Configuration (appsettings.json) and Serilog.Settings.AppSettings (app.config) fail loudly with migration guidance, while code callers get a compile error instead of a type mismatch on the second argument. The overload mirrors the surviving overload's parameters plus `dsn`. With only `string dsn` it loses Serilog's overload ranking whenever a configuration supplies two or more of the surviving arguments, which would restore the silent behaviour. Part of #5245 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Yes, and we might be able to use SentryOptions to automatically register the The only issue currently is that In this case, the user should still call One thing I ran into while testing (unrelated to this PR) was the I've drafted #5612 just so you have an idea... the test suite passes but we might need to discuss it more. Your call if we follow this path or not (totally fine you see a problem with the approach, or if we should break this down in separate PRs, etc.) |
…ration The migration guard works only because of Serilog's overload ranking, and nothing exercised that path. These tests bind a sink from IConfiguration the way a provider does, so a Serilog change that stops selecting the tombstone fails here rather than silently dropping the DSN again. Verified they fail without the tombstone overload. Selection behaves the same on Serilog.Settings.Configuration 3.4.0 (Serilog 2.12) and 10.0.1 (Serilog 4.3); 3.4.0 is referenced to avoid bumping Serilog in the tests. Part of #5245 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Restores the DSN comment dropped from the Serilog sample's appsettings.json, pointing at where this sample actually sets it, and records in AGENTS.md that "prefer no comments" covers the library rather than samples - including their JSON configuration files. Part of #5245 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Hi @jamescrosswell,
I tested that sample with a Log.Error(...) call:
We can't make it fail at startup, because logging before UseSentry initializes is legitimate (bootstrap loggers). But we could signal it once at runtime. On the first event at or above MinimumEventLevel, if Sentry isn't initialized and a DSN can be found, write one line to SelfLog and to Console.Error. SelfLog alone only reaches people who have enabled it, so it wouldn't help anyone who doesn't know something is wrong. Either way, I think this belongs under Breaking changes, with a migration note. |
Before this PR, With the automatic registration from my last PR here, the sink registers the processor itself, in its constructor or on its first log event. So for everyone using the sink, LogContext properties are now added as tags on every event: unhandled exceptions and This matches Sentry.Extensions.Logging, where
Suggestions:
What do you think? |
Most of those things were true before this PR... we can create a follow up issue to try to make these more consistent. Some of the differences may be necessary due to functional differences between MEL and SeriLog though. The only one that's actually changed is the last one. With I've added a follow up to look into this - I think it can be prioritised separately though (not a stopper for v7):
Agreed.
Yeah sure, we'll have all this in the release notes for version 7.0. |
…try is not initialized The tombstoned overloads catch everyone who passes a DSN to the sink, but they cannot see the `WriteTo.Sentry(o => ...)` callback that only sets sink options and gets its DSN from SENTRY_DSN or a [Dsn] assembly attribute. On 6.x that overload initialized the SDK itself; now it compiles, nothing calls Init, and the sink drops everything silently. Warn once, on the first event at or above MinimumEventLevel, when the hub is disabled and a DSN can still be found. There is no DiagnosticLogger to write to in that state, so the warning goes to Serilog's SelfLog and to standard error. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 2925072. Configure here.
| { | ||
| return; | ||
| _uninitializedSdkWarning.WarnOnce(UninitializedSdkMessage); | ||
| } |
There was a problem hiding this comment.
Shutdown logs warn SDK never initialized
Low Severity
A disabled hub after Close or disposing the Init handle is treated the same as never initializing. If SENTRY_DSN (or a Dsn attribute) is still present, a later event-level log can write the one-time stderr warning telling the app to call SentrySdk.Init, even when Sentry was initialized correctly and then shut down.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 2925072. Configure here.
There was a problem hiding this comment.
Highly unlikely... that implies the SDK has been initialised, the app has been running and the SDK closed all before a single log is made (since the warning only ever fires once - it will only be triggered by the first log message if the SDK is not initialised at that time).
@ric-oliv implemented across all 4 PRs - adds a bit of complexity but for the transition from version6 to version7, probably worth keeping. We can always remove this in a later version. |
Co-authored-by: Ricardo Colombo Oliveira <github@ricoliv.com>
Conflict resolutions: - global.json, Directory.Build.props: kept version7's .NET 11 SDK/workload pins and the 7.0.0-prerelease version. - AGENTS.md: kept both sides. main added the good/bad comment examples; version7 has the samples exemption, which scopes the whole section and follows them. - integration-test/ios.Tests.ps1: kept version7's renamed source app (net9-maui became maui-device, so main's path no longer exists) plus main's stale bin/obj cleanup. - samples/Sentry.Samples.Google.Cloud.Functions/appsettings.json: took main. EnableTracing is gone from both SentryOptions and BindableSentryOptions, so it would be ignored. - samples/Sentry.Samples.AspNetCore.Serilog/appsettings.json: kept version7's DSN comment (main's commented-out Dsn would now mislead, since the sink takes no DSN) plus main's TracesSampleRate. - test/Sentry.Serilog.Tests/SentrySinkTests.cs: kept EmitBreadcrumb_WithException_ ProvidesExceptionInHint, which main added in #5523 and version7 has never had. Dropped Emit_SerilogSdk_Name and Emit_SerilogSdk_Packages, which #5573 removed along with the sink's SDK name. Two fixes the merge made necessary: - main pins SQLitePCLRaw.bundle_e_sqlite3 forward to 2.1.13 for every .NETCoreApp target to dodge GHSA-2m69-gcr7-jv3q, but EF Core 11 already floors it at 3.0.5, so on net11.0 the pin is a downgrade and NU1605 fails the build. net11.0 is now excluded from it. - ApiApprovalTests.Run.DotNet11_0 regenerated for HintTypes.Exception, the AddBreadcrumb hint overload and StringOrRegex.IsRegex. main updated its own snapshots and those merged cleanly, but the net11.0 one only exists here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…-logging-nlog-5245 version7 now has main merged into it, and #5573 has landed there, so this picks both up. All four conflicts took version7's side, which is strictly newer in each case: - AGENTS.md, samples/Sentry.Samples.AspNetCore.Serilog/appsettings.json and test/Sentry.Serilog.Tests/SentrySinkTests.cs were resolved when main was merged into version7; this branch only carried the pre-merge side of them. - src/Sentry.Serilog/SentryOptionExtensions.cs: the UseSerilog() doc comment here still said the sink "cannot do this for you", which stopped being true when #5612 made the sink register the scope processor itself. version7 has the corrected wording. The merge also brings #5523's breadcrumb hint to the NLog target (hint: exception.ToHint()) and its test, which don't overlap with anything in this PR. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…245' into feat/no-init-from-logging-log4net-5245 Picks up version7 (which now has main merged in) and #5573 via the NLog branch. No conflicts. main added test/Sentry.Log4Net.V3.Tests, which shares the appender test sources by <Compile Include> and runs them against log4net 3.4.0 instead of 2.0.12. The new SentryAppenderUninitializedSdkTests.cs is now included there too, so the runtime warning is covered on both log4net majors. That also confirms LogLog.Warn(Type, string) is binary compatible across the two, which the appender relies on: Sentry.Log4Net compiles against 2.0.12 but the V3 tests load it against 3.4.0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…t-5245' into feat/no-init-from-logging-mel-5245 Picks up version7 (which now has main merged in) plus #5573 and the NLog and log4net branches. test/Sentry.Extensions.Logging.Tests/SentryLoggingOptionsSetupTests.cs was the only textual conflict, and kept this branch's side: the test asserts only MinimumBreadcrumbLevel and MinimumEventLevel because SentryLoggingOptions no longer derives from SentryOptions, so the core SDK properties the other side binds don't exist on it. version7's only change to that file was #5631 removing three assertions this version never makes. Two things merged cleanly but did not compile, where main's new log entry filters meet this branch's options split: - Main moved the category, EF and filter checks out of ShouldCaptureEvent into their own early return, leaving it level-only, so the runtime warning's gate no longer had a 3-argument overload to call. It now goes through WouldCaptureEvent, which mirrors the real path. - SentryLogger reported a failing filter callback via _options.LogError, which only resolves while SentryLoggingOptions is a SentryOptions. It now reports through the hub's options, matching how the structured logger reads its defaults on this branch. The two tests covering it set the diagnostic logger substitute on the hub's options instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>


The Serilog portion of #5245, following the design in this comment. The Sentry sink for Serilog now only configures the sink; Sentry has to be initialized separately via
SentrySdk.Init,UseSentry, etc.Part of #5245
Breaking changes
SentrySerilogOptionsno longer derives fromSentryOptions. It carries only sink settings:MinimumEventLevel,MinimumBreadcrumbLevel,FormatProvider,TextFormatter,RestrictedToMinimumLevel,LevelSwitch. Anything else (Dsn,Release,SampleRate, …) belongs on the options used to initialize Sentry.SentrySerilogOptions.InitializeSdkis removed.WriteTo.Sentry(string dsn, …)overload is removed. Note thatSerilog.Settings.Configurationdoes not fail when a configuration still suppliesdsn: it filters candidate methods on the method's own parameters, so unmatched arguments are dropped silently — not even toSelfLog. The sink is attached, Sentry is never initialized, and the app reports nothing. feat(serilog): configuring a DSN on the sink now fails with a migration error #5611 stacks a tombstone overload on this branch to turn that into a loud failure.ApplySerilogScopeToEvents()is renamedUseSerilog(), to matchUseOpenTelemetry(). It returnsvoidlike its siblings and is now idempotent.WriteTo.Sentry(o => …)configuration that only sets sink settings still compiles, and nothing in the API can catch it. On 6.x that overload initialized the SDK itself (InitializeSdkdefaulted totrue), taking the DSN fromSENTRY_DSNor a[Dsn]assembly attribute; now the sink is attached,Initis never called, and every event is dropped. The sink therefore warns at runtime: on the first log event at or aboveMinimumEventLevel, if Sentry is not initialized but a DSN can still be found, it writes one line to Serilog'sSelfLogand to standard error, at most once per sink. Thanks to @ric-oliv for spotting this.sentry.dotnet.serilogas the SDK name.Sdk.Nameidentifies the integration that initialized Sentry, and the sink identifies itself through the log origin (auto.log.serilog). See Metrics andSentrySdk.Loggerlogs emitted during a request carry nosentry.sdk.name/sentry.sdk.versionon ASP.NET Core #5497.Before:
After:
Notes for review
UseSerilog()registers the processor that copies SerilogLogContextproperties onto events. It has to live on theSentryOptionsused for init, because it enriches every event, not only ones the sink creates. To keep that discoverable, the sink logs a one-time diagnostic warning when it's missing (only visible withDebug = true).WriteTo.Sentry(o => …)overload initialized the SDK but never registered that processor — only thedsnparameter overload did. That's whyIntegrationTests.Simplesnapshots gaininventory/MyTaskIdtags: the test now callsUseSerilog(), and the processor is actually running.SelfLogbecause there is noDiagnosticLoggerto write to when the SDK was never initialized, andSelfLogonly reaches people who already suspect a problem. It is gated on a DSN being discoverable, so an app that deliberately runs without Sentry stays quiet. The shared policy lives inSentry.Internal.UninitializedSdkWarning; each integration supplies its own channel and message.IDisposable. It never owned the hub, soLog.CloseAndFlush()no longer disposes the SDK; disposing the handle fromSentrySdk.Initdoes that.SerilogAspNetSentrySdkTestFixturewas initializing the SDK twice (once viaWriteTo.Sentry(ValidDsn), then again viaUseSentry); it now only initializes viaUseSentry.ApiApprovalTests.Run.Net4_8.verified.txtcan't regenerate on macOS; it was byte-identical to theDotNet10_0snapshot before this change, so it's a copy of the regenerated one.NLog, log4net and Microsoft.Extensions.Logging follow separately. The generic host replacement for
builder.Logging.AddSentry(dsn)is tracked in #5572.🤖 Generated with Claude Code