Skip to content

Commit 3e70c31

Browse files
Added trace origin to distinguish Sentry traces from custom instrumented traces (#3400)
1 parent 2947946 commit 3e70c31

File tree

41 files changed

+286
-12
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+286
-12
lines changed

Diff for: Sentry.sln.DotSettings

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
2+
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=EF/@EntryIndexedValue">EF</s:String>
23
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IO/@EntryIndexedValue">IO</s:String>
34
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=OS/@EntryIndexedValue">OS</s:String>
45
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=QL/@EntryIndexedValue">QL</s:String>

Diff for: src/Sentry.AspNet/HttpContextExtensions.cs

+3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Sentry.Extensibility;
2+
using Sentry.Protocol;
23

34
namespace Sentry.AspNet;
45

@@ -9,6 +10,7 @@ namespace Sentry.AspNet;
910
public static class HttpContextExtensions
1011
{
1112
private const string HttpContextTransactionItemName = "__SentryTransaction";
13+
internal const string AspNetOrigin = "auto.http.aspnet";
1214

1315
private static SentryTraceHeader? TryGetSentryTraceHeader(HttpContext context, SentryOptions? options)
1416
{
@@ -114,6 +116,7 @@ public static ITransactionTracer StartSentryTransaction(this HttpContext httpCon
114116
}
115117

116118
var transaction = SentrySdk.StartTransaction(transactionContext, customSamplingContext, dynamicSamplingContext);
119+
transaction.Contexts.Trace.Origin = AspNetOrigin;
117120

118121
SentrySdk.ConfigureScope(scope => scope.Transaction = transaction);
119122
httpContext.Items[HttpContextTransactionItemName] = transaction;

Diff for: src/Sentry.AspNetCore/SentryTracingMiddleware.cs

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ namespace Sentry.AspNetCore;
1212
internal class SentryTracingMiddleware
1313
{
1414
private const string OperationName = "http.server";
15+
internal const string AspNetCoreOrigin = "auto.http.aspnetcore";
1516

1617
private readonly RequestDelegate _next;
1718
private readonly Func<IHub> _getHub;
@@ -78,6 +79,7 @@ public SentryTracingMiddleware(
7879
}
7980

8081
var transaction = _getHub().StartTransaction(transactionContext, customSamplingContext, dynamicSamplingContext);
82+
transaction.Contexts.Trace.Origin = AspNetCoreOrigin;
8183

8284
_options.LogInfo(
8385
"Started transaction with span ID '{0}' and trace ID '{1}'.",

Diff for: src/Sentry.Azure.Functions.Worker/SentryFunctionsWorkerMiddleware.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ namespace Sentry.Azure.Functions.Worker;
88
internal class SentryFunctionsWorkerMiddleware : IFunctionsWorkerMiddleware
99
{
1010
private const string Operation = "function";
11+
internal const string AzureFunctionsOrigin = "auto.function.azure";
1112

1213
private readonly IHub _hub;
1314
private readonly IDiagnosticLogger? _logger;
@@ -23,6 +24,7 @@ public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next
2324
{
2425
var transactionContext = await StartOrContinueTraceAsync(context);
2526
var transaction = _hub.StartTransaction(transactionContext);
27+
transaction.Contexts.Trace.Origin = AzureFunctionsOrigin;
2628
Exception? unhandledException = null;
2729

2830
try
@@ -98,7 +100,7 @@ private async Task<TransactionContext> StartOrContinueTraceAsync(FunctionContext
98100
// Note that, when Trimming is enabled, we can't use reflection to read route data from the HttpTrigger
99101
// attribute. In that case the route name will always be /api/<FUNCTION_NAME>
100102
// If this is ever a problem for customers, we can potentially see if there are alternate ways to get this info
101-
// from route tables or something. We're not even sure if anyone will use this functionality for now though.
103+
// from route tables or something. We're not even sure if anyone will use this functionality for now though.
102104
if (!AotHelper.IsNativeAot && !TransactionNameCache.TryGetValue(transactionNameKey, out transactionName))
103105
{
104106
// Find the HTTP Trigger attribute via reflection

Diff for: src/Sentry.DiagnosticSource/Internal/DiagnosticSource/EFDiagnosticSourceHelper.cs

+1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ internal void AddSpan(object? diagnosticSourceValue)
5959
// 2. Sentry's performance errors functionality only works when all queries have the same parent span.
6060
var parent = Transaction.GetDbParentSpan();
6161
var child = parent.StartChild(Operation, GetDescription(diagnosticSourceValue));
62+
child.SetOrigin(SentryEFCoreListener.EFCoreListenerOrigin);
6263

6364
SetDbData(child, diagnosticSourceValue);
6465
SetSpanReference(child, diagnosticSourceValue);

Diff for: src/Sentry.DiagnosticSource/Internal/DiagnosticSource/SentryEFCoreListener.cs

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ internal class SentryEFCoreListener : IObserver<KeyValuePair<string, object?>>
1313
internal const string EFCommandExecuted = "Microsoft.EntityFrameworkCore.Database.Command.CommandExecuted";
1414
internal const string EFCommandFailed = "Microsoft.EntityFrameworkCore.Database.Command.CommandError";
1515

16+
internal const string EFCoreListenerOrigin = "auto.db.ef_core_listener";
17+
1618
/// <summary>
1719
/// Used for EF Core 2.X and 3.X.
1820
/// <seealso href="https://docs.microsoft.com/dotnet/api/microsoft.entityframeworkcore.diagnostics.coreeventid.querymodelcompiling?view=efcore-3.1"/>

Diff for: src/Sentry.DiagnosticSource/Internal/DiagnosticSource/SentrySqlListener.cs

+3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ private enum SentrySqlSpanType
1111
Execution
1212
};
1313

14+
internal const string SqlListenerOrigin = "auto.db.sql_listener";
15+
1416
internal const string SqlDataWriteConnectionOpenBeforeCommand = "System.Data.SqlClient.WriteConnectionOpenBefore";
1517
internal const string SqlMicrosoftWriteConnectionOpenBeforeCommand = "Microsoft.Data.SqlClient.WriteConnectionOpenBefore";
1618

@@ -80,6 +82,7 @@ private void AddSpan(string operation, object? value)
8082

8183
var parent = transaction.GetDbParentSpan();
8284
var span = parent.StartChild(operation);
85+
span.SetOrigin(SqlListenerOrigin);
8386
span.SetExtra(OTelKeys.DbSystem, "sql");
8487
SetOperationId(span, value?.GetGuidProperty("OperationId", _options.DiagnosticLogger));
8588
SetConnectionId(span, value?.GetGuidProperty("ConnectionId", _options.DiagnosticLogger));

Diff for: src/Sentry.EntityFramework/SentryQueryPerformanceListener.cs

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using Sentry.Internal;
2+
13
namespace Sentry.EntityFramework;
24

35
internal class SentryQueryPerformanceListener : IDbCommandInterceptor
@@ -7,6 +9,8 @@ internal class SentryQueryPerformanceListener : IDbCommandInterceptor
79
internal const string DbNonQueryKey = "db.execute";
810
internal const string DbScalarKey = "db.query.scalar";
911

12+
internal static readonly string EntityFrameworkOrigin = "auto.db.entity_framework";
13+
1014
private SentryOptions _options;
1115
private IHub _hub;
1216

@@ -39,6 +43,7 @@ private void CreateSpan<T>(string key, string? command,
3943
{
4044
if (_hub.GetSpan()?.StartChild(key, command) is { } span)
4145
{
46+
span.SetOrigin(EntityFrameworkOrigin);
4247
interceptionContext.AttachSpan(span);
4348
}
4449
}

Diff for: src/Sentry.OpenTelemetry/SentrySpanProcessor.cs

+3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public class SentrySpanProcessor : BaseProcessor<Activity>
1313
{
1414
private readonly IHub _hub;
1515
internal readonly IEnumerable<IOpenTelemetryEnricher> _enrichers;
16+
internal const string OpenTelemetryOrigin = "auto.otel";
1617

1718
// ReSharper disable once MemberCanBePrivate.Global - Used by tests
1819
internal readonly ConcurrentDictionary<ActivitySpanId, ISpan> _map = new();
@@ -123,6 +124,7 @@ private void CreateChildSpan(Activity data, ISpan parentSpan, SpanId? parentSpan
123124
};
124125

125126
var span = (SpanTracer)parentSpan.StartChild(context);
127+
span.Origin = OpenTelemetryOrigin;
126128
span.StartTimestamp = data.StartTimeUtc;
127129
// Used to filter out spans that are not recorded when finishing a transaction.
128130
span.SetFused(data);
@@ -152,6 +154,7 @@ private void CreateRootSpan(Activity data)
152154
var transaction = (TransactionTracer)_hub.StartTransaction(
153155
transactionContext, new Dictionary<string, object?>(), dynamicSamplingContext
154156
);
157+
transaction.Contexts.Trace.Origin = OpenTelemetryOrigin;
155158
transaction.StartTimestamp = data.StartTimeUtc;
156159
_hub.ConfigureScope(scope => scope.Transaction = transaction);
157160
transaction.SetFused(data);

Diff for: src/Sentry/IBaseTracer.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
namespace Sentry;
22

3-
internal interface IBaseTracer
3+
internal interface IBaseTracer : ITraceContextInternal
44
{
55
internal bool IsOtelInstrumenter { get; }
66
}

Diff for: src/Sentry/ITraceContextInternal.cs

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
namespace Sentry;
2+
3+
/// <summary>
4+
/// Internal interface defining members to be added to the TraceContexts for the next major release
5+
/// (since adding members to interfaces is a breaking change).
6+
/// </summary>
7+
/// <remarks>
8+
/// TODO: Remove this interface in the next major version.
9+
/// </remarks>
10+
internal interface ITraceContextInternal
11+
{
12+
/// <summary>
13+
/// Specifies the origin of the trace. If no origin is set then the trace origin is assumed to be "manual".
14+
/// </summary>
15+
/// <remarks>
16+
/// See https://develop.sentry.dev/sdk/performance/trace-origin/ for more information.
17+
/// </remarks>
18+
string? Origin { get; }
19+
}

Diff for: src/Sentry/Internal/NoOpSpan.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace Sentry.Internal;
55
/// <summary>
66
/// Span class to use when we can't return null but a request to create a span couldn't be completed.
77
/// </summary>
8-
internal class NoOpSpan : ISpan
8+
internal class NoOpSpan : ISpan, ITraceContextInternal
99
{
1010
public static ISpan Instance { get; } = new NoOpSpan();
1111

@@ -78,4 +78,6 @@ public void SetExtra(string key, object? value)
7878
public void SetMeasurement(string name, Measurement measurement)
7979
{
8080
}
81+
82+
public string? Origin { get; set; }
8183
}

Diff for: src/Sentry/Internal/OriginHelper.cs

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
namespace Sentry.Internal;
2+
3+
internal static partial class OriginHelper
4+
{
5+
internal const string Manual = "manual";
6+
private const string ValidOriginPattern = @"^(auto|manual)(\.[\w]+){0,3}$";
7+
8+
#if NET8_0_OR_GREATER
9+
[GeneratedRegex(ValidOriginPattern, RegexOptions.Compiled)]
10+
private static partial Regex ValidOriginRegEx();
11+
private static readonly Regex ValidOrigin = ValidOriginRegEx();
12+
#else
13+
private static readonly Regex ValidOrigin = new (ValidOriginPattern, RegexOptions.Compiled);
14+
#endif
15+
16+
public static bool IsValidOrigin(string? value) => value == null || ValidOrigin.IsMatch(value);
17+
18+
internal static string? TryParse(string origin) => IsValidOrigin(origin) ? origin : null;
19+
20+
/// <summary>
21+
/// Convenience method to let us set the origin on interfaces whose concrete implementations
22+
/// typically have an Origin property.
23+
/// </summary>
24+
/// <remarks>We can remove once we deprecate the ITraceContextInternal interface</remarks>
25+
public static void SetOrigin(this ISpan span, string origin)
26+
{
27+
switch (span)
28+
{
29+
case SpanTracer spanTracer:
30+
spanTracer.Origin = origin;
31+
break;
32+
case TransactionTracer transactionTracer:
33+
transactionTracer.Contexts.Trace.Origin = origin;
34+
break;
35+
}
36+
}
37+
}

Diff for: src/Sentry/Protocol/Trace.cs

+20-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ namespace Sentry.Protocol;
77
/// <summary>
88
/// Trace context data.
99
/// </summary>
10-
public class Trace : ITraceContext, ISentryJsonSerializable, ICloneable<Trace>, IUpdatable<Trace>
10+
public class Trace : ITraceContext, ITraceContextInternal, ISentryJsonSerializable, ICloneable<Trace>, IUpdatable<Trace>
1111
{
1212
/// <summary>
1313
/// Tells Sentry which type of context this is.
@@ -26,6 +26,21 @@ public class Trace : ITraceContext, ISentryJsonSerializable, ICloneable<Trace>,
2626
/// <inheritdoc />
2727
public string Operation { get; set; } = "";
2828

29+
/// <inheritdoc />
30+
public string? Origin
31+
{
32+
get => _origin;
33+
internal set
34+
{
35+
if (!OriginHelper.IsValidOrigin(value))
36+
{
37+
throw new ArgumentException("Invalid origin");
38+
}
39+
_origin = value;
40+
}
41+
}
42+
private string? _origin;
43+
2944
/// <inheritdoc />
3045
public string? Description { get; set; }
3146

@@ -46,6 +61,7 @@ public class Trace : ITraceContext, ISentryJsonSerializable, ICloneable<Trace>,
4661
ParentSpanId = ParentSpanId,
4762
TraceId = TraceId,
4863
Operation = Operation,
64+
Origin = Origin,
4965
Status = Status,
5066
IsSampled = IsSampled
5167
};
@@ -84,6 +100,7 @@ public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger)
84100
writer.WriteSerializableIfNotNull("parent_span_id", ParentSpanId?.NullIfDefault(), logger);
85101
writer.WriteSerializableIfNotNull("trace_id", TraceId.NullIfDefault(), logger);
86102
writer.WriteStringIfNotWhiteSpace("op", Operation);
103+
writer.WriteString("origin", Origin ?? Internal.OriginHelper.Manual);
87104
writer.WriteStringIfNotWhiteSpace("description", Description);
88105
writer.WriteStringIfNotWhiteSpace("status", Status?.ToString().ToSnakeCase());
89106

@@ -99,6 +116,7 @@ public static Trace FromJson(JsonElement json)
99116
var parentSpanId = json.GetPropertyOrNull("parent_span_id")?.Pipe(SpanId.FromJson);
100117
var traceId = json.GetPropertyOrNull("trace_id")?.Pipe(SentryId.FromJson) ?? SentryId.Empty;
101118
var operation = json.GetPropertyOrNull("op")?.GetString() ?? "";
119+
var origin = Internal.OriginHelper.TryParse(json.GetPropertyOrNull("origin")?.GetString() ?? "");
102120
var description = json.GetPropertyOrNull("description")?.GetString();
103121
var status = json.GetPropertyOrNull("status")?.GetString()?.Replace("_", "").ParseEnum<SpanStatus>();
104122
var isSampled = json.GetPropertyOrNull("sampled")?.GetBoolean();
@@ -109,6 +127,7 @@ public static Trace FromJson(JsonElement json)
109127
ParentSpanId = parentSpanId,
110128
TraceId = traceId,
111129
Operation = operation,
130+
Origin = origin,
112131
Description = description,
113132
Status = status,
114133
IsSampled = isSampled

Diff for: src/Sentry/SentryGraphQLHttpMessageHandler.cs

+3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ public class SentryGraphQLHttpMessageHandler : SentryMessageHandler
1313
private readonly SentryOptions? _options;
1414
private readonly ISentryFailedRequestHandler? _failedRequestHandler;
1515

16+
internal const string GraphQlOrigin = "auto.graphql";
17+
1618
/// <summary>
1719
/// Constructs an instance of <see cref="SentryGraphQLHttpMessageHandler"/>.
1820
/// </summary>
@@ -54,6 +56,7 @@ internal SentryGraphQLHttpMessageHandler(IHub? hub, SentryOptions? options,
5456
"http.client",
5557
$"{method} {url}" // e.g. "GET https://example.com"
5658
);
59+
span?.SetOrigin(GraphQlOrigin);
5760
span?.SetExtra(OtelSemanticConventions.AttributeHttpRequestMethod, method);
5861
if (!string.IsNullOrWhiteSpace(request.RequestUri?.Host))
5962
{

Diff for: src/Sentry/SentryHttpMessageHandler.cs

+4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Sentry.Extensibility;
2+
using Sentry.Internal;
23
using Sentry.Internal.OpenTelemetry;
34

45
namespace Sentry;
@@ -12,6 +13,8 @@ public class SentryHttpMessageHandler : SentryMessageHandler
1213
private readonly SentryOptions? _options;
1314
private readonly ISentryFailedRequestHandler? _failedRequestHandler;
1415

16+
internal const string HttpClientOrigin = "auto.http.client";
17+
1518
/// <summary>
1619
/// Constructs an instance of <see cref="SentryHttpMessageHandler"/>.
1720
/// </summary>
@@ -67,6 +70,7 @@ internal SentryHttpMessageHandler(IHub? hub, SentryOptions? options, HttpMessage
6770
"http.client",
6871
$"{method} {url}" // e.g. "GET https://example.com"
6972
);
73+
span?.SetOrigin(HttpClientOrigin);
7074
span?.SetExtra(OtelSemanticConventions.AttributeHttpRequestMethod, method);
7175
if (request.RequestUri is not null && !string.IsNullOrWhiteSpace(request.RequestUri.Host))
7276
{

Diff for: src/Sentry/SentrySpan.cs

+16-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace Sentry;
1010
/// <summary>
1111
/// Transaction span.
1212
/// </summary>
13-
public class SentrySpan : ISpanData, ISentryJsonSerializable
13+
public class SentrySpan : ISpanData, ISentryJsonSerializable, ITraceContextInternal
1414
{
1515
/// <inheritdoc />
1616
public SpanId SpanId { get; private set; }
@@ -182,4 +182,19 @@ internal void Redact()
182182
{
183183
Description = Description?.RedactUrl();
184184
}
185+
186+
/// <inheritdoc />
187+
public string? Origin
188+
{
189+
get => _origin;
190+
internal set
191+
{
192+
if (!OriginHelper.IsValidOrigin(value))
193+
{
194+
throw new ArgumentException("Invalid origin");
195+
}
196+
_origin = value;
197+
}
198+
}
199+
private string? _origin;
185200
}

Diff for: src/Sentry/SentryTransaction.cs

+8-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace Sentry;
1010
/// <summary>
1111
/// Sentry performance transaction.
1212
/// </summary>
13-
public class SentryTransaction : ITransactionData, ISentryJsonSerializable
13+
public class SentryTransaction : ITransactionData, ISentryJsonSerializable, ITraceContextInternal
1414
{
1515
/// <summary>
1616
/// Transaction's event ID.
@@ -24,6 +24,13 @@ public SpanId SpanId
2424
private set => Contexts.Trace.SpanId = value;
2525
}
2626

27+
/// <inheritdoc />
28+
public string? Origin
29+
{
30+
get => Contexts.Trace.Origin;
31+
private set => Contexts.Trace.Origin = value;
32+
}
33+
2734
// A transaction normally does not have a parent because it represents
2835
// the top node in the span hierarchy.
2936
// However, a transaction may also be continued from a trace header

0 commit comments

Comments
 (0)