Skip to content

Commit 90bf268

Browse files
QOL: Allow finishing spans and transactions with using (#4627)
1 parent fed5244 commit 90bf268

12 files changed

+152
-41
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
- `BreadcrumbLevel.Critical` has been renamed to `BreadcrumbLevel.Fatal` for consistency with the other Sentry SDKs ([#4605](https://github.com/getsentry/sentry-dotnet/pull/4605))
88
- SentryOptions.IsEnvironmentUser now defaults to false on MAUI. The means the User.Name will no longer be set, by default, to the name of the device ([#4606](https://github.com/getsentry/sentry-dotnet/pull/4606))
9+
- Spans and Transactions now implement `IDisposable` so that they can be used with `using` statements/declarations that will automatically finish the span with a status of OK when it passes out of scope, if it has not already been finished, to be consistent with `Activity` classes when using OpenTelemetry ([#4627](https://github.com/getsentry/sentry-dotnet/pull/4627))
10+
- SpanTracer and TransactionTracer are still public but these are now `sealed` (see also [#4627](https://github.com/getsentry/sentry-dotnet/pull/4627))
911
- Remove unnecessary files from SentryCocoaFramework before packing ([#4602](https://github.com/getsentry/sentry-dotnet/pull/4602))
1012
- CaptureFeedback now returns a `SentryId` and a `CaptureFeedbackResult` out parameter that indicate whether feedback was captured successfully and what the reason for failure was otherwise ([#4613](https://github.com/getsentry/sentry-dotnet/pull/4613))
1113
- Removed obsolete APIs ([#4619](https://github.com/getsentry/sentry-dotnet/pull/4619))

samples/Sentry.Samples.Console.Basic/Program.cs

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363

6464
// Always try to finish the transaction successfully.
6565
// Unhandled exceptions will fail the transaction automatically.
66-
// Optionally, you can try/catch the exception, and call transaction.Finish(exception) on failure.
66+
// Optionally, you can try/catch the exception and call transaction.Finish(exception) on failure.
6767
transaction.Finish();
6868

6969
async Task FirstFunction()
@@ -79,7 +79,6 @@ async Task FirstFunction()
7979
async Task SecondFunction()
8080
{
8181
var span = transaction.StartChild("function", nameof(SecondFunction));
82-
8382
try
8483
{
8584
// Simulate doing some work
@@ -97,26 +96,25 @@ async Task SecondFunction()
9796
SentrySdk.Logger.LogError(static log => log.SetAttribute("method", nameof(SecondFunction)),
9897
"Error with message: {0}", exception.Message);
9998
}
100-
101-
span.Finish();
99+
finally
100+
{
101+
span.Finish();
102+
}
102103
}
103104

104105
async Task ThirdFunction()
105106
{
106-
var span = transaction.StartChild("function", nameof(ThirdFunction));
107-
try
108-
{
109-
// Simulate doing some work
110-
await Task.Delay(100);
107+
// The `using` here ensures the span gets finished when we leave this method... This is unnecessary here,
108+
// since the method always throws and the span will be finished automatically when the exception is captured,
109+
// but this gives you another way to ensure spans are finished.
110+
using var span = transaction.StartChild("function", nameof(ThirdFunction));
111111

112-
SentrySdk.Logger.LogFatal(static log => log.SetAttribute("suppress", true),
113-
"Crash imminent!");
112+
// Simulate doing some work
113+
await Task.Delay(100);
114114

115-
// This is an example of an unhandled exception. It will be captured automatically.
116-
throw new InvalidOperationException("Something happened that crashed the app!");
117-
}
118-
finally
119-
{
120-
span.Finish();
121-
}
115+
SentrySdk.Logger.LogFatal(static log => log.SetAttribute("suppress", true),
116+
"Crash imminent!");
117+
118+
// This is an example of an unhandled exception. It will be captured automatically.
119+
throw new InvalidOperationException("Something happened that crashed the app!");
122120
}

src/Sentry/ISpan.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace Sentry;
55
/// <summary>
66
/// SpanTracer interface
77
/// </summary>
8-
public interface ISpan : ISpanData
8+
public interface ISpan : ISpanData, IDisposable
99
{
1010
/// <summary>
1111
/// Span description.

src/Sentry/Internal/NoOpSpan.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,8 @@ public void SetMeasurement(string name, Measurement measurement)
8585
}
8686

8787
public string? Origin { get; set; }
88+
89+
public void Dispose()
90+
{
91+
}
8892
}

src/Sentry/SpanTracer.cs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ namespace Sentry;
66
/// <summary>
77
/// Transaction span tracer.
88
/// </summary>
9-
public class SpanTracer : IBaseTracer, ISpan
9+
public sealed class SpanTracer : IBaseTracer, ISpan
1010
{
1111
private readonly IHub _hub;
1212
private readonly SentryStopwatch _stopwatch = SentryStopwatch.StartNew();
13+
private string? _origin;
1314

1415
private readonly Instrumenter _instrumenter = Instrumenter.Sentry;
1516

@@ -190,5 +191,18 @@ internal set
190191
_origin = value;
191192
}
192193
}
193-
private string? _origin;
194+
195+
/// <summary>
196+
/// <para>
197+
/// Automatically finishes the span with a status of <see cref="SpanStatus.Ok" /> at the end of a
198+
/// <c>using</c> block, if it has not already been finished.
199+
/// </para>
200+
/// <para>
201+
/// This is the equivalent of calling <see cref="Finish()" /> when the span passes out of scope.
202+
/// </para>
203+
/// /// </summary>
204+
public void Dispose()
205+
{
206+
Finish();
207+
}
194208
}

src/Sentry/TransactionTracer.cs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@ namespace Sentry;
77
/// <summary>
88
/// Transaction tracer.
99
/// </summary>
10-
public class TransactionTracer : IBaseTracer, ITransactionTracer
10+
public sealed class TransactionTracer : IBaseTracer, ITransactionTracer
1111
{
1212
private readonly IHub _hub;
1313
private readonly SentryOptions? _options;
1414
private readonly Timer? _idleTimer;
1515
private readonly SentryStopwatch _stopwatch = SentryStopwatch.StartNew();
16+
private InterlockedBoolean _hasFinished;
1617

1718
private InterlockedBoolean _cancelIdleTimeout;
1819

@@ -362,6 +363,11 @@ public void Clear()
362363
/// <inheritdoc />
363364
public void Finish()
364365
{
366+
if (_hasFinished.Exchange(true))
367+
{
368+
return;
369+
}
370+
365371
_options?.LogDebug("Attempting to finish Transaction '{0}'.", SpanId);
366372
if (_cancelIdleTimeout.Exchange(false) == true)
367373
{
@@ -432,4 +438,19 @@ private void ReleaseSpans()
432438
#endif
433439
_activeSpanTracker.Clear();
434440
}
441+
442+
/// <summary>
443+
/// <para>
444+
/// Automatically finishes the transaction with a status of <see cref="SpanStatus.Ok" /> at the end of a
445+
/// <c>using</c> block, if it has not already been finished.
446+
/// </para>
447+
/// <para>
448+
/// This is the equivalent of calling <see cref="Finish()" /> when the transaction passes out of scope.
449+
/// </para>
450+
/// </summary>
451+
/// <remarks>This is a convenience method only. Disposing is not required.</remarks>
452+
public void Dispose()
453+
{
454+
Finish();
455+
}
435456
}

test/Sentry.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ namespace Sentry
269269
{
270270
Sentry.SentryUser? Create();
271271
}
272-
public interface ISpan : Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpanData, Sentry.Protocol.ITraceContext
272+
public interface ISpan : Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpanData, Sentry.Protocol.ITraceContext, System.IDisposable
273273
{
274274
new string? Description { get; set; }
275275
new string Operation { get; set; }
@@ -299,7 +299,7 @@ namespace Sentry
299299
{
300300
string? Platform { get; set; }
301301
}
302-
public interface ITransactionTracer : Sentry.IEventLike, Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.ITransactionContext, Sentry.ITransactionData, Sentry.Protocol.ITraceContext
302+
public interface ITransactionTracer : Sentry.IEventLike, Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.ITransactionContext, Sentry.ITransactionData, Sentry.Protocol.ITraceContext, System.IDisposable
303303
{
304304
new bool? IsParentSampled { get; set; }
305305
new string Name { get; set; }
@@ -1167,7 +1167,7 @@ namespace Sentry
11671167
OutOfRange = 15,
11681168
DataLoss = 16,
11691169
}
1170-
public class SpanTracer : Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.Protocol.ITraceContext
1170+
public sealed class SpanTracer : Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.Protocol.ITraceContext, System.IDisposable
11711171
{
11721172
public SpanTracer(Sentry.IHub hub, Sentry.TransactionTracer transaction, Sentry.SpanId? parentSpanId, Sentry.SentryId traceId, string operation) { }
11731173
public System.Collections.Generic.IReadOnlyDictionary<string, object?> Data { get; }
@@ -1185,6 +1185,7 @@ namespace Sentry
11851185
public Sentry.SpanStatus? Status { get; set; }
11861186
public System.Collections.Generic.IReadOnlyDictionary<string, string> Tags { get; }
11871187
public Sentry.SentryId TraceId { get; }
1188+
public void Dispose() { }
11881189
public void Finish() { }
11891190
public void Finish(Sentry.SpanStatus status) { }
11901191
public void Finish(System.Exception exception) { }
@@ -1246,7 +1247,7 @@ namespace Sentry
12461247
public System.Collections.Generic.IReadOnlyDictionary<string, object?> CustomSamplingContext { get; }
12471248
public Sentry.ITransactionContext TransactionContext { get; }
12481249
}
1249-
public class TransactionTracer : Sentry.IEventLike, Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.ITransactionContext, Sentry.ITransactionData, Sentry.ITransactionTracer, Sentry.Protocol.ITraceContext
1250+
public sealed class TransactionTracer : Sentry.IEventLike, Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.ITransactionContext, Sentry.ITransactionData, Sentry.ITransactionTracer, Sentry.Protocol.ITraceContext, System.IDisposable
12501251
{
12511252
public TransactionTracer(Sentry.IHub hub, Sentry.ITransactionContext context) { }
12521253
public System.Collections.Generic.IReadOnlyCollection<Sentry.Breadcrumb> Breadcrumbs { get; }
@@ -1282,6 +1283,7 @@ namespace Sentry
12821283
public Sentry.SentryId TraceId { get; }
12831284
public Sentry.SentryUser User { get; set; }
12841285
public void AddBreadcrumb(Sentry.Breadcrumb breadcrumb) { }
1286+
public void Dispose() { }
12851287
public void Finish() { }
12861288
public void Finish(Sentry.SpanStatus status) { }
12871289
public void Finish(System.Exception exception) { }

test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ namespace Sentry
269269
{
270270
Sentry.SentryUser? Create();
271271
}
272-
public interface ISpan : Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpanData, Sentry.Protocol.ITraceContext
272+
public interface ISpan : Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpanData, Sentry.Protocol.ITraceContext, System.IDisposable
273273
{
274274
new string? Description { get; set; }
275275
new string Operation { get; set; }
@@ -299,7 +299,7 @@ namespace Sentry
299299
{
300300
string? Platform { get; set; }
301301
}
302-
public interface ITransactionTracer : Sentry.IEventLike, Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.ITransactionContext, Sentry.ITransactionData, Sentry.Protocol.ITraceContext
302+
public interface ITransactionTracer : Sentry.IEventLike, Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.ITransactionContext, Sentry.ITransactionData, Sentry.Protocol.ITraceContext, System.IDisposable
303303
{
304304
new bool? IsParentSampled { get; set; }
305305
new string Name { get; set; }
@@ -1167,7 +1167,7 @@ namespace Sentry
11671167
OutOfRange = 15,
11681168
DataLoss = 16,
11691169
}
1170-
public class SpanTracer : Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.Protocol.ITraceContext
1170+
public sealed class SpanTracer : Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.Protocol.ITraceContext, System.IDisposable
11711171
{
11721172
public SpanTracer(Sentry.IHub hub, Sentry.TransactionTracer transaction, Sentry.SpanId? parentSpanId, Sentry.SentryId traceId, string operation) { }
11731173
public System.Collections.Generic.IReadOnlyDictionary<string, object?> Data { get; }
@@ -1185,6 +1185,7 @@ namespace Sentry
11851185
public Sentry.SpanStatus? Status { get; set; }
11861186
public System.Collections.Generic.IReadOnlyDictionary<string, string> Tags { get; }
11871187
public Sentry.SentryId TraceId { get; }
1188+
public void Dispose() { }
11881189
public void Finish() { }
11891190
public void Finish(Sentry.SpanStatus status) { }
11901191
public void Finish(System.Exception exception) { }
@@ -1246,7 +1247,7 @@ namespace Sentry
12461247
public System.Collections.Generic.IReadOnlyDictionary<string, object?> CustomSamplingContext { get; }
12471248
public Sentry.ITransactionContext TransactionContext { get; }
12481249
}
1249-
public class TransactionTracer : Sentry.IEventLike, Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.ITransactionContext, Sentry.ITransactionData, Sentry.ITransactionTracer, Sentry.Protocol.ITraceContext
1250+
public sealed class TransactionTracer : Sentry.IEventLike, Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.ITransactionContext, Sentry.ITransactionData, Sentry.ITransactionTracer, Sentry.Protocol.ITraceContext, System.IDisposable
12501251
{
12511252
public TransactionTracer(Sentry.IHub hub, Sentry.ITransactionContext context) { }
12521253
public System.Collections.Generic.IReadOnlyCollection<Sentry.Breadcrumb> Breadcrumbs { get; }
@@ -1282,6 +1283,7 @@ namespace Sentry
12821283
public Sentry.SentryId TraceId { get; }
12831284
public Sentry.SentryUser User { get; set; }
12841285
public void AddBreadcrumb(Sentry.Breadcrumb breadcrumb) { }
1286+
public void Dispose() { }
12851287
public void Finish() { }
12861288
public void Finish(Sentry.SpanStatus status) { }
12871289
public void Finish(System.Exception exception) { }

test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ namespace Sentry
269269
{
270270
Sentry.SentryUser? Create();
271271
}
272-
public interface ISpan : Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpanData, Sentry.Protocol.ITraceContext
272+
public interface ISpan : Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpanData, Sentry.Protocol.ITraceContext, System.IDisposable
273273
{
274274
new string? Description { get; set; }
275275
new string Operation { get; set; }
@@ -299,7 +299,7 @@ namespace Sentry
299299
{
300300
string? Platform { get; set; }
301301
}
302-
public interface ITransactionTracer : Sentry.IEventLike, Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.ITransactionContext, Sentry.ITransactionData, Sentry.Protocol.ITraceContext
302+
public interface ITransactionTracer : Sentry.IEventLike, Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.ITransactionContext, Sentry.ITransactionData, Sentry.Protocol.ITraceContext, System.IDisposable
303303
{
304304
new bool? IsParentSampled { get; set; }
305305
new string Name { get; set; }
@@ -1167,7 +1167,7 @@ namespace Sentry
11671167
OutOfRange = 15,
11681168
DataLoss = 16,
11691169
}
1170-
public class SpanTracer : Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.Protocol.ITraceContext
1170+
public sealed class SpanTracer : Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.Protocol.ITraceContext, System.IDisposable
11711171
{
11721172
public SpanTracer(Sentry.IHub hub, Sentry.TransactionTracer transaction, Sentry.SpanId? parentSpanId, Sentry.SentryId traceId, string operation) { }
11731173
public System.Collections.Generic.IReadOnlyDictionary<string, object?> Data { get; }
@@ -1185,6 +1185,7 @@ namespace Sentry
11851185
public Sentry.SpanStatus? Status { get; set; }
11861186
public System.Collections.Generic.IReadOnlyDictionary<string, string> Tags { get; }
11871187
public Sentry.SentryId TraceId { get; }
1188+
public void Dispose() { }
11881189
public void Finish() { }
11891190
public void Finish(Sentry.SpanStatus status) { }
11901191
public void Finish(System.Exception exception) { }
@@ -1246,7 +1247,7 @@ namespace Sentry
12461247
public System.Collections.Generic.IReadOnlyDictionary<string, object?> CustomSamplingContext { get; }
12471248
public Sentry.ITransactionContext TransactionContext { get; }
12481249
}
1249-
public class TransactionTracer : Sentry.IEventLike, Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.ITransactionContext, Sentry.ITransactionData, Sentry.ITransactionTracer, Sentry.Protocol.ITraceContext
1250+
public sealed class TransactionTracer : Sentry.IEventLike, Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.ITransactionContext, Sentry.ITransactionData, Sentry.ITransactionTracer, Sentry.Protocol.ITraceContext, System.IDisposable
12501251
{
12511252
public TransactionTracer(Sentry.IHub hub, Sentry.ITransactionContext context) { }
12521253
public System.Collections.Generic.IReadOnlyCollection<Sentry.Breadcrumb> Breadcrumbs { get; }
@@ -1282,6 +1283,7 @@ namespace Sentry
12821283
public Sentry.SentryId TraceId { get; }
12831284
public Sentry.SentryUser User { get; set; }
12841285
public void AddBreadcrumb(Sentry.Breadcrumb breadcrumb) { }
1286+
public void Dispose() { }
12851287
public void Finish() { }
12861288
public void Finish(Sentry.SpanStatus status) { }
12871289
public void Finish(System.Exception exception) { }

0 commit comments

Comments
 (0)