Skip to content

Commit 38a3d14

Browse files
test: pin the multiple-container behaviour behind Func<IHub> (#5646)
The hub is registered as Func<IHub> returning HubAdapter.Instance so that nothing captures a hub instance. That shape fixed #103 (ObjectDisposedException when a second container was built and disposed) but shipped without a regression test. Adds tests that fail if the registration is simplified to a singleton IHub: - a second service provider built and disposed after the app is running must not stop the middleware capturing unhandled exceptions - IHub/ISentryClient resolved from DI follow SentrySdk's current hub - disposing the container does not dispose the SDK's hub Closes #5645 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent c68728b commit 38a3d14

4 files changed

Lines changed: 94 additions & 0 deletions

File tree

‎src/Sentry.Extensions.Logging/Extensions/DependencyInjection/ServiceCollectionExtensions.cs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public static IServiceCollection AddSentry<TOptions>(this IServiceCollection ser
2222
services.TryAddSingleton<SentryOptions>(
2323
c => c.GetRequiredService<IOptions<TOptions>>().Value);
2424

25+
// Must not capture a hub instance: https://github.com/getsentry/sentry-dotnet/issues/103
2526
services.TryAddTransient<ISentryClient>(c => c.GetRequiredService<IHub>());
2627
services.TryAddTransient(c => c.GetRequiredService<Func<IHub>>()());
2728

‎test/Sentry.AspNetCore.Tests/IntegrationMockedBackgroundWorker.cs‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,32 @@ public void Hub_CaptureMessage_EventCaptured()
170170
));
171171
}
172172

173+
[Fact]
174+
public async Task UnhandledException_AnotherServiceProviderBuiltAndDisposed_EventCaptured()
175+
{
176+
IServiceCollection services = null;
177+
ConfigureServices = s => services = s;
178+
Build();
179+
180+
using (var throwaway = services.BuildServiceProvider())
181+
{
182+
_ = throwaway.GetRequiredService<IHub>();
183+
}
184+
185+
_ = await HttpClient.GetAsync("/throw");
186+
187+
_ = Worker.Received(1).EnqueueEnvelope(Arg.Is<Envelope>(e =>
188+
e.Items
189+
.Select(i => i.Payload)
190+
.OfType<JsonSerializable>()
191+
.Select(i => i.Source)
192+
.OfType<SentryEvent>()
193+
.Single()
194+
.Exception
195+
.Message == "test error"
196+
));
197+
}
198+
173199
[Fact]
174200
public async Task SendDefaultPii_FalseWithoutUserInRequest_NoUserNameSent()
175201
{
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace Sentry.Extensions.Logging.Tests;
2+
3+
[CollectionDefinition(nameof(SentrySdkCollection), DisableParallelization = true)]
4+
public sealed class SentrySdkCollection
5+
{
6+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
using Microsoft.Extensions.Logging;
3+
4+
namespace Sentry.Extensions.Logging.Tests;
5+
6+
[Collection(nameof(SentrySdkCollection))]
7+
public sealed class ServiceCollectionExtensionsTests : IDisposable
8+
{
9+
private readonly IBackgroundWorker _worker = Substitute.For<IBackgroundWorker>();
10+
11+
private ServiceProvider BuildServiceProvider()
12+
{
13+
var services = new ServiceCollection();
14+
services.AddLogging(builder => builder.AddSentry(o =>
15+
{
16+
o.Dsn = ValidDsn;
17+
o.BackgroundWorker = _worker;
18+
o.InitNativeSdks = false;
19+
}));
20+
return services.BuildServiceProvider();
21+
}
22+
23+
[Fact]
24+
public void AddSentry_SdkHubReplacedAfterResolution_ResolvedHubUsesNewHub()
25+
{
26+
using var provider = BuildServiceProvider();
27+
var hub = provider.GetRequiredService<IHub>();
28+
var client = provider.GetRequiredService<ISentryClient>();
29+
30+
var newHub = Substitute.For<IHub>();
31+
using var _ = SentrySdk.UseHub(newHub);
32+
33+
var hubEvent = new SentryEvent();
34+
var clientEvent = new SentryEvent();
35+
hub.CaptureEvent(hubEvent);
36+
client.CaptureEvent(clientEvent);
37+
38+
newHub.Received(1).CaptureEvent(hubEvent);
39+
newHub.Received(1).CaptureEvent(clientEvent);
40+
}
41+
42+
[Fact]
43+
public void AddSentry_ServiceProviderDisposed_SdkHubStillCaptures()
44+
{
45+
var provider = BuildServiceProvider();
46+
_ = provider.GetRequiredService<IHub>();
47+
_ = provider.GetRequiredService<ISentryClient>();
48+
_ = provider.GetRequiredService<ILoggerFactory>();
49+
50+
provider.Dispose();
51+
SentrySdk.CaptureMessage("after dispose");
52+
53+
_worker.Received(1).EnqueueEnvelope(Arg.Is<Envelope>(e =>
54+
e.Items
55+
.Select(i => i.Payload).OfType<JsonSerializable>()
56+
.Select(i => i.Source).OfType<SentryEvent>()
57+
.Any(evt => evt.Message!.Message == "after dispose")));
58+
}
59+
60+
public void Dispose() => SentrySdk.Close();
61+
}

0 commit comments

Comments
 (0)