diff --git a/CHANGELOG.md b/CHANGELOG.md
index dbbcb63b73..5fe72d5117 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,7 +10,8 @@
### API changes
- Removed `SentryOptionsExtensions` class - all the public methods moved directly to `SentryOptions` ([#3195](https://github.com/getsentry/sentry-dotnet/pull/3195))
-
+- Sentry now uses System.Diagnostics.DiagnosticSource internally for tracing by default when targeting .NET 6 or later, meaning any traces created by Sentry can be captured via OpenTelemetry. If you are using .NET Core 3.1 or earlier, you can opt into this via the `Sentry.DiagnosticSource` package. ([#3238](https://github.com/getsentry/sentry-dotnet/pull/3238))
+
### Dependencies
- Bump CLI from v2.30.0 to v2.30.2 ([#3214](https://github.com/getsentry/sentry-dotnet/pull/3214), [#3218](https://github.com/getsentry/sentry-dotnet/pull/3218))
diff --git a/modules/sentry-native b/modules/sentry-native
index 4ec95c0725..9bc0fc75e3 160000
--- a/modules/sentry-native
+++ b/modules/sentry-native
@@ -1 +1 @@
-Subproject commit 4ec95c0725df5f34440db8fa8d37b4c519fce74e
+Subproject commit 9bc0fc75e34cb43e7019d76b0decb6c0cddbfd34
diff --git a/src/Sentry.DiagnosticSource/Internal/Tracing/ActivitySpanProcessor.cs b/src/Sentry.DiagnosticSource/Internal/Tracing/ActivitySpanProcessor.cs
new file mode 100644
index 0000000000..9258e080eb
--- /dev/null
+++ b/src/Sentry.DiagnosticSource/Internal/Tracing/ActivitySpanProcessor.cs
@@ -0,0 +1,470 @@
+using Sentry.Extensibility;
+using Sentry.Internal.Extensions;
+using Sentry.Internal.OpenTelemetry;
+
+namespace Sentry.Internal.Tracing;
+
+///
+/// Helper class to convert events to Sentry Spans.
+///
+internal class ActivitySpanProcessor
+{
+ private readonly IHub _hub;
+
+ private Action? _beforeFinish;
+ private readonly Instrumenter _instrumenter;
+
+ // ReSharper disable once MemberCanBePrivate.Global - Used by tests
+ internal readonly ConcurrentDictionary _map = new();
+ private readonly SentryOptions? _options;
+ private readonly Lazy> _resourceAttributes;
+
+ private static readonly long PruningInterval = TimeSpan.FromSeconds(5).Ticks;
+ internal long _lastPruned = 0;
+ private readonly Lazy _realHub;
+
+ static ActivitySpanProcessor()
+ {
+#if !NET5_0_OR_GREATER
+ if (Activity.DefaultIdFormat == ActivityIdFormat.W3C)
+ {
+ return;
+ }
+
+ // TODO: Could customers potentially be relying on the Hierarchical format? If so, this will get us in trouble.
+ //
+ // Another option would be to warn customers and have them set this themselves (that would be more deliberate).
+ //
+ // Finally, we can override the ActivityIdFormat for each trace by using ActivitySource.CreateActivity instead
+ // of ActivitySource.StartActivity. That's a bit more fragile and will only work for spans created by the SDK
+ // (not for anything users instrument themselves).
+ //
+ // Activity.SpanId only gets a non-zero value if the activity ID format is W3C (the default since net5.0). The
+ // default is Hierarchical on .NET Framework, .NET Core 3.1 and below, which won't work with Sentry tracing.
+ Debug.WriteLine("Setting Activity.DefaultIdFormat to W3C.");
+ Activity.DefaultIdFormat = ActivityIdFormat.W3C;
+#endif
+ }
+
+ internal ActivitySpanProcessor(IHub hub, Instrumenter instrumenter)
+ : this(hub, null, null, instrumenter)
+ {
+ }
+
+ internal ActivitySpanProcessor(IHub hub, Action? beforeFinish,
+ Func>? resourceAttributeResolver)
+ : this(hub, beforeFinish, resourceAttributeResolver, Instrumenter.OpenTelemetry)
+ {
+ }
+
+ private ActivitySpanProcessor(IHub hub, Action? beforeFinish,
+ Func>? resourceAttributeResolver, Instrumenter instrumenter)
+ {
+ _hub = hub;
+ _beforeFinish = beforeFinish;
+ _instrumenter = instrumenter;
+ _realHub = new Lazy(() =>
+ _hub switch
+ {
+ Hub thisHub => thisHub,
+ HubAdapter when SentrySdk.CurrentHub is Hub sdkHub => sdkHub,
+ _ => null
+ });
+
+ _options = hub.GetSentryOptions();
+
+ if (_options is null)
+ {
+ throw new InvalidOperationException(
+ "The Sentry SDK has not been initialised. To use tracing you need to initialize the Sentry SDK.");
+ }
+
+ // Resource attributes are consistent between spans, but not available during construction.
+ // Thus, get a single instance lazily.
+ resourceAttributeResolver ??= () => new Dictionary();
+ _resourceAttributes = new Lazy>(resourceAttributeResolver);
+ }
+
+ internal ISpan? GetMappedSpan(ActivitySpanId spanId) => _map.GetValueOrDefault(spanId);
+
+ ///
+ public void OnStart(System.Diagnostics.Activity data)
+ {
+ if (data.ParentSpanId != default && _map.TryGetValue(data.ParentSpanId, out var parentSpan))
+ {
+ // We can find the parent span - start a child span.
+ var context = new SpanContext(
+ data.OperationName,
+ data.SpanId.AsSentrySpanId(),
+ data.ParentSpanId.AsSentrySpanId(),
+ data.TraceId.AsSentryId(),
+ data.DisplayName,
+ null,
+ null)
+ {
+ Instrumenter = _instrumenter
+ };
+
+ var span = (SpanTracer)parentSpan.StartChild(context);
+ span.StartTimestamp = data.StartTimeUtc;
+ // Used to filter out spans that are not recorded when finishing a transaction.
+ data.BindSentrySpan(span);
+ span.IsFiltered = () => span.GetActivity()
+ is { IsAllDataRequested: false, Recorded: false };
+ _map[data.SpanId] = span;
+ }
+ else
+ {
+#if HAS_DIAGNOSTICS_7_OR_GREATER
+ // If a parent exists, then copy its sampling decision.
+ bool? isSampled = data.HasRemoteParent ? data.Recorded : null;
+#else
+ bool? isSampled = null;
+#endif
+
+ // No parent span found - start a new transaction
+ var transactionContext = new TransactionContext(data.DisplayName,
+ data.OperationName,
+ data.SpanId.AsSentrySpanId(),
+ data.ParentSpanId.AsSentrySpanId(),
+ data.TraceId.AsSentryId(),
+ data.DisplayName, null, isSampled, isSampled)
+ {
+ Instrumenter = _instrumenter
+ };
+
+ var baggageHeader = data.Baggage.AsBaggageHeader();
+ var dynamicSamplingContext = baggageHeader.CreateDynamicSamplingContext();
+ var transaction = (TransactionTracer)_hub.StartTransaction(
+ transactionContext, new Dictionary(), dynamicSamplingContext
+ );
+ transaction.StartTimestamp = data.StartTimeUtc;
+ _hub.ConfigureScope(scope => scope.Transaction = transaction);
+ data.BindSentrySpan(transaction);
+ _map[data.SpanId] = transaction;
+ }
+
+ // Housekeeping
+ PruneFilteredSpans();
+ }
+
+ public void OnEnd(System.Diagnostics.Activity data)
+ {
+ // Make a dictionary of the attributes (aka "tags") for faster lookup when used throughout the processor.
+ var attributes = data.TagObjects.ToDict();
+
+ var url =
+ attributes.TryGetTypedValue(OtelSemanticConventions.AttributeUrlFull, out string? tempUrl) ? tempUrl
+ : attributes.TryGetTypedValue(OtelSemanticConventions.AttributeHttpUrl, out string? fallbackUrl) ? fallbackUrl // Falling back to pre-1.5.0
+ : null;
+
+ if (!string.IsNullOrEmpty(url) && (_options?.IsSentryRequest(url) ?? false))
+ {
+ _options?.DiagnosticLogger?.LogDebug($"Ignoring Activity {data.SpanId} for Sentry request.");
+
+ if (_map.TryRemove(data.SpanId, out var removed))
+ {
+ if (removed is SpanTracer spanTracerToRemove)
+ {
+ spanTracerToRemove.IsSentryRequest = true;
+ }
+
+ if (removed is TransactionTracer transactionTracer)
+ {
+ transactionTracer.IsSentryRequest = true;
+ }
+ }
+
+ return;
+ }
+
+ if (!_map.TryGetValue(data.SpanId, out var span))
+ {
+ _options?.DiagnosticLogger?.LogError($"Span not found for SpanId: {data.SpanId}. Did OnStart run? We might have a bug in the SDK.");
+ return;
+ }
+
+ var (operation, description, source) = ParseOtelSpanDescription(data, attributes);
+ span.Operation = operation;
+ span.Description = description;
+
+ if (span is TransactionTracer transaction)
+ {
+ transaction.Name = description;
+ transaction.NameSource = source;
+
+ // Use the end timestamp from the activity data.
+ transaction.EndTimestamp = data.StartTimeUtc + data.Duration;
+
+ // Transactions set otel attributes (and resource attributes) as context.
+ transaction.Contexts["otel"] = GetOtelContext(attributes);
+ }
+ else
+ {
+ // Use the end timestamp from the activity data.
+ ((SpanTracer)span).EndTimestamp = data.StartTimeUtc + data.Duration;
+
+ // Spans set otel attributes in extras (passed to Sentry as "data" on the span).
+ // Resource attributes do not need to be set, as they would be identical as those set on the transaction.
+ span.SetExtras(attributes);
+ span.SetExtra("otel.kind", data.Kind);
+ }
+
+ // In ASP.NET Core the middleware finishes up (and the scope gets popped) before the activity is ended. So we
+ // need to restore the scope here (it's saved by our middleware when the request starts)
+ var activityScope = GetSavedScope(data);
+ if (activityScope is { } savedScope)
+ {
+ var hub = _realHub.Value;
+ hub?.RestoreScope(savedScope);
+ }
+ GenerateSentryErrorsFromOtelSpan(data, attributes);
+ _beforeFinish?.Invoke(span, data);
+ if (data.GetException() is { } exception)
+ {
+ span.Finish(exception);
+ }
+ else
+ {
+ // TODO: Does this override a status that we might be setting manually? This logic worked for OTel spans but
+ // might need to be more sophisticated for ActivityTraceSpans... alternatively we need to be more
+ // sophisticated about how we set status in the first place (leveraging attributes that will be applied
+ // appropriately by this GetSpanStatus method).
+ var status = GetSpanStatus(data.Status, attributes);
+ span.Finish(status);
+ }
+
+ _map.TryRemove(data.SpanId, out _);
+
+ // Housekeeping
+ PruneFilteredSpans();
+ }
+
+ ///
+ /// Clean up items that may have been filtered out.
+ /// See https://github.com/getsentry/sentry-dotnet/pull/3198
+ ///
+ internal void PruneFilteredSpans(bool force = false)
+ {
+ if (!force && !NeedsPruning())
+ {
+ return;
+ }
+
+ foreach (var mappedItem in _map)
+ {
+ var (spanId, span) = mappedItem;
+ if (span.GetActivity() is { Recorded: false, IsAllDataRequested: false })
+ {
+ _map.TryRemove(spanId, out _);
+ }
+ }
+ }
+
+ private bool NeedsPruning()
+ {
+ var lastPruned = Interlocked.Read(ref _lastPruned);
+ if (lastPruned > DateTime.UtcNow.Ticks - PruningInterval)
+ {
+ return false;
+ }
+
+ var thisPruned = DateTime.UtcNow.Ticks;
+ Interlocked.CompareExchange(ref _lastPruned, thisPruned, lastPruned);
+ // May be false if another thread gets there first
+ return Interlocked.Read(ref _lastPruned) == thisPruned;
+ }
+
+ private static Scope? GetSavedScope(System.Diagnostics.Activity? activity)
+ {
+ while (activity is not null)
+ {
+ if (activity.GetFused() is { } savedScope)
+ {
+ return savedScope;
+ }
+ activity = activity.Parent;
+ }
+ return null;
+ }
+
+ internal static SpanStatus GetSpanStatus(ActivityStatusCode status, IDictionary attributes)
+ {
+ // See https://github.com/open-telemetry/opentelemetry-dotnet/discussions/4703
+ if (attributes.TryGetValue(OtelSpanAttributeConstants.StatusCodeKey, out var statusCode)
+ && statusCode is OtelStatusTags.ErrorStatusCodeTagValue
+ )
+ {
+ return GetErrorSpanStatus(attributes);
+ }
+ return status switch
+ {
+ ActivityStatusCode.Unset => SpanStatus.Ok,
+ ActivityStatusCode.Ok => SpanStatus.Ok,
+ ActivityStatusCode.Error => GetErrorSpanStatus(attributes),
+ _ => SpanStatus.UnknownError
+ };
+ }
+
+ private static SpanStatus GetErrorSpanStatus(IDictionary attributes)
+ {
+ if (attributes.TryGetTypedValue("http.status_code", out int httpCode))
+ {
+ return SpanStatusConverter.FromHttpStatusCode(httpCode);
+ }
+
+ if (attributes.TryGetTypedValue("rpc.grpc.status_code", out int grpcCode))
+ {
+ return SpanStatusConverter.FromGrpcStatusCode(grpcCode);
+ }
+
+ return SpanStatus.UnknownError;
+ }
+
+ private static (string operation, string description, TransactionNameSource source) ParseOtelSpanDescription(
+ System.Diagnostics.Activity activity,
+ IDictionary attributes)
+ {
+ // This function should loosely match the JavaScript implementation at:
+ // https://github.com/getsentry/sentry-javascript/blob/3487fa3af7aa72ac7fdb0439047cb7367c591e77/packages/opentelemetry-node/src/utils/parseOtelSpanDescription.ts
+ // However, it should also follow the OpenTelemetry semantic conventions specification, as indicated.
+
+ // HTTP span
+ // https://opentelemetry.io/docs/specs/otel/trace/semantic_conventions/http/
+ if (attributes.TryGetTypedValue(OtelSemanticConventions.AttributeHttpMethod, out string httpMethod))
+ {
+ if (activity.Kind == ActivityKind.Client)
+ {
+ // Per OpenTelemetry spec, client spans use only the method.
+ return ("http.client", httpMethod, TransactionNameSource.Custom);
+ }
+
+ if (attributes.TryGetTypedValue(OtelSemanticConventions.AttributeHttpRoute, out string httpRoute))
+ {
+ // A route exists. Use the method and route.
+ return ("http.server", $"{httpMethod} {httpRoute}", TransactionNameSource.Route);
+ }
+
+ if (attributes.TryGetTypedValue(OtelSemanticConventions.AttributeHttpTarget, out string httpTarget))
+ {
+ // A target exists. Use the method and target. If the target is "/" we can treat it like a route.
+ var source = httpTarget == "/" ? TransactionNameSource.Route : TransactionNameSource.Url;
+ return ("http.server", $"{httpMethod} {httpTarget}", source);
+ }
+
+ // Some other type of HTTP server span. Pass it through with the original name.
+ return ("http.server", activity.DisplayName, TransactionNameSource.Custom);
+ }
+
+ // DB span
+ // https://opentelemetry.io/docs/specs/otel/trace/semantic_conventions/database/
+ if (attributes.ContainsKey(OtelSemanticConventions.AttributeDbSystem))
+ {
+ if (attributes.TryGetTypedValue(OtelSemanticConventions.AttributeDbStatement, out string dbStatement))
+ {
+ // We have a database statement. Use it.
+ return ("db", dbStatement, TransactionNameSource.Task);
+ }
+
+ // Some other type of DB span. Pass it through with the original name.
+ return ("db", activity.DisplayName, TransactionNameSource.Task);
+ }
+
+ // RPC span
+ // https://opentelemetry.io/docs/specs/otel/trace/semantic_conventions/rpc/
+ if (attributes.ContainsKey(OtelSemanticConventions.AttributeRpcService))
+ {
+ return ("rpc", activity.DisplayName, TransactionNameSource.Route);
+ }
+
+ // Messaging span
+ // https://opentelemetry.io/docs/specs/otel/trace/semantic_conventions/messaging/
+ if (attributes.ContainsKey(OtelSemanticConventions.AttributeMessagingSystem))
+ {
+ return ("message", activity.DisplayName, TransactionNameSource.Route);
+ }
+
+ // FaaS (Functions/Lambda) span
+ // https://opentelemetry.io/docs/specs/otel/trace/semantic_conventions/faas/
+ if (attributes.TryGetTypedValue(OtelSemanticConventions.AttributeFaasTrigger, out string faasTrigger))
+ {
+ return (faasTrigger, activity.DisplayName, TransactionNameSource.Route);
+ }
+
+ // Default - pass through unmodified.
+ return (activity.OperationName, activity.DisplayName, TransactionNameSource.Custom);
+ }
+
+ private Dictionary GetOtelContext(IDictionary attributes)
+ {
+ var otelContext = new Dictionary();
+ if (attributes.Count > 0)
+ {
+ otelContext.Add("attributes", attributes);
+ }
+
+ var resourceAttributes = _resourceAttributes.Value;
+ if (resourceAttributes.Count > 0)
+ {
+ otelContext.Add("resource", resourceAttributes);
+ }
+
+ return otelContext;
+ }
+
+ private void GenerateSentryErrorsFromOtelSpan(System.Diagnostics.Activity activity, IDictionary spanAttributes)
+ {
+ // https://develop.sentry.dev/sdk/performance/opentelemetry/#step-7-define-generatesentryerrorsfromotelspan
+ // https://opentelemetry.io/docs/specs/otel/trace/semantic_conventions/exceptions/
+ foreach (var @event in activity.Events.Where(e => e.Name == OtelSemanticConventions.AttributeExceptionEventName))
+ {
+ var eventAttributes = @event.Tags.ToDict();
+ // This would be where we would ideally implement full exception capture. That's not possible at the
+ // moment since the full exception isn't yet available via the OpenTelemetry API.
+ // See https://github.com/open-telemetry/opentelemetry-dotnet/issues/2439#issuecomment-1577314568
+ // if (!eventAttributes.TryGetTypedValue("exception", out Exception exception))
+ // {
+ // continue;
+ // }
+
+ // At the moment, OTEL only gives us `exception.type`, `exception.message`, and `exception.stacktrace`...
+ // So the best we can do is a poor man's exception (no accurate symbolication or anything)
+ if (!eventAttributes.TryGetTypedValue(OtelSemanticConventions.AttributeExceptionType, out string exceptionType))
+ {
+ continue;
+ }
+ eventAttributes.TryGetTypedValue(OtelSemanticConventions.AttributeExceptionMessage, out string message);
+ eventAttributes.TryGetTypedValue(OtelSemanticConventions.AttributeExceptionStacktrace, out string stackTrace);
+
+ Exception exception;
+ try
+ {
+ var type = Type.GetType(exceptionType)!;
+ exception = (Exception)Activator.CreateInstance(type, message)!;
+ exception.SetSentryMechanism("SentrySpanProcessor.ErrorSpan");
+ }
+ catch
+ {
+ _options?.DiagnosticLogger?.LogError($"Failed to create poor man's exception for type : {exceptionType}");
+ continue;
+ }
+
+ // TODO: Validate that our `DuplicateEventDetectionEventProcessor` prevents this from doubling exceptions
+ // that are also caught by other means, such as our AspNetCore middleware, etc.
+ // (When options.RecordException = true is set on AddAspNetCoreInstrumentation...)
+ // Also, in such cases - how will we get the otel scope and trace context on the other one?
+
+ var sentryEvent = new SentryEvent(exception, @event.Timestamp);
+ var otelContext = GetOtelContext(spanAttributes);
+ otelContext.Add("stack_trace", stackTrace);
+ sentryEvent.Contexts["otel"] = otelContext;
+ _hub.CaptureEvent(sentryEvent, scope =>
+ {
+ var trace = scope.Contexts.Trace;
+ trace.SpanId = activity.SpanId.AsSentrySpanId();
+ trace.ParentSpanId = activity.ParentSpanId.AsSentrySpanId();
+ trace.TraceId = activity.TraceId.AsSentryId();
+ });
+ }
+ }
+}
diff --git a/src/Sentry.DiagnosticSource/Internal/Tracing/ActivityTraceProvider.cs b/src/Sentry.DiagnosticSource/Internal/Tracing/ActivityTraceProvider.cs
new file mode 100644
index 0000000000..740754c8ee
--- /dev/null
+++ b/src/Sentry.DiagnosticSource/Internal/Tracing/ActivityTraceProvider.cs
@@ -0,0 +1,15 @@
+namespace Sentry.Internal.Tracing;
+
+///
+/// The concrete implementation of that uses
+/// and from the
+/// namespace to implement tracing.
+///
+internal class ActivityTraceProvider : ITraceProvider
+{
+ private Lazy> _lazyActivitySources = new();
+ private ConcurrentDictionary _activitySources => _lazyActivitySources.Value;
+
+ public ITracer GetTracer(string name, string? version = "")
+ => _activitySources.GetOrAdd(name, new ActivityTracer(name, version));
+}
diff --git a/src/Sentry.DiagnosticSource/Internal/Tracing/ActivityTraceSpan.cs b/src/Sentry.DiagnosticSource/Internal/Tracing/ActivityTraceSpan.cs
new file mode 100644
index 0000000000..a7c4e2ec06
--- /dev/null
+++ b/src/Sentry.DiagnosticSource/Internal/Tracing/ActivityTraceSpan.cs
@@ -0,0 +1,74 @@
+namespace Sentry.Internal.Tracing;
+
+#if !NET6_0_OR_GREATER
+using System.Diagnostics;
+#endif
+
+///
+/// Wraps the functionality that we use from Activity in an interface so that we can
+/// access this from our integrations without taking a hard dependency on
+/// System.Diagnostics.Activity (which is only available in .NET 5.0 and later)
+///
+internal class ActivityTraceSpan(System.Diagnostics.Activity activity) : ITraceSpan
+{
+ public string? Description => activity.DisplayName;
+
+ public ITraceSpan AddEvent(string message)
+ {
+ activity.AddEvent(new ActivityEvent(message));
+ return this;
+ }
+
+ public ITraceSpan SetAttribute(string key, object value)
+ {
+ activity.SetTag(key, value);
+ return this;
+ }
+
+ public ITraceSpan SetDescription(string? description)
+ {
+ if (description != null)
+ {
+ activity.DisplayName = description;
+ }
+ return this;
+ }
+
+ public ITraceSpan SetStatus(SpanStatus status, string? description = default)
+ {
+ if (status == SpanStatus.Ok)
+ {
+ activity.SetStatus(ActivityStatusCode.Ok);
+ return this;
+ }
+ var errorMessage = description ?? status.ToString();
+ activity.SetStatus(ActivityStatusCode.Error, errorMessage);
+ return this;
+ }
+
+ public ITraceSpan Stop()
+ {
+ activity.Stop();
+ return this;
+ }
+
+ public ITraceSpan SetExtra(string key, object? value)
+ {
+ // TODO: Not sure what we want to do about Extra. This would change that data from being stored as "Extra" data
+ // (which is documented as being obsolete) to being stored as tags... which is a change in behaviour. The docs
+ // say structured contexts should be used instead, so maybe we need to create some new structured contexts for
+ // this "arbitrary" extra data that both we and users are currently storing. Alternatively, we can continue to
+ // store it as extra (in which case we need to implement this in some kind of custom property both here and when
+ // converting the activity into a SentrySpan for transmission)
+ activity.SetTag(key, value);
+ return this;
+ }
+
+ public ITraceSpan Finish(Exception exception)
+ {
+ activity.BindException(exception);
+ return Stop();
+ }
+
+ public void Dispose() => activity.Dispose();
+}
diff --git a/src/Sentry.DiagnosticSource/Internal/Tracing/ActivityTracer.cs b/src/Sentry.DiagnosticSource/Internal/Tracing/ActivityTracer.cs
new file mode 100644
index 0000000000..bb12e5d087
--- /dev/null
+++ b/src/Sentry.DiagnosticSource/Internal/Tracing/ActivityTracer.cs
@@ -0,0 +1,27 @@
+namespace Sentry.Internal.Tracing;
+
+///
+/// Wraps the functionality that we use from ActivitySource in an interface so that we
+/// can access this from our integrations without taking a hard dependency on
+/// System.Diagnostics.ActivitySource (which is only available in .NET 5.0 and later)
+///
+internal class ActivityTracer(string name, string? version = "") : ITracer
+{
+ private readonly ActivitySource _activitySource = new(name, version);
+
+ public ITraceSpan? StartSpan(string operationName, string? description = null)
+ {
+ var activity = _activitySource.CreateActivity(operationName, ActivityKind.Internal)
+ ?.SetIdFormat(ActivityIdFormat.W3C)
+ ?.Start();
+ if (activity is not null)
+ {
+ activity.DisplayName = description ?? operationName;
+ }
+ return activity is not null ? new ActivityTraceSpan(activity) : null;
+ }
+
+ public ITraceSpan? CurrentSpan => System.Diagnostics.Activity.Current == null
+ ? null
+ : new ActivityTraceSpan(System.Diagnostics.Activity.Current);
+}
diff --git a/src/Sentry.DiagnosticSource/Internal/Tracing/ActivityTracingIntegration.cs b/src/Sentry.DiagnosticSource/Internal/Tracing/ActivityTracingIntegration.cs
new file mode 100644
index 0000000000..68e33ca105
--- /dev/null
+++ b/src/Sentry.DiagnosticSource/Internal/Tracing/ActivityTracingIntegration.cs
@@ -0,0 +1,29 @@
+using Sentry.Extensibility;
+using Sentry.Integrations;
+
+namespace Sentry.Internal.Tracing;
+
+internal class ActivityTracingIntegration : ISdkIntegration
+{
+ /*
+ TODO: Think about where to put this... would be good if it sat on an IDisposable but also something internal.
+ We could possibly put it on the Hub - add an ITracingHub interface that was internal and held both this and
+ the SentryTraceProvider. Alternatively we could rename IMetricHub to IInternalHub and put it there... metrics
+ should probably be using this mechanism for tracing anyway (as it's all internal) but that would complicate
+ things like code locations... which we could maybe store as custom properties on the Activity.
+ */
+ private SentryActivityListener? _listener;
+
+ public void Register(IHub hub, SentryOptions options)
+ {
+ if (!options.IsPerformanceMonitoringEnabled)
+ {
+ options.Log(SentryLevel.Info, "SentryTracing Integration is disabled because tracing is disabled.");
+ return;
+ }
+
+ // TODO: Should we be registering this if OpenTelemetry is enabled?
+ options.InternalTraceProvider = new ActivityTraceProvider();
+ _listener = new SentryActivityListener(hub, Instrumenter.ActivitySource);
+ }
+}
diff --git a/src/Sentry.DiagnosticSource/Internal/Tracing/SentryActivityListener.cs b/src/Sentry.DiagnosticSource/Internal/Tracing/SentryActivityListener.cs
new file mode 100644
index 0000000000..ebfa8f7a90
--- /dev/null
+++ b/src/Sentry.DiagnosticSource/Internal/Tracing/SentryActivityListener.cs
@@ -0,0 +1,44 @@
+namespace Sentry.Internal.Tracing;
+
+internal class SentryActivityListener : IDisposable
+{
+ private readonly ActivitySpanProcessor _activitySpanProcessor;
+ private readonly ActivityListener? _listener;
+
+ public SentryActivityListener(IHub hub, Instrumenter instrumenter) : this(new ActivitySpanProcessor(hub, instrumenter))
+ {
+ }
+
+ public SentryActivityListener(ActivitySpanProcessor activitySpanProcessor)
+ {
+ _activitySpanProcessor = activitySpanProcessor;
+ _listener = new ActivityListener()
+ {
+ // This is only for internal Sentry events
+ ShouldListenTo = (source) => source.Name.StartsWith("Sentry"),
+ Sample = ShouldSample,
+ ActivityStarted = OnActivityStarted,
+ ActivityStopped = OnActivityStopped
+ };
+ ActivitySource.AddActivityListener(_listener);
+ }
+
+ // We sample all the activities... these will get filtered out by the Hub when they get converted to spans
+ public ActivitySamplingResult ShouldSample(ref ActivityCreationOptions _)
+ => ActivitySamplingResult.AllDataAndRecorded;
+
+ public void OnActivityStarted(System.Diagnostics.Activity activity)
+ {
+ _activitySpanProcessor.OnStart(activity);
+ }
+
+ public void OnActivityStopped(System.Diagnostics.Activity activity)
+ {
+ _activitySpanProcessor.OnEnd(activity);
+ }
+
+ public void Dispose()
+ {
+ _listener?.Dispose();
+ }
+}
diff --git a/src/Sentry.DiagnosticSource/Internal/Tracing/TraceExtensions.cs b/src/Sentry.DiagnosticSource/Internal/Tracing/TraceExtensions.cs
new file mode 100644
index 0000000000..2b4c32b94a
--- /dev/null
+++ b/src/Sentry.DiagnosticSource/Internal/Tracing/TraceExtensions.cs
@@ -0,0 +1,41 @@
+namespace Sentry.Internal.Tracing;
+
+internal static class TraceExtensions
+{
+ private const string SentrySpanKey = "_Sentry.SentrySpan";
+ private const string SentryExceptionKey = "_Sentry.Exception";
+
+ public static SpanId AsSentrySpanId(this ActivitySpanId id) => SpanId.Parse(id.ToHexString());
+
+ public static ActivitySpanId AsActivitySpanId(this SpanId id) => ActivitySpanId.CreateFromString(id.ToString().AsSpan());
+
+ public static SentryId AsSentryId(this ActivityTraceId id) => SentryId.Parse(id.ToHexString());
+
+ public static ActivityTraceId AsActivityTraceId(this SentryId id) => ActivityTraceId.CreateFromString(id.ToString().AsSpan());
+
+ public static BaggageHeader AsBaggageHeader(this IEnumerable> baggage, bool useSentryPrefix = false) =>
+ BaggageHeader.Create(
+ baggage.Where(member => member.Value != null)
+ .Select(kvp => (KeyValuePair)kvp!),
+ useSentryPrefix
+ );
+
+ public static void BindException(this System.Diagnostics.Activity activity, Exception exception)
+ {
+ activity.SetCustomProperty(SentryExceptionKey, exception);
+ }
+
+ public static Exception? GetException(this System.Diagnostics.Activity activity) =>
+ activity.GetCustomProperty(SentryExceptionKey) as Exception;
+
+ public static void BindSentrySpan(this System.Diagnostics.Activity activity, ISpan span)
+ {
+ activity.SetCustomProperty(SentrySpanKey, span);
+ span.SetFused(activity); // We use a weak reference to allow the Activity to be disposed
+ }
+
+ public static ISpan? GetSentrySpan(this System.Diagnostics.Activity activity)
+ => activity.GetCustomProperty(SentrySpanKey) is ISpan span ? span : null;
+
+ public static System.Diagnostics.Activity? GetActivity(this ISpan span) => span.GetFused();
+}
diff --git a/src/Sentry.DiagnosticSource/Sentry.DiagnosticSource.csproj b/src/Sentry.DiagnosticSource/Sentry.DiagnosticSource.csproj
index 2bb043f837..1c7bcbc65b 100644
--- a/src/Sentry.DiagnosticSource/Sentry.DiagnosticSource.csproj
+++ b/src/Sentry.DiagnosticSource/Sentry.DiagnosticSource.csproj
@@ -14,7 +14,7 @@
-
+
diff --git a/src/Sentry.Extensions.Logging/SentryHttpMessageHandlerBuilderFilter.cs b/src/Sentry.Extensions.Logging/SentryHttpMessageHandlerBuilderFilter.cs
index f35cad05d9..22a2af3c0c 100644
--- a/src/Sentry.Extensions.Logging/SentryHttpMessageHandlerBuilderFilter.cs
+++ b/src/Sentry.Extensions.Logging/SentryHttpMessageHandlerBuilderFilter.cs
@@ -17,7 +17,7 @@ public Action Configure(Action h is SentryHttpMessageHandler))
{
handlerBuilder.AdditionalHandlers.Add(
- new SentryHttpMessageHandler(hub)
+ new SentryHttpMessageHandler(hub, default, default, null, true)
);
}
diff --git a/src/Sentry.OpenTelemetry/OpenTelemetryExtensions.cs b/src/Sentry.OpenTelemetry/OpenTelemetryExtensions.cs
deleted file mode 100644
index fecc538723..0000000000
--- a/src/Sentry.OpenTelemetry/OpenTelemetryExtensions.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-namespace Sentry.OpenTelemetry;
-
-internal static class OpenTelemetryExtensions
-{
- public static SpanId AsSentrySpanId(this ActivitySpanId id) => SpanId.Parse(id.ToHexString());
-
- public static ActivitySpanId AsActivitySpanId(this SpanId id) => ActivitySpanId.CreateFromString(id.ToString().AsSpan());
-
- public static SentryId AsSentryId(this ActivityTraceId id) => SentryId.Parse(id.ToHexString());
-
- public static ActivityTraceId AsActivityTraceId(this SentryId id) => ActivityTraceId.CreateFromString(id.ToString().AsSpan());
-
- public static BaggageHeader AsBaggageHeader(this IEnumerable> baggage, bool useSentryPrefix = false) =>
- BaggageHeader.Create(
- baggage.Where(member => member.Value != null)
- .Select(kvp => (KeyValuePair)kvp!),
- useSentryPrefix
- );
-}
diff --git a/src/Sentry.OpenTelemetry/OpenTelemetryTransactionProcessor.cs b/src/Sentry.OpenTelemetry/OpenTelemetryTransactionProcessor.cs
index 4ab485dde1..b8580014c4 100644
--- a/src/Sentry.OpenTelemetry/OpenTelemetryTransactionProcessor.cs
+++ b/src/Sentry.OpenTelemetry/OpenTelemetryTransactionProcessor.cs
@@ -1,4 +1,5 @@
using Sentry.Extensibility;
+using Sentry.Internal.Tracing;
namespace Sentry.OpenTelemetry;
diff --git a/src/Sentry.OpenTelemetry/Sentry.OpenTelemetry.csproj b/src/Sentry.OpenTelemetry/Sentry.OpenTelemetry.csproj
index 66a780f05a..46c13b97f3 100644
--- a/src/Sentry.OpenTelemetry/Sentry.OpenTelemetry.csproj
+++ b/src/Sentry.OpenTelemetry/Sentry.OpenTelemetry.csproj
@@ -25,4 +25,22 @@
+
+
+ $(DefineConstants);HAS_DIAGNOSTICS_7_OR_GREATER
+
+
+
+
+
+ Internal\Tracing\%(RecursiveDir)%(Filename)%(Extension)
+
+
+
+
diff --git a/src/Sentry.OpenTelemetry/SentryPropagator.cs b/src/Sentry.OpenTelemetry/SentryPropagator.cs
index 78fd960d6b..f803dcda6c 100644
--- a/src/Sentry.OpenTelemetry/SentryPropagator.cs
+++ b/src/Sentry.OpenTelemetry/SentryPropagator.cs
@@ -2,6 +2,7 @@
using OpenTelemetry;
using OpenTelemetry.Context.Propagation;
using Sentry.Extensibility;
+using Sentry.Internal.Tracing;
namespace Sentry.OpenTelemetry;
diff --git a/src/Sentry.OpenTelemetry/SentrySpanProcessor.cs b/src/Sentry.OpenTelemetry/SentrySpanProcessor.cs
index 520c09fbd8..d9db8daee3 100644
--- a/src/Sentry.OpenTelemetry/SentrySpanProcessor.cs
+++ b/src/Sentry.OpenTelemetry/SentrySpanProcessor.cs
@@ -1,8 +1,7 @@
using OpenTelemetry;
using Sentry.Extensibility;
-using Sentry.Internal;
using Sentry.Internal.Extensions;
-using Sentry.Internal.OpenTelemetry;
+using Sentry.Internal.Tracing;
namespace Sentry.OpenTelemetry;
@@ -11,17 +10,11 @@ namespace Sentry.OpenTelemetry;
///
public class SentrySpanProcessor : BaseProcessor
{
- private readonly IHub _hub;
internal readonly IEnumerable _enrichers;
- // ReSharper disable once MemberCanBePrivate.Global - Used by tests
- internal readonly ConcurrentDictionary _map = new();
+ private readonly ActivitySpanProcessor _activitySpanProcessor;
+ private readonly IHub _hub;
private readonly SentryOptions? _options;
- private readonly Lazy> _resourceAttributes;
-
- private static readonly long PruningInterval = TimeSpan.FromSeconds(5).Ticks;
- internal long _lastPruned = 0;
- private readonly Lazy _realHub;
///
/// Constructs a .
@@ -40,14 +33,6 @@ public SentrySpanProcessor(IHub hub) : this(hub, null)
internal SentrySpanProcessor(IHub hub, IEnumerable? enrichers)
{
_hub = hub;
- _realHub = new Lazy(() =>
- _hub switch
- {
- Hub thisHub => thisHub,
- HubAdapter when SentrySdk.CurrentHub is Hub sdkHub => sdkHub,
- _ => null
- });
-
if (_hub is DisabledHub)
{
// This would only happen if someone tried to create a SentrySpanProcessor manually
@@ -56,9 +41,7 @@ internal SentrySpanProcessor(IHub hub, IEnumerable? enri
"You should use the TracerProviderBuilderExtensions to configure Sentry with OpenTelemetry");
}
- _enrichers = enrichers ?? Enumerable.Empty();
_options = hub.GetSentryOptions();
-
if (_options is null)
{
throw new InvalidOperationException(
@@ -73,380 +56,34 @@ internal SentrySpanProcessor(IHub hub, IEnumerable? enri
"to initialize the Sentry SDK with options.UseOpenTelemetry()");
}
- // Resource attributes are consistent between spans, but not available during construction.
- // Thus, get a single instance lazily.
- _resourceAttributes = new Lazy>(() =>
- ParentProvider?.GetResource().Attributes.ToDict() ?? new Dictionary(0));
+ _enrichers = enrichers ?? Enumerable.Empty();
+ _activitySpanProcessor = new ActivitySpanProcessor(hub, ApplyEnrichers, GetResourceAttributes);
}
+ internal ISpan? GetMappedSpan(ActivitySpanId spanId) => _activitySpanProcessor.GetMappedSpan(spanId);
+
///
public override void OnStart(Activity data)
{
- if (data.ParentSpanId != default && _map.TryGetValue(data.ParentSpanId, out var parentSpan))
- {
- // We can find the parent span - start a child span.
- var context = new SpanContext(
- data.OperationName,
- data.SpanId.AsSentrySpanId(),
- data.ParentSpanId.AsSentrySpanId(),
- data.TraceId.AsSentryId(),
- data.DisplayName,
- null,
- null)
- {
- Instrumenter = Instrumenter.OpenTelemetry
- };
-
- var span = (SpanTracer)parentSpan.StartChild(context);
- span.StartTimestamp = data.StartTimeUtc;
- // Used to filter out spans that are not recorded when finishing a transaction.
- span.SetFused(data);
- span.IsFiltered = () => span.GetFused() is { IsAllDataRequested: false, Recorded: false };
- _map[data.SpanId] = span;
- }
- else
- {
- // If a parent exists at all, then copy its sampling decision.
- bool? isSampled = data.HasRemoteParent ? data.Recorded : null;
-
- // No parent span found - start a new transaction
- var transactionContext = new TransactionContext(data.DisplayName,
- data.OperationName,
- data.SpanId.AsSentrySpanId(),
- data.ParentSpanId.AsSentrySpanId(),
- data.TraceId.AsSentryId(),
- data.DisplayName, null, isSampled, isSampled)
- {
- Instrumenter = Instrumenter.OpenTelemetry
- };
-
- var baggageHeader = data.Baggage.AsBaggageHeader();
- var dynamicSamplingContext = baggageHeader.CreateDynamicSamplingContext();
- var transaction = (TransactionTracer)_hub.StartTransaction(
- transactionContext, new Dictionary(), dynamicSamplingContext
- );
- transaction.StartTimestamp = data.StartTimeUtc;
- _hub.ConfigureScope(scope => scope.Transaction = transaction);
- transaction.SetFused(data);
- _map[data.SpanId] = transaction;
- }
-
- // Housekeeping
- PruneFilteredSpans();
+ _activitySpanProcessor.OnStart(data);
}
///
public override void OnEnd(Activity data)
{
- // Make a dictionary of the attributes (aka "tags") for faster lookup when used throughout the processor.
- var attributes = data.TagObjects.ToDict();
-
- var url =
- attributes.TryGetTypedValue(OtelSemanticConventions.AttributeUrlFull, out string? tempUrl) ? tempUrl
- : attributes.TryGetTypedValue(OtelSemanticConventions.AttributeHttpUrl, out string? fallbackUrl) ? fallbackUrl // Falling back to pre-1.5.0
- : null;
-
- if (!string.IsNullOrEmpty(url) && (_options?.IsSentryRequest(url) ?? false))
- {
- _options?.DiagnosticLogger?.LogDebug($"Ignoring Activity {data.SpanId} for Sentry request.");
-
- if (_map.TryRemove(data.SpanId, out var removed))
- {
- if (removed is SpanTracer spanTracerToRemove)
- {
- spanTracerToRemove.IsSentryRequest = true;
- }
-
- if (removed is TransactionTracer transactionTracer)
- {
- transactionTracer.IsSentryRequest = true;
- }
- }
-
- return;
- }
-
- if (!_map.TryGetValue(data.SpanId, out var span))
- {
- _options?.DiagnosticLogger?.LogError($"Span not found for SpanId: {data.SpanId}. Did OnStart run? We might have a bug in the SDK.");
- return;
- }
-
- var (operation, description, source) = ParseOtelSpanDescription(data, attributes);
- span.Operation = operation;
- span.Description = description;
-
- if (span is TransactionTracer transaction)
- {
- transaction.Name = description;
- transaction.NameSource = source;
-
- // Use the end timestamp from the activity data.
- transaction.EndTimestamp = data.StartTimeUtc + data.Duration;
-
- // Transactions set otel attributes (and resource attributes) as context.
- transaction.Contexts["otel"] = GetOtelContext(attributes);
- }
- else
- {
- // Use the end timestamp from the activity data.
- ((SpanTracer)span).EndTimestamp = data.StartTimeUtc + data.Duration;
-
- // Spans set otel attributes in extras (passed to Sentry as "data" on the span).
- // Resource attributes do not need to be set, as they would be identical as those set on the transaction.
- span.SetExtras(attributes);
- span.SetExtra("otel.kind", data.Kind);
- }
-
- // In ASP.NET Core the middleware finishes up (and the scope gets popped) before the activity is ended. So we
- // need to restore the scope here (it's saved by our middleware when the request starts)
- var activityScope = GetSavedScope(data);
- if (activityScope is { } savedScope)
- {
- var hub = _realHub.Value;
- hub?.RestoreScope(savedScope);
- }
- GenerateSentryErrorsFromOtelSpan(data, attributes);
-
- var status = GetSpanStatus(data.Status, attributes);
- foreach (var enricher in _enrichers)
- {
- enricher.Enrich(span, data, _hub, _options);
- }
- span.Finish(status);
-
- _map.TryRemove(data.SpanId, out _);
-
- // Housekeeping
- PruneFilteredSpans();
- }
-
- ///
- /// Clean up items that may have been filtered out.
- /// See https://github.com/getsentry/sentry-dotnet/pull/3198
- ///
- internal void PruneFilteredSpans(bool force = false)
- {
- if (!force && !NeedsPruning())
- {
- return;
- }
-
- foreach (var mappedItem in _map)
- {
- var (spanId, span) = mappedItem;
- var activity = span.GetFused();
- if (activity is { Recorded: false, IsAllDataRequested: false })
- {
- _map.TryRemove(spanId, out _);
- }
- }
- }
-
- private bool NeedsPruning()
- {
- var lastPruned = Interlocked.Read(ref _lastPruned);
- if (lastPruned > DateTime.UtcNow.Ticks - PruningInterval)
- {
- return false;
- }
-
- var thisPruned = DateTime.UtcNow.Ticks;
- Interlocked.CompareExchange(ref _lastPruned, thisPruned, lastPruned);
- // May be false if another thread gets there first
- return Interlocked.Read(ref _lastPruned) == thisPruned;
- }
-
- private static Scope? GetSavedScope(Activity? activity)
- {
- while (activity is not null)
- {
- if (activity.GetFused() is { } savedScope)
- {
- return savedScope;
- }
- activity = activity.Parent;
- }
- return null;
- }
-
- internal static SpanStatus GetSpanStatus(ActivityStatusCode status, IDictionary attributes)
- {
- // See https://github.com/open-telemetry/opentelemetry-dotnet/discussions/4703
- if (attributes.TryGetValue(OtelSpanAttributeConstants.StatusCodeKey, out var statusCode)
- && statusCode is OtelStatusTags.ErrorStatusCodeTagValue
- )
- {
- return GetErrorSpanStatus(attributes);
- }
- return status switch
- {
- ActivityStatusCode.Unset => SpanStatus.Ok,
- ActivityStatusCode.Ok => SpanStatus.Ok,
- ActivityStatusCode.Error => GetErrorSpanStatus(attributes),
- _ => SpanStatus.UnknownError
- };
- }
-
- private static SpanStatus GetErrorSpanStatus(IDictionary attributes)
- {
- if (attributes.TryGetTypedValue("http.status_code", out int httpCode))
- {
- return SpanStatusConverter.FromHttpStatusCode(httpCode);
- }
-
- if (attributes.TryGetTypedValue("rpc.grpc.status_code", out int grpcCode))
- {
- return SpanStatusConverter.FromGrpcStatusCode(grpcCode);
- }
-
- return SpanStatus.UnknownError;
- }
-
- private static (string operation, string description, TransactionNameSource source) ParseOtelSpanDescription(
- Activity activity,
- IDictionary attributes)
- {
- // This function should loosely match the JavaScript implementation at:
- // https://github.com/getsentry/sentry-javascript/blob/3487fa3af7aa72ac7fdb0439047cb7367c591e77/packages/opentelemetry-node/src/utils/parseOtelSpanDescription.ts
- // However, it should also follow the OpenTelemetry semantic conventions specification, as indicated.
-
- // HTTP span
- // https://opentelemetry.io/docs/specs/otel/trace/semantic_conventions/http/
- if (attributes.TryGetTypedValue(OtelSemanticConventions.AttributeHttpMethod, out string httpMethod))
- {
- if (activity.Kind == ActivityKind.Client)
- {
- // Per OpenTelemetry spec, client spans use only the method.
- return ("http.client", httpMethod, TransactionNameSource.Custom);
- }
-
- if (attributes.TryGetTypedValue(OtelSemanticConventions.AttributeHttpRoute, out string httpRoute))
- {
- // A route exists. Use the method and route.
- return ("http.server", $"{httpMethod} {httpRoute}", TransactionNameSource.Route);
- }
-
- if (attributes.TryGetTypedValue(OtelSemanticConventions.AttributeHttpTarget, out string httpTarget))
- {
- // A target exists. Use the method and target. If the target is "/" we can treat it like a route.
- var source = httpTarget == "/" ? TransactionNameSource.Route : TransactionNameSource.Url;
- return ("http.server", $"{httpMethod} {httpTarget}", source);
- }
-
- // Some other type of HTTP server span. Pass it through with the original name.
- return ("http.server", activity.DisplayName, TransactionNameSource.Custom);
- }
-
- // DB span
- // https://opentelemetry.io/docs/specs/otel/trace/semantic_conventions/database/
- if (attributes.ContainsKey(OtelSemanticConventions.AttributeDbSystem))
- {
- if (attributes.TryGetTypedValue(OtelSemanticConventions.AttributeDbStatement, out string dbStatement))
- {
- // We have a database statement. Use it.
- return ("db", dbStatement, TransactionNameSource.Task);
- }
-
- // Some other type of DB span. Pass it through with the original name.
- return ("db", activity.DisplayName, TransactionNameSource.Task);
- }
-
- // RPC span
- // https://opentelemetry.io/docs/specs/otel/trace/semantic_conventions/rpc/
- if (attributes.ContainsKey(OtelSemanticConventions.AttributeRpcService))
- {
- return ("rpc", activity.DisplayName, TransactionNameSource.Route);
- }
-
- // Messaging span
- // https://opentelemetry.io/docs/specs/otel/trace/semantic_conventions/messaging/
- if (attributes.ContainsKey(OtelSemanticConventions.AttributeMessagingSystem))
- {
- return ("message", activity.DisplayName, TransactionNameSource.Route);
- }
-
- // FaaS (Functions/Lambda) span
- // https://opentelemetry.io/docs/specs/otel/trace/semantic_conventions/faas/
- if (attributes.TryGetTypedValue(OtelSemanticConventions.AttributeFaasTrigger, out string faasTrigger))
- {
- return (faasTrigger, activity.DisplayName, TransactionNameSource.Route);
- }
-
- // Default - pass through unmodified.
- return (activity.OperationName, activity.DisplayName, TransactionNameSource.Custom);
+ _activitySpanProcessor.OnEnd(data);
}
- private Dictionary GetOtelContext(IDictionary attributes)
+ private void ApplyEnrichers(ISpan span, Activity data)
{
- var otelContext = new Dictionary();
- if (attributes.Count > 0)
- {
- otelContext.Add("attributes", attributes);
- }
-
- var resourceAttributes = _resourceAttributes.Value;
- if (resourceAttributes.Count > 0)
+ foreach (var enricher in _enrichers)
{
- otelContext.Add("resource", resourceAttributes);
+ enricher.Enrich(span, data, _hub, _options);
}
-
- return otelContext;
}
- private void GenerateSentryErrorsFromOtelSpan(Activity activity, IDictionary spanAttributes)
+ private Dictionary GetResourceAttributes()
{
- // https://develop.sentry.dev/sdk/performance/opentelemetry/#step-7-define-generatesentryerrorsfromotelspan
- // https://opentelemetry.io/docs/specs/otel/trace/semantic_conventions/exceptions/
- foreach (var @event in activity.Events.Where(e => e.Name == OtelSemanticConventions.AttributeExceptionEventName))
- {
- var eventAttributes = @event.Tags.ToDict();
- // This would be where we would ideally implement full exception capture. That's not possible at the
- // moment since the full exception isn't yet available via the OpenTelemetry API.
- // See https://github.com/open-telemetry/opentelemetry-dotnet/issues/2439#issuecomment-1577314568
- // if (!eventAttributes.TryGetTypedValue("exception", out Exception exception))
- // {
- // continue;
- // }
-
- // At the moment, OTEL only gives us `exception.type`, `exception.message`, and `exception.stacktrace`...
- // So the best we can do is a poor man's exception (no accurate symbolication or anything)
- if (!eventAttributes.TryGetTypedValue(OtelSemanticConventions.AttributeExceptionType, out string exceptionType))
- {
- continue;
- }
- eventAttributes.TryGetTypedValue(OtelSemanticConventions.AttributeExceptionMessage, out string message);
- eventAttributes.TryGetTypedValue(OtelSemanticConventions.AttributeExceptionStacktrace, out string stackTrace);
-
- Exception exception;
- try
- {
- var type = Type.GetType(exceptionType)!;
- exception = (Exception)Activator.CreateInstance(type, message)!;
- exception.SetSentryMechanism("SentrySpanProcessor.ErrorSpan");
- }
- catch
- {
- _options?.DiagnosticLogger?.LogError($"Failed to create poor man's exception for type : {exceptionType}");
- continue;
- }
-
- // TODO: Validate that our `DuplicateEventDetectionEventProcessor` prevents this from doubling exceptions
- // that are also caught by other means, such as our AspNetCore middleware, etc.
- // (When options.RecordException = true is set on AddAspNetCoreInstrumentation...)
- // Also, in such cases - how will we get the otel scope and trace context on the other one?
-
- var sentryEvent = new SentryEvent(exception, @event.Timestamp);
- var otelContext = GetOtelContext(spanAttributes);
- otelContext.Add("stack_trace", stackTrace);
- sentryEvent.Contexts["otel"] = otelContext;
- _hub.CaptureEvent(sentryEvent, scope =>
- {
- var trace = scope.Contexts.Trace;
- trace.SpanId = activity.SpanId.AsSentrySpanId();
- trace.ParentSpanId = activity.ParentSpanId.AsSentrySpanId();
- trace.TraceId = activity.TraceId.AsSentryId();
- });
- }
+ return ParentProvider?.GetResource().Attributes.ToDict() ?? new Dictionary(0);
}
}
diff --git a/src/Sentry/DefaultIntegrationsExtensions.cs b/src/Sentry/DefaultIntegrationsExtensions.cs
new file mode 100644
index 0000000000..e9ddca023d
--- /dev/null
+++ b/src/Sentry/DefaultIntegrationsExtensions.cs
@@ -0,0 +1,7 @@
+namespace Sentry;
+
+internal static class DefaultIntegrationsExtensions
+{
+ public static bool Includes(this SentryOptions.DefaultIntegrations integrations, SentryOptions.DefaultIntegrations value)
+ => (integrations & value) != 0;
+}
diff --git a/src/Sentry/HubExtensions.cs b/src/Sentry/HubExtensions.cs
index a4bedfddec..7735c9bb6b 100644
--- a/src/Sentry/HubExtensions.cs
+++ b/src/Sentry/HubExtensions.cs
@@ -11,6 +11,18 @@ namespace Sentry;
[EditorBrowsable(EditorBrowsableState.Never)]
public static class HubExtensions
{
+ ///
+ /// Starts a child span for the current transaction or, if there is no active transaction, starts a new transaction.
+ ///
+ internal static ISpan StartSpan(this IHub hub, string operation, string description)
+ {
+ ITransactionTracer? currentTransaction = null;
+ hub.ConfigureScope(s => currentTransaction = s.Transaction);
+ return currentTransaction is { } transaction
+ ? transaction.StartChild(operation, description)
+ : hub.StartTransaction(operation, description);
+ }
+
///
/// Starts a transaction.
///
diff --git a/src/Sentry/IMetricHub.cs b/src/Sentry/IMetricHub.cs
index 99f9d7d778..c486e8cb52 100644
--- a/src/Sentry/IMetricHub.cs
+++ b/src/Sentry/IMetricHub.cs
@@ -14,9 +14,7 @@ internal interface IMetricHub
///
void CaptureCodeLocations(CodeLocations codeLocations);
- ///
- /// Starts a child span for the current transaction or, if there is no active transaction, starts a new transaction.
- ///
+ ///
ISpan StartSpan(string operation, string description);
///
diff --git a/src/Sentry/Instrumenter.cs b/src/Sentry/Instrumenter.cs
index f169216255..cdf3bfe575 100644
--- a/src/Sentry/Instrumenter.cs
+++ b/src/Sentry/Instrumenter.cs
@@ -10,6 +10,11 @@ public enum Instrumenter
///
Sentry,
+ ///
+ /// Spans are instrumented with Sentry using ActivitySource.
+ ///
+ ActivitySource,
+
///
/// Spans are instrumented via OpenTelemetry.
///
diff --git a/src/Sentry/Internal/Hub.cs b/src/Sentry/Internal/Hub.cs
index 1a82aa4fa9..7f08c51ba6 100644
--- a/src/Sentry/Internal/Hub.cs
+++ b/src/Sentry/Internal/Hub.cs
@@ -1,5 +1,6 @@
using Sentry.Extensibility;
using Sentry.Infrastructure;
+using Sentry.Internal.Tracing;
using Sentry.Protocol.Envelopes;
using Sentry.Protocol.Metrics;
@@ -20,7 +21,7 @@ internal class Hub : IHub, IMetricHub, IDisposable
// Internal for testability
internal ConditionalWeakTable ExceptionToSpanMap { get; } = new();
- internal IInternalScopeManager ScopeManager { get; }
+ public IInternalScopeManager ScopeManager { get; }
///
public IMetricAggregator Metrics { get; }
@@ -75,6 +76,9 @@ internal Hub(
options.LogDebug("Registering integration: '{0}'.", integration.GetType().Name);
integration.Register(this, options);
}
+
+ // If no tracing integration was registered, fall back to Sentry tracing
+ options.InternalTraceProvider ??= new SentryTraceProvider(this);
}
public void ConfigureScope(Action configureScope)
@@ -119,16 +123,6 @@ internal ITransactionTracer StartTransaction(
IReadOnlyDictionary customSamplingContext,
DynamicSamplingContext? dynamicSamplingContext)
{
- var instrumenter = (context as SpanContext)?.Instrumenter;
- if (instrumenter != _options.Instrumenter)
- {
- _options.LogWarning(
- $"Attempted to start a transaction via {instrumenter} instrumentation when the SDK is" +
- $" configured for {_options.Instrumenter} instrumentation. The transaction will not be created.");
-
- return NoOpTransaction.Instance;
- }
-
var transaction = new TransactionTracer(this, context);
// If the hub is disabled, we will always sample out. In other words, starting a transaction
@@ -539,14 +533,10 @@ public void CaptureCodeLocations(CodeLocations codeLocations)
}
}
- ///
+ ///
public ISpan StartSpan(string operation, string description)
{
- ITransactionTracer? currentTransaction = null;
- ConfigureScope(s => currentTransaction = s.Transaction);
- return currentTransaction is { } transaction
- ? transaction.StartChild(operation, description)
- : this.StartTransaction(operation, description);
+ return HubExtensions.StartSpan(this, operation, description);
}
public void CaptureSession(SessionUpdate sessionUpdate)
diff --git a/src/Sentry/Internal/Tracing/ITraceProvider.cs b/src/Sentry/Internal/Tracing/ITraceProvider.cs
new file mode 100644
index 0000000000..f1bd67a537
--- /dev/null
+++ b/src/Sentry/Internal/Tracing/ITraceProvider.cs
@@ -0,0 +1,6 @@
+namespace Sentry.Internal.Tracing;
+
+internal interface ITraceProvider
+{
+ public ITracer GetTracer(string name, string? version = "");
+}
diff --git a/src/Sentry/Internal/Tracing/ITraceSpan.cs b/src/Sentry/Internal/Tracing/ITraceSpan.cs
new file mode 100644
index 0000000000..de72b6b636
--- /dev/null
+++ b/src/Sentry/Internal/Tracing/ITraceSpan.cs
@@ -0,0 +1,28 @@
+namespace Sentry.Internal.Tracing;
+
+internal interface ITraceSpan : IDisposable
+{
+ string? Description { get; }
+ ITraceSpan AddEvent(string message);
+ ITraceSpan SetAttribute(string key, object value);
+ ITraceSpan SetDescription(string? description);
+ ITraceSpan SetStatus(SpanStatus status, string? description = default);
+ ITraceSpan Stop();
+
+ ITraceSpan SetExtra(string key, object? value);
+
+ ITraceSpan Finish(Exception exception);
+}
+
+internal static class TraceSpanExtensions
+{
+ internal static ITraceSpan SetExtras(this ITraceSpan traceSpan, IEnumerable> values)
+ {
+ foreach (var (key, value) in values)
+ {
+ traceSpan.SetExtra(key, value);
+ }
+
+ return traceSpan;
+ }
+}
diff --git a/src/Sentry/Internal/Tracing/ITracer.cs b/src/Sentry/Internal/Tracing/ITracer.cs
new file mode 100644
index 0000000000..006023b02b
--- /dev/null
+++ b/src/Sentry/Internal/Tracing/ITracer.cs
@@ -0,0 +1,7 @@
+namespace Sentry.Internal.Tracing;
+
+internal interface ITracer
+{
+ ITraceSpan? StartSpan(string operationName, string? description = null);
+ ITraceSpan? CurrentSpan { get; }
+}
diff --git a/src/Sentry/Internal/Tracing/SentryTraceProvider.cs b/src/Sentry/Internal/Tracing/SentryTraceProvider.cs
new file mode 100644
index 0000000000..ee02f1807a
--- /dev/null
+++ b/src/Sentry/Internal/Tracing/SentryTraceProvider.cs
@@ -0,0 +1,11 @@
+namespace Sentry.Internal.Tracing;
+
+internal class SentryTraceProvider(IHub hub) : ITraceProvider
+{
+ private readonly SentryTracer _tracer = new(hub);
+
+ // Sentry doesn't have the same concept of "Tracers" as the DiagnosticSource classes do, so we always return the
+ // same tracer for Sentry... it's just a wrapper around the Hub which is actually what Starts and Stops spans when
+ // using Sentry tracing.
+ public ITracer GetTracer(string name, string? version = "") => _tracer;
+}
diff --git a/src/Sentry/Internal/Tracing/SentryTraceSpan.cs b/src/Sentry/Internal/Tracing/SentryTraceSpan.cs
new file mode 100644
index 0000000000..d00c48c6d4
--- /dev/null
+++ b/src/Sentry/Internal/Tracing/SentryTraceSpan.cs
@@ -0,0 +1,74 @@
+namespace Sentry.Internal.Tracing;
+
+internal class SentryTraceSpan : ITraceSpan
+{
+ private readonly ISpan _span;
+ private Scope? _scope;
+
+ public string? Description => _span.Description;
+
+ public SentryTraceSpan(IHub hub, ISpan span)
+ {
+ _span = span;
+ hub.ConfigureScope(scope => _scope = scope);
+ }
+
+ public void Dispose()
+ {
+ // ISpan doesn't implement IDisposable
+ }
+
+ public ITraceSpan AddEvent(string message)
+ {
+ _scope?.AddBreadcrumb(message);
+ return this;
+ }
+
+ public ITraceSpan SetAttribute(string key, object value)
+ {
+ var stringValue = $"{value}";
+ if (string.IsNullOrWhiteSpace(stringValue))
+ {
+ _span.UnsetTag(key);
+ }
+ else
+ {
+ _span.SetTag(key, stringValue);
+ }
+ return this;
+ }
+
+ public ITraceSpan SetDescription(string? description)
+ {
+ _span.Description = description;
+ return this;
+ }
+
+ public ITraceSpan SetStatus(SpanStatus status, string? description = default)
+ {
+ _span.Status = status;
+ if (_span.Status != SpanStatus.Ok)
+ {
+ _span.Description = description;
+ }
+ return this;
+ }
+
+ public ITraceSpan Stop()
+ {
+ _span.Finish();
+ return this;
+ }
+
+ public ITraceSpan SetExtra(string key, object? value)
+ {
+ _span.SetExtra(key, value);
+ return this;
+ }
+
+ public ITraceSpan Finish(Exception exception)
+ {
+ _span.Finish(exception);
+ return this;
+ }
+}
diff --git a/src/Sentry/Internal/Tracing/SentryTracer.cs b/src/Sentry/Internal/Tracing/SentryTracer.cs
new file mode 100644
index 0000000000..5d9209db4f
--- /dev/null
+++ b/src/Sentry/Internal/Tracing/SentryTracer.cs
@@ -0,0 +1,13 @@
+namespace Sentry.Internal.Tracing;
+
+internal class SentryTracer(IHub hub) : ITracer
+{
+ public ITraceSpan? StartSpan(string operationName, string? description = null) => new SentryTraceSpan(
+ hub,
+ hub.StartSpan(operationName, description ?? operationName)
+ );
+
+ public ITraceSpan? CurrentSpan => hub.GetSpan() is { } span
+ ? new SentryTraceSpan(hub, span)
+ : null;
+}
diff --git a/src/Sentry/Internal/Tracing/SentryTracingIntegration.cs b/src/Sentry/Internal/Tracing/SentryTracingIntegration.cs
new file mode 100644
index 0000000000..28b51beb16
--- /dev/null
+++ b/src/Sentry/Internal/Tracing/SentryTracingIntegration.cs
@@ -0,0 +1,19 @@
+using Sentry.Extensibility;
+using Sentry.Integrations;
+
+namespace Sentry.Internal.Tracing;
+
+internal class SentryTracingIntegration : ISdkIntegration
+{
+ public void Register(IHub hub, SentryOptions options)
+ {
+ if (!options.IsPerformanceMonitoringEnabled)
+ {
+ options.Log(SentryLevel.Info, "SentryTracing Integration is disabled because tracing is disabled.");
+ return;
+ }
+
+ // TODO: Should we be registering this if OpenTelemetry is enabled?
+ options.InternalTraceProvider = new SentryTraceProvider(hub);
+ }
+}
diff --git a/src/Sentry/Sentry.csproj b/src/Sentry/Sentry.csproj
index 77d1d4b965..f08e8c4dc8 100644
--- a/src/Sentry/Sentry.csproj
+++ b/src/Sentry/Sentry.csproj
@@ -52,12 +52,19 @@
$(DefineConstants);HAS_DIAGNOSTIC_INTEGRATION
+ $(DefineConstants);HAS_ACTIVITY_TRACING_INTEGRATION
-
- Internal\%(RecursiveDir)%(Filename)%(Extension)
+
+ Internal\DiagnosticSource\%(RecursiveDir)%(Filename)%(Extension)
+
+
+ Internal\Tracing\%(RecursiveDir)%(Filename)%(Extension)
+
+ $(DefineConstants);HAS_DIAGNOSTICS_7_OR_GREATER
+
-
-
-
-
-
-
-
-
+
diff --git a/test/Sentry.OpenTelemetry.Tests/SentryPropagatorTests.cs b/test/Sentry.OpenTelemetry.Tests/SentryPropagatorTests.cs
index 693619aece..940b405319 100644
--- a/test/Sentry.OpenTelemetry.Tests/SentryPropagatorTests.cs
+++ b/test/Sentry.OpenTelemetry.Tests/SentryPropagatorTests.cs
@@ -1,6 +1,7 @@
using Microsoft.Extensions.Primitives;
using OpenTelemetry;
using OpenTelemetry.Context.Propagation;
+using Sentry.Internal.Tracing;
namespace Sentry.OpenTelemetry.Tests;
diff --git a/test/Sentry.OpenTelemetry.Tests/SentrySpanProcessorTests.cs b/test/Sentry.OpenTelemetry.Tests/SentrySpanProcessorTests.cs
index aab7671d1b..52f58ff113 100644
--- a/test/Sentry.OpenTelemetry.Tests/SentrySpanProcessorTests.cs
+++ b/test/Sentry.OpenTelemetry.Tests/SentrySpanProcessorTests.cs
@@ -1,4 +1,5 @@
using Sentry.Internal.OpenTelemetry;
+using Sentry.Internal.Tracing;
namespace Sentry.OpenTelemetry.Tests;
@@ -65,247 +66,6 @@ public void Ctor_Instrumenter_Not_OpenTelemetry_Throws()
Assert.Throws(() => _fixture.GetSut());
}
- [Fact]
- public void GetSpanStatus()
- {
- using (new AssertionScope())
- {
- var noAttributes = new Dictionary();
-
- // Unset and OK -> OK
- SentrySpanProcessor.GetSpanStatus(ActivityStatusCode.Unset, noAttributes).Should().Be(SpanStatus.Ok);
- SentrySpanProcessor.GetSpanStatus(ActivityStatusCode.Ok, noAttributes).Should().Be(SpanStatus.Ok);
-
- // Error (no attributes) -> UnknownError
- SentrySpanProcessor.GetSpanStatus(ActivityStatusCode.Error, noAttributes)
- .Should().Be(SpanStatus.UnknownError);
-
- // Unknown status code -> UnknownError
- SentrySpanProcessor.GetSpanStatus((ActivityStatusCode)42, noAttributes)
- .Should().Be(SpanStatus.UnknownError);
-
- // We only test one http scenario, just to make sure the SpanStatusConverter is called for these headers.
- // Tests for SpanStatusConverter ensure other http status codes would also work though
- var notFoundAttributes = new Dictionary { ["http.status_code"] = 404 };
- SentrySpanProcessor.GetSpanStatus(ActivityStatusCode.Error, notFoundAttributes)
- .Should().Be(SpanStatus.NotFound);
-
- // We only test one grpc scenario, just to make sure the SpanStatusConverter is called for these headers.
- // Tests for SpanStatusConverter ensure other grpc status codes would also work though
- var grpcAttributes = new Dictionary { ["rpc.grpc.status_code"] = 7 };
- SentrySpanProcessor.GetSpanStatus(ActivityStatusCode.Error, grpcAttributes)
- .Should().Be(SpanStatus.PermissionDenied);
-
- var errorAttributes = new Dictionary { [OtelSpanAttributeConstants.StatusCodeKey] = OtelStatusTags.ErrorStatusCodeTagValue };
- SentrySpanProcessor.GetSpanStatus(ActivityStatusCode.Ok, errorAttributes).Should().Be(SpanStatus.UnknownError);
- SentrySpanProcessor.GetSpanStatus(ActivityStatusCode.Unset, errorAttributes).Should().Be(SpanStatus.UnknownError);
- }
- }
-
- [Fact]
- public void OnStart_Transaction_With_DynamicSamplingContext()
- {
- // Arrange
- _fixture.Options.Instrumenter = Instrumenter.OpenTelemetry;
- var sut = _fixture.GetSut();
-
- var expected = new Dictionary()
- {
- { "trace_id", SentryId.Create().ToString() },
- { "public_key", "d4d82fc1c2c4032a83f3a29aa3a3aff" },
- { "sample_rate", "0.5" },
- };
- var data = Tracer.StartActivity("test op")!;
- data.AddBaggage($"{BaggageHeader.SentryKeyPrefix}trace_id", expected["trace_id"]);
- data.AddBaggage($"{BaggageHeader.SentryKeyPrefix}public_key", expected["public_key"]);
- data.AddBaggage($"{BaggageHeader.SentryKeyPrefix}sample_rate", expected["sample_rate"]);
-
- // Act
- sut.OnStart(data!);
-
- // Assert
- Assert.True(sut._map.TryGetValue(data.SpanId, out var span));
- if (span is not TransactionTracer transaction)
- {
- Assert.Fail("Span is not a transaction tracer");
- return;
- }
- if (transaction.DynamicSamplingContext is not { } actual)
- {
- Assert.Fail("Transaction does not have a dynamic sampling context");
- return;
- }
- using (new AssertionScope())
- {
- actual.Items["trace_id"].Should().Be(expected["trace_id"]);
- actual.Items["public_key"].Should().Be(expected["public_key"]);
- actual.Items["sample_rate"].Should().Be(expected["sample_rate"]);
- }
- }
-
- [Fact]
- public void OnStart_WithParentSpanId_StartsChildSpan()
- {
- // Arrange
- _fixture.Options.Instrumenter = Instrumenter.OpenTelemetry;
- var sut = _fixture.GetSut();
-
- using var parent = Tracer.StartActivity("Parent");
- sut.OnStart(parent);
-
- using var data = Tracer.StartActivity("TestActivity");
-
- // Act
- sut.OnStart(data!);
-
- // Assert
- Assert.True(sut._map.TryGetValue(data.SpanId, out var span));
- using (new AssertionScope())
- {
- span.Should().BeOfType();
- span.SpanId.Should().Be(data.SpanId.AsSentrySpanId());
- span.ParentSpanId.Should().Be(data.ParentSpanId.AsSentrySpanId());
- if (span is not SpanTracer spanTracer)
- {
- Assert.Fail("Span is not a span tracer");
- return;
- }
- using (new AssertionScope())
- {
- spanTracer.SpanId.Should().Be(data.SpanId.AsSentrySpanId());
- spanTracer.ParentSpanId.Should().Be(data.ParentSpanId.AsSentrySpanId());
- spanTracer.TraceId.Should().Be(data.TraceId.AsSentryId());
- spanTracer.Operation.Should().Be(data.OperationName);
- spanTracer.Description.Should().Be(data.DisplayName);
- spanTracer.Status.Should().BeNull();
- spanTracer.StartTimestamp.Should().Be(data.StartTimeUtc);
- }
- }
- }
-
- [Fact]
- public void OnStart_WithoutParentSpanId_StartsNewTransaction()
- {
- // Arrange
- _fixture.Options.Instrumenter = Instrumenter.OpenTelemetry;
- _fixture.ScopeManager = Substitute.For();
- var sut = _fixture.GetSut();
-
- var data = Tracer.StartActivity("test op");
-
- // Act
- sut.OnStart(data!);
-
- // Assert
- Assert.True(sut._map.TryGetValue(data.SpanId, out var span));
- if (span is not TransactionTracer transaction)
- {
- Assert.Fail("Span is not a transaction tracer");
- return;
- }
- using (new AssertionScope())
- {
- transaction.SpanId.Should().Be(data.SpanId.AsSentrySpanId());
- transaction.ParentSpanId.Should().Be(new ActivitySpanId().AsSentrySpanId());
- transaction.TraceId.Should().Be(data.TraceId.AsSentryId());
- transaction.Name.Should().Be(data.DisplayName);
- transaction.Operation.Should().Be(data.OperationName);
- transaction.Description.Should().Be(data.DisplayName);
- transaction.Status.Should().BeNull();
- transaction.StartTimestamp.Should().Be(data.StartTimeUtc);
- _fixture.ScopeManager.Received(1).ConfigureScope(Arg.Any>());
- }
- }
-
- [Fact]
- public void OnEnd_FinishesSpan()
- {
- // Arrange
- _fixture.Options.Instrumenter = Instrumenter.OpenTelemetry;
- var sut = _fixture.GetSut();
-
- var parent = Tracer.StartActivity(name: "transaction")!;
- sut.OnStart(parent);
-
- var tags = new Dictionary {
- { "foo", "bar" }
- };
- var data = Tracer.StartActivity(name: "test operation", kind: ActivityKind.Internal, parentContext: default, tags)!;
- data.DisplayName = "test display name";
- sut.OnStart(data);
-
- sut._map.TryGetValue(data.SpanId, out var span);
-
- // Act
- sut.OnEnd(data);
-
- // Assert
- if (span is not SpanTracer spanTracer)
- {
- Assert.Fail("Span is not a span tracer");
- return;
- }
-
- using (new AssertionScope())
- {
- spanTracer.ParentSpanId.Should().Be(parent.SpanId.AsSentrySpanId());
- spanTracer.Operation.Should().Be(data.OperationName);
- spanTracer.Description.Should().Be(data.DisplayName);
- spanTracer.EndTimestamp.Should().NotBeNull();
- spanTracer.Extra["otel.kind"].Should().Be(data.Kind);
- foreach (var keyValuePair in tags)
- {
- span.Extra[keyValuePair.Key].Should().Be(keyValuePair.Value);
- }
-
- spanTracer.Status.Should().Be(SpanStatus.Ok);
- }
- }
-
- [Fact]
- public void OnEnd_Transaction_RestoresSavedScope()
- {
- // Arrange
- _fixture.Options.Instrumenter = Instrumenter.OpenTelemetry;
- _fixture.ScopeManager = Substitute.For();
- var sut = _fixture.GetSut();
-
- var scope = new Scope();
- var data = Tracer.StartActivity("transaction")!;
- data.SetFused(scope);
- sut.OnStart(data);
-
- // Act
- sut.OnEnd(data);
-
- // Assert
- _fixture.ScopeManager.Received(1).RestoreScope(scope);
- }
-
- [Fact]
- public void OnEnd_Span_RestoresSavedScope()
- {
- // Arrange
- _fixture.Options.Instrumenter = Instrumenter.OpenTelemetry;
- _fixture.ScopeManager = Substitute.For();
- var sut = _fixture.GetSut();
-
- var scope = new Scope();
- var parent = Tracer.StartActivity("transaction")!;
- parent.SetFused(scope);
- sut.OnStart(parent);
-
- var data = Tracer.StartActivity("test operation")!;
- data.DisplayName = "test display name";
- sut.OnStart(data);
-
- // Act
- sut.OnEnd(data);
-
- // Assert
- _fixture.ScopeManager.Received(1).RestoreScope(scope);
- }
-
[Fact]
public void OnEnd_SpansEnriched()
{
@@ -319,7 +79,7 @@ public void OnEnd_SpansEnriched()
var parent = Tracer.StartActivity(name: "transaction")!;
sut.OnStart(parent);
- sut._map.TryGetValue(parent.SpanId, out var span);
+ var span = sut.GetMappedSpan(parent.SpanId);
// Act
sut.OnEnd(parent);
@@ -334,172 +94,4 @@ public void OnEnd_SpansEnriched()
transactionTracer.Tags.TryGetValue("foo", out var foo).Should().BeTrue();
foo.Should().Be("bar");
}
-
- [Fact]
- public void OnEnd_FinishesTransaction()
- {
- // Arrange
- _fixture.Options.Instrumenter = Instrumenter.OpenTelemetry;
- var sut = _fixture.GetSut();
-
- var tags = new Dictionary {
- { "foo", "bar" }
- };
- var data = Tracer.StartActivity(name: "test operation", kind: ActivityKind.Internal, parentContext: default, tags)!;
- data.DisplayName = "test display name";
- sut.OnStart(data);
-
- sut._map.TryGetValue(data.SpanId, out var span);
-
- // Act
- sut.OnEnd(data);
-
- // Assert
- if (span is not TransactionTracer transaction)
- {
- Assert.Fail("Span is not a transaction tracer");
- return;
- }
-
- using (new AssertionScope())
- {
- transaction.ParentSpanId.Should().Be(new ActivitySpanId().AsSentrySpanId());
- transaction.Operation.Should().Be(data.OperationName);
- transaction.Description.Should().Be(data.DisplayName);
- transaction.Name.Should().Be(data.DisplayName);
- transaction.NameSource.Should().Be(TransactionNameSource.Custom);
- transaction.EndTimestamp.Should().NotBeNull();
- transaction.Contexts["otel"].Should().BeEquivalentTo(new Dictionary
- {
- { "attributes", tags }
- });
- transaction.Status.Should().Be(SpanStatus.Ok);
- }
- }
-
- [Theory]
- [InlineData(OtelSemanticConventions.AttributeUrlFull)]
- [InlineData(OtelSemanticConventions.AttributeHttpUrl)]
- public void OnEnd_IsSentryRequest_DoesNotFinishTransaction(string urlKey)
- {
- // Arrange
- _fixture.Options.Instrumenter = Instrumenter.OpenTelemetry;
- var sut = _fixture.GetSut();
-
- var tags = new Dictionary { { "foo", "bar" }, { urlKey, _fixture.Options.Dsn } };
- var data = Tracer.StartActivity(name: "test operation", kind: ActivityKind.Internal, parentContext: default, tags)!;
- data.DisplayName = "test display name";
- sut.OnStart(data);
-
- sut._map.TryGetValue(data.SpanId, out var span);
-
- // Act
- sut.OnEnd(data);
-
- // Assert
- if (span is not TransactionTracer transaction)
- {
- Assert.Fail("Span is not a transaction tracer");
- return;
- }
-
- transaction.IsSentryRequest.Should().BeTrue();
- }
-
- private static void FilterActivity(Activity activity)
- {
- // Simulates filtering an activity - see https://github.com/getsentry/sentry-dotnet/pull/3198
- activity.IsAllDataRequested = false;
- activity.ActivityTraceFlags &= ~ActivityTraceFlags.Recorded;
- }
-
- [Fact]
- public void PruneFilteredSpans_FilteredTransactions_Pruned()
- {
- // Arrange
- _fixture.Options.Instrumenter = Instrumenter.OpenTelemetry;
- var sut = _fixture.GetSut();
-
- using var parent = Tracer.StartActivity();
- sut.OnStart(parent!);
-
- FilterActivity(parent);
-
- // Act
- sut.PruneFilteredSpans(true);
-
- // Assert
- Assert.False(sut._map.TryGetValue(parent.SpanId, out var _));
- }
-
- [Fact]
- public void PruneFilteredSpans_UnFilteredTransactions_NotPruned()
- {
- // Arrange
- _fixture.Options.Instrumenter = Instrumenter.OpenTelemetry;
- var sut = _fixture.GetSut();
-
- using var parent = Tracer.StartActivity();
- sut.OnStart(parent!);
-
- // Act
- sut.PruneFilteredSpans(true);
-
- // Assert
- Assert.True(sut._map.TryGetValue(parent.SpanId, out var _));
- }
-
- [Fact]
- public void PruneFilteredSpans_FilteredSpans_Pruned()
- {
- // Arrange
- _fixture.Options.Instrumenter = Instrumenter.OpenTelemetry;
- var sut = _fixture.GetSut();
-
- using var parent = Tracer.StartActivity();
- sut.OnStart(parent!);
-
- using var activity1 = Tracer.StartActivity();
- sut.OnStart(activity1!);
-
- using var activity2 = Tracer.StartActivity();
- sut.OnStart(activity2!);
-
- FilterActivity(activity2);
-
- // Act
- sut.PruneFilteredSpans(true);
-
- // Assert
- Assert.True(sut._map.TryGetValue(activity1.SpanId, out var _));
- Assert.False(sut._map.TryGetValue(activity2.SpanId, out var _));
- }
-
- [Fact]
- public void PruneFilteredSpans_RecentlyPruned_DoesNothing()
- {
- // Arrange
- _fixture.Options.Instrumenter = Instrumenter.OpenTelemetry;
- var sut = _fixture.GetSut();
-
- sut._lastPruned = DateTimeOffset.MaxValue.Ticks; // fake a recent prune
-
- using var parent = Tracer.StartActivity();
- sut.OnStart(parent!);
-
- using var activity1 = Tracer.StartActivity();
- sut.OnStart(activity1!);
-
- using var activity2 = Tracer.StartActivity();
- sut.OnStart(activity2!);
-
- FilterActivity(activity2);
-
- // Act
- sut.PruneFilteredSpans();
-
- // Assert
- Assert.True(sut._map.TryGetValue(activity1.SpanId, out var _));
- Assert.True(sut._map.TryGetValue(activity2.SpanId, out var _));
- }
}
diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt
index cca662c335..1af3c4e205 100644
--- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt
+++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt
@@ -317,7 +317,8 @@ namespace Sentry
public enum Instrumenter
{
Sentry = 0,
- OpenTelemetry = 1,
+ ActivitySource = 1,
+ OpenTelemetry = 2,
}
public readonly struct MeasurementUnit : System.IEquatable
{
@@ -545,7 +546,9 @@ namespace Sentry
public class SentryGraphQLHttpMessageHandler : Sentry.SentryMessageHandler
{
public SentryGraphQLHttpMessageHandler(System.Net.Http.HttpMessageHandler? innerHandler = null, Sentry.IHub? hub = null) { }
+ [System.Obsolete("This method is obsolete and will be removed in a future version.")]
protected override void HandleResponse(System.Net.Http.HttpResponseMessage response, Sentry.ISpan? span, string method, string url) { }
+ [System.Obsolete("This method is obsolete and will be removed in a future version.")]
protected override Sentry.ISpan? ProcessRequest(System.Net.Http.HttpRequestMessage request, string method, string url) { }
}
public class SentryHint
@@ -564,7 +567,9 @@ namespace Sentry
public SentryHttpMessageHandler(Sentry.IHub hub) { }
public SentryHttpMessageHandler(System.Net.Http.HttpMessageHandler innerHandler) { }
public SentryHttpMessageHandler(System.Net.Http.HttpMessageHandler innerHandler, Sentry.IHub hub) { }
+ [System.Obsolete("This method will be removed in future versions.")]
protected override void HandleResponse(System.Net.Http.HttpResponseMessage response, Sentry.ISpan? span, string method, string url) { }
+ [System.Obsolete("This method will be removed in future versions.")]
protected override Sentry.ISpan? ProcessRequest(System.Net.Http.HttpRequestMessage request, string method, string url) { }
}
public readonly struct SentryId : Sentry.ISentryJsonSerializable, System.IEquatable
@@ -613,7 +618,9 @@ namespace Sentry
protected SentryMessageHandler(Sentry.IHub hub) { }
protected SentryMessageHandler(System.Net.Http.HttpMessageHandler innerHandler) { }
protected SentryMessageHandler(System.Net.Http.HttpMessageHandler innerHandler, Sentry.IHub hub) { }
+ [System.Obsolete("This method will be removed in future versions.")]
protected abstract void HandleResponse(System.Net.Http.HttpResponseMessage response, Sentry.ISpan? span, string method, string url);
+ [System.Obsolete("This method will be removed in future versions.")]
protected abstract Sentry.ISpan? ProcessRequest(System.Net.Http.HttpRequestMessage request, string method, string url);
protected override System.Net.Http.HttpResponseMessage Send(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { }
protected override System.Threading.Tasks.Task SendAsync(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { }
@@ -698,6 +705,7 @@ namespace Sentry
public void AddTransactionProcessorProvider(System.Func> processorProvider) { }
public void AddTransactionProcessors(System.Collections.Generic.IEnumerable processors) { }
public void ApplyDefaultTags(Sentry.IHasTags hasTags) { }
+ public void DisableActivityTracingIntegration() { }
public void DisableAppDomainProcessExitFlush() { }
public void DisableAppDomainUnhandledExceptionCapture() { }
public void DisableDiagnosticSourceIntegration() { }
diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt
index cca662c335..1af3c4e205 100644
--- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt
+++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt
@@ -317,7 +317,8 @@ namespace Sentry
public enum Instrumenter
{
Sentry = 0,
- OpenTelemetry = 1,
+ ActivitySource = 1,
+ OpenTelemetry = 2,
}
public readonly struct MeasurementUnit : System.IEquatable
{
@@ -545,7 +546,9 @@ namespace Sentry
public class SentryGraphQLHttpMessageHandler : Sentry.SentryMessageHandler
{
public SentryGraphQLHttpMessageHandler(System.Net.Http.HttpMessageHandler? innerHandler = null, Sentry.IHub? hub = null) { }
+ [System.Obsolete("This method is obsolete and will be removed in a future version.")]
protected override void HandleResponse(System.Net.Http.HttpResponseMessage response, Sentry.ISpan? span, string method, string url) { }
+ [System.Obsolete("This method is obsolete and will be removed in a future version.")]
protected override Sentry.ISpan? ProcessRequest(System.Net.Http.HttpRequestMessage request, string method, string url) { }
}
public class SentryHint
@@ -564,7 +567,9 @@ namespace Sentry
public SentryHttpMessageHandler(Sentry.IHub hub) { }
public SentryHttpMessageHandler(System.Net.Http.HttpMessageHandler innerHandler) { }
public SentryHttpMessageHandler(System.Net.Http.HttpMessageHandler innerHandler, Sentry.IHub hub) { }
+ [System.Obsolete("This method will be removed in future versions.")]
protected override void HandleResponse(System.Net.Http.HttpResponseMessage response, Sentry.ISpan? span, string method, string url) { }
+ [System.Obsolete("This method will be removed in future versions.")]
protected override Sentry.ISpan? ProcessRequest(System.Net.Http.HttpRequestMessage request, string method, string url) { }
}
public readonly struct SentryId : Sentry.ISentryJsonSerializable, System.IEquatable
@@ -613,7 +618,9 @@ namespace Sentry
protected SentryMessageHandler(Sentry.IHub hub) { }
protected SentryMessageHandler(System.Net.Http.HttpMessageHandler innerHandler) { }
protected SentryMessageHandler(System.Net.Http.HttpMessageHandler innerHandler, Sentry.IHub hub) { }
+ [System.Obsolete("This method will be removed in future versions.")]
protected abstract void HandleResponse(System.Net.Http.HttpResponseMessage response, Sentry.ISpan? span, string method, string url);
+ [System.Obsolete("This method will be removed in future versions.")]
protected abstract Sentry.ISpan? ProcessRequest(System.Net.Http.HttpRequestMessage request, string method, string url);
protected override System.Net.Http.HttpResponseMessage Send(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { }
protected override System.Threading.Tasks.Task SendAsync(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { }
@@ -698,6 +705,7 @@ namespace Sentry
public void AddTransactionProcessorProvider(System.Func> processorProvider) { }
public void AddTransactionProcessors(System.Collections.Generic.IEnumerable processors) { }
public void ApplyDefaultTags(Sentry.IHasTags hasTags) { }
+ public void DisableActivityTracingIntegration() { }
public void DisableAppDomainProcessExitFlush() { }
public void DisableAppDomainUnhandledExceptionCapture() { }
public void DisableDiagnosticSourceIntegration() { }
diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt
index d61bb3c82d..763c645d5e 100644
--- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt
+++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt
@@ -318,7 +318,8 @@ namespace Sentry
public enum Instrumenter
{
Sentry = 0,
- OpenTelemetry = 1,
+ ActivitySource = 1,
+ OpenTelemetry = 2,
}
public readonly struct MeasurementUnit : System.IEquatable
{
@@ -546,7 +547,9 @@ namespace Sentry
public class SentryGraphQLHttpMessageHandler : Sentry.SentryMessageHandler
{
public SentryGraphQLHttpMessageHandler(System.Net.Http.HttpMessageHandler? innerHandler = null, Sentry.IHub? hub = null) { }
+ [System.Obsolete("This method is obsolete and will be removed in a future version.")]
protected override void HandleResponse(System.Net.Http.HttpResponseMessage response, Sentry.ISpan? span, string method, string url) { }
+ [System.Obsolete("This method is obsolete and will be removed in a future version.")]
protected override Sentry.ISpan? ProcessRequest(System.Net.Http.HttpRequestMessage request, string method, string url) { }
}
public class SentryHint
@@ -565,7 +568,9 @@ namespace Sentry
public SentryHttpMessageHandler(Sentry.IHub hub) { }
public SentryHttpMessageHandler(System.Net.Http.HttpMessageHandler innerHandler) { }
public SentryHttpMessageHandler(System.Net.Http.HttpMessageHandler innerHandler, Sentry.IHub hub) { }
+ [System.Obsolete("This method will be removed in future versions.")]
protected override void HandleResponse(System.Net.Http.HttpResponseMessage response, Sentry.ISpan? span, string method, string url) { }
+ [System.Obsolete("This method will be removed in future versions.")]
protected override Sentry.ISpan? ProcessRequest(System.Net.Http.HttpRequestMessage request, string method, string url) { }
}
public readonly struct SentryId : Sentry.ISentryJsonSerializable, System.IEquatable
@@ -614,7 +619,9 @@ namespace Sentry
protected SentryMessageHandler(Sentry.IHub hub) { }
protected SentryMessageHandler(System.Net.Http.HttpMessageHandler innerHandler) { }
protected SentryMessageHandler(System.Net.Http.HttpMessageHandler innerHandler, Sentry.IHub hub) { }
+ [System.Obsolete("This method will be removed in future versions.")]
protected abstract void HandleResponse(System.Net.Http.HttpResponseMessage response, Sentry.ISpan? span, string method, string url);
+ [System.Obsolete("This method will be removed in future versions.")]
protected abstract Sentry.ISpan? ProcessRequest(System.Net.Http.HttpRequestMessage request, string method, string url);
protected override System.Net.Http.HttpResponseMessage Send(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { }
protected override System.Threading.Tasks.Task SendAsync(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { }
@@ -699,6 +706,7 @@ namespace Sentry
public void AddTransactionProcessorProvider(System.Func> processorProvider) { }
public void AddTransactionProcessors(System.Collections.Generic.IEnumerable processors) { }
public void ApplyDefaultTags(Sentry.IHasTags hasTags) { }
+ public void DisableActivityTracingIntegration() { }
public void DisableAppDomainProcessExitFlush() { }
public void DisableAppDomainUnhandledExceptionCapture() { }
public void DisableDiagnosticSourceIntegration() { }
diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt
index 0ea27a9ec7..37b2261a6d 100644
--- a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt
+++ b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt
@@ -316,7 +316,8 @@ namespace Sentry
public enum Instrumenter
{
Sentry = 0,
- OpenTelemetry = 1,
+ ActivitySource = 1,
+ OpenTelemetry = 2,
}
public readonly struct MeasurementUnit : System.IEquatable
{
@@ -544,7 +545,9 @@ namespace Sentry
public class SentryGraphQLHttpMessageHandler : Sentry.SentryMessageHandler
{
public SentryGraphQLHttpMessageHandler(System.Net.Http.HttpMessageHandler? innerHandler = null, Sentry.IHub? hub = null) { }
+ [System.Obsolete("This method is obsolete and will be removed in a future version.")]
protected override void HandleResponse(System.Net.Http.HttpResponseMessage response, Sentry.ISpan? span, string method, string url) { }
+ [System.Obsolete("This method is obsolete and will be removed in a future version.")]
protected override Sentry.ISpan? ProcessRequest(System.Net.Http.HttpRequestMessage request, string method, string url) { }
}
public class SentryHint
@@ -563,7 +566,9 @@ namespace Sentry
public SentryHttpMessageHandler(Sentry.IHub hub) { }
public SentryHttpMessageHandler(System.Net.Http.HttpMessageHandler innerHandler) { }
public SentryHttpMessageHandler(System.Net.Http.HttpMessageHandler innerHandler, Sentry.IHub hub) { }
+ [System.Obsolete("This method will be removed in future versions.")]
protected override void HandleResponse(System.Net.Http.HttpResponseMessage response, Sentry.ISpan? span, string method, string url) { }
+ [System.Obsolete("This method will be removed in future versions.")]
protected override Sentry.ISpan? ProcessRequest(System.Net.Http.HttpRequestMessage request, string method, string url) { }
}
public readonly struct SentryId : Sentry.ISentryJsonSerializable, System.IEquatable
@@ -612,7 +617,9 @@ namespace Sentry
protected SentryMessageHandler(Sentry.IHub hub) { }
protected SentryMessageHandler(System.Net.Http.HttpMessageHandler innerHandler) { }
protected SentryMessageHandler(System.Net.Http.HttpMessageHandler innerHandler, Sentry.IHub hub) { }
+ [System.Obsolete("This method will be removed in future versions.")]
protected abstract void HandleResponse(System.Net.Http.HttpResponseMessage response, Sentry.ISpan? span, string method, string url);
+ [System.Obsolete("This method will be removed in future versions.")]
protected abstract Sentry.ISpan? ProcessRequest(System.Net.Http.HttpRequestMessage request, string method, string url);
protected override System.Threading.Tasks.Task SendAsync(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { }
}
diff --git a/test/Sentry.Tests/HubTests.cs b/test/Sentry.Tests/HubTests.cs
index e99a9e304e..64a5988f3d 100644
--- a/test/Sentry.Tests/HubTests.cs
+++ b/test/Sentry.Tests/HubTests.cs
@@ -616,46 +616,6 @@ public void StartTransaction_DisableTracing_SampledOut()
transaction.IsSampled.Should().BeFalse();
}
- [Fact]
- public void StartTransaction_SameInstrumenter_SampledIn()
- {
- // Arrange
- _fixture.Options.EnableTracing = true;
- _fixture.Options.Instrumenter = Instrumenter.Sentry; // The default... making it explicit for this test though
- var hub = _fixture.GetSut();
-
- var transactionContext = new TransactionContext("name", "operation")
- {
- Instrumenter = _fixture.Options.Instrumenter
- };
-
- // Act
- var transaction = hub.StartTransaction(transactionContext);
-
- // Assert
- transaction.IsSampled.Should().BeTrue();
- }
-
- [Fact]
- public void StartTransaction_DifferentInstrumenter_NoOp()
- {
- // Arrange
- _fixture.Options.EnableTracing = true;
- _fixture.Options.Instrumenter = Instrumenter.OpenTelemetry;
- var hub = _fixture.GetSut();
-
- var transactionContext = new TransactionContext("name", "operation")
- {
- Instrumenter = Instrumenter.Sentry // The default... making it explicit for this test though
- };
-
- // Act
- var transaction = hub.StartTransaction(transactionContext);
-
- // Assert
- transaction.Should().Be(NoOpTransaction.Instance);
- }
-
[Fact]
public void StartTransaction_EnableTracing_Sampler_SampledIn()
{
diff --git a/test/Sentry.Tests/SentryGraphQlHttpMessageHandlerTests.cs b/test/Sentry.Tests/SentryGraphQlHttpMessageHandlerTests.cs
index 4aa1ac21f0..513d04f018 100644
--- a/test/Sentry.Tests/SentryGraphQlHttpMessageHandlerTests.cs
+++ b/test/Sentry.Tests/SentryGraphQlHttpMessageHandlerTests.cs
@@ -23,6 +23,7 @@ public class SentryGraphQlHttpMessageHandlerTests
private StringContent ValidResponseContent => SentryGraphQlTestHelpers.ResponesContent(ValidResponse);
[Fact]
+ [Obsolete("Obsolete")]
public void ProcessRequest_ExtractsGraphQlRequestContent()
{
// Arrange
@@ -44,6 +45,7 @@ public void ProcessRequest_ExtractsGraphQlRequestContent()
}
[Fact]
+ [Obsolete("Obsolete")]
public void ProcessRequest_SetsSpanData()
{
// Arrange
@@ -74,6 +76,7 @@ public void ProcessRequest_SetsSpanData()
// [Theory]
// [InlineData(ValidQuery)]
[Fact]
+ [Obsolete("Obsolete")]
public void HandleResponse_AddsBreadcrumb()
{
// Arrange
@@ -115,6 +118,7 @@ public void HandleResponse_AddsBreadcrumb()
}
[Fact]
+ [Obsolete("Obsolete")]
public void HandleResponse_SetsSpanData()
{
// Arrange
diff --git a/test/Sentry.Tests/SentryHttpMessageHandlerTests.cs b/test/Sentry.Tests/SentryHttpMessageHandlerTests.cs
index fc38202019..744e18cef5 100644
--- a/test/Sentry.Tests/SentryHttpMessageHandlerTests.cs
+++ b/test/Sentry.Tests/SentryHttpMessageHandlerTests.cs
@@ -240,6 +240,7 @@ public async Task SendAsync_Executed_FailedRequestsCaptured()
}
[Fact]
+ [Obsolete("Obsolete")]
public void ProcessRequest_SetsSpanData()
{
// Arrange
@@ -267,6 +268,7 @@ public void ProcessRequest_SetsSpanData()
}
[Fact]
+ [Obsolete("Obsolete")]
public void HandleResponse_SetsSpanData()
{
// Arrange
diff --git a/test/Sentry.Tests/SentryOptionsTests.Integrations_default_ones_are_properly_registered.DotNet6_0.DotNet.verified.txt b/test/Sentry.Tests/SentryOptionsTests.Integrations_default_ones_are_properly_registered.DotNet6_0.DotNet.verified.txt
index dfbc55fc32..3d12fd4b6b 100644
--- a/test/Sentry.Tests/SentryOptionsTests.Integrations_default_ones_are_properly_registered.DotNet6_0.DotNet.verified.txt
+++ b/test/Sentry.Tests/SentryOptionsTests.Integrations_default_ones_are_properly_registered.DotNet6_0.DotNet.verified.txt
@@ -34,5 +34,11 @@
Args: [
SentryDiagnosticListenerIntegration
]
+ },
+ {
+ Message: Registering integration: '{0}'.,
+ Args: [
+ ActivityTracingIntegration
+ ]
}
]
\ No newline at end of file
diff --git a/test/Sentry.Tests/SentryOptionsTests.Integrations_default_ones_are_properly_registered.DotNet6_0.Windows.DotNet.verified.txt b/test/Sentry.Tests/SentryOptionsTests.Integrations_default_ones_are_properly_registered.DotNet6_0.Windows.DotNet.verified.txt
index 10eaeaf749..44ec09a1a1 100644
--- a/test/Sentry.Tests/SentryOptionsTests.Integrations_default_ones_are_properly_registered.DotNet6_0.Windows.DotNet.verified.txt
+++ b/test/Sentry.Tests/SentryOptionsTests.Integrations_default_ones_are_properly_registered.DotNet6_0.Windows.DotNet.verified.txt
@@ -40,5 +40,11 @@
Args: [
WinUIUnhandledExceptionIntegration
]
+ },
+ {
+ Message: Registering integration: '{0}'.,
+ Args: [
+ ActivityTracingIntegration
+ ]
}
]
\ No newline at end of file
diff --git a/test/Sentry.Tests/SentryOptionsTests.Integrations_default_ones_are_properly_registered.DotNet7_0.DotNet.verified.txt b/test/Sentry.Tests/SentryOptionsTests.Integrations_default_ones_are_properly_registered.DotNet7_0.DotNet.verified.txt
index dfbc55fc32..3d12fd4b6b 100644
--- a/test/Sentry.Tests/SentryOptionsTests.Integrations_default_ones_are_properly_registered.DotNet7_0.DotNet.verified.txt
+++ b/test/Sentry.Tests/SentryOptionsTests.Integrations_default_ones_are_properly_registered.DotNet7_0.DotNet.verified.txt
@@ -34,5 +34,11 @@
Args: [
SentryDiagnosticListenerIntegration
]
+ },
+ {
+ Message: Registering integration: '{0}'.,
+ Args: [
+ ActivityTracingIntegration
+ ]
}
]
\ No newline at end of file
diff --git a/test/Sentry.Tests/SentryOptionsTests.Integrations_default_ones_are_properly_registered.DotNet7_0.Windows.DotNet.verified.txt b/test/Sentry.Tests/SentryOptionsTests.Integrations_default_ones_are_properly_registered.DotNet7_0.Windows.DotNet.verified.txt
index 10eaeaf749..44ec09a1a1 100644
--- a/test/Sentry.Tests/SentryOptionsTests.Integrations_default_ones_are_properly_registered.DotNet7_0.Windows.DotNet.verified.txt
+++ b/test/Sentry.Tests/SentryOptionsTests.Integrations_default_ones_are_properly_registered.DotNet7_0.Windows.DotNet.verified.txt
@@ -40,5 +40,11 @@
Args: [
WinUIUnhandledExceptionIntegration
]
+ },
+ {
+ Message: Registering integration: '{0}'.,
+ Args: [
+ ActivityTracingIntegration
+ ]
}
]
\ No newline at end of file
diff --git a/test/Sentry.Tests/SentryOptionsTests.Integrations_default_ones_are_properly_registered.DotNet8_0.DotNet.verified.txt b/test/Sentry.Tests/SentryOptionsTests.Integrations_default_ones_are_properly_registered.DotNet8_0.DotNet.verified.txt
index 9e3f2681b5..3bb7b3eecf 100644
--- a/test/Sentry.Tests/SentryOptionsTests.Integrations_default_ones_are_properly_registered.DotNet8_0.DotNet.verified.txt
+++ b/test/Sentry.Tests/SentryOptionsTests.Integrations_default_ones_are_properly_registered.DotNet8_0.DotNet.verified.txt
@@ -44,5 +44,11 @@
{
Level: info,
Message: System.Diagnostics.Metrics Integration is disabled because no listeners are configured.
+ },
+ {
+ Message: Registering integration: '{0}'.,
+ Args: [
+ ActivityTracingIntegration
+ ]
}
]
\ No newline at end of file
diff --git a/test/Sentry.Tests/SentryOptionsTests.Integrations_default_ones_are_properly_registered.DotNet8_0.Windows.DotNet.verified.txt b/test/Sentry.Tests/SentryOptionsTests.Integrations_default_ones_are_properly_registered.DotNet8_0.Windows.DotNet.verified.txt
index e14cfaf075..25a5ea7a08 100644
--- a/test/Sentry.Tests/SentryOptionsTests.Integrations_default_ones_are_properly_registered.DotNet8_0.Windows.DotNet.verified.txt
+++ b/test/Sentry.Tests/SentryOptionsTests.Integrations_default_ones_are_properly_registered.DotNet8_0.Windows.DotNet.verified.txt
@@ -50,5 +50,11 @@
{
Level: info,
Message: System.Diagnostics.Metrics Integration is disabled because no listeners are configured.
+ },
+ {
+ Message: Registering integration: '{0}'.,
+ Args: [
+ ActivityTracingIntegration
+ ]
}
]
\ No newline at end of file