Skip to content

Commit 818e404

Browse files
OpenTelemetry integration throws an exception when Sentry is disabled (#3156)
1 parent 8ad81aa commit 818e404

File tree

5 files changed

+148
-15
lines changed

5 files changed

+148
-15
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Fixes
66

77
- Metric unit names are now sanitized correctly. This was preventing some built in metrics from showing in the Sentry dashboard ([#3151](https://github.com/getsentry/sentry-dotnet/pull/3151))
8+
- The Sentry OpenTelemetry integration no longer throws an exception with the SDK disabled ([#3156](https://github.com/getsentry/sentry-dotnet/pull/3156))
89

910
## 4.1.1
1011

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using OpenTelemetry;
2+
3+
namespace Sentry.OpenTelemetry;
4+
5+
internal class DisabledSpanProcessor : BaseProcessor<Activity>
6+
{
7+
private static readonly Lazy<DisabledSpanProcessor> LazyInstance = new();
8+
internal static DisabledSpanProcessor Instance => LazyInstance.Value;
9+
10+
public override void OnStart(Activity activity)
11+
{
12+
// No-Op
13+
}
14+
15+
public override void OnEnd(Activity activity)
16+
{
17+
// No-Op
18+
}
19+
}

src/Sentry.OpenTelemetry/SentrySpanProcessor.cs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace Sentry.OpenTelemetry;
1212
public class SentrySpanProcessor : BaseProcessor<Activity>
1313
{
1414
private readonly IHub _hub;
15-
private readonly IEnumerable<IOpenTelemetryEnricher> _enrichers;
15+
internal readonly IEnumerable<IOpenTelemetryEnricher> _enrichers;
1616

1717
// ReSharper disable once MemberCanBePrivate.Global - Used by tests
1818
internal readonly ConcurrentDictionary<ActivitySpanId, ISpan> _map = new();
@@ -36,14 +36,23 @@ public SentrySpanProcessor(IHub hub) : this(hub, null)
3636
internal SentrySpanProcessor(IHub hub, IEnumerable<IOpenTelemetryEnricher>? enrichers)
3737
{
3838
_hub = hub;
39+
40+
if (_hub is DisabledHub)
41+
{
42+
// This would only happen if someone tried to create a SentrySpanProcessor manually
43+
throw new InvalidOperationException(
44+
"Attempted to creates a SentrySpanProcessor for a Disabled hub. " +
45+
"You should use the TracerProviderBuilderExtensions to configure Sentry with OpenTelemetry");
46+
}
47+
3948
_enrichers = enrichers ?? Enumerable.Empty<IOpenTelemetryEnricher>();
4049
_options = hub.GetSentryOptions();
4150

42-
if (_options is not { })
51+
if (_options is null)
4352
{
4453
throw new InvalidOperationException(
45-
"The Sentry SDK has not been initialised. To use Sentry with OpenTelemetry tracing you need to " +
46-
"initialize the Sentry SDK.");
54+
"The Sentry SDK has not been initialised. To use Sentry with OpenTelemetry " +
55+
"tracing you need to initialize the Sentry SDK.");
4756
}
4857

4958
if (_options.Instrumenter != Instrumenter.OpenTelemetry)

src/Sentry.OpenTelemetry/TracerProviderBuilderExtensions.cs

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using OpenTelemetry;
33
using OpenTelemetry.Context.Propagation;
44
using OpenTelemetry.Trace;
5+
using Sentry.Extensibility;
56

67
namespace Sentry.OpenTelemetry;
78

@@ -30,19 +31,28 @@ public static TracerProviderBuilder AddSentry(this TracerProviderBuilder tracerP
3031
{
3132
defaultTextMapPropagator ??= new SentryPropagator();
3233
Sdk.SetDefaultTextMapPropagator(defaultTextMapPropagator);
33-
return tracerProviderBuilder.AddProcessor(services =>
34-
{
35-
List<IOpenTelemetryEnricher> enrichers = new();
34+
return tracerProviderBuilder.AddProcessor(ImplementationFactory);
35+
}
3636

37-
// AspNetCoreEnricher
38-
var userFactory = services.GetService<ISentryUserFactory>();
39-
if (userFactory is not null)
40-
{
41-
enrichers.Add(new AspNetCoreEnricher(userFactory));
42-
}
37+
internal static BaseProcessor<Activity> ImplementationFactory(IServiceProvider services)
38+
{
39+
List<IOpenTelemetryEnricher> enrichers = new();
4340

44-
var hub = services.GetService<IHub>() ?? SentrySdk.CurrentHub;
41+
// AspNetCoreEnricher
42+
var userFactory = services.GetService<ISentryUserFactory>();
43+
if (userFactory is not null)
44+
{
45+
enrichers.Add(new AspNetCoreEnricher(userFactory));
46+
}
47+
48+
var hub = services.GetService<IHub>() ?? SentrySdk.CurrentHub;
49+
if (hub.IsEnabled)
50+
{
4551
return new SentrySpanProcessor(hub, enrichers);
46-
});
52+
}
53+
54+
var logger = services.GetService<IDiagnosticLogger>();
55+
logger?.LogWarning("Sentry is disabled so no OpenTelemetry spans will be sent to Sentry.");
56+
return DisabledSpanProcessor.Instance;
4757
}
4858
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
namespace Sentry.OpenTelemetry.Tests;
2+
3+
public class TracerProviderBuilderExtensionsTests
4+
{
5+
private class Fixture
6+
{
7+
public IServiceProvider ServiceProvider { get; } = Substitute.For<IServiceProvider>();
8+
public IHub Hub { get; } = Substitute.For<IHub>();
9+
10+
public Fixture()
11+
{
12+
ServiceProvider.GetService(typeof(IHub)).Returns(Hub);
13+
}
14+
15+
public SentryOptions GetOptions(string dsn = "https://[email protected]/789") => new()
16+
{
17+
Instrumenter = Instrumenter.OpenTelemetry,
18+
Dsn = dsn
19+
};
20+
21+
public IServiceProvider GetServiceProvider() => ServiceProvider;
22+
}
23+
24+
25+
[Fact]
26+
public void ImplementationFactory_WithUserFactory_AddsAspNetCoreEnricher()
27+
{
28+
// Arrange
29+
var fixture = new Fixture();
30+
SentryClientExtensions.SentryOptionsForTestingOnly = fixture.GetOptions();
31+
fixture.Hub.IsEnabled.Returns(true);
32+
var userFactory = Substitute.For<ISentryUserFactory>();
33+
fixture.ServiceProvider.GetService(typeof(ISentryUserFactory)).Returns(userFactory);
34+
var services = fixture.GetServiceProvider();
35+
36+
// Act
37+
var result = TracerProviderBuilderExtensions.ImplementationFactory(services);
38+
39+
// Assert
40+
result.Should().BeOfType<SentrySpanProcessor>(); // FluentAssertions
41+
var spanProcessor = (SentrySpanProcessor)result;
42+
spanProcessor._enrichers.Should().NotBeEmpty();
43+
}
44+
45+
[Fact]
46+
public void ImplementationFactory_WithoutUserFactory_DoesNotAddAspNetCoreEnricher()
47+
{
48+
// Arrange
49+
var fixture = new Fixture();
50+
SentryClientExtensions.SentryOptionsForTestingOnly = fixture.GetOptions();
51+
fixture.Hub.IsEnabled.Returns(true);
52+
var services = fixture.GetServiceProvider();
53+
54+
// Act
55+
var result = TracerProviderBuilderExtensions.ImplementationFactory(services);
56+
57+
// Assert
58+
result.Should().BeOfType<SentrySpanProcessor>(); // FluentAssertions
59+
var spanProcessor = (SentrySpanProcessor)result;
60+
spanProcessor._enrichers.Should().BeEmpty();
61+
}
62+
63+
[Fact]
64+
public void ImplementationFactory_WithEnabledHub_ReturnsSentrySpanProcessor()
65+
{
66+
// Arrange
67+
var fixture = new Fixture();
68+
SentryClientExtensions.SentryOptionsForTestingOnly = fixture.GetOptions();
69+
fixture.Hub.IsEnabled.Returns(true);
70+
var services = fixture.GetServiceProvider();
71+
72+
// Act
73+
var result = TracerProviderBuilderExtensions.ImplementationFactory(services);
74+
75+
// Assert
76+
result.Should().BeOfType<SentrySpanProcessor>(); // FluentAssertions
77+
}
78+
79+
[Fact]
80+
public void ImplementationFactory_WithDisabledHub_ReturnsDisabledSpanProcessor()
81+
{
82+
// Arrange
83+
var fixture = new Fixture();
84+
SentryClientExtensions.SentryOptionsForTestingOnly = fixture.GetOptions();
85+
fixture.Hub.IsEnabled.Returns(false);
86+
var services = fixture.GetServiceProvider();
87+
88+
// Act
89+
var result = TracerProviderBuilderExtensions.ImplementationFactory(services);
90+
91+
// Assert
92+
result.Should().BeOfType<DisabledSpanProcessor>(); // FluentAssertions
93+
}
94+
}

0 commit comments

Comments
 (0)