From 01beb42931717d3d0893e443c7ec1afeabf8b0a2 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Mon, 18 Mar 2024 22:18:34 +1300 Subject: [PATCH 1/2] Experimenting with implicit OpenTelemetry initialization --- .../Program.cs | 44 ++++++++--- .../InternalTracerProvider.cs | 76 +++++++++++++++++++ .../SentryOptionsExtensions.cs | 3 + .../TracerProviderBuilderExtensions.cs | 23 +++++- 4 files changed, 134 insertions(+), 12 deletions(-) create mode 100644 src/Sentry.OpenTelemetry/InternalTracerProvider.cs diff --git a/samples/Sentry.Samples.OpenTelemetry.Console/Program.cs b/samples/Sentry.Samples.OpenTelemetry.Console/Program.cs index 3371ec0cde..9438978130 100644 --- a/samples/Sentry.Samples.OpenTelemetry.Console/Program.cs +++ b/samples/Sentry.Samples.OpenTelemetry.Console/Program.cs @@ -6,25 +6,47 @@ */ using OpenTelemetry; -using OpenTelemetry.Resources; using OpenTelemetry.Trace; using Sentry.OpenTelemetry; -var serviceName = "Sentry.Samples.OpenTelemetry.Console"; -var serviceVersion = "1.0.0"; +var activitySource = Sentry.OpenTelemetry.TracerProviderBuilderExtensions.DefaultActivitySource; SentrySdk.Init(options => { // options.Dsn = "... Your DSN ..."; + options.Dsn = "https://eb18e953812b41c3aeb042e666fd3b5c@o447951.ingest.sentry.io/5428537"; options.TracesSampleRate = 1.0; options.UseOpenTelemetry(); // <-- Configure Sentry to use OpenTelemetry trace information }); -using var tracerProvider = Sdk.CreateTracerProviderBuilder() - .AddSource(serviceName) - .ConfigureResource(resource => - resource.AddService( - serviceName: serviceName, - serviceVersion: serviceVersion)) - .AddSentry() // <-- Configure OpenTelemetry to send traces to Sentry - .Build(); +// using var tracerProvider = Sdk.CreateTracerProviderBuilder() +// .AddSentry() // <-- Configure OpenTelemetry to send traces to Sentry +// .Build(); + +Console.WriteLine("Hello World!"); +Question(); +Console.WriteLine("Goodbye cruel world..."); + +void Question() +{ + using var disposable = SentrySdk.PushScope(); + SentrySdk.ConfigureScope(scope => + { + scope.AddBreadcrumb("Asking the question..."); + scope.SetTag("Question", "Meaning of life"); + using var task = activitySource.StartActivity("Question"); + Thread.Sleep(100); // simulate some work + Answer(); + }); +} + +void Answer() +{ + SentrySdk.ConfigureScope(scope => + { + scope.AddBreadcrumb("Giving the answer..."); + scope.SetTag("Answer", "42"); + using var task = activitySource.StartActivity("Answer"); + Thread.Sleep(100); // simulate some work + }); +} diff --git a/src/Sentry.OpenTelemetry/InternalTracerProvider.cs b/src/Sentry.OpenTelemetry/InternalTracerProvider.cs new file mode 100644 index 0000000000..f94fd88422 --- /dev/null +++ b/src/Sentry.OpenTelemetry/InternalTracerProvider.cs @@ -0,0 +1,76 @@ +using OpenTelemetry; +using OpenTelemetry.Trace; +using Sentry.Extensibility; + +namespace Sentry.OpenTelemetry; + +internal static class InternalTracerProvider +{ + internal static IHub? FallbackHub; + private static TracerProvider? FallbackTracerProvider; + private static bool WasInitializedExternally; + + internal static bool InitializedExternally + { + get => WasInitializedExternally; + set + { + if (value) + { + ClearProvider(); + } + + WasInitializedExternally = value; + } + } + + public static void InitializeFallbackTracerProvider(this SentryOptions options) + { + try + { + ClearProvider(); + // This is a dummy/naive example. It works for something like a Console app. Wiring up an ASP.NET Core app + // should be done using the OpenTelemetryBuilder extensions... If we knew OpenTelemetry was being used as + // the trace implementation, we could do something like this in the SentryOptions class, rather than in an + // extension method, and override it in SentryAspNetCoreOptions etc. to be platform specific. + options.PostInitCallbacks.Add(hub => + { + FallbackHub = hub; + FallbackTracerProvider = Sdk.CreateTracerProviderBuilder() + .AddSentryInternal(true) // <-- Configure OpenTelemetry to send traces to Sentry + .Build(); + }); + } + catch (InternalTracerProviderException) + { + options.LogDebug("OpenTelemetry has been initialized externally: skipping auto-initialization"); + } + } + + public static void CancelInitialization() + { + throw new InternalTracerProviderException(); + } + + public static void ClearProvider() + { + FallbackHub = null; + FallbackTracerProvider?.Dispose(); + FallbackTracerProvider = null; + } + + internal class InternalTracerProviderException : Exception + { + public InternalTracerProviderException(string message) : base(message) + { + } + + public InternalTracerProviderException() : base() + { + } + + public InternalTracerProviderException(string? message, Exception? innerException) : base(message, innerException) + { + } + } +} diff --git a/src/Sentry.OpenTelemetry/SentryOptionsExtensions.cs b/src/Sentry.OpenTelemetry/SentryOptionsExtensions.cs index 2035787a19..3614c7726f 100644 --- a/src/Sentry.OpenTelemetry/SentryOptionsExtensions.cs +++ b/src/Sentry.OpenTelemetry/SentryOptionsExtensions.cs @@ -1,5 +1,7 @@ +using OpenTelemetry; using OpenTelemetry.Context.Propagation; using OpenTelemetry.Trace; +using Sentry.Extensibility; namespace Sentry.OpenTelemetry; @@ -53,5 +55,6 @@ public static void UseOpenTelemetry(this SentryOptions options) options.AddTransactionProcessor( new OpenTelemetryTransactionProcessor() ); + options.InitializeFallbackTracerProvider(); } } diff --git a/src/Sentry.OpenTelemetry/TracerProviderBuilderExtensions.cs b/src/Sentry.OpenTelemetry/TracerProviderBuilderExtensions.cs index 96472eda98..8f563f387a 100644 --- a/src/Sentry.OpenTelemetry/TracerProviderBuilderExtensions.cs +++ b/src/Sentry.OpenTelemetry/TracerProviderBuilderExtensions.cs @@ -11,6 +11,11 @@ namespace Sentry.OpenTelemetry; /// public static class TracerProviderBuilderExtensions { + /// + /// The default for spans created by Sentry's instrumentation + /// + public static readonly ActivitySource DefaultActivitySource = new ("Sentry"); + /// /// Ensures OpenTelemetry trace information is sent to Sentry. /// @@ -29,6 +34,22 @@ public static class TracerProviderBuilderExtensions /// The supplied for chaining. public static TracerProviderBuilder AddSentry(this TracerProviderBuilder tracerProviderBuilder, TextMapPropagator? defaultTextMapPropagator = null) { + return tracerProviderBuilder.AddSentryInternal(false, defaultTextMapPropagator); + } + + internal static TracerProviderBuilder AddSentryInternal(this TracerProviderBuilder tracerProviderBuilder, bool autoInitialized = false, TextMapPropagator? defaultTextMapPropagator = null) + { + // Don't automatically initialize if the user has already initialized + if (autoInitialized && InternalTracerProvider.InitializedExternally) + { + InternalTracerProvider.CancelInitialization(); + } + if (!autoInitialized) + { + InternalTracerProvider.InitializedExternally = true; + } + tracerProviderBuilder.AddSource(DefaultActivitySource.Name); + defaultTextMapPropagator ??= new SentryPropagator(); Sdk.SetDefaultTextMapPropagator(defaultTextMapPropagator); return tracerProviderBuilder.AddProcessor(ImplementationFactory); @@ -45,7 +66,7 @@ internal static BaseProcessor ImplementationFactory(IServiceProvider s enrichers.Add(new AspNetCoreEnricher(userFactory)); } - var hub = services.GetService() ?? SentrySdk.CurrentHub; + var hub = services.GetService() ?? InternalTracerProvider.FallbackHub ?? SentrySdk.CurrentHub; if (hub.IsEnabled) { return new SentrySpanProcessor(hub, enrichers); From 20f807a13219859d8688e3f8ea9fe13a236020e4 Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Mon, 18 Mar 2024 09:32:19 +0000 Subject: [PATCH 2/2] Format code --- src/Sentry.OpenTelemetry/TracerProviderBuilderExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Sentry.OpenTelemetry/TracerProviderBuilderExtensions.cs b/src/Sentry.OpenTelemetry/TracerProviderBuilderExtensions.cs index 8f563f387a..5135cf398c 100644 --- a/src/Sentry.OpenTelemetry/TracerProviderBuilderExtensions.cs +++ b/src/Sentry.OpenTelemetry/TracerProviderBuilderExtensions.cs @@ -14,7 +14,7 @@ public static class TracerProviderBuilderExtensions /// /// The default for spans created by Sentry's instrumentation /// - public static readonly ActivitySource DefaultActivitySource = new ("Sentry"); + public static readonly ActivitySource DefaultActivitySource = new("Sentry"); /// /// Ensures OpenTelemetry trace information is sent to Sentry.