diff --git a/src/OpenTelemetry.Instrumentation.Hangfire/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.Hangfire/CHANGELOG.md index 16ee02ba04..f3b6a9ea6a 100644 --- a/src/OpenTelemetry.Instrumentation.Hangfire/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.Hangfire/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +* Fix 'FailedToInjectActivityContext' when no ActivityContext exists. + ([#2990](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/2990)) + ## 1.12.0-beta.1 Released 2025-Jun-23 diff --git a/src/OpenTelemetry.Instrumentation.Hangfire/Implementation/HangfireInstrumentationJobFilterAttribute.cs b/src/OpenTelemetry.Instrumentation.Hangfire/Implementation/HangfireInstrumentationJobFilterAttribute.cs index df367de5d3..13eb0c8988 100644 --- a/src/OpenTelemetry.Instrumentation.Hangfire/Implementation/HangfireInstrumentationJobFilterAttribute.cs +++ b/src/OpenTelemetry.Instrumentation.Hangfire/Implementation/HangfireInstrumentationJobFilterAttribute.cs @@ -100,14 +100,14 @@ public void OnCreating(CreatingContext creatingContext) return; } - ActivityContext contextToInject = default; - if (Activity.Current != null) + var activity = Activity.Current; + if (activity == null) { - contextToInject = Activity.Current.Context; + return; } var activityContextData = new Dictionary(); - Propagators.DefaultTextMapPropagator.Inject(new PropagationContext(contextToInject, Baggage.Current), activityContextData, InjectActivityProperties); + Propagators.DefaultTextMapPropagator.Inject(new PropagationContext(activity.Context, Baggage.Current), activityContextData, InjectActivityProperties); creatingContext.SetJobParameter(HangfireInstrumentationConstants.ActivityContextKey, activityContextData); } diff --git a/test/OpenTelemetry.Instrumentation.Hangfire.Tests/HangfireInstrumentationJobFilterAttributeTests.cs b/test/OpenTelemetry.Instrumentation.Hangfire.Tests/HangfireInstrumentationJobFilterAttributeTests.cs index 84d27be4b3..1fed8d0f0c 100644 --- a/test/OpenTelemetry.Instrumentation.Hangfire.Tests/HangfireInstrumentationJobFilterAttributeTests.cs +++ b/test/OpenTelemetry.Instrumentation.Hangfire.Tests/HangfireInstrumentationJobFilterAttributeTests.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using System.Diagnostics; +using System.Diagnostics.Tracing; using Hangfire; using OpenTelemetry.Trace; using Xunit; @@ -26,6 +27,7 @@ public async Task Should_Create_Activity() using var tel = Sdk.CreateTracerProviderBuilder() .AddHangfireInstrumentation() .AddInMemoryExporter(exportedItems) + .SetSampler() .Build(); // Act @@ -170,6 +172,27 @@ public async Task Should_Respect_Filter_Option(string filter, bool shouldRecord) Assert.Equal(shouldRecord, activity.ActivityTraceFlags.HasFlag(ActivityTraceFlags.Recorded)); } + [Fact] + public async Task Should_Not_Inject_Invalid_Context() + { + // Arrange + var exportedItems = new List(); + using var tel = Sdk.CreateTracerProviderBuilder() + .AddHangfireInstrumentation() + .AddInMemoryExporter(exportedItems) + .SetSampler() + .Build(); + + using var listener = new OpenTelemetryEventListener(); + + // Act + var jobId = BackgroundJob.Enqueue(x => x.Execute()); + await this.WaitJobProcessedAsync(jobId, 5); + + // Assert + Assert.All(listener.Messages, args => Assert.NotEqual("FailedToInjectActivityContext", args.EventName)); + } + private async Task WaitJobProcessedAsync(string jobId, int timeToWaitInSeconds) { var timeout = TimeSpan.FromSeconds(timeToWaitInSeconds); @@ -197,4 +220,59 @@ bool Completed() return !history.All(h => states.Contains(h.StateName)); } } + + private class OpenTelemetryEventListener : EventListener + { + private const string EventSourceName = "OpenTelemetry-Api"; + + private readonly Queue events = new(); + private readonly AutoResetEvent eventWritten = new(false); + private EventSource? apiEventSource; + + public IEnumerable Messages + { + get + { + if (this.events.Count == 0) + { + this.eventWritten.WaitOne(TimeSpan.FromSeconds(3)); + } + + while (this.events.Count != 0) + { + yield return this.events.Dequeue(); + } + } + } + + public override void Dispose() + { + if (this.apiEventSource != null) + { + this.DisableEvents(this.apiEventSource); + } + + base.Dispose(); + } + + protected override void OnEventSourceCreated(EventSource eventSource) + { + if (eventSource.Name == EventSourceName) + { + this.apiEventSource = eventSource; + this.EnableEvents(eventSource, EventLevel.Verbose, EventKeywords.All); + } + + base.OnEventSourceCreated(eventSource); + } + + protected override void OnEventWritten(EventWrittenEventArgs eventData) + { + if (eventData.EventSource.Name == EventSourceName) + { + this.events.Enqueue(eventData); + this.eventWritten.Set(); + } + } + } }