diff --git a/.craft.yml b/.craft.yml index 45e4f66610..5875bcbd7c 100644 --- a/.craft.yml +++ b/.craft.yml @@ -20,5 +20,6 @@ targets: nuget:Sentry.Maui: nuget:Sentry.NLog: nuget:Sentry.OpenTelemetry: + nuget:Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol: nuget:Sentry.Serilog: nuget:Sentry.Profiling: diff --git a/.generated.NoMobile.slnx b/.generated.NoMobile.slnx index 3ed20edf11..45de3e4077 100644 --- a/.generated.NoMobile.slnx +++ b/.generated.NoMobile.slnx @@ -142,6 +142,7 @@ + @@ -171,6 +172,7 @@ + diff --git a/CHANGELOG.md b/CHANGELOG.md index f699acc9d5..1819735499 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Features + +- Add support to send OTEL traces via OTLP ([#4899](https://github.com/getsentry/sentry-dotnet/pull/4899)) + ### Fixes - The SDK now logs a `Warning` instead of an `Error` when being ratelimited ([#4927](https://github.com/getsentry/sentry-dotnet/pull/4927)) diff --git a/Sentry-CI-Build-Linux-NoMobile.slnf b/Sentry-CI-Build-Linux-NoMobile.slnf index 69921c2bcb..c5a37bdd6e 100644 --- a/Sentry-CI-Build-Linux-NoMobile.slnf +++ b/Sentry-CI-Build-Linux-NoMobile.slnf @@ -44,6 +44,7 @@ "src\\Sentry.Hangfire\\Sentry.Hangfire.csproj", "src\\Sentry.Log4Net\\Sentry.Log4Net.csproj", "src\\Sentry.NLog\\Sentry.NLog.csproj", + "src\\Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol\\Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.csproj", "src\\Sentry.OpenTelemetry\\Sentry.OpenTelemetry.csproj", "src\\Sentry.Profiling\\Sentry.Profiling.csproj", "src\\Sentry.Serilog\\Sentry.Serilog.csproj", @@ -63,6 +64,7 @@ "test\\Sentry.Hangfire.Tests\\Sentry.Hangfire.Tests.csproj", "test\\Sentry.Log4Net.Tests\\Sentry.Log4Net.Tests.csproj", "test\\Sentry.NLog.Tests\\Sentry.NLog.Tests.csproj", + "test\\Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests\\Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj", "test\\Sentry.OpenTelemetry.Tests\\Sentry.OpenTelemetry.Tests.csproj", "test\\Sentry.Profiling.Tests\\Sentry.Profiling.Tests.csproj", "test\\Sentry.Serilog.Tests\\Sentry.Serilog.Tests.csproj", diff --git a/Sentry-CI-Build-Linux.slnf b/Sentry-CI-Build-Linux.slnf index 14ffc09833..58caee81de 100644 --- a/Sentry-CI-Build-Linux.slnf +++ b/Sentry-CI-Build-Linux.slnf @@ -50,6 +50,7 @@ "src\\Sentry.Maui.CommunityToolkit.Mvvm\\Sentry.Maui.CommunityToolkit.Mvvm.csproj", "src\\Sentry.Maui\\Sentry.Maui.csproj", "src\\Sentry.NLog\\Sentry.NLog.csproj", + "src\\Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol\\Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.csproj", "src\\Sentry.OpenTelemetry\\Sentry.OpenTelemetry.csproj", "src\\Sentry.Profiling\\Sentry.Profiling.csproj", "src\\Sentry.Serilog\\Sentry.Serilog.csproj", @@ -72,6 +73,7 @@ "test\\Sentry.Maui.CommunityToolkit.Mvvm.Tests\\Sentry.Maui.CommunityToolkit.Mvvm.Tests.csproj", "test\\Sentry.Maui.Tests\\Sentry.Maui.Tests.csproj", "test\\Sentry.NLog.Tests\\Sentry.NLog.Tests.csproj", + "test\\Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests\\Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj", "test\\Sentry.OpenTelemetry.Tests\\Sentry.OpenTelemetry.Tests.csproj", "test\\Sentry.Profiling.Tests\\Sentry.Profiling.Tests.csproj", "test\\Sentry.Serilog.Tests\\Sentry.Serilog.Tests.csproj", diff --git a/Sentry-CI-Build-Windows-arm64.slnf b/Sentry-CI-Build-Windows-arm64.slnf index 41d924ac5f..316108212f 100644 --- a/Sentry-CI-Build-Windows-arm64.slnf +++ b/Sentry-CI-Build-Windows-arm64.slnf @@ -52,6 +52,7 @@ "src\\Sentry.Maui.CommunityToolkit.Mvvm\\Sentry.Maui.CommunityToolkit.Mvvm.csproj", "src\\Sentry.Maui\\Sentry.Maui.csproj", "src\\Sentry.NLog\\Sentry.NLog.csproj", + "src\\Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol\\Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.csproj", "src\\Sentry.OpenTelemetry\\Sentry.OpenTelemetry.csproj", "src\\Sentry.Profiling\\Sentry.Profiling.csproj", "src\\Sentry.Serilog\\Sentry.Serilog.csproj", @@ -71,6 +72,7 @@ "test\\Sentry.Maui.CommunityToolkit.Mvvm.Tests\\Sentry.Maui.CommunityToolkit.Mvvm.Tests.csproj", "test\\Sentry.Maui.Tests\\Sentry.Maui.Tests.csproj", "test\\Sentry.NLog.Tests\\Sentry.NLog.Tests.csproj", + "test\\Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests\\Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj", "test\\Sentry.OpenTelemetry.Tests\\Sentry.OpenTelemetry.Tests.csproj", "test\\Sentry.Profiling.Tests\\Sentry.Profiling.Tests.csproj", "test\\Sentry.Serilog.Tests\\Sentry.Serilog.Tests.csproj", diff --git a/Sentry-CI-Build-Windows.slnf b/Sentry-CI-Build-Windows.slnf index 1b158939c9..3d66c34922 100644 --- a/Sentry-CI-Build-Windows.slnf +++ b/Sentry-CI-Build-Windows.slnf @@ -52,6 +52,7 @@ "src\\Sentry.Maui.CommunityToolkit.Mvvm\\Sentry.Maui.CommunityToolkit.Mvvm.csproj", "src\\Sentry.Maui\\Sentry.Maui.csproj", "src\\Sentry.NLog\\Sentry.NLog.csproj", + "src\\Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol\\Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.csproj", "src\\Sentry.OpenTelemetry\\Sentry.OpenTelemetry.csproj", "src\\Sentry.Profiling\\Sentry.Profiling.csproj", "src\\Sentry.Serilog\\Sentry.Serilog.csproj", @@ -74,6 +75,7 @@ "test\\Sentry.Maui.CommunityToolkit.Mvvm.Tests\\Sentry.Maui.CommunityToolkit.Mvvm.Tests.csproj", "test\\Sentry.Maui.Tests\\Sentry.Maui.Tests.csproj", "test\\Sentry.NLog.Tests\\Sentry.NLog.Tests.csproj", + "test\\Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests\\Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj", "test\\Sentry.OpenTelemetry.Tests\\Sentry.OpenTelemetry.Tests.csproj", "test\\Sentry.Profiling.Tests\\Sentry.Profiling.Tests.csproj", "test\\Sentry.Serilog.Tests\\Sentry.Serilog.Tests.csproj", diff --git a/Sentry-CI-Build-macOS.slnf b/Sentry-CI-Build-macOS.slnf index 08b1295241..a8d7cea54b 100644 --- a/Sentry-CI-Build-macOS.slnf +++ b/Sentry-CI-Build-macOS.slnf @@ -57,6 +57,7 @@ "src\\Sentry.Maui.CommunityToolkit.Mvvm\\Sentry.Maui.CommunityToolkit.Mvvm.csproj", "src\\Sentry.Maui\\Sentry.Maui.csproj", "src\\Sentry.NLog\\Sentry.NLog.csproj", + "src\\Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol\\Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.csproj", "src\\Sentry.OpenTelemetry\\Sentry.OpenTelemetry.csproj", "src\\Sentry.Profiling\\Sentry.Profiling.csproj", "src\\Sentry.Serilog\\Sentry.Serilog.csproj", @@ -80,6 +81,7 @@ "test\\Sentry.Maui.Device.TestApp\\Sentry.Maui.Device.TestApp.csproj", "test\\Sentry.Maui.Tests\\Sentry.Maui.Tests.csproj", "test\\Sentry.NLog.Tests\\Sentry.NLog.Tests.csproj", + "test\\Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests\\Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj", "test\\Sentry.OpenTelemetry.Tests\\Sentry.OpenTelemetry.Tests.csproj", "test\\Sentry.Profiling.Tests\\Sentry.Profiling.Tests.csproj", "test\\Sentry.Serilog.Tests\\Sentry.Serilog.Tests.csproj", diff --git a/Sentry-CI-CodeQL.slnf b/Sentry-CI-CodeQL.slnf index f155eb8373..f61f3cebf1 100644 --- a/Sentry-CI-CodeQL.slnf +++ b/Sentry-CI-CodeQL.slnf @@ -19,6 +19,7 @@ "src\\Sentry.Maui.CommunityToolkit.Mvvm\\Sentry.Maui.CommunityToolkit.Mvvm.csproj", "src\\Sentry.Maui\\Sentry.Maui.csproj", "src\\Sentry.NLog\\Sentry.NLog.csproj", + "src\\Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol\\Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.csproj", "src\\Sentry.OpenTelemetry\\Sentry.OpenTelemetry.csproj", "src\\Sentry.Profiling\\Sentry.Profiling.csproj", "src\\Sentry.Serilog\\Sentry.Serilog.csproj", diff --git a/Sentry.slnx b/Sentry.slnx index 3ed20edf11..45de3e4077 100644 --- a/Sentry.slnx +++ b/Sentry.slnx @@ -142,6 +142,7 @@ + @@ -171,6 +172,7 @@ + diff --git a/SentryNoMobile.slnf b/SentryNoMobile.slnf index a5db92897e..60fb62453e 100644 --- a/SentryNoMobile.slnf +++ b/SentryNoMobile.slnf @@ -46,6 +46,7 @@ "src\\Sentry.Hangfire\\Sentry.Hangfire.csproj", "src\\Sentry.Log4Net\\Sentry.Log4Net.csproj", "src\\Sentry.NLog\\Sentry.NLog.csproj", + "src\\Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol\\Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.csproj", "src\\Sentry.OpenTelemetry\\Sentry.OpenTelemetry.csproj", "src\\Sentry.Profiling\\Sentry.Profiling.csproj", "src\\Sentry.Serilog\\Sentry.Serilog.csproj", @@ -68,6 +69,7 @@ "test\\Sentry.Hangfire.Tests\\Sentry.Hangfire.Tests.csproj", "test\\Sentry.Log4Net.Tests\\Sentry.Log4Net.Tests.csproj", "test\\Sentry.NLog.Tests\\Sentry.NLog.Tests.csproj", + "test\\Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests\\Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj", "test\\Sentry.OpenTelemetry.Tests\\Sentry.OpenTelemetry.Tests.csproj", "test\\Sentry.Profiling.Tests\\Sentry.Profiling.Tests.csproj", "test\\Sentry.Serilog.Tests\\Sentry.Serilog.Tests.csproj", diff --git a/SentryNoSamples.slnf b/SentryNoSamples.slnf index 81a7c4243b..9605b36f6a 100644 --- a/SentryNoSamples.slnf +++ b/SentryNoSamples.slnf @@ -20,6 +20,7 @@ "src\\Sentry.Maui.CommunityToolkit.Mvvm\\Sentry.Maui.CommunityToolkit.Mvvm.csproj", "src\\Sentry.Maui\\Sentry.Maui.csproj", "src\\Sentry.NLog\\Sentry.NLog.csproj", + "src\\Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol\\Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.csproj", "src\\Sentry.OpenTelemetry\\Sentry.OpenTelemetry.csproj", "src\\Sentry.Profiling\\Sentry.Profiling.csproj", "src\\Sentry.Serilog\\Sentry.Serilog.csproj", @@ -44,6 +45,7 @@ "test\\Sentry.Maui.CommunityToolkit.Mvvm.Tests\\Sentry.Maui.CommunityToolkit.Mvvm.Tests.csproj", "test\\Sentry.Maui.Tests\\Sentry.Maui.Tests.csproj", "test\\Sentry.NLog.Tests\\Sentry.NLog.Tests.csproj", + "test\\Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests\\Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj", "test\\Sentry.OpenTelemetry.Tests\\Sentry.OpenTelemetry.Tests.csproj", "test\\Sentry.Profiling.Tests\\Sentry.Profiling.Tests.csproj", "test\\Sentry.Serilog.Tests\\Sentry.Serilog.Tests.csproj", diff --git a/samples/Sentry.Samples.AspNetCore.Blazor.Server/Sentry.Samples.AspNetCore.Blazor.Server.csproj b/samples/Sentry.Samples.AspNetCore.Blazor.Server/Sentry.Samples.AspNetCore.Blazor.Server.csproj index 07200d7743..3a4426aa55 100644 --- a/samples/Sentry.Samples.AspNetCore.Blazor.Server/Sentry.Samples.AspNetCore.Blazor.Server.csproj +++ b/samples/Sentry.Samples.AspNetCore.Blazor.Server/Sentry.Samples.AspNetCore.Blazor.Server.csproj @@ -22,9 +22,9 @@ - - - + + + diff --git a/samples/Sentry.Samples.GraphQL.Server/Sentry.Samples.GraphQL.Server.csproj b/samples/Sentry.Samples.GraphQL.Server/Sentry.Samples.GraphQL.Server.csproj index c7ed799d41..0ff52ef056 100644 --- a/samples/Sentry.Samples.GraphQL.Server/Sentry.Samples.GraphQL.Server.csproj +++ b/samples/Sentry.Samples.GraphQL.Server/Sentry.Samples.GraphQL.Server.csproj @@ -14,9 +14,9 @@ - - - + + + diff --git a/samples/Sentry.Samples.OpenTelemetry.AspNetCore/Program.cs b/samples/Sentry.Samples.OpenTelemetry.AspNetCore/Program.cs index e53d2b20b2..b834e5245d 100644 --- a/samples/Sentry.Samples.OpenTelemetry.AspNetCore/Program.cs +++ b/samples/Sentry.Samples.OpenTelemetry.AspNetCore/Program.cs @@ -3,10 +3,20 @@ using OpenTelemetry.Resources; using OpenTelemetry.Trace; using Sentry.OpenTelemetry; +using Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol; using Sentry.Samples.OpenTelemetry.AspNetCore; var builder = WebApplication.CreateBuilder(args); +#if SENTRY_DSN_DEFINED_IN_ENV +var dsn = Environment.GetEnvironmentVariable("SENTRY_DSN") + ?? throw new InvalidOperationException("SENTRY_DSN environment variable is not set"); +#else +// A DSN is required. You can set here in code, or you can set it in the SENTRY_DSN environment variable. +// See https://docs.sentry.io/product/sentry-basics/dsn-explainer/ +var dsn = SamplesShared.Dsn; +#endif + // OpenTelemetry Configuration // See https://opentelemetry.io/docs/instrumentation/net/getting-started/ builder.Services.AddOpenTelemetry() @@ -20,22 +30,17 @@ // The two lines below take care of configuring sources for ASP.NET Core and HttpClient .AddAspNetCoreInstrumentation() .AddHttpClientInstrumentation() - // Finally we configure OpenTelemetry to send traces to Sentry - .AddSentry() + // Finally, we configure OpenTelemetry over OTLP to send traces to Sentry + .AddSentryOtlp(dsn) ); builder.WebHost.UseSentry(options => { -#if !SENTRY_DSN_DEFINED_IN_ENV - // A DSN is required. You can set here in code, or you can set it in the SENTRY_DSN environment variable. - // See https://docs.sentry.io/product/sentry-basics/dsn-explainer/ - options.Dsn = SamplesShared.Dsn; -#endif - + options.Dsn = dsn; options.Debug = builder.Environment.IsDevelopment(); options.SendDefaultPii = true; options.TracesSampleRate = 1.0; - options.UseOpenTelemetry(); // <-- Configure Sentry to use OpenTelemetry trace information + options.UseOtlp(); // <-- Configure Sentry to use OpenTelemetry trace information }); builder.Services diff --git a/samples/Sentry.Samples.OpenTelemetry.AspNetCore/Sentry.Samples.OpenTelemetry.AspNetCore.csproj b/samples/Sentry.Samples.OpenTelemetry.AspNetCore/Sentry.Samples.OpenTelemetry.AspNetCore.csproj index a07956f015..87ed8ed44b 100644 --- a/samples/Sentry.Samples.OpenTelemetry.AspNetCore/Sentry.Samples.OpenTelemetry.AspNetCore.csproj +++ b/samples/Sentry.Samples.OpenTelemetry.AspNetCore/Sentry.Samples.OpenTelemetry.AspNetCore.csproj @@ -7,10 +7,11 @@ - - - - + + + + + @@ -20,9 +21,10 @@ true + - + diff --git a/samples/Sentry.Samples.OpenTelemetry.AzureFunctions/Program.cs b/samples/Sentry.Samples.OpenTelemetry.AzureFunctions/Program.cs index 7be6020a50..9a35d5a2a7 100644 --- a/samples/Sentry.Samples.OpenTelemetry.AzureFunctions/Program.cs +++ b/samples/Sentry.Samples.OpenTelemetry.AzureFunctions/Program.cs @@ -3,6 +3,16 @@ using Microsoft.Extensions.Logging; using OpenTelemetry.Trace; using Sentry.OpenTelemetry; +using Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol; + +#if SENTRY_DSN_DEFINED_IN_ENV +var dsn = Environment.GetEnvironmentVariable("SENTRY_DSN") + ?? throw new InvalidOperationException("SENTRY_DSN environment variable is not set"); +#else +// A DSN is required. You can set here in code, or you can set it in the SENTRY_DSN environment variable. +// See https://docs.sentry.io/product/sentry-basics/dsn-explainer/ +var dsn = SamplesShared.Dsn; +#endif var host = new HostBuilder() .ConfigureFunctionsWorkerDefaults() @@ -11,7 +21,7 @@ services.AddOpenTelemetry().WithTracing(builder => { builder - .AddSentry() // <-- Configure OpenTelemetry to send traces to Sentry + .AddSentryOtlp(dsn) // <-- Configure OpenTelemetry to send traces to Sentry .AddHttpClientInstrumentation(); // From OpenTelemetry.Instrumentation.Http... adds automatic tracing for outgoing HTTP requests }); }) @@ -19,12 +29,9 @@ { logging.AddSentry(options => { -#if !SENTRY_DSN_DEFINED_IN_ENV - // A DSN is required. You can set here in code, or you can set it in the SENTRY_DSN environment variable. - options.Dsn = SamplesShared.Dsn; -#endif + options.Dsn = dsn; options.TracesSampleRate = 1.0; - options.UseOpenTelemetry(); // <-- Configure Sentry to use open telemetry + options.UseOtlp(); // <-- Configure Sentry to use open telemetry options.DisableSentryHttpMessageHandler = true; // So Sentry doesn't also create spans for outbound HTTP requests options.Debug = true; }); diff --git a/samples/Sentry.Samples.OpenTelemetry.AzureFunctions/Sentry.Samples.OpenTelemetry.AzureFunctions.csproj b/samples/Sentry.Samples.OpenTelemetry.AzureFunctions/Sentry.Samples.OpenTelemetry.AzureFunctions.csproj index 564884ea00..775f9b74c2 100644 --- a/samples/Sentry.Samples.OpenTelemetry.AzureFunctions/Sentry.Samples.OpenTelemetry.AzureFunctions.csproj +++ b/samples/Sentry.Samples.OpenTelemetry.AzureFunctions/Sentry.Samples.OpenTelemetry.AzureFunctions.csproj @@ -10,9 +10,9 @@ - - - + + + @@ -31,9 +31,9 @@ true + - - + diff --git a/samples/Sentry.Samples.OpenTelemetry.Console/Program.cs b/samples/Sentry.Samples.OpenTelemetry.Console/Program.cs index 0ce193ad13..cc33278495 100644 --- a/samples/Sentry.Samples.OpenTelemetry.Console/Program.cs +++ b/samples/Sentry.Samples.OpenTelemetry.Console/Program.cs @@ -9,26 +9,31 @@ using OpenTelemetry; using OpenTelemetry.Trace; using Sentry.OpenTelemetry; +using Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol; + +#if SENTRY_DSN_DEFINED_IN_ENV +var dsn = Environment.GetEnvironmentVariable("SENTRY_DSN") + ?? throw new InvalidOperationException("SENTRY_DSN environment variable is not set"); +#else +// A DSN is required. You can set here in code, or you can set it in the SENTRY_DSN environment variable. +// See https://docs.sentry.io/product/sentry-basics/dsn-explainer/ +var dsn = SamplesShared.Dsn; +#endif var activitySource = new ActivitySource("Sentry.Samples.OpenTelemetry.Console"); SentrySdk.Init(options => { -#if !SENTRY_DSN_DEFINED_IN_ENV - // A DSN is required. You can set here in code, or you can set it in the SENTRY_DSN environment variable. - // See https://docs.sentry.io/product/sentry-basics/dsn-explainer/ - options.Dsn = SamplesShared.Dsn; -#endif - + options.Dsn = dsn; options.Debug = true; options.TracesSampleRate = 1.0; - options.UseOpenTelemetry(); // <-- Configure Sentry to use OpenTelemetry trace information + options.UseOtlp(); // <-- Configure Sentry to use OpenTelemetry trace information }); using var tracerProvider = Sdk.CreateTracerProviderBuilder() .AddSource(activitySource.Name) .AddHttpClientInstrumentation() - .AddSentry() // <-- Configure OpenTelemetry to send traces to Sentry + .AddSentryOtlp(dsn) // <-- Configure OpenTelemetry to send traces to Sentry over OTLP .Build(); Console.WriteLine("Hello World!"); @@ -42,7 +47,7 @@ task?.SetTag("Answer", 42); Thread.Sleep(100); // simulate some work Console.WriteLine("Task 1 completed"); - task?.SetStatus(Status.Ok); + task?.SetStatus(ActivityStatusCode.Ok); } // Since we use `AddHttpClientInstrumentation` when initializing OpenTelemetry, the following Http request will also diff --git a/samples/Sentry.Samples.OpenTelemetry.Console/Sentry.Samples.OpenTelemetry.Console.csproj b/samples/Sentry.Samples.OpenTelemetry.Console/Sentry.Samples.OpenTelemetry.Console.csproj index 0aed012a4f..b8aeeedef1 100644 --- a/samples/Sentry.Samples.OpenTelemetry.Console/Sentry.Samples.OpenTelemetry.Console.csproj +++ b/samples/Sentry.Samples.OpenTelemetry.Console/Sentry.Samples.OpenTelemetry.Console.csproj @@ -8,8 +8,8 @@ - - + + @@ -19,10 +19,10 @@ true - + - + diff --git a/src/Sentry.DiagnosticSource/Internal/DiagnosticSource/SentryDiagnosticListenerIntegration.cs b/src/Sentry.DiagnosticSource/Internal/DiagnosticSource/SentryDiagnosticListenerIntegration.cs index 39ced41aa5..45da25e6bc 100644 --- a/src/Sentry.DiagnosticSource/Internal/DiagnosticSource/SentryDiagnosticListenerIntegration.cs +++ b/src/Sentry.DiagnosticSource/Internal/DiagnosticSource/SentryDiagnosticListenerIntegration.cs @@ -3,7 +3,7 @@ namespace Sentry.Internal.DiagnosticSource; -internal class SentryDiagnosticListenerIntegration : ISdkIntegration +internal class SentryDiagnosticListenerIntegration : ISdkIntegration, ISentryTracingIntegration { public void Register(IHub hub, SentryOptions options) { diff --git a/src/Sentry.EntityFramework/DbInterceptionIntegration.cs b/src/Sentry.EntityFramework/DbInterceptionIntegration.cs index bf561ef039..a75b873625 100644 --- a/src/Sentry.EntityFramework/DbInterceptionIntegration.cs +++ b/src/Sentry.EntityFramework/DbInterceptionIntegration.cs @@ -1,6 +1,6 @@ namespace Sentry.EntityFramework; -internal class DbInterceptionIntegration : ISdkIntegration +internal class DbInterceptionIntegration : ISdkIntegration, ISentryTracingIntegration { // Internal for testing. internal IDbInterceptor? SqlInterceptor { get; private set; } diff --git a/src/Sentry.Maui.CommunityToolkit.Mvvm/SentryOptionsExtensions.cs b/src/Sentry.Maui.CommunityToolkit.Mvvm/SentryOptionsExtensions.cs index 2f22bcbbbd..eebe40e6d6 100644 --- a/src/Sentry.Maui.CommunityToolkit.Mvvm/SentryOptionsExtensions.cs +++ b/src/Sentry.Maui.CommunityToolkit.Mvvm/SentryOptionsExtensions.cs @@ -1,3 +1,4 @@ +using Sentry.Extensibility; using Sentry.Maui.CommunityToolkit.Mvvm; namespace Sentry.Maui; @@ -12,7 +13,14 @@ public static class SentryOptionsExtensions /// public static SentryMauiOptions AddCommunityToolkitIntegration(this SentryMauiOptions options) { - options.AddIntegrationEventBinder(); + if (options.DisableSentryTracing) + { + options.LogWarning("Skipping CommunityToolkit.Mvvm integration because OpenTelemetry is enabled."); + } + else + { + options.AddIntegrationEventBinder(); + } return options; } } diff --git a/src/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol/ActivityExtensions.cs b/src/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol/ActivityExtensions.cs new file mode 100644 index 0000000000..df986a1574 --- /dev/null +++ b/src/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol/ActivityExtensions.cs @@ -0,0 +1,14 @@ +namespace Sentry.Internal; + +internal static class ActivityExtensions +{ + 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()); +} diff --git a/src/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol/OtelPropagationContext.cs b/src/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol/OtelPropagationContext.cs new file mode 100644 index 0000000000..f73ba6cd56 --- /dev/null +++ b/src/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol/OtelPropagationContext.cs @@ -0,0 +1,28 @@ +using Sentry.Extensibility; +using Sentry.Internal; + +namespace Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol; + +internal class OtelPropagationContext : IPropagationContext +{ + public DynamicSamplingContext? DynamicSamplingContext { get; private set; } + + public SentryId TraceId => Activity.Current?.TraceId.AsSentryId() ?? default; + public SpanId SpanId => Activity.Current?.SpanId.AsSentrySpanId() ?? default; + public SpanId? ParentSpanId => Activity.Current?.ParentSpanId.AsSentrySpanId(); + + /// + /// Warning: this method may throw an exception if Activity.Current is null. + /// This method should not be used when instrumenting with OTEL. + /// + public DynamicSamplingContext GetOrCreateDynamicSamplingContext(SentryOptions options, IReplaySession replaySession) + { + if (DynamicSamplingContext is null) + { + options.LogDebug("Creating the Dynamic Sampling Context from the Propagation Context."); + DynamicSamplingContext = this.CreateDynamicSamplingContext(options, replaySession); + } + + return DynamicSamplingContext; + } +} diff --git a/src/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.csproj b/src/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.csproj new file mode 100644 index 0000000000..16808d248a --- /dev/null +++ b/src/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.csproj @@ -0,0 +1,31 @@ + + + + Official OpenTelemetry integration for Sentry - Open-source error tracking that helps developers monitor and fix crashes in real time. + $(PackageTags);OpenTelemetry;OTLP + $(CurrentTfms);netstandard2.1;netstandard2.0;net462 + enable + + $(NoWarn);AD0001 + + + + true + + + + + + + + + + + + + + + + + + diff --git a/src/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol/SentryOptionsExtensions.cs b/src/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol/SentryOptionsExtensions.cs new file mode 100644 index 0000000000..3ecf286513 --- /dev/null +++ b/src/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol/SentryOptionsExtensions.cs @@ -0,0 +1,62 @@ +using OpenTelemetry.Context.Propagation; +using OpenTelemetry.Trace; + +namespace Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol; + +/// +/// OpenTelemetry Extensions for . +/// +public static class SentryOptionsExtensions +{ + /// + /// Configures Sentry to use OpenTelemetry for distributed tracing. Sentry instrumented traces will be + /// disabled (so all tracing instrumentation must be done using the OpenTelemetry classes). + /// + /// + /// This is the recommended way to set up Sentry's OpenTelemetry integration. + /// + /// + /// The instance. + /// + /// + /// The default TextMapPropagator to be used by OpenTelemetry. + /// + /// If this parameter is not supplied, the will be used, which propagates the + /// baggage header as well as Sentry trace headers. + /// + /// + /// The is required for Sentry's OpenTelemetry integration to work but you + /// could wrap this in a if you needed other propagators as well. + /// + /// + public static void UseOtlp(this SentryOptions options, TracerProviderBuilder builder, TextMapPropagator? textMapPropagator = null) + { + if (string.IsNullOrWhiteSpace(options.Dsn)) + { + throw new ArgumentException("Sentry DSN must be set before calling `SentryOptions.UseOTLP`", nameof(options.Dsn)); + } + builder.AddSentryOtlp(options.Dsn, textMapPropagator); + options.UseOtlp(); + } + + /// + /// Configures Sentry to use OpenTelemetry for distributed tracing. Sentry instrumented traces will be + /// disabled (so all tracing instrumentation must be done using the OpenTelemetry classes). + /// + /// + /// This is the recommended way to set up Sentry's OpenTelemetry integration. + /// + /// + /// + /// Note: if you are using this overload to configure Sentry to work with OpenTelemetry, you will also have to call + /// , when building your + /// to ensure OpenTelemetry sends trace information to Sentry. + /// + /// The instance. + public static void UseOtlp(this SentryOptions options) + { + options.Instrumenter = Instrumenter.OpenTelemetry; + options.DisableSentryTracing = true; + options.PropagationContextFactory = _ => new OtelPropagationContext(); + } +} diff --git a/src/Sentry.OpenTelemetry/SentryPropagator.cs b/src/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol/SentryPropagator.cs similarity index 93% rename from src/Sentry.OpenTelemetry/SentryPropagator.cs rename to src/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol/SentryPropagator.cs index 78fd960d6b..fbadd80fff 100644 --- a/src/Sentry.OpenTelemetry/SentryPropagator.cs +++ b/src/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol/SentryPropagator.cs @@ -2,6 +2,7 @@ using OpenTelemetry; using OpenTelemetry.Context.Propagation; using Sentry.Extensibility; +using Sentry.Internal; namespace Sentry.OpenTelemetry; @@ -49,12 +50,12 @@ private static class OTelKeys } /// - public override PropagationContext Extract(PropagationContext context, T carrier, Func> getter) + public override PropagationContext Extract(PropagationContext context, T carrier, Func?> getter) { Options?.LogDebug("SentryPropagator.Extract"); var result = base.Extract(context, carrier, getter); - var baggage = result.Baggage; // The Otel .NET SDK takes care of baggage headers alread + var baggage = result.Baggage; // The Otel .NET SDK takes care of baggage headers already Options?.LogDebug("Baggage"); foreach (var entry in baggage) @@ -121,13 +122,17 @@ public override void Inject(PropagationContext context, T carrier, Action(T carrier, Func> getter) + private static SentryTraceHeader? TryGetSentryTraceHeader(T carrier, Func?> getter) { var headerValue = getter(carrier, SentryTraceHeader.HttpHeaderName); + if (headerValue is null) + { + return null; + } try { var value = new StringValues(headerValue.ToArray()); - return SentryTraceHeader.Parse(value); + return SentryTraceHeader.Parse(value!); } catch (Exception) { diff --git a/src/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol/TracerProviderBuilderExtensions.cs b/src/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol/TracerProviderBuilderExtensions.cs new file mode 100644 index 0000000000..28d0d03b47 --- /dev/null +++ b/src/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol/TracerProviderBuilderExtensions.cs @@ -0,0 +1,69 @@ +using Microsoft.Extensions.DependencyInjection; +using OpenTelemetry; +using OpenTelemetry.Context.Propagation; +using OpenTelemetry.Exporter; +using OpenTelemetry.Trace; +using Sentry.Extensibility; +using Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol; + +namespace Sentry.OpenTelemetry; + +/// +/// Contains extension methods for the class. +/// +public static class TracerProviderBuilderExtensions +{ + internal const string MissingDsnWarning = "Invalid DSN passed to AddSentryOTLP"; + + /// + /// + /// Ensures OpenTelemetry trace information is sent to the Sentry OTLP endpoint. + /// + /// + /// Note that if you use this method to configure the trace builder, you will also need to call + /// when initialising Sentry, for Sentry to work + /// properly with OpenTelemetry. + /// + /// + /// The . + /// The DSN for your Sentry project + /// + /// The default TextMapPropagator to be used by OpenTelemetry. + /// + /// If this parameter is not supplied, the will be used, which propagates the + /// baggage header as well as Sentry trace headers. + /// + /// + /// The is required for Sentry's OpenTelemetry integration to work but you + /// could wrap this in a if you needed other propagators as well. + /// + /// + /// The supplied for chaining. + public static TracerProviderBuilder AddSentryOtlp(this TracerProviderBuilder tracerProviderBuilder, string dsnString, + TextMapPropagator? defaultTextMapPropagator = null) + { + if (Dsn.TryParse(dsnString) is not { } dsn) + { + throw new ArgumentException(MissingDsnWarning, nameof(dsnString)); + } + + defaultTextMapPropagator ??= new SentryPropagator(); + Sdk.SetDefaultTextMapPropagator(defaultTextMapPropagator); + + tracerProviderBuilder.AddOtlpExporter(options => OtlpConfigurationCallback(options, dsn)); + return tracerProviderBuilder; + } + + // Internal helper method for testing purposes + internal static void OtlpConfigurationCallback(OtlpExporterOptions options, Dsn dsn) + { + options.Endpoint = dsn.GetOtlpTracesEndpointUri(); + options.Protocol = OtlpExportProtocol.HttpProtobuf; + options.HttpClientFactory = () => + { + var client = new HttpClient(); + client.DefaultRequestHeaders.Add("X-Sentry-Auth", $"sentry sentry_key={dsn.PublicKey}"); + return client; + }; + } +} diff --git a/src/Sentry.OpenTelemetry/OpenTelemetryExtensions.cs b/src/Sentry.OpenTelemetry/OpenTelemetryExtensions.cs index 26970194ac..007a788e72 100644 --- a/src/Sentry.OpenTelemetry/OpenTelemetryExtensions.cs +++ b/src/Sentry.OpenTelemetry/OpenTelemetryExtensions.cs @@ -5,16 +5,6 @@ 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( diff --git a/src/Sentry.OpenTelemetry/OpenTelemetryTransactionProcessor.cs b/src/Sentry.OpenTelemetry/OpenTelemetryTransactionProcessor.cs index 4ab485dde1..d7d285eebb 100644 --- a/src/Sentry.OpenTelemetry/OpenTelemetryTransactionProcessor.cs +++ b/src/Sentry.OpenTelemetry/OpenTelemetryTransactionProcessor.cs @@ -1,4 +1,5 @@ using Sentry.Extensibility; +using Sentry.Internal; namespace Sentry.OpenTelemetry; diff --git a/src/Sentry.OpenTelemetry/Sentry.OpenTelemetry.csproj b/src/Sentry.OpenTelemetry/Sentry.OpenTelemetry.csproj index 6067738ff9..2b284b767d 100644 --- a/src/Sentry.OpenTelemetry/Sentry.OpenTelemetry.csproj +++ b/src/Sentry.OpenTelemetry/Sentry.OpenTelemetry.csproj @@ -13,6 +13,13 @@ + + + diff --git a/src/Sentry.OpenTelemetry/SentryOptionsExtensions.cs b/src/Sentry.OpenTelemetry/SentryOptionsExtensions.cs index 2035787a19..8138facda1 100644 --- a/src/Sentry.OpenTelemetry/SentryOptionsExtensions.cs +++ b/src/Sentry.OpenTelemetry/SentryOptionsExtensions.cs @@ -24,17 +24,18 @@ public static class SentryOptionsExtensions /// could wrap this in a if you needed other propagators as well. /// /// - public static void UseOpenTelemetry( - this SentryOptions options, - TracerProviderBuilder traceProviderBuilder, - TextMapPropagator? defaultTextMapPropagator = null - ) + /// Whether to disable traces created using Sentry's tracing instrumentation. + /// It's recommended that you set this to true since mixing OpenTelemetry and Sentry traces may yield + /// unexpected results. It is false by default for backward compatibility only. + /// + /// + /// This method of initialising the Sentry OpenTelemetry integration will be depricated in a future major release. + /// We recommend you use the Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol integration instead. + /// + public static void UseOpenTelemetry(this SentryOptions options, TracerProviderBuilder traceProviderBuilder, + TextMapPropagator? defaultTextMapPropagator = null, bool disableSentryTracing = false) { - options.Instrumenter = Instrumenter.OpenTelemetry; - options.AddTransactionProcessor( - new OpenTelemetryTransactionProcessor() - ); - + options.UseOpenTelemetry(disableSentryTracing); traceProviderBuilder.AddSentry(defaultTextMapPropagator); } @@ -46,12 +47,21 @@ public static void UseOpenTelemetry( /// to ensure OpenTelemetry sends trace information to Sentry. /// /// - /// instance - public static void UseOpenTelemetry(this SentryOptions options) + /// The instance. + /// Whether to disable traces created using Sentry's tracing instrumentation. + /// It's recommended that you set this to true since mixing OpenTelemetry and Sentry traces may yield + /// unexpected results. It is false by default for backward compatibility only. + /// + /// + /// This method of initialising the Sentry OpenTelemetry integration will be depricated in a future major release. + /// We recommend you use the Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol integration instead. + /// + public static void UseOpenTelemetry(this SentryOptions options, bool disableSentryTracing = false) { options.Instrumenter = Instrumenter.OpenTelemetry; + options.DisableSentryTracing = disableSentryTracing; options.AddTransactionProcessor( new OpenTelemetryTransactionProcessor() - ); + ); } } diff --git a/src/Sentry.OpenTelemetry/TracerProviderBuilderExtensions.cs b/src/Sentry.OpenTelemetry/TracerProviderBuilderExtensions.cs index 96472eda98..cc5fb8e12b 100644 --- a/src/Sentry.OpenTelemetry/TracerProviderBuilderExtensions.cs +++ b/src/Sentry.OpenTelemetry/TracerProviderBuilderExtensions.cs @@ -12,9 +12,17 @@ namespace Sentry.OpenTelemetry; public static class TracerProviderBuilderExtensions { /// - /// Ensures OpenTelemetry trace information is sent to Sentry. + /// + /// Ensures OpenTelemetry trace information is sent to Sentry. OpenTelemetry spans will be converted to Sentry spans + /// using a span processor. + /// + /// + /// Note that if you use this method to configure the trace builder, you will also need to call + /// when initialising Sentry, for Sentry + /// to work properly with OpenTelemetry. + /// /// - /// . + /// The . /// /// The default TextMapPropagator to be used by OpenTelemetry. /// @@ -27,7 +35,12 @@ public static class TracerProviderBuilderExtensions /// /// /// The supplied for chaining. - public static TracerProviderBuilder AddSentry(this TracerProviderBuilder tracerProviderBuilder, TextMapPropagator? defaultTextMapPropagator = null) + /// + /// This method of initialising the Sentry OpenTelemetry integration will be depricated in a future major release. + /// We recommend you use the Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol integration instead. + /// + public static TracerProviderBuilder AddSentry(this TracerProviderBuilder tracerProviderBuilder, + TextMapPropagator? defaultTextMapPropagator = null) { defaultTextMapPropagator ??= new SentryPropagator(); Sdk.SetDefaultTextMapPropagator(defaultTextMapPropagator); @@ -36,7 +49,7 @@ public static TracerProviderBuilder AddSentry(this TracerProviderBuilder tracerP internal static BaseProcessor ImplementationFactory(IServiceProvider services) { - List enrichers = new(); + List enrichers = []; // AspNetCoreEnricher var userFactory = services.GetService(); diff --git a/src/Sentry/Dsn.cs b/src/Sentry/Dsn.cs index 011af39169..a132d89945 100644 --- a/src/Sentry/Dsn.cs +++ b/src/Sentry/Dsn.cs @@ -58,6 +58,8 @@ private Dsn( public Uri GetEnvelopeEndpointUri() => new(ApiBaseUri, "envelope/"); + public Uri GetOtlpTracesEndpointUri() => new(ApiBaseUri, "integration/otlp/v1/traces"); + public override string ToString() => Source; public static bool IsDisabled(string? dsn) => diff --git a/src/Sentry/DynamicSamplingContext.cs b/src/Sentry/DynamicSamplingContext.cs index f39d7fa5a7..b3b6a3fb62 100644 --- a/src/Sentry/DynamicSamplingContext.cs +++ b/src/Sentry/DynamicSamplingContext.cs @@ -1,5 +1,4 @@ using Sentry.Internal; -using Sentry.Internal.Extensions; namespace Sentry; @@ -228,8 +227,16 @@ public static DynamicSamplingContext CreateFromUnsampledTransaction(UnsampledTra replaySession); } - public static DynamicSamplingContext CreateFromPropagationContext(SentryPropagationContext propagationContext, SentryOptions options, IReplaySession? replaySession) + /// + /// Creates a from the given . + /// + /// + /// This method should not be used when instrumenting with OTEL. + /// It will throw an exception if Activity.Current is null. + /// + public static DynamicSamplingContext CreateFromPropagationContext(IPropagationContext propagationContext, SentryOptions options, IReplaySession? replaySession) { + Debug.Assert(options.Instrumenter is not Instrumenter.OpenTelemetry, "This method should not be used when instrumenting with OTEL."); var traceId = propagationContext.TraceId; var publicKey = options.ParsedDsn.PublicKey; var release = options.SettingLocator.GetRelease(); @@ -257,6 +264,13 @@ public static DynamicSamplingContext CreateDynamicSamplingContext(this Transacti public static DynamicSamplingContext CreateDynamicSamplingContext(this UnsampledTransaction transaction, SentryOptions options, IReplaySession? replaySession) => DynamicSamplingContext.CreateFromUnsampledTransaction(transaction, options, replaySession); - public static DynamicSamplingContext CreateDynamicSamplingContext(this SentryPropagationContext propagationContext, SentryOptions options, IReplaySession? replaySession) + /// + /// Creates a from the given . + /// + /// + /// This method should not be used when instrumenting with OTEL. + /// It will throw an exception if Activity.Current is null. + /// + public static DynamicSamplingContext CreateDynamicSamplingContext(this IPropagationContext propagationContext, SentryOptions options, IReplaySession? replaySession) => DynamicSamplingContext.CreateFromPropagationContext(propagationContext, options, replaySession); } diff --git a/src/Sentry/IHub.cs b/src/Sentry/IHub.cs index a5a7afd9c9..c42a1f784d 100644 --- a/src/Sentry/IHub.cs +++ b/src/Sentry/IHub.cs @@ -70,6 +70,9 @@ public ITransactionTracer StartTransaction( /// /// Gets the Sentry baggage header that allows tracing across services /// + /// Can be thrown when using OpenTelemetry instrumentation and + /// System.Diagnostics.Activity.Current is null. This method should not be used when instrumenting with OTEL. + /// public BaggageHeader? GetBaggage(); /// diff --git a/src/Sentry/Integrations/ISdkIntegration.cs b/src/Sentry/Integrations/ISdkIntegration.cs index 898e5b39db..1a1eadc558 100644 --- a/src/Sentry/Integrations/ISdkIntegration.cs +++ b/src/Sentry/Integrations/ISdkIntegration.cs @@ -15,3 +15,11 @@ public interface ISdkIntegration /// The options. public void Register(IHub hub, SentryOptions options); } + +/// +/// Marker interface to indicate that an integration provides native Sentry tracing capabilities. We do NOT initialise +/// these integrations when using OTEL instrumentation. +/// +internal interface ISentryTracingIntegration +{ +} diff --git a/src/Sentry/Internal/Hub.cs b/src/Sentry/Internal/Hub.cs index be71b2f1d3..2150fcc825 100644 --- a/src/Sentry/Internal/Hub.cs +++ b/src/Sentry/Internal/Hub.cs @@ -185,6 +185,12 @@ internal ITransactionTracer StartTransaction( return NoOpTransaction.Instance; } + if (_options.DisableSentryTracing) + { + _options.LogWarning("Sentry transaction dropped because OpenTelemetry is enabled"); + return NoOpTransaction.Instance; + } + bool? isSampled = null; double? sampleRate = null; DiscardReason? discardReason = null; @@ -306,6 +312,12 @@ public SentryTraceHeader GetTraceHeader() public BaggageHeader GetBaggage() { + if (_options.Instrumenter is Instrumenter.OpenTelemetry) + { + _options.LogWarning("GetBaggage should not be called when using OpenTelemetry."); + return BaggageHeader.Create([]); + } + var span = GetSpan(); if (span?.GetTransaction().GetDynamicSamplingContext() is { IsEmpty: false } dsc) { @@ -478,12 +490,15 @@ private void ApplyTraceContextToEvent(SentryEvent evt, ISpan span) } } - private void ApplyTraceContextToEvent(SentryEvent evt, SentryPropagationContext propagationContext) + private void ApplyTraceContextToEvent(SentryEvent evt, IPropagationContext propagationContext) { evt.Contexts.Trace.TraceId = propagationContext.TraceId; evt.Contexts.Trace.SpanId = propagationContext.SpanId; evt.Contexts.Trace.ParentSpanId = propagationContext.ParentSpanId; - evt.DynamicSamplingContext = propagationContext.GetOrCreateDynamicSamplingContext(_options, _replaySession); + if (_options.Instrumenter is Instrumenter.Sentry) + { + evt.DynamicSamplingContext = propagationContext.GetOrCreateDynamicSamplingContext(_options, _replaySession); + } } public bool CaptureEnvelope(Envelope envelope) => CurrentClient.CaptureEnvelope(envelope); diff --git a/src/Sentry/Internal/IPropagationContext.cs b/src/Sentry/Internal/IPropagationContext.cs new file mode 100644 index 0000000000..00f2c781af --- /dev/null +++ b/src/Sentry/Internal/IPropagationContext.cs @@ -0,0 +1,12 @@ +namespace Sentry.Internal; + +internal interface IPropagationContext +{ + public DynamicSamplingContext? DynamicSamplingContext { get; } + + public SentryId TraceId { get; } + public SpanId SpanId { get; } + public SpanId? ParentSpanId { get; } + + public DynamicSamplingContext GetOrCreateDynamicSamplingContext(SentryOptions options, IReplaySession replaySession); +} diff --git a/src/Sentry/Scope.cs b/src/Sentry/Scope.cs index 04fab75ff1..4638a4255a 100644 --- a/src/Sentry/Scope.cs +++ b/src/Sentry/Scope.cs @@ -1,11 +1,7 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; using Sentry.Extensibility; using Sentry.Internal; using Sentry.Internal.Extensions; +using Sentry.Internal.OpenTelemetry; namespace Sentry; @@ -249,7 +245,7 @@ public ITransactionTracer? Transaction } } - internal SentryPropagationContext PropagationContext { get; private set; } + internal IPropagationContext PropagationContext { get; private set; } internal SessionUpdate? SessionUpdate { get; set; } @@ -297,10 +293,10 @@ public Scope(SentryOptions? options) { } - internal Scope(SentryOptions? options, SentryPropagationContext? propagationContext) + internal Scope(SentryOptions? options, IPropagationContext? propagationContext) { Options = options ?? new SentryOptions(); - PropagationContext = new SentryPropagationContext(propagationContext); + PropagationContext = Options.PropagationContextFactory(propagationContext); } // For testing. Should explicitly require SentryOptions. @@ -420,7 +416,7 @@ public void Clear() _extra.Clear(); _tags.Clear(); ClearAttachments(); - PropagationContext = new(); + PropagationContext = Options.PropagationContextFactory(null); } /// diff --git a/src/Sentry/Sentry.csproj b/src/Sentry/Sentry.csproj index 2fce203ef5..3419f545b2 100644 --- a/src/Sentry/Sentry.csproj +++ b/src/Sentry/Sentry.csproj @@ -169,6 +169,8 @@ + + diff --git a/src/Sentry/SentryGraphQLHttpMessageHandler.cs b/src/Sentry/SentryGraphQLHttpMessageHandler.cs index 7666e46455..229ddafd5b 100644 --- a/src/Sentry/SentryGraphQLHttpMessageHandler.cs +++ b/src/Sentry/SentryGraphQLHttpMessageHandler.cs @@ -50,6 +50,12 @@ internal SentryGraphQLHttpMessageHandler(IHub? hub, SentryOptions? options, } request.SetFused(graphQlRequestContent); + if (_options?.DisableSentryTracing ?? false) + { + _options.LogDebug("Skipping span creation in SentryGraphQLHttpMessageHandler because OpenTelemetry is enabled"); + return null; + } + // Start a span that tracks this request // (may be null if transaction is not set on the scope) var span = _hub.GetSpan()?.StartChild( diff --git a/src/Sentry/SentryHttpMessageHandler.cs b/src/Sentry/SentryHttpMessageHandler.cs index f75f958b8a..eb9d62f8f2 100644 --- a/src/Sentry/SentryHttpMessageHandler.cs +++ b/src/Sentry/SentryHttpMessageHandler.cs @@ -65,6 +65,12 @@ internal SentryHttpMessageHandler(IHub? hub, SentryOptions? options, HttpMessage /// protected internal override ISpan? ProcessRequest(HttpRequestMessage request, string method, string url) { + if (_options?.DisableSentryTracing ?? false) + { + _options.LogDebug("Skipping span creation in SentryHttpMessageHandler because OpenTelemetry is enabled"); + return null; + } + // Start a span that tracks this request // (may be null if transaction is not set on the scope) var span = _hub.GetSpan()?.StartChild( diff --git a/src/Sentry/SentryMessageHandler.cs b/src/Sentry/SentryMessageHandler.cs index 9c8515e93e..bf5f53176c 100644 --- a/src/Sentry/SentryMessageHandler.cs +++ b/src/Sentry/SentryMessageHandler.cs @@ -133,6 +133,15 @@ private void PropagateTraceHeaders(HttpRequestMessage request, string url, ISpan } } + // We only propogate trace headers for Sentry's native intstumentation. It isn't possible to propogate + // headers when OTEL instrumentation is used since the traceId can be SentryId.Empty if there is no active + // OTEL span... which would result in an exception being thrown when trying to create the + // DynamicSamplingContext. + if (_options?.Instrumenter is Instrumenter.OpenTelemetry) + { + return; + } + if (_options?.TracePropagationTargets.MatchesSubstringOrRegex(url) is true or null) { AddSentryTraceHeader(request, parentSpan); diff --git a/src/Sentry/SentryOptions.cs b/src/Sentry/SentryOptions.cs index 7fc69a600f..bedcc39bf1 100644 --- a/src/Sentry/SentryOptions.cs +++ b/src/Sentry/SentryOptions.cs @@ -206,7 +206,7 @@ internal IEnumerable Integrations #endif #if HAS_DIAGNOSTIC_INTEGRATION - if ((_defaultIntegrations & DefaultIntegrations.SentryDiagnosticListenerIntegration) != 0) + if (!DisableSentryTracing && (_defaultIntegrations & DefaultIntegrations.SentryDiagnosticListenerIntegration) != 0) { yield return new SentryDiagnosticListenerIntegration(); } @@ -222,6 +222,10 @@ internal IEnumerable Integrations foreach (var integration in _integrations) { + if (DisableSentryTracing && integration is ISentryTracingIntegration) + { + continue; + } yield return integration; } } @@ -1156,6 +1160,20 @@ public StackTraceMode StackTraceMode /// internal Instrumenter Instrumenter { get; set; } = Instrumenter.Sentry; + /// + /// During the transition period to OTLP we give SDK users the option to keep using Sentry's tracing in conjunction + /// with OTEL instrumentation. Setting this to true will disable Sentry's tracing entirely, which is the recommended + /// setting but would be a moajor change in behaviour, so we've made it opt-in for now. + /// TODO: Remove this option in a future major release and make it true / non-optional when using OTEL (i.e. implied by the Instrumenter) + /// + internal bool DisableSentryTracing { get; set; } = false; + + /// + /// The default factory creates SentryPropagationContext instances... this should be replaced when using OTEL + /// + internal Func PropagationContextFactory { get; set; } = sourceContext => + new SentryPropagationContext(sourceContext); + /// /// /// Set to `true` to prevents Sentry from automatically registering . diff --git a/src/Sentry/SentryPropagationContext.cs b/src/Sentry/SentryPropagationContext.cs index 6183262241..2ae4adba09 100644 --- a/src/Sentry/SentryPropagationContext.cs +++ b/src/Sentry/SentryPropagationContext.cs @@ -3,23 +3,23 @@ namespace Sentry; -internal class SentryPropagationContext +internal class SentryPropagationContext : IPropagationContext { public SentryId TraceId { get; } public SpanId SpanId { get; } public SpanId? ParentSpanId { get; } - internal DynamicSamplingContext? _dynamicSamplingContext; + public DynamicSamplingContext? DynamicSamplingContext { get; private set; } public DynamicSamplingContext GetOrCreateDynamicSamplingContext(SentryOptions options, IReplaySession replaySession) { - if (_dynamicSamplingContext is null) + if (DynamicSamplingContext is null) { options.LogDebug("Creating the Dynamic Sampling Context from the Propagation Context."); - _dynamicSamplingContext = this.CreateDynamicSamplingContext(options, replaySession); + DynamicSamplingContext = this.CreateDynamicSamplingContext(options, replaySession); } - return _dynamicSamplingContext; + return DynamicSamplingContext; } internal SentryPropagationContext( @@ -30,7 +30,7 @@ internal SentryPropagationContext( TraceId = traceId; SpanId = SpanId.Create(); ParentSpanId = parentSpanId; - _dynamicSamplingContext = dynamicSamplingContext; + DynamicSamplingContext = dynamicSamplingContext; } public SentryPropagationContext() @@ -39,13 +39,13 @@ public SentryPropagationContext() SpanId = SpanId.Create(); } - public SentryPropagationContext(SentryPropagationContext? other) + public SentryPropagationContext(IPropagationContext? other) { TraceId = other?.TraceId ?? SentryId.Create(); SpanId = other?.SpanId ?? SpanId.Create(); ParentSpanId = other?.ParentSpanId; - _dynamicSamplingContext = other?._dynamicSamplingContext; + DynamicSamplingContext = other?.DynamicSamplingContext; } public static SentryPropagationContext CreateFromHeaders(IDiagnosticLogger? logger, SentryTraceHeader? traceHeader, BaggageHeader? baggageHeader, IReplaySession replaySession) diff --git a/test/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/ActivitySourceTests.cs b/test/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/ActivitySourceTests.cs new file mode 100644 index 0000000000..97f76597aa --- /dev/null +++ b/test/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/ActivitySourceTests.cs @@ -0,0 +1,29 @@ +using OpenTelemetry; +using OpenTelemetry.Trace; +using Sentry.OpenTelemetry.Tests.OpenTelemetry; + +namespace Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests; + +public abstract class ActivitySourceTests : IDisposable +{ + protected readonly ActivitySource Tracer; + private readonly TracerProvider _traceProvider; + + protected ActivitySourceTests() + { + var activitySourceName = "SentrySpanProcessorTests"; + var testSampler = new TestSampler(); + Tracer = new ActivitySource(activitySourceName); + _traceProvider = Sdk.CreateTracerProviderBuilder() + .AddSource(activitySourceName) + .SetSampler(testSampler) + .Build(); + } + + public void Dispose() + { + _traceProvider?.Dispose(); + Tracer.Dispose(); + GC.SuppressFinalize(this); + } +} diff --git a/test/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OpenTelemetry/ATTRIBUTION.txt b/test/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OpenTelemetry/ATTRIBUTION.txt new file mode 100644 index 0000000000..327c24d113 --- /dev/null +++ b/test/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OpenTelemetry/ATTRIBUTION.txt @@ -0,0 +1,18 @@ +Parts of the code in this subdirectory have been adapted from +https://github.com/open-telemetry/ + +The original license is as follows: + +Copyright The OpenTelemetry Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/test/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OpenTelemetry/TestSampler.cs b/test/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OpenTelemetry/TestSampler.cs new file mode 100644 index 0000000000..3e1c51ffb4 --- /dev/null +++ b/test/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OpenTelemetry/TestSampler.cs @@ -0,0 +1,35 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using OpenTelemetry.Trace; + +namespace Sentry.OpenTelemetry.Tests.OpenTelemetry +{ + // This is a copy of OpenTelemetry.Tests.TestSampler: + // https://github.com/open-telemetry/opentelemetry-dotnet/blob/b23b1460e96efb5ecd78d1b36c2e00e84de7086b/test/OpenTelemetry.Tests/Shared/TestSampler.cs + internal class TestSampler : Sampler + { + public Func SamplingAction { get; set; } + + public SamplingParameters LatestSamplingParameters { get; private set; } + + public override SamplingResult ShouldSample(in SamplingParameters samplingParameters) + { + LatestSamplingParameters = samplingParameters; + return SamplingAction?.Invoke(samplingParameters) ?? new SamplingResult(SamplingDecision.RecordAndSample); + } + } +} diff --git a/test/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtelPropagationContextTests.cs b/test/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtelPropagationContextTests.cs new file mode 100644 index 0000000000..030a1a9fe9 --- /dev/null +++ b/test/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtelPropagationContextTests.cs @@ -0,0 +1,169 @@ +using Sentry.OpenTelemetry.Tests; + +namespace Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests; + +public class OtelPropagationContextTests : ActivitySourceTests +{ + private class Fixture + { + public SentryId ActiveReplayId { get; } = SentryId.Create(); + public IReplaySession ActiveReplaySession { get; } + public SentryOptions SentryOptions { get; } + + public Fixture() + { + ActiveReplaySession = Substitute.For(); + ActiveReplaySession.ActiveReplayId.Returns(ActiveReplayId); + + SentryOptions = new SentryOptions { Dsn = "https://examplePublicKey@o0.ingest.sentry.io/123456" }; + } + } + + private readonly Fixture _fixture = new(); + + [Fact] + public void TraceId_NoActivityCurrent_ReturnsDefault() + { + // Arrange + var sut = new OtelPropagationContext(); + Activity.Current = null; + + // Act + var traceId = sut.TraceId; + + // Assert + traceId.Should().Be(default(SentryId)); + } + + [Fact] + public void TraceId_WithActivityCurrent_ReturnsSentryIdFromActivityTraceId() + { + // Arrange + using var activity = Tracer.StartActivity(); + var sut = new OtelPropagationContext(); + + // Act + var traceId = sut.TraceId; + + // Assert + traceId.Should().NotBe(default(SentryId)); + traceId.Should().Be(activity.TraceId.AsSentryId()); + } + + [Fact] + public void SpanId_NoActivityCurrent_ReturnsDefault() + { + // Arrange + var sut = new OtelPropagationContext(); + Activity.Current = null; + + // Act + var spanId = sut.SpanId; + + // Assert + spanId.Should().Be(default(SpanId)); + } + + [Fact] + public void SpanId_WithActivityCurrent_ReturnsSpanIdFromActivitySpanId() + { + // Arrange + using var activity = Tracer.StartActivity(); + var sut = new OtelPropagationContext(); + + // Act + var spanId = sut.SpanId; + + // Assert + spanId.Should().NotBe(default(SpanId)); + spanId.Should().Be(activity.SpanId.AsSentrySpanId()); + } + + [Fact] + public void ParentSpanId_NoActivityCurrent_ReturnsNull() + { + // Arrange + var sut = new OtelPropagationContext(); + Activity.Current = null; + + // Act + var parentSpanId = sut.ParentSpanId; + + // Assert + parentSpanId.Should().BeNull(); + } + + [Fact] + public void ParentSpanId_WithActivityCurrent_ReturnsParentSpanIdFromActivity() + { + // Arrange + using var parentActivity = new Activity("parent").Start(); + using var childActivity = new Activity("child").Start(); + var sut = new OtelPropagationContext(); + + // Act + var parentSpanId = sut.ParentSpanId; + + // Assert + parentSpanId.Should().NotBeNull(); + parentSpanId.Should().Be(parentActivity.SpanId.AsSentrySpanId()); + } + + [Fact] + public void DynamicSamplingContext_ByDefault_IsNull() + { + // Arrange & Act + var sut = new OtelPropagationContext(); + + // Assert + sut.DynamicSamplingContext.Should().BeNull(); + } + + [Fact] + public void GetOrCreateDynamicSamplingContext_DynamicSamplingContextIsNull_CreatesDynamicSamplingContext() + { + // Arrange + using var activity = Tracer.StartActivity(); + var sut = new OtelPropagationContext(); + sut.DynamicSamplingContext.Should().BeNull(); + + // Act + var result = sut.GetOrCreateDynamicSamplingContext(_fixture.SentryOptions, _fixture.ActiveReplaySession); + + // Assert + result.Should().NotBeNull(); + sut.DynamicSamplingContext.Should().NotBeNull(); + sut.DynamicSamplingContext.Should().BeSameAs(result); + } + + [Fact] + public void GetOrCreateDynamicSamplingContext_DynamicSamplingContextIsNotNull_ReturnsSameDynamicSamplingContext() + { + // Arrange + using var activity = Tracer.StartActivity(); + var sut = new OtelPropagationContext(); + var firstResult = sut.GetOrCreateDynamicSamplingContext(_fixture.SentryOptions, _fixture.ActiveReplaySession); + + // Act + var secondResult = sut.GetOrCreateDynamicSamplingContext(_fixture.SentryOptions, _fixture.ActiveReplaySession); + + // Assert + firstResult.Should().BeSameAs(secondResult); + sut.DynamicSamplingContext.Should().BeSameAs(firstResult); + } + + [Fact] + public void GetOrCreateDynamicSamplingContext_WithActiveReplaySession_IncludesReplayIdInDynamicSamplingContext() + { + // Arrange + using var activity = Tracer.StartActivity(); + var sut = new OtelPropagationContext(); + + // Act + var result = sut.GetOrCreateDynamicSamplingContext(_fixture.SentryOptions, _fixture.ActiveReplaySession); + + // Assert + result.Should().NotBeNull(); + result.Items.Should().Contain(kvp => kvp.Key == "replay_id" && kvp.Value == _fixture.ActiveReplayId.ToString()); + } +} diff --git a/test/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj b/test/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj new file mode 100644 index 0000000000..4a40648bb8 --- /dev/null +++ b/test/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj @@ -0,0 +1,29 @@ + + + + $(CurrentTfms) + + $(TargetFrameworks);net48 + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/Sentry.OpenTelemetry.Tests/SentryPropagatorTests.cs b/test/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/SentryPropagatorTests.cs similarity index 98% rename from test/Sentry.OpenTelemetry.Tests/SentryPropagatorTests.cs rename to test/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/SentryPropagatorTests.cs index 693619aece..38cd0078c7 100644 --- a/test/Sentry.OpenTelemetry.Tests/SentryPropagatorTests.cs +++ b/test/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/SentryPropagatorTests.cs @@ -2,7 +2,7 @@ using OpenTelemetry; using OpenTelemetry.Context.Propagation; -namespace Sentry.OpenTelemetry.Tests; +namespace Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests; public class SentryPropagatorTests { diff --git a/test/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/TracerProviderBuilderExtensionsTests.cs b/test/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/TracerProviderBuilderExtensionsTests.cs new file mode 100644 index 0000000000..b79bc7bca4 --- /dev/null +++ b/test/Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/TracerProviderBuilderExtensionsTests.cs @@ -0,0 +1,80 @@ +using OpenTelemetry.Exporter; +using OpenTelemetry.Trace; + +namespace Sentry.OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests; + +public class TracerProviderBuilderExtensionsTests +{ + private class Fixture + { + public IServiceProvider ServiceProvider { get; } = Substitute.For(); + public IHub Hub { get; } = Substitute.For(); + + public Fixture() + { + ServiceProvider.GetService(typeof(IHub)).Returns(Hub); + } + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("foo")] + public void AddSentryOltp_InvalidDsn_ThrowsArgumentException(string dsn) + { + // Arrange + var tracerProviderBuilder = Substitute.For(); + + // Act + Action act = () => tracerProviderBuilder.AddSentryOtlp(dsn); + + // Assert + act.Should().Throw() + .WithMessage($"{TracerProviderBuilderExtensions.MissingDsnWarning}*"); + } + + [Fact] + public void OtlpConfigurationCallback_SetsEndpointFromDsn() + { + // Arrange + var dsn = Dsn.Parse("https://examplePublicKey@o0.ingest.sentry.io/123456"); + var options = new OtlpExporterOptions(); + + // Act + TracerProviderBuilderExtensions.OtlpConfigurationCallback(options, dsn); + + // Assert + options.Endpoint.Should().Be(dsn.GetOtlpTracesEndpointUri()); + } + + [Fact] + public void OtlpConfigurationCallback_SetsProtocolToHttpProtobuf() + { + // Arrange + var dsn = Dsn.Parse("https://examplePublicKey@o0.ingest.sentry.io/123456"); + var options = new OtlpExporterOptions(); + + // Act + TracerProviderBuilderExtensions.OtlpConfigurationCallback(options, dsn); + + // Assert + options.Protocol.Should().Be(OtlpExportProtocol.HttpProtobuf); + } + + [Fact] + public void OtlpConfigurationCallback_HttpClientFactoryCreatesClientWithSentryAuthHeader() + { + // Arrange + var dsn = Dsn.Parse("https://examplePublicKey@o0.ingest.sentry.io/123456"); + var options = new OtlpExporterOptions(); + + // Act + TracerProviderBuilderExtensions.OtlpConfigurationCallback(options, dsn); + var client = options.HttpClientFactory!.Invoke(); + + // Assert + client.DefaultRequestHeaders.Should().Contain(h => h.Key == "X-Sentry-Auth"); + var headerValues = client.DefaultRequestHeaders.GetValues("X-Sentry-Auth"); + headerValues.Should().ContainSingle(v => v == $"sentry sentry_key={dsn.PublicKey}"); + } +} diff --git a/test/Sentry.OpenTelemetry.Tests/OpenTelemetryExtensionsTests.cs b/test/Sentry.OpenTelemetry.Tests/OpenTelemetryExtensionsTests.cs index 636a54f9fb..580002204d 100644 --- a/test/Sentry.OpenTelemetry.Tests/OpenTelemetryExtensionsTests.cs +++ b/test/Sentry.OpenTelemetry.Tests/OpenTelemetryExtensionsTests.cs @@ -1,3 +1,5 @@ +using Sentry.Internal.OpenTelemetry; + namespace Sentry.OpenTelemetry.Tests; public class OpenTelemetryExtensionsTests diff --git a/test/Sentry.OpenTelemetry.Tests/OpenTelemetryTransactionProcessorTests.cs b/test/Sentry.OpenTelemetry.Tests/OpenTelemetryTransactionProcessorTests.cs index 714caf1cb7..cffe5eb6c4 100644 --- a/test/Sentry.OpenTelemetry.Tests/OpenTelemetryTransactionProcessorTests.cs +++ b/test/Sentry.OpenTelemetry.Tests/OpenTelemetryTransactionProcessorTests.cs @@ -1,3 +1,5 @@ +using Sentry.Internal.OpenTelemetry; + namespace Sentry.OpenTelemetry.Tests; public class OpenTelemetryTransactionProcessorTests : ActivitySourceTests diff --git a/test/Sentry.OpenTelemetry.Tests/Sentry.OpenTelemetry.Tests.csproj b/test/Sentry.OpenTelemetry.Tests/Sentry.OpenTelemetry.Tests.csproj index 5371e42135..278e9bf103 100644 --- a/test/Sentry.OpenTelemetry.Tests/Sentry.OpenTelemetry.Tests.csproj +++ b/test/Sentry.OpenTelemetry.Tests/Sentry.OpenTelemetry.Tests.csproj @@ -7,8 +7,8 @@ - - + + diff --git a/test/Sentry.OpenTelemetry.Tests/TracerProviderBuilderExtensionsTests.cs b/test/Sentry.OpenTelemetry.Tests/TracerProviderBuilderExtensionsTests.cs index d63f64c985..5ef804c08f 100644 --- a/test/Sentry.OpenTelemetry.Tests/TracerProviderBuilderExtensionsTests.cs +++ b/test/Sentry.OpenTelemetry.Tests/TracerProviderBuilderExtensionsTests.cs @@ -1,3 +1,6 @@ +using OpenTelemetry.Exporter; +using OpenTelemetry.Trace; + namespace Sentry.OpenTelemetry.Tests; public class TracerProviderBuilderExtensionsTests diff --git a/test/Sentry.Testing/SentryOptionsExtensions.cs b/test/Sentry.Testing/SentryOptionsExtensions.cs index 7e18d2b8ab..6fe10016c3 100644 --- a/test/Sentry.Testing/SentryOptionsExtensions.cs +++ b/test/Sentry.Testing/SentryOptionsExtensions.cs @@ -31,6 +31,14 @@ private static SentryOptions ReceivedLog(this SentryOptions substitute, SentryLe return substitute; } + public static SentryOptions AddDiagnosticLoggerSubsititute(this SentryOptions substitute) + { + substitute.Debug = true; + substitute.DiagnosticLogger = Substitute.For(); + substitute.DiagnosticLogger!.IsEnabled(Arg.Any()).Returns(true); + return substitute; + } + public static SentryOptions ReceivedLogDebug(this SentryOptions substitute) => ReceivedLog(substitute, SentryLevel.Debug); diff --git a/test/Sentry.Tests/HubTests.cs b/test/Sentry.Tests/HubTests.cs index c8bb6ea702..34a963c0fe 100644 --- a/test/Sentry.Tests/HubTests.cs +++ b/test/Sentry.Tests/HubTests.cs @@ -1441,6 +1441,26 @@ public void GetBaggage_NoSpanActive_ReturnsBaggageFromPropagationContext() Assert.Contains("sentry-trace_id=43365712692146d08ee11a729dfbcaca", baggage!.ToString()); } + [Fact] + public void GetBaggage_OtelInstrumenter_ReturnsEmptyBaggage() + { + // Arrange + _fixture.Options.Instrumenter = Instrumenter.OpenTelemetry; + _fixture.Options.AddDiagnosticLoggerSubsititute(); + var hub = _fixture.GetSut(); + + // Act + var baggage = hub.GetBaggage(); + + // Assert + baggage.Members.Should().BeEmpty(); + _fixture.Options.DiagnosticLogger.Received(1).Log( + SentryLevel.Warning, + Arg.Is(s => s.Contains("GetBaggage should not be called when using OpenTelemetry.")), + null, + Arg.Any()); + } + [Theory] [InlineData(true)] [InlineData(false)] @@ -1510,8 +1530,8 @@ public void ContinueTrace_ReceivesHeaders_SetsPropagationContextAndReturnsTransa { scope.PropagationContext.TraceId.Should().Be(SentryId.Parse("5bd5f6d346b442dd9177dce9302fd737")); scope.PropagationContext.ParentSpanId.Should().Be(SpanId.Parse("2000000000000000")); - Assert.NotNull(scope.PropagationContext._dynamicSamplingContext); - scope.PropagationContext._dynamicSamplingContext.Items.Should().Contain(baggageHeader.GetSentryMembers()); + Assert.NotNull(scope.PropagationContext.DynamicSamplingContext); + scope.PropagationContext.DynamicSamplingContext.Items.Should().Contain(baggageHeader.GetSentryMembers()); }); transactionContext.TraceId.Should().Be(SentryId.Parse("5bd5f6d346b442dd9177dce9302fd737")); @@ -1531,7 +1551,7 @@ public void ContinueTrace_DoesNotReceiveHeaders_CreatesRootTrace() hub.ScopeManager.ConfigureScope(scope => { Assert.Null(scope.PropagationContext.ParentSpanId); - Assert.Null(scope.PropagationContext._dynamicSamplingContext); + Assert.Null(scope.PropagationContext.DynamicSamplingContext); }); transactionContext.Name.Should().Be("test-name"); @@ -1564,8 +1584,8 @@ public void ContinueTrace_ReceivesHeadersAsStrings_SetsPropagationContextAndRetu { scope.PropagationContext.TraceId.Should().Be(SentryId.Parse("5bd5f6d346b442dd9177dce9302fd737")); scope.PropagationContext.ParentSpanId.Should().Be(SpanId.Parse("2000000000000000")); - Assert.NotNull(scope.PropagationContext._dynamicSamplingContext); - scope.PropagationContext._dynamicSamplingContext.ToBaggageHeader().Members.Should().Contain(BaggageHeader.TryParse(baggageHeader)!.Members); + Assert.NotNull(scope.PropagationContext.DynamicSamplingContext); + scope.PropagationContext.DynamicSamplingContext.ToBaggageHeader().Members.Should().Contain(BaggageHeader.TryParse(baggageHeader)!.Members); }); transactionContext.TraceId.Should().Be(SentryId.Parse("5bd5f6d346b442dd9177dce9302fd737")); @@ -1585,7 +1605,7 @@ public void ContinueTrace_DoesNotReceiveHeadersAsStrings_CreatesRootTrace() hub.ScopeManager.ConfigureScope(scope => { Assert.Null(scope.PropagationContext.ParentSpanId); - Assert.Null(scope.PropagationContext._dynamicSamplingContext); + Assert.Null(scope.PropagationContext.DynamicSamplingContext); }); transactionContext.Name.Should().Be("test-name"); @@ -2550,9 +2570,7 @@ public void CaptureFeedback_ValidEmail_FeedbackRegistered(string email) public void CaptureFeedback_InvalidEmail_FeedbackDropped(string email) { // Arrange - _fixture.Options.Debug = true; - _fixture.Options.DiagnosticLogger = Substitute.For(); - _fixture.Options.DiagnosticLogger!.IsEnabled(Arg.Any()).Returns(true); + _fixture.Options.AddDiagnosticLoggerSubsititute(); var hub = _fixture.GetSut(); var feedback = new SentryFeedback("Test feedback", email); @@ -2628,9 +2646,7 @@ public void Dispose_CleanupThrowsException_ExceptionHandledAndLogged() _fixture.Options.AddIntegration(integration1); _fixture.Options.AddIntegration(integration2); _fixture.Options.AddIntegration(integration3); - _fixture.Options.Debug = true; - _fixture.Options.DiagnosticLogger = Substitute.For(); - _fixture.Options.DiagnosticLogger!.IsEnabled(Arg.Any()).Returns(true); + _fixture.Options.AddDiagnosticLoggerSubsititute(); var hub = _fixture.GetSut(); // Act diff --git a/test/Sentry.Tests/Internals/MemoryMonitorTests.cs b/test/Sentry.Tests/Internals/MemoryMonitorTests.cs index 27f3968a1c..05f74ec2b0 100644 --- a/test/Sentry.Tests/Internals/MemoryMonitorTests.cs +++ b/test/Sentry.Tests/Internals/MemoryMonitorTests.cs @@ -10,12 +10,7 @@ private class Fixture { private IGCImplementation GCImplementation { get; set; } - public SentryOptions Options { get; set; } = new() - { - Dsn = ValidDsn, - Debug = true, - DiagnosticLogger = Substitute.For() - }; + public SentryOptions Options { get; set; } = new() { Dsn = ValidDsn }; public Action OnDumpCollected { get; set; } = _ => { }; @@ -32,7 +27,7 @@ public static IGCImplementation MockGCImplementation() public MemoryMonitor GetSut() { - Options.DiagnosticLogger?.IsEnabled(Arg.Any()).Returns(true); + Options.AddDiagnosticLoggerSubsititute(); return new MemoryMonitor(Options, OnDumpCollected, OnCaptureDump, GCImplementation ?? MockGCImplementation()); } } diff --git a/test/Sentry.Tests/SentryPropagationContextTests.cs b/test/Sentry.Tests/SentryPropagationContextTests.cs index bb90359d0a..fa307db89a 100644 --- a/test/Sentry.Tests/SentryPropagationContextTests.cs +++ b/test/Sentry.Tests/SentryPropagationContextTests.cs @@ -32,12 +32,12 @@ public void CopyConstructor_CreatesCopyWithReplayId(bool replaySessionIsActive) Assert.Equal(original.TraceId, copy.TraceId); Assert.Equal(original.SpanId, copy.SpanId); - Assert.Equal(original._dynamicSamplingContext!.Items.Count, copy._dynamicSamplingContext!.Items.Count); - foreach (var dscItem in original._dynamicSamplingContext!.Items) + Assert.Equal(original.DynamicSamplingContext!.Items.Count, copy.DynamicSamplingContext!.Items.Count); + foreach (var dscItem in original.DynamicSamplingContext!.Items) { if (dscItem.Key == "replay_id") { - copy._dynamicSamplingContext!.Items["replay_id"].Should().Be(replaySessionIsActive + copy.DynamicSamplingContext!.Items["replay_id"].Should().Be(replaySessionIsActive // We overwrite the replay_id when we have an active replay session ? _fixture.ActiveReplayId.ToString() // Otherwise we propagate whatever was in the baggage header @@ -45,7 +45,7 @@ public void CopyConstructor_CreatesCopyWithReplayId(bool replaySessionIsActive) } else { - copy._dynamicSamplingContext!.Items.Should() + copy.DynamicSamplingContext!.Items.Should() .Contain(kvp => kvp.Key == dscItem.Key && kvp.Value == dscItem.Value); } } @@ -59,18 +59,18 @@ public void GetOrCreateDynamicSamplingContext_DynamicSamplingContextIsNull_Creat var options = new SentryOptions { Dsn = ValidDsn }; var propagationContext = new SentryPropagationContext(); - Assert.Null(propagationContext._dynamicSamplingContext); // Sanity check + Assert.Null(propagationContext.DynamicSamplingContext); // Sanity check _ = propagationContext.GetOrCreateDynamicSamplingContext(options, replaySessionIsActive ? _fixture.ActiveReplaySession : _fixture.InactiveReplaySession); - Assert.NotNull(propagationContext._dynamicSamplingContext); + Assert.NotNull(propagationContext.DynamicSamplingContext); if (replaySessionIsActive) { // We add the replay_id automatically when we have an active replay session - Assert.Equal(_fixture.ActiveReplayId.ToString(), Assert.Contains("replay_id", propagationContext._dynamicSamplingContext.Items)); + Assert.Equal(_fixture.ActiveReplayId.ToString(), Assert.Contains("replay_id", propagationContext.DynamicSamplingContext.Items)); } else { - Assert.DoesNotContain("replay_id", propagationContext._dynamicSamplingContext.Items); + Assert.DoesNotContain("replay_id", propagationContext.DynamicSamplingContext.Items); } } @@ -95,7 +95,7 @@ public void CreateFromHeaders_HeadersNull_CreatesPropagationContextWithTraceAndS Assert.NotEqual(propagationContext.TraceId, SentryId.Empty); Assert.NotEqual(propagationContext.SpanId, SpanId.Empty); - Assert.Null(propagationContext._dynamicSamplingContext); + Assert.Null(propagationContext.DynamicSamplingContext); } [Fact] @@ -108,7 +108,7 @@ public void CreateFromHeaders_TraceHeaderNotNull_CreatesPropagationContextFromTr Assert.Equal(traceHeader.TraceId, propagationContext.TraceId); Assert.NotEqual(traceHeader.SpanId, propagationContext.SpanId); // Sanity check Assert.Equal(traceHeader.SpanId, propagationContext.ParentSpanId); - Assert.Null(propagationContext._dynamicSamplingContext); + Assert.Null(propagationContext.DynamicSamplingContext); } [Fact] @@ -124,7 +124,7 @@ public void CreateFromHeaders_BaggageExistsButTraceHeaderNull_CreatesPropagation var propagationContext = SentryPropagationContext.CreateFromHeaders(null, null, baggageHeader, _fixture.InactiveReplaySession); - Assert.Null(propagationContext._dynamicSamplingContext); + Assert.Null(propagationContext.DynamicSamplingContext); } [Theory]