Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
120 commits
Select commit Hold shift + click to select a range
0add668
feat(logs): initial experiment
Flash0ver Apr 30, 2025
db7d558
feat(logs): basic Logger Module API shape
Flash0ver Apr 30, 2025
d96b092
style(logs): consolidate
Flash0ver May 5, 2025
2958a47
ref(logs): remove generic WriteSerializable overload
Flash0ver May 5, 2025
a63371b
ref(logs): consolidate experimental Diagnostic-ID
Flash0ver May 5, 2025
d8d2567
feat(logs): add experimental options
Flash0ver May 7, 2025
fefd24c
Merge branch 'main' into feat/logs-initial-api
Flash0ver May 7, 2025
165996a
ref(logs): remove custom polyfills now provided through Polyfill
Flash0ver May 7, 2025
32e7e25
Format code
getsentry-bot May 7, 2025
fc88722
Merge branch 'feat/logs-initial-api' of https://github.com/getsentry/…
Flash0ver May 7, 2025
2ba87e4
ref(logs): move types out of Experimental namespace
Flash0ver May 7, 2025
0f1d4a4
feat(logs): change 'integer' from Int32 to Int64
Flash0ver May 7, 2025
8dec5d5
ref(logs): refine API surface area
Flash0ver May 8, 2025
a664f7e
ref(logs): match SeverityLevel to OTel spec
Flash0ver May 8, 2025
96693d0
ref(logs): rename SentrySeverity to LogSeverityLevel
Flash0ver May 8, 2025
83964cf
Format code
getsentry-bot May 8, 2025
dadc69b
ref(logs): hide underlying Dictionary`2 for Attributes
Flash0ver May 9, 2025
c91cdde
ref(logs): restructure attributes
Flash0ver May 9, 2025
0740c3b
ref(logs): extract TraceId and ParentSpanId methods
Flash0ver May 9, 2025
eee06bf
ref(logs): remove `SentryOptions.LogsSampleRate`
Flash0ver May 12, 2025
8c61d8b
feat(logs): support ISystemClock abstraction
Flash0ver May 12, 2025
80683ae
ref(logs): disambiguate SentryLogger names
Flash0ver May 12, 2025
cb20118
ref(logs): consolidate names of Log-Methods
Flash0ver May 12, 2025
2cb306f
ref(logs): rename LogSeverityLevel to SentryLogLevel
Flash0ver May 12, 2025
dcc0ec1
ref(logs): re-rename new logger type
Flash0ver May 13, 2025
58dce74
ref(logs): move Logger instances to Hubs
Flash0ver May 14, 2025
f2e1ba2
test(logs): add tests
Flash0ver May 14, 2025
0220015
Merge branch 'main' into feat/logs-initial-api
Flash0ver May 14, 2025
6822b23
Format code
getsentry-bot May 14, 2025
dd39fae
fix(logs): incorrectly serializing attributes
Flash0ver May 14, 2025
6eb5b9b
fix(logs): do not capture log when template/parameters are invalid
Flash0ver May 14, 2025
69c05b8
fix(logs): do not capture log on user callback exceptions
Flash0ver May 14, 2025
430cf82
ref(logs): move new public types to root namespace
Flash0ver May 14, 2025
31a8f1f
ref(logs): rework sample
Flash0ver May 14, 2025
2ae4476
ref(logs): ensure that DisabledHub dues not capture logs
Flash0ver May 14, 2025
69678ce
ref(logs): allow out-of-range Log-Level
Flash0ver May 14, 2025
97995a8
docs(logs): add XML comments indicating that logs will be ignored on …
Flash0ver May 14, 2025
fbe747d
docs(logs): add to changelog
Flash0ver May 15, 2025
64adf33
fix(logs): add to Bindable-Options
Flash0ver May 15, 2025
cdfa901
fix(logs): add to ApiApprovalTests
Flash0ver May 15, 2025
d2ac53b
test(logs): add missing net48 ApiApproval
Flash0ver May 15, 2025
4011ba6
test(logs): fix line endings on Windows
Flash0ver May 15, 2025
4ae82d0
Update src/Sentry/Protocol/Envelopes/Envelope.cs
Flash0ver May 15, 2025
bc1c465
Update SentryLog.cs
Flash0ver May 15, 2025
79fb190
test(logs): fix floating-point ToString expectation for .NET Framework
Flash0ver May 15, 2025
b4e80f4
ref(logs): remove some using declarations
Flash0ver May 15, 2025
b21adef
test(ci): trying to work around floating-point formatter on .NET Fram…
Flash0ver May 15, 2025
0032858
test(logs): skip failing tests on Mono (non-Windows)
Flash0ver May 15, 2025
a9769f8
test(log): fix Skip.If missing SkippableFact
Flash0ver May 15, 2025
9a51033
try: fix floating-point formatting on Windows
Flash0ver May 15, 2025
8d03449
feat(logs): Sentry.Extensions.Logging
Flash0ver May 16, 2025
1bc3a6f
feat(logs): Sentry.AspNetCore
Flash0ver May 16, 2025
7624baa
feat(logs): Sentry.Maui
Flash0ver May 16, 2025
11fe02b
fix(logs): Logging Filter
Flash0ver May 16, 2025
fd532ca
Format code
getsentry-bot May 16, 2025
1fe9cc0
feat(logs): add CHANGELOG entry
Flash0ver May 16, 2025
476cbc3
Merge branch 'feat/logs-microsoft-extensions-logging' of https://gith…
Flash0ver May 16, 2025
9a09832
Merge branch 'main' into feat/logs-initial-api
Flash0ver May 21, 2025
d9ce523
Merge branch 'main' into feat/logs-microsoft-extensions-logging
Flash0ver May 21, 2025
3e6dba5
release: 5.8.0-alpha.0
getsentry-bot May 21, 2025
23934dc
Merge branch 'release/5.8.0-alpha.0' into feat/logs-microsoft-extensi…
May 22, 2025
f133118
Merge branch 'main' into feat/logs-initial-api
Flash0ver Jun 3, 2025
0985a76
Merge branch 'main' into feat/logs-microsoft-extensions-logging
Flash0ver Jun 3, 2025
72c9a93
ref: make SentryStructuredLogger abstract
Flash0ver Jun 4, 2025
c97f4ad
docs: add comment to sample usage of SetBeforeSendLog
Flash0ver Jun 5, 2025
a9eea90
ref: clarify intent of usages of Debug.Assert
Flash0ver Jun 5, 2025
8bd0ed2
docs: improve XML comments
Flash0ver Jun 5, 2025
51892de
test: range of Severity-Number specification
Flash0ver Jun 5, 2025
acc8995
test: GetValuesAsUnderlyingType of the new enum
Flash0ver Jun 5, 2025
6bd4c96
ref: move Log options to Experimental section
Flash0ver Jun 5, 2025
f673d1e
ref: move Logger to Experimental section of SDK
Flash0ver Jun 5, 2025
daafd7f
test: add Hub tests
Flash0ver Jun 5, 2025
62ee5d5
Merge branch 'feat/logs' into feat/logs-initial-api
Flash0ver Jun 6, 2025
6a54203
test: update verified public API
Flash0ver Jun 6, 2025
479cab8
docs: update CHANGELOG.md
Flash0ver Jun 6, 2025
f59160b
Merge branch 'feat/logs-initial-api' into feat/logs-microsoft-extensi…
Flash0ver Jun 6, 2025
18a8284
ref: move Log-rerelated Options to Experimental sub-section
Flash0ver Jun 6, 2025
97a87f8
Merge branch 'main' into feat/logs-initial-api
jamescrosswell Jun 9, 2025
0467449
Merge branch 'feat/logs' into feat/logs-initial-api
Flash0ver Jun 16, 2025
54062d2
ref: reuse Disabled-Instance when Structured-Logging is not enabled
Flash0ver Jun 16, 2025
7107bce
ref: remove Enabled-checks on Default-Logger
Flash0ver Jun 16, 2025
c0a1cd5
ref: rename DisabledSentryStructuredLogger to NoOpSentryStructuredLogger
Flash0ver Jun 16, 2025
45b8687
ref: make `BindableSentryOptions.Experimental` internal
Flash0ver Jun 16, 2025
3192534
Revert "ref: make `BindableSentryOptions.Experimental` internal"
Flash0ver Jun 16, 2025
b8bcea6
docs: Update CHANGELOG.md
Flash0ver Jun 16, 2025
d4c82a2
Revert "ref: rename DisabledSentryStructuredLogger to NoOpSentryStruc…
Flash0ver Jun 18, 2025
9193a96
ref: replace use of ScopeManager with TraceHeader
Flash0ver Jun 18, 2025
7cb4043
test: remove Skip as we no longer test net48 against non-Windows
Flash0ver Jun 18, 2025
e3ca5b5
feat: support more numeric types
Flash0ver Jun 20, 2025
afb135e
feat: support char attributes
Flash0ver Jun 20, 2025
7f675aa
fix: build error targeting .NET Standard 2.0 and .NET Framework
Flash0ver Jun 20, 2025
750a388
fix: exception when passing null as message parameter
Flash0ver Jun 20, 2025
9f62d3b
test: add Attributes-To-Json test
Flash0ver Jun 20, 2025
5b00c21
fix: missing type on .NET Framework
Flash0ver Jun 24, 2025
6d17918
feat: support Attribute-Types from spec
Flash0ver Jun 24, 2025
7e2c57b
ref: clarify internal identifiers
Flash0ver Jun 24, 2025
7115c42
test: update approved API
Flash0ver Jun 24, 2025
cd5246b
test: fix incorrect expectation
Flash0ver Jun 24, 2025
baf5569
Merge branch 'feat/logs' into feat/logs-initial-api
Flash0ver Jun 24, 2025
e592d03
Merge branch 'feat/logs-initial-api' into feat/logs-microsoft-extensi…
Flash0ver Jun 24, 2025
6e13e95
feat: use "wrapping" SDK's Name and Version
Flash0ver Jun 25, 2025
0a9a3b1
fix: Get-Attribute-API
Flash0ver Jun 25, 2025
934fb36
Merge branch 'feat/logs' into feat/logs-initial-api
Flash0ver Jun 25, 2025
2c1608e
docs: update CHANGELOG
Flash0ver Jun 25, 2025
2b09a79
Merge branch 'feat/logs-initial-api' into feat/logs-microsoft-extensi…
Flash0ver Jun 25, 2025
2950be9
docs: update CHANGELOG
Flash0ver Jun 25, 2025
d428163
Merge branch 'feat/logs' into feat/logs-microsoft-extensions-logging
Flash0ver Jul 11, 2025
a936ec6
test: change options to Experimental section
Flash0ver Jul 11, 2025
0fae148
Merge branch 'feat/logs' into feat/logs-microsoft-extensions-logging
Flash0ver Jul 23, 2025
8fc7a23
Merge branch 'feat/logs' into feat/logs-microsoft-extensions-logging
Flash0ver Jul 29, 2025
459e45d
docs: merge CHANGELOG
Flash0ver Jul 29, 2025
92698dd
ref/fix: M.E.L integrations
Flash0ver Jul 29, 2025
1277a2f
fix: M.E.L. formatter may throw when CA2017 is violated
Flash0ver Jul 30, 2025
5a880e0
fix: Logger uses .NET MAUI SDK name
Flash0ver Jul 30, 2025
d6083bd
fix: send log timestamp with milliseconds
Flash0ver Jul 30, 2025
353a8de
perf: avoid redundant array copy
Flash0ver Jul 30, 2025
0de2ba9
Update CHANGELOG.md
Flash0ver Jul 31, 2025
bef0de6
ref(docs): remove ExperimentalAttribute on internal types and members
Flash0ver Aug 2, 2025
9463aa7
style(logs): use Recursive Pattern Matching
Flash0ver Aug 2, 2025
8a89441
test(logs): add coverage for missing message
Flash0ver Aug 2, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## Unreleased

### Features

- Add experimental support for [Sentry Structured Logging](https://docs.sentry.io/product/explore/logs/) API ([#4158](https://github.com/getsentry/sentry-dotnet/pull/4158), [#4310](https://github.com/getsentry/sentry-dotnet/pull/4310))
- Add experimental integrations for `Sentry.Extensions.Logging`, `Sentry.AspNetCore` and `Sentry.Maui` ([#4193](https://github.com/getsentry/sentry-dotnet/pull/4193))

### Fixes

- Native AOT: don't load SentryNative on unsupported platforms ([#4347](https://github.com/getsentry/sentry-dotnet/pull/4347))
Expand All @@ -20,7 +25,6 @@

### Features

- Add experimental support for [Sentry Structured Logging](https://docs.sentry.io/product/explore/logs/) via `SentrySdk.Experimental.Logger` ([#4158](https://github.com/getsentry/sentry-dotnet/pull/4158))
- Added StartSpan and GetTransaction methods to the SentrySdk ([#4303](https://github.com/getsentry/sentry-dotnet/pull/4303))

### Fixes
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
```

BenchmarkDotNet v0.13.12, macOS 15.5 (24F74) [Darwin 24.5.0]
Apple M3 Pro, 1 CPU, 12 logical and 12 physical cores
.NET SDK 9.0.301
[Host] : .NET 8.0.14 (8.0.1425.11118), Arm64 RyuJIT AdvSIMD
DefaultJob : .NET 8.0.14 (8.0.1425.11118), Arm64 RyuJIT AdvSIMD


```
| Method | Mean | Error | StdDev | Gen0 | Allocated |
|------- |---------:|--------:|--------:|-------:|----------:|
| Log | 288.4 ns | 1.28 ns | 1.20 ns | 0.1163 | 976 B |
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#nullable enable

using BenchmarkDotNet.Attributes;
using Microsoft.Extensions.Logging;
using Sentry.Extensibility;
using Sentry.Extensions.Logging;
using Sentry.Internal;
using Sentry.Testing;

namespace Sentry.Benchmarks.Extensions.Logging;

public class SentryStructuredLoggerBenchmarks
{
private Hub _hub = null!;
private Sentry.Extensions.Logging.SentryStructuredLogger _logger = null!;
private LogRecord _logRecord = null!;
private SentryLog? _lastLog;

[GlobalSetup]
public void Setup()
{
SentryLoggingOptions options = new()
{
Dsn = DsnSamples.ValidDsn,
Experimental =
{
EnableLogs = true,
},
ExperimentalLogging =
{
MinimumLogLevel = LogLevel.Information,
}
};
options.Experimental.SetBeforeSendLog((SentryLog log) =>
{
_lastLog = log;
return null;
});

MockClock clock = new(new DateTimeOffset(2025, 04, 22, 14, 51, 00, 789, TimeSpan.FromHours(2)));
SdkVersion sdk = new()
{
Name = "SDK Name",
Version = "SDK Version",
};

_hub = new Hub(options, DisabledHub.Instance);
_logger = new Sentry.Extensions.Logging.SentryStructuredLogger("CategoryName", options, _hub, clock, sdk);
_logRecord = new LogRecord(LogLevel.Information, new EventId(2025, "EventName"), new InvalidOperationException("exception-message"), "Number={Number}, Text={Text}", 2018, "message");
}

[Benchmark]
public void Log()
{
_logger.Log(_logRecord.LogLevel, _logRecord.EventId, _logRecord.Exception, _logRecord.Message, _logRecord.Args);
}

[GlobalCleanup]
public void Cleanup()
{
_hub.Dispose();

if (_lastLog is null)
{
throw new InvalidOperationException("Last Log is null");
}
if (_lastLog.Message != "Number=2018, Text=message")
{
throw new InvalidOperationException($"Last Log with Message: '{_lastLog.Message}'");
}
}

private sealed class LogRecord
{
public LogRecord(LogLevel logLevel, EventId eventId, Exception? exception, string? message, params object?[] args)
{
LogLevel = logLevel;
EventId = eventId;
Exception = exception;
Message = message;
Args = args;
}

public LogLevel LogLevel { get; }
public EventId EventId { get; }
public Exception? Exception { get; }
public string? Message { get; }
public object?[] Args { get; }
}
}
1 change: 1 addition & 0 deletions benchmarks/Sentry.Benchmarks/Sentry.Benchmarks.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

<ItemGroup>
<ProjectReference Include="..\..\src\Sentry\Sentry.csproj" />
<ProjectReference Include="..\..\src\Sentry.Extensions.Logging\Sentry.Extensions.Logging.csproj" />
<ProjectReference Include="..\..\src\Sentry.Profiling\Sentry.Profiling.csproj" />
<ProjectReference Include="..\..\test\Sentry.Testing\Sentry.Testing.csproj" />
</ItemGroup>
Expand Down
3 changes: 3 additions & 0 deletions samples/Sentry.Samples.AspNetCore.Basic/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
// Log debug information about the Sentry SDK
options.Debug = true;
#endif

// This option enables Logs sent to Sentry.
options.Experimental.EnableLogs = true;
});

var app = builder.Build();
Expand Down
10 changes: 10 additions & 0 deletions samples/Sentry.Samples.ME.Logging/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,17 @@
// Optionally configure options: The default values are:
options.MinimumBreadcrumbLevel = LogLevel.Information; // It requires at least this level to store breadcrumb
options.MinimumEventLevel = LogLevel.Error; // This level or above will result in event sent to Sentry
options.ExperimentalLogging.MinimumLogLevel = LogLevel.Trace; // This level or above will result in log sent to Sentry

// This option enables Logs sent to Sentry.
options.Experimental.EnableLogs = true;
options.Experimental.SetBeforeSendLog(static log =>
{
log.SetAttribute("attribute-key", "attribute-value");
return log;
});

// TODO: AddLogEntryFilter
// Don't keep as a breadcrumb or send events for messages of level less than Critical with exception of type DivideByZeroException
options.AddLogEntryFilter((_, level, _, exception) => level < LogLevel.Critical && exception is DivideByZeroException);

Expand Down
1 change: 1 addition & 0 deletions samples/Sentry.Samples.Maui/MauiProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public static MauiApp CreateMauiApp()
options.AttachScreenshot = true;

options.Debug = true;
options.Experimental.EnableLogs = true;
options.SampleRate = 1.0F;

// The Sentry MVVM Community Toolkit integration automatically creates traces for async relay commands,
Expand Down
32 changes: 32 additions & 0 deletions src/Sentry.AspNetCore/SentryAspNetCoreStructuredLoggerProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Sentry.Extensions.Logging;
using Sentry.Infrastructure;

namespace Sentry.AspNetCore;

/// <summary>
/// Structured Logger Provider for Sentry.
/// </summary>
[ProviderAlias("SentryLogs")]
internal sealed class SentryAspNetCoreStructuredLoggerProvider : SentryStructuredLoggerProvider
{
public SentryAspNetCoreStructuredLoggerProvider(IOptions<SentryAspNetCoreOptions> options, IHub hub)
: this(options.Value, hub, SystemClock.Clock, CreateSdkVersion())
{
}

internal SentryAspNetCoreStructuredLoggerProvider(SentryAspNetCoreOptions options, IHub hub, ISystemClock clock, SdkVersion sdk)
: base(options, hub, clock, sdk)
{
}

private static SdkVersion CreateSdkVersion()
{
return new SdkVersion
{
Name = Constants.SdkName,
Version = SentryMiddleware.NameAndVersion.Version,
};
}
}
6 changes: 6 additions & 0 deletions src/Sentry.AspNetCore/SentryWebHostBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,16 @@ public static IWebHostBuilder UseSentry(
_ = logging.Services
.AddSingleton<IConfigureOptions<SentryAspNetCoreOptions>, SentryAspNetCoreOptionsSetup>();
_ = logging.Services.AddSingleton<ILoggerProvider, SentryAspNetCoreLoggerProvider>();
_ = logging.Services.AddSingleton<ILoggerProvider, SentryAspNetCoreStructuredLoggerProvider>();

_ = logging.AddFilter<SentryAspNetCoreLoggerProvider>(
"Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware",
LogLevel.None);
_ = logging.AddFilter<SentryAspNetCoreStructuredLoggerProvider>(static (string? categoryName, LogLevel logLevel) =>
{
return categoryName is null
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are these guaranteed to be all of the categories we add?
I wonder if there's some "Constant" or something with these, we could reflect on and write a test to make sure we filter all of them out

Copy link
Member Author

@Flash0ver Flash0ver May 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fairly certain ... not quite positive though.
Those are the ILogger<T> usages that I found in our packages.
I definitely have to add a test for that ... probably as an ASP.NET Core integration test ... I see we have the infrastructure already setup via the "TestUtils" project.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's this one (although admittedly it's not used):

btw: Why an integration test for this? It just needs to use reflection to iterate over a bunch of assemblies right? Should be possible in a unit test?

|| (categoryName != "Sentry.ISentryClient" && categoryName != "Sentry.AspNetCore.SentryMiddleware");
});

var sentryBuilder = logging.Services.AddSentry();
configureSentry?.Invoke(context, sentryBuilder);
Expand Down
9 changes: 9 additions & 0 deletions src/Sentry.Extensions.Logging/BindableSentryLoggingOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,20 @@ internal class BindableSentryLoggingOptions : BindableSentryOptions
public LogLevel? MinimumEventLevel { get; set; }
public bool? InitializeSdk { get; set; }

public BindableSentryLoggingExperimentalOptions ExperimentalLogging { get; set; } = new();

internal sealed class BindableSentryLoggingExperimentalOptions
{
public LogLevel? MinimumLogLevel { get; set; }
}

public void ApplyTo(SentryLoggingOptions options)
{
base.ApplyTo(options);
options.MinimumBreadcrumbLevel = MinimumBreadcrumbLevel ?? options.MinimumBreadcrumbLevel;
options.MinimumEventLevel = MinimumEventLevel ?? options.MinimumEventLevel;
options.InitializeSdk = InitializeSdk ?? options.InitializeSdk;

options.ExperimentalLogging.MinimumLogLevel = ExperimentalLogging.MinimumLogLevel ?? options.ExperimentalLogging.MinimumLogLevel;
}
}
15 changes: 15 additions & 0 deletions src/Sentry.Extensions.Logging/LogLevelExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,19 @@ public static SentryLevel ToSentryLevel(this LogLevel level)
_ => SentryLevel.Debug
};
}

public static SentryLogLevel ToSentryLogLevel(this LogLevel logLevel)
{
return logLevel switch
{
LogLevel.Trace => SentryLogLevel.Trace,
LogLevel.Debug => SentryLogLevel.Debug,
LogLevel.Information => SentryLogLevel.Info,
LogLevel.Warning => SentryLogLevel.Warning,
LogLevel.Error => SentryLogLevel.Error,
LogLevel.Critical => SentryLogLevel.Fatal,
LogLevel.None => default,
_ => default,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

default for an enum value is always 0 right? And in this case, there is no enum value defined for SentryLogLevel with a value of 0. Won't that potentially cause problems?

Copy link
Member Author

@Flash0ver Flash0ver Aug 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

potentially constructed scenario, but possible

logger.Log((LogLevel)-1, "message");
logger.Log((LogLevel)7, "message");

What should happen?
Well .. these paths are unreachable, since we do an ILogger.IsEnabled check before.
Let's do a Debug.Fail in these cases.

};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Invalid SentryLogLevel for Unhandled LogLevels

The ToSentryLogLevel method returns default(SentryLogLevel) (0) for LogLevel.None and unhandled LogLevel values. SentryLogLevel has no defined value for 0 (it starts at 1), creating an invalid enum state. This can cause issues when the value is used downstream, such as during serialization or in ToSeverityTextAndOptionalSeverityNumber, and conflicts with Debug.Assert(level != default) in SentryStructuredLogger.cs.

Fix in Cursor Fix in Web

}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Invalid SentryLogLevel for Unhandled LogLevels

The ToSentryLogLevel method returns default(SentryLogLevel) (value 0) for LogLevel.None and unhandled LogLevel enum values. The SentryLogLevel enum, however, defines no member for 0 (values start from Trace = 1), resulting in an invalid enum state. This can lead to Debug.Assert failures in debug builds and undefined behavior when the value is processed downstream.

Fix in Cursor Fix in Web

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Invalid SentryLogLevel for LogLevel.None

The ToSentryLogLevel method returns default (0) for LogLevel.None and unknown LogLevel values. Since the SentryLogLevel enum starts at 1 and has no member for 0, this produces an invalid enum value. This invalid value causes Debug.Assert(level != default) in SentryStructuredLogger to fail. While LogLevel.None is often filtered, unknown LogLevel values could still trigger the issue.

Fix in Cursor Fix in Web

}
9 changes: 9 additions & 0 deletions src/Sentry.Extensions.Logging/LoggingBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,22 @@ internal static ILoggingBuilder AddSentry<TOptions>(

builder.Services.AddSingleton<IConfigureOptions<TOptions>, SentryLoggingOptionsSetup>();
builder.Services.AddSingleton<ILoggerProvider, SentryLoggerProvider>();
builder.Services.AddSingleton<ILoggerProvider, SentryStructuredLoggerProvider>();
builder.Services.AddSentry<TOptions>();

// All logs should flow to the SentryLogger, regardless of level.
// Filtering of events is handled in SentryLogger, using SentryOptions.MinimumEventLevel
// Filtering of breadcrumbs is handled in SentryLogger, using SentryOptions.MinimumBreadcrumbLevel
builder.AddFilter<SentryLoggerProvider>(_ => true);

// Logs from the SentryLogger should not flow to the SentryStructuredLogger as this may cause recursive invocations.
// Filtering of logs is handled in SentryStructuredLogger, using SentryOptions.MinimumLogLevel
builder.AddFilter<SentryStructuredLoggerProvider>(static (string? categoryName, LogLevel logLevel) =>
{
return categoryName is null
|| categoryName != "Sentry.ISentryClient";
});

return builder;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
<InternalsVisibleTo Include="Sentry.AspNetCore.Grpc.Tests" PublicKey="$(SentryPublicKey)" />
<InternalsVisibleTo Include="Sentry.Azure.Functions.Worker" PublicKey="$(SentryPublicKey)" />
<InternalsVisibleTo Include="Sentry.Azure.Functions.Worker.Tests" PublicKey="$(SentryPublicKey)" />
<InternalsVisibleTo Include="Sentry.Benchmarks" PublicKey="$(SentryPublicKey)" />
<InternalsVisibleTo Include="Sentry.DiagnosticSource.IntegrationTests" PublicKey="$(SentryPublicKey)" />
<InternalsVisibleTo Include="Sentry.Google.Cloud.Functions" PublicKey="$(SentryPublicKey)" />
<InternalsVisibleTo Include="Sentry.Google.Cloud.Functions.Tests" PublicKey="$(SentryPublicKey)" />
Expand Down
41 changes: 39 additions & 2 deletions src/Sentry.Extensions.Logging/SentryLoggingOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ public class SentryLoggingOptions : SentryOptions
/// <summary>
/// Gets or sets the minimum breadcrumb level.
/// </summary>
/// <remarks>Events with this level or higher will be stored as <see cref="Breadcrumb"/></remarks>
/// <remarks>
/// Events with this level or higher will be stored as <see cref="Breadcrumb"/>.
/// </remarks>
/// <value>
/// The minimum breadcrumb level.
/// </value>
Expand All @@ -21,7 +23,7 @@ public class SentryLoggingOptions : SentryOptions
/// Gets or sets the minimum event level.
/// </summary>
/// <remarks>
/// Events with this level or higher will be sent to Sentry
/// Events with this level or higher will be sent to Sentry.
/// </remarks>
/// <value>
/// The minimum event level.
Expand All @@ -48,4 +50,39 @@ public class SentryLoggingOptions : SentryOptions
/// List of callbacks to be invoked when initializing the SDK
/// </summary>
internal Action<Scope>[] ConfigureScopeCallbacks { get; set; } = Array.Empty<Action<Scope>>();

/// <summary>
/// Experimental Sentry Logging features.
/// </summary>
/// <remarks>
/// This and related experimental APIs may change in the future.
/// </remarks>
[Experimental(Infrastructure.DiagnosticId.ExperimentalFeature)]
public SentryLoggingExperimentalOptions ExperimentalLogging { get; set; } = new();

/// <summary>
/// Experimental Sentry Logging options.
/// </summary>
/// <remarks>
/// This and related experimental APIs may change in the future.
/// </remarks>
[Experimental(Infrastructure.DiagnosticId.ExperimentalFeature)]
public sealed class SentryLoggingExperimentalOptions
{
internal SentryLoggingExperimentalOptions()
{
}

/// <summary>
/// Gets or sets the minimum log level.
Comment on lines +76 to +77

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default minimum log level is set to LogLevel.Trace, which is the most verbose level. This could generate excessive log volume in production environments. Consider setting a more conservative default like LogLevel.Information or LogLevel.Warning.

Did we get this right? 👍 / 👎 to inform future reviews.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point.

The spec/developer docs, see https://develop.sentry.dev/sdk/telemetry/logs/, mention:

SDK Integrations

SDKs should aim to have it so that console/logger integrations create logs as per the appropriate log level if enableLogs/enable_logs is set to true. Examples of this include JavaScript's console object and Pythons logging standard library.

If SDK authors feel the need, they can also introduce additional options to beyond enableLogs/enable_logs to gate this functionality. For example an option to control log appenders added via external config like with Log4j in the Java SDK.

Per default, no logs are sent to Sentry, regardless of the MinimumLogLevel.
An explicit opt-in via SentryOptions.Experimental.EnableLogs = true (or through configuration and the BindableSentryOptions) enables both the Sentry-Logger API, as well as the Sentry.Extensions.Logging/Sentry.AspNetCore/Sentry.Maui integration.

@jamescrosswell @bruno-garcia
What do you think? What should be the defaults? Currently it's:

SentryOptions.Experimental.EnableLogs = false;
SentryLoggingOptions.ExperimentalLogging.MinimumLogLevel = LogLevel.Trace;

which means that if EnableLogs is true, all Microsoft.Extensions.Logging.LogLevel are sent.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, that could be quite verbose. Information might be a more typical default log level.

MEL let's you configure different log levels for different categories as well... so eventually we might want to mimic/reuse that configuration.

/// <para>This API is experimental and it may change in the future.</para>
/// </summary>
/// <remarks>
/// Logs with this level or higher will be stored as <see cref="SentryLog"/>.
/// </remarks>
/// <value>
/// The minimum log level.
/// </value>
public LogLevel MinimumLogLevel { get; set; } = LogLevel.Trace;
Copy link
Member Author

@Flash0ver Flash0ver Aug 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: rename to MinimumStructuredLogLevel

also ensure XML docs mention "structured" as well

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider: removing this option altogether

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will remove in upcoming changeset

}
}
Loading
Loading