From 06b8bb79ed9752e23319b7147e1d9796988941eb Mon Sep 17 00:00:00 2001 From: Artur Gordashnikov Date: Thu, 14 Dec 2023 05:22:37 +0200 Subject: [PATCH] Add Filter to HangfireInstrumentationOptions (#1440) --- .../netstandard2.0/PublicAPI.Unshipped.txt | 2 + .../CHANGELOG.md | 3 + .../HangfireInstrumentationOptions.cs | 21 ++++- ...ngfireInstrumentationJobFilterAttribute.cs | 16 ++++ .../README.md | 77 ++++++++++++++++++- ...eInstrumentationJobFilterAttributeTests.cs | 37 +++++++++ .../ProcessorMock.cs | 28 +++++++ 7 files changed, 182 insertions(+), 2 deletions(-) create mode 100644 test/OpenTelemetry.Instrumentation.Hangfire.Tests/ProcessorMock.cs diff --git a/src/OpenTelemetry.Instrumentation.Hangfire/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Instrumentation.Hangfire/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt index eaa7631d77..7c2225b43f 100644 --- a/src/OpenTelemetry.Instrumentation.Hangfire/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Instrumentation.Hangfire/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -1,6 +1,8 @@ OpenTelemetry.Trace.HangfireInstrumentationOptions OpenTelemetry.Trace.HangfireInstrumentationOptions.DisplayNameFunc.get -> System.Func! OpenTelemetry.Trace.HangfireInstrumentationOptions.DisplayNameFunc.set -> void +OpenTelemetry.Trace.HangfireInstrumentationOptions.Filter.get -> System.Func? +OpenTelemetry.Trace.HangfireInstrumentationOptions.Filter.set -> void OpenTelemetry.Trace.HangfireInstrumentationOptions.HangfireInstrumentationOptions() -> void OpenTelemetry.Trace.HangfireInstrumentationOptions.RecordException.get -> bool OpenTelemetry.Trace.HangfireInstrumentationOptions.RecordException.set -> void diff --git a/src/OpenTelemetry.Instrumentation.Hangfire/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.Hangfire/CHANGELOG.md index 6d24250490..432396546e 100644 --- a/src/OpenTelemetry.Instrumentation.Hangfire/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.Hangfire/CHANGELOG.md @@ -11,6 +11,9 @@ options management ([#1442](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1442)) +* Add Filter to HangfireInstrumentationOptions. + ([#1440](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1440)) + ## 1.5.0-beta.1 Released 2023-Jun-23 diff --git a/src/OpenTelemetry.Instrumentation.Hangfire/HangfireInstrumentationOptions.cs b/src/OpenTelemetry.Instrumentation.Hangfire/HangfireInstrumentationOptions.cs index 447c3bc902..baec4e1dba 100644 --- a/src/OpenTelemetry.Instrumentation.Hangfire/HangfireInstrumentationOptions.cs +++ b/src/OpenTelemetry.Instrumentation.Hangfire/HangfireInstrumentationOptions.cs @@ -16,7 +16,7 @@ public class HangfireInstrumentationOptions /// Gets or sets a value indicating whether the exception will be recorded as ActivityEvent or not. /// /// - /// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/exceptions.md. + /// https://github.com/open-telemetry/semantic-conventions/blob/main/docs/exceptions/exceptions-spans.md. /// public bool RecordException { get; set; } @@ -27,4 +27,23 @@ public class HangfireInstrumentationOptions /// Defaults to {backgroundJob.Job.Type.Name}.{backgroundJob.Job.Method.Name}. /// public Func DisplayNameFunc { get; set; } = HangfireInstrumentation.DefaultDisplayNameFunc; + + /// + /// Gets or sets a filter function that determines whether or not to + /// collect telemetry about the the being executed. + /// + /// + /// Notes: + /// + /// The first parameter passed to the filter function is being executed. + /// The return value for the filter: + /// + /// If filter returns , the command is + /// collected. + /// If filter returns or throws an + /// exception, the command is NOT collected. + /// + /// + /// + public Func? Filter { get; set; } } diff --git a/src/OpenTelemetry.Instrumentation.Hangfire/Implementation/HangfireInstrumentationJobFilterAttribute.cs b/src/OpenTelemetry.Instrumentation.Hangfire/Implementation/HangfireInstrumentationJobFilterAttribute.cs index 03c12fc944..a222ae4bb8 100644 --- a/src/OpenTelemetry.Instrumentation.Hangfire/Implementation/HangfireInstrumentationJobFilterAttribute.cs +++ b/src/OpenTelemetry.Instrumentation.Hangfire/Implementation/HangfireInstrumentationJobFilterAttribute.cs @@ -52,6 +52,22 @@ public void OnPerforming(PerformingContext performingContext) if (activity.IsAllDataRequested) { + try + { + if (this.options.Filter?.Invoke(performingContext.BackgroundJob) == false) + { + activity.IsAllDataRequested = false; + activity.ActivityTraceFlags &= ~ActivityTraceFlags.Recorded; + return; + } + } + catch (Exception) + { + activity.IsAllDataRequested = false; + activity.ActivityTraceFlags &= ~ActivityTraceFlags.Recorded; + return; + } + activity.SetTag(HangfireInstrumentationConstants.JobIdTag, performingContext.BackgroundJob.Id); activity.SetTag(HangfireInstrumentationConstants.JobCreatedAtTag, performingContext.BackgroundJob.CreatedAt.ToString("O")); } diff --git a/src/OpenTelemetry.Instrumentation.Hangfire/README.md b/src/OpenTelemetry.Instrumentation.Hangfire/README.md index 03ebe5ddf1..fec0182421 100644 --- a/src/OpenTelemetry.Instrumentation.Hangfire/README.md +++ b/src/OpenTelemetry.Instrumentation.Hangfire/README.md @@ -42,7 +42,8 @@ public class Program { public static void Main(string[] args) { - using var tracerProvider = Sdk.CreateTracerProviderBuilder() + using var tracerProvider = Sdk + .CreateTracerProviderBuilder() .AddHangfireInstrumentation() .AddConsoleExporter() .Build(); @@ -58,6 +59,22 @@ For an ASP.NET application, adding instrumentation is typically done in the ## Advanced configuration +This instrumentation can be configured to change the default behavior by using +`HangfireInstrumentationOptions`. + +```csharp +using var tracerProvider = Sdk + .CreateTracerProviderBuilder() + .AddHangfireInstrumentation(options => + { + options.DisplayNameFunc = job => $"JOB {job.Id}"; + options.Filter = job => job.Id == "Filter this job"; + options.RecordException = true; + }) + .AddConsoleExporter() + .Build(); +``` + When used with [`OpenTelemetry.Extensions.Hosting`](https://github.com/open-telemetry/opentelemetry-dotnet/blob/main/src/OpenTelemetry.Extensions.Hosting/README.md), all configurations to `HangfireInstrumentationOptions` @@ -79,6 +96,64 @@ services.AddOpenTelemetry() .AddConsoleExporter()); ``` +### RecordException + +Configures a value indicating whether the exception will be recorded as +ActivityEvent or not. See +[Semantic Conventions for Exceptions on Spans](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/exceptions/exceptions-spans.md) + +```csharp +using var tracerProvider = Sdk + .CreateTracerProviderBuilder() + .AddHangfireInstrumentation(options => + { + options.RecordException = true; + }) + .AddConsoleExporter() + .Build(); +``` + +### DisplayNameFunc + +This option allows changing activity display name. + +```C# +using var tracerProvider = Sdk + .CreateTracerProviderBuilder() + .AddHangfireInstrumentation(options => + { + options.DisplayNameFunc = job => $"JOB {job.Id}"; + }) + .AddConsoleExporter() + .Build(); +``` + +If not configured the default is + +```C# +$"JOB {BackgroundJob.Job.Type.Name}.{BackgroundJob.Job.Method.Name}" +``` + +### Filter + +This option can be used to filter out activities based on the `BackgroundJob` +being executed. The `Filter` function should return `true` if the telemetry is +to be collected, and `false` if it should not. + +The following code snippet shows how to use `Filter` to filter out traces for +job with a specified job id. + +```csharp +using var tracerProvider = Sdk + .CreateTracerProviderBuilder() + .AddHangfireInstrumentation(options => + { + options.Filter = job => job.Id == "Filter this job"; + }) + .AddConsoleExporter() + .Build(); +``` + ## References * [OpenTelemetry Project](https://opentelemetry.io/) diff --git a/test/OpenTelemetry.Instrumentation.Hangfire.Tests/HangfireInstrumentationJobFilterAttributeTests.cs b/test/OpenTelemetry.Instrumentation.Hangfire.Tests/HangfireInstrumentationJobFilterAttributeTests.cs index 39a21b8322..08e7d3cc6c 100644 --- a/test/OpenTelemetry.Instrumentation.Hangfire.Tests/HangfireInstrumentationJobFilterAttributeTests.cs +++ b/test/OpenTelemetry.Instrumentation.Hangfire.Tests/HangfireInstrumentationJobFilterAttributeTests.cs @@ -136,6 +136,43 @@ public async Task Should_Create_Activity_With_Custom_DisplayName() Assert.Equal(ActivityKind.Internal, activity.Kind); } + [Theory] + [InlineData("null", true)] + [InlineData("true", true)] + [InlineData("false", false)] + [InlineData("throw", false)] + public async Task Should_Respect_Filter_Option(string filter, bool shouldRecord) + { + // Arrange + Action configure = filter switch + { + "null" => options => options.Filter = null, + "true" => options => options.Filter = _ => true, + "false" => options => options.Filter = _ => false, + "throw" => options => options.Filter = _ => throw new Exception("Filter throws exception"), + _ => throw new ArgumentOutOfRangeException(nameof(filter), filter, "Unexpected value"), + }; + + var processedItems = new List(); + var activityProcessor = new ProcessorMock(onStart: processedItems.Add); + + using var tel = Sdk.CreateTracerProviderBuilder() + .AddHangfireInstrumentation(configure) + .AddProcessor(activityProcessor) + .Build(); + + // Act + var jobId = BackgroundJob.Enqueue(x => x.Execute()); + await this.WaitJobProcessedAsync(jobId, 5); + + // Assert + Assert.Single(processedItems); + var activity = processedItems.First(); + + Assert.Equal(shouldRecord, activity.IsAllDataRequested); + Assert.Equal(shouldRecord, activity.ActivityTraceFlags.HasFlag(ActivityTraceFlags.Recorded)); + } + private async Task WaitJobProcessedAsync(string jobId, int timeToWaitInSeconds) { var timeout = DateTime.Now.AddSeconds(timeToWaitInSeconds); diff --git a/test/OpenTelemetry.Instrumentation.Hangfire.Tests/ProcessorMock.cs b/test/OpenTelemetry.Instrumentation.Hangfire.Tests/ProcessorMock.cs new file mode 100644 index 0000000000..6c556045de --- /dev/null +++ b/test/OpenTelemetry.Instrumentation.Hangfire.Tests/ProcessorMock.cs @@ -0,0 +1,28 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using System; + +namespace OpenTelemetry.Instrumentation.Hangfire.Tests; + +public class ProcessorMock : BaseProcessor +{ + private readonly Action? onStart; + private readonly Action? onEnd; + + public ProcessorMock(Action? onStart = null, Action? onEnd = null) + { + this.onStart = onStart; + this.onEnd = onEnd; + } + + public override void OnStart(T data) + { + this.onStart?.Invoke(data); + } + + public override void OnEnd(T data) + { + this.onEnd?.Invoke(data); + } +}