Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Sentry.Internal.Tracing;

/// <summary>
/// The concrete implementation of <see cref="ITraceProvider"/> that uses
/// <see cref="System.Diagnostics.ActivitySource"/> and <see cref="System.Diagnostics.Activity"/> from the
/// <see cref="System.Diagnostics"/> namespace to implement tracing.
/// </summary>
internal class ActivityTraceProvider : ITraceProvider
{
private Lazy<ConcurrentDictionary<string, ActivityTracer>> _lazyActivitySources = new();
private ConcurrentDictionary<string, ActivityTracer> _activitySources => _lazyActivitySources.Value;

public ITracer GetTracer(string name, string? version = "")
=> _activitySources.GetOrAdd(name, new ActivityTracer(name, version));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
namespace Sentry.Internal.Tracing;

#if !NET6_0_OR_GREATER
using System.Diagnostics;
#endif

/// <summary>
/// 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)
/// </summary>
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();
}
27 changes: 27 additions & 0 deletions src/Sentry.DiagnosticSource/Internal/Tracing/ActivityTracer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
namespace Sentry.Internal.Tracing;

/// <summary>
/// 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)
/// </summary>
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);
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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<ActivityContext> _)
=> 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();
}
}
41 changes: 41 additions & 0 deletions src/Sentry.DiagnosticSource/Internal/Tracing/TraceExtensions.cs
Original file line number Diff line number Diff line change
@@ -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<KeyValuePair<string, string?>> baggage, bool useSentryPrefix = false) =>
BaggageHeader.Create(
baggage.Where(member => member.Value != null)
.Select(kvp => (KeyValuePair<string, string>)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<System.Diagnostics.Activity>();
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="4.5.0" />
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="8.0.0" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public Action<HttpMessageHandlerBuilder> Configure(Action<HttpMessageHandlerBuil
if (!handlerBuilder.AdditionalHandlers.Any(h => h is SentryHttpMessageHandler))
{
handlerBuilder.AdditionalHandlers.Add(
new SentryHttpMessageHandler(hub)
new SentryHttpMessageHandler(hub, default, default, null, true)
);
}

Expand Down
19 changes: 0 additions & 19 deletions src/Sentry.OpenTelemetry/OpenTelemetryExtensions.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Sentry.Extensibility;
using Sentry.Internal.Tracing;

namespace Sentry.OpenTelemetry;

Expand Down
18 changes: 18 additions & 0 deletions src/Sentry.OpenTelemetry/Sentry.OpenTelemetry.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,22 @@
<InternalsVisibleTo Include="Sentry.Benchmarks" PublicKey="$(SentryPublicKey)" />
</ItemGroup>

<PropertyGroup>
<!-- OpenTelemetry depends on System.Diagnostics.DiagnosticSource 8.0.0 or later -->
<DefineConstants>$(DefineConstants);HAS_DIAGNOSTICS_7_OR_GREATER</DefineConstants>
</PropertyGroup>

<!--
Include these here if they haven't been included in the core Sentry package already. The only reason they don't
always get included in the Sentry package is that they depend on System.Diagnostics.DiagnosticSource 7.0.0 or later,
which is only available for certain targets. However, OpenTelemetry depends on System.Diagnostics.DiagnosticSource
8.0.0 so we're safe to include these here.
-->
<ItemGroup Condition="$(TargetFramework.StartsWith('netstandard')) or $(TargetFramework.StartsWith('net4'))">
<Compile Include="..\Sentry.DiagnosticSource\Internal\Tracing\**\*.cs">
<Link>Internal\Tracing\%(RecursiveDir)%(Filename)%(Extension)</Link>
</Compile>
</ItemGroup>


</Project>
1 change: 1 addition & 0 deletions src/Sentry.OpenTelemetry/SentryPropagator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using OpenTelemetry;
using OpenTelemetry.Context.Propagation;
using Sentry.Extensibility;
using Sentry.Internal.Tracing;

namespace Sentry.OpenTelemetry;

Expand Down
Loading