Skip to content

Commit d43ee2c

Browse files
committed
Configure metrics in HangfireMetricsInstrumentationOptions class, add DisplayNameFunc option
1 parent 08198ac commit d43ee2c

14 files changed

+178
-56
lines changed

src/OpenTelemetry.Instrumentation.Hangfire/.publicApi/PublicAPI.Unshipped.txt

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,16 @@ OpenTelemetry.Trace.HangfireInstrumentationOptions.Filter.set -> void
77
OpenTelemetry.Trace.HangfireInstrumentationOptions.HangfireInstrumentationOptions() -> void
88
OpenTelemetry.Trace.HangfireInstrumentationOptions.RecordException.get -> bool
99
OpenTelemetry.Trace.HangfireInstrumentationOptions.RecordException.set -> void
10-
OpenTelemetry.Trace.HangfireInstrumentationOptions.RecordQueueLatency.get -> bool
11-
OpenTelemetry.Trace.HangfireInstrumentationOptions.RecordQueueLatency.set -> void
1210
OpenTelemetry.Trace.TracerProviderBuilderExtensions
1311
static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddHangfireInstrumentation(this OpenTelemetry.Metrics.MeterProviderBuilder! builder) -> OpenTelemetry.Metrics.MeterProviderBuilder!
14-
static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddHangfireInstrumentation(this OpenTelemetry.Metrics.MeterProviderBuilder! builder, string? name, System.Action<OpenTelemetry.Trace.HangfireInstrumentationOptions!>? configure) -> OpenTelemetry.Metrics.MeterProviderBuilder!
15-
static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddHangfireInstrumentation(this OpenTelemetry.Metrics.MeterProviderBuilder! builder, System.Action<OpenTelemetry.Trace.HangfireInstrumentationOptions!>? configure) -> OpenTelemetry.Metrics.MeterProviderBuilder!
12+
static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddHangfireInstrumentation(this OpenTelemetry.Metrics.MeterProviderBuilder! builder, string? name, System.Action<OpenTelemetry.Metrics.HangfireMetricsInstrumentationOptions!>? configure) -> OpenTelemetry.Metrics.MeterProviderBuilder!
13+
static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddHangfireInstrumentation(this OpenTelemetry.Metrics.MeterProviderBuilder! builder, System.Action<OpenTelemetry.Metrics.HangfireMetricsInstrumentationOptions!>? configure) -> OpenTelemetry.Metrics.MeterProviderBuilder!
14+
OpenTelemetry.Metrics.HangfireMetricsInstrumentationOptions
15+
OpenTelemetry.Metrics.HangfireMetricsInstrumentationOptions.DisplayNameFunc.get -> System.Func<Hangfire.BackgroundJob!, string!>!
16+
OpenTelemetry.Metrics.HangfireMetricsInstrumentationOptions.DisplayNameFunc.set -> void
17+
OpenTelemetry.Metrics.HangfireMetricsInstrumentationOptions.HangfireMetricsInstrumentationOptions() -> void
18+
OpenTelemetry.Metrics.HangfireMetricsInstrumentationOptions.RecordQueueLatency.get -> bool
19+
OpenTelemetry.Metrics.HangfireMetricsInstrumentationOptions.RecordQueueLatency.set -> void
1620
static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddHangfireInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! builder) -> OpenTelemetry.Trace.TracerProviderBuilder!
1721
static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddHangfireInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! builder, string? name, System.Action<OpenTelemetry.Trace.HangfireInstrumentationOptions!>? configure) -> OpenTelemetry.Trace.TracerProviderBuilder!
1822
static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddHangfireInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! builder, System.Action<OpenTelemetry.Trace.HangfireInstrumentationOptions!>? configure) -> OpenTelemetry.Trace.TracerProviderBuilder!

src/OpenTelemetry.Instrumentation.Hangfire/HangfireInstrumentationOptions.cs

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,4 @@ public class HangfireInstrumentationOptions
4545
/// </list>
4646
/// </remarks>
4747
public Func<BackgroundJob, bool>? Filter { get; set; }
48-
49-
/// <summary>
50-
/// Gets or sets a value indicating whether to record the pending state duration in metrics.
51-
/// </summary>
52-
/// <remarks>
53-
/// When enabled, records workflow.execution.duration with state="pending", representing
54-
/// the time jobs spend waiting in the queue before execution starts.
55-
/// This requires an additional database call per job execution to retrieve the enqueue timestamp.
56-
/// Default is <see langword="false"/> to avoid performance impact in high-throughput scenarios.
57-
/// When disabled, only execution duration (state="executing") is recorded.
58-
/// </remarks>
59-
public bool RecordQueueLatency { get; set; }
6048
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
using Hangfire;
5+
6+
namespace OpenTelemetry.Metrics;
7+
8+
/// <summary>
9+
/// Options for Hangfire metrics instrumentation.
10+
/// </summary>
11+
public sealed class HangfireMetricsInstrumentationOptions
12+
{
13+
/// <summary>
14+
/// Gets or sets a value indicating whether to record the pending state duration in metrics.
15+
/// </summary>
16+
/// <remarks>
17+
/// When enabled, records workflow.execution.duration with state="pending", representing
18+
/// the time jobs spend waiting in the queue before execution starts.
19+
/// This requires an additional database call per job execution to retrieve the enqueue timestamp.
20+
/// Default is <see langword="false"/> to avoid performance impact in high-throughput scenarios.
21+
/// When disabled, only execution duration (state="executing") is recorded.
22+
/// </remarks>
23+
public bool RecordQueueLatency { get; set; }
24+
25+
/// <summary>
26+
/// Gets or sets a delegate used to format the job name for metrics.
27+
/// </summary>
28+
/// <remarks>
29+
/// Defaults to <c>backgroundJob.Job.ToString()</c>.
30+
/// </remarks>
31+
public Func<BackgroundJob, string> DisplayNameFunc { get; set; } =
32+
backgroundJob => backgroundJob.Job?.ToString() ?? "unknown";
33+
}

src/OpenTelemetry.Instrumentation.Hangfire/Implementation/HangfireMetricsErrorFilterAttribute.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using Hangfire.Common;
55
using Hangfire.Server;
6+
using OpenTelemetry.Metrics;
67

78
namespace OpenTelemetry.Instrumentation.Hangfire.Implementation;
89

@@ -12,6 +13,15 @@ namespace OpenTelemetry.Instrumentation.Hangfire.Implementation;
1213
/// </summary>
1314
internal sealed class HangfireMetricsErrorFilterAttribute : JobFilterAttribute, IServerFilter
1415
{
16+
private readonly HangfireMetricsInstrumentationOptions options;
17+
18+
#pragma warning disable CA1019 // Define accessors for attribute arguments
19+
public HangfireMetricsErrorFilterAttribute(HangfireMetricsInstrumentationOptions options)
20+
#pragma warning restore CA1019 // Define accessors for attribute arguments
21+
{
22+
this.options = options ?? throw new ArgumentNullException(nameof(options));
23+
}
24+
1525
public void OnPerforming(PerformingContext performingContext)
1626
{
1727
}
@@ -21,7 +31,8 @@ public void OnPerformed(PerformedContext performedContext)
2131
if (performedContext.Exception != null)
2232
{
2333
var errorTags = HangfireTagBuilder.BuildErrorTags(
24-
performedContext.BackgroundJob.Job,
34+
performedContext.BackgroundJob,
35+
this.options.DisplayNameFunc,
2536
performedContext.Exception);
2637

2738
HangfireMetrics.ExecutionErrors.Add(1, errorTags);

src/OpenTelemetry.Instrumentation.Hangfire/Implementation/HangfireMetricsInstrumentation.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
using Hangfire;
5-
using OpenTelemetry.Trace;
5+
using OpenTelemetry.Metrics;
66

77
namespace OpenTelemetry.Instrumentation.Hangfire.Implementation;
88

@@ -13,16 +13,16 @@ internal sealed class HangfireMetricsInstrumentation : IDisposable
1313
{
1414
private readonly List<object> filters = new();
1515

16-
public HangfireMetricsInstrumentation(HangfireInstrumentationOptions options)
16+
public HangfireMetricsInstrumentation(HangfireMetricsInstrumentationOptions options)
1717
{
18-
this.AddFilter(new HangfireMetricsJobFilterAttribute());
19-
this.AddFilter(new HangfireMetricsStateFilter());
20-
this.AddFilter(new HangfireMetricsErrorFilterAttribute());
18+
this.AddFilter(new HangfireMetricsJobFilterAttribute(options));
19+
this.AddFilter(new HangfireMetricsStateFilter(options));
20+
this.AddFilter(new HangfireMetricsErrorFilterAttribute(options));
2121

2222
// Only register pending duration filter if enabled (requires DB call per job)
2323
if (options.RecordQueueLatency)
2424
{
25-
this.AddFilter(new HangfirePendingDurationFilterAttribute());
25+
this.AddFilter(new HangfirePendingDurationFilterAttribute(options));
2626
}
2727
}
2828

src/OpenTelemetry.Instrumentation.Hangfire/Implementation/HangfireMetricsJobFilterAttribute.cs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Diagnostics;
55
using Hangfire.Common;
66
using Hangfire.Server;
7+
using OpenTelemetry.Metrics;
78

89
namespace OpenTelemetry.Instrumentation.Hangfire.Implementation;
910

@@ -13,6 +14,14 @@ namespace OpenTelemetry.Instrumentation.Hangfire.Implementation;
1314
internal sealed class HangfireMetricsJobFilterAttribute : JobFilterAttribute, IServerFilter
1415
{
1516
private const string StopwatchKey = "OpenTelemetry.Metrics.Stopwatch";
17+
private readonly HangfireMetricsInstrumentationOptions options;
18+
19+
#pragma warning disable CA1019 // Define accessors for attribute arguments
20+
public HangfireMetricsJobFilterAttribute(HangfireMetricsInstrumentationOptions options)
21+
#pragma warning restore CA1019 // Define accessors for attribute arguments
22+
{
23+
this.options = options ?? throw new ArgumentNullException(nameof(options));
24+
}
1625

1726
public void OnPerforming(PerformingContext performingContext)
1827
{
@@ -34,9 +43,13 @@ public void OnPerformed(PerformedContext performedContext)
3443
// If we can't get the recurring job ID, treat it as a non-recurring job
3544
}
3645

46+
var backgroundJob = performedContext.BackgroundJob;
47+
var displayNameFunc = this.options.DisplayNameFunc;
48+
3749
// Record execution count (without state attribute per semantic conventions)
3850
var countTags = HangfireTagBuilder.BuildExecutionCountTags(
39-
performedContext.BackgroundJob.Job,
51+
backgroundJob,
52+
displayNameFunc,
4053
performedContext.Exception);
4154

4255
HangfireMetrics.ExecutionCount.Add(1, countTags);
@@ -48,7 +61,8 @@ public void OnPerformed(PerformedContext performedContext)
4861
var duration = stopwatch.Elapsed.TotalSeconds;
4962

5063
var durationTags = HangfireTagBuilder.BuildExecutionTags(
51-
performedContext.BackgroundJob.Job,
64+
backgroundJob,
65+
displayNameFunc,
5266
performedContext.Exception,
5367
workflowState: HangfireTagBuilder.StateExecuting);
5468

@@ -57,7 +71,8 @@ public void OnPerformed(PerformedContext performedContext)
5771

5872
// Record workflow-level metrics (includes trigger type)
5973
var workflowTags = HangfireTagBuilder.BuildWorkflowTags(
60-
performedContext.BackgroundJob.Job,
74+
backgroundJob,
75+
displayNameFunc,
6176
performedContext.Exception,
6277
recurringJobId);
6378

src/OpenTelemetry.Instrumentation.Hangfire/Implementation/HangfireMetricsStateFilter.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using Hangfire.Common;
55
using Hangfire.States;
66
using Hangfire.Storage;
7+
using OpenTelemetry.Metrics;
78

89
namespace OpenTelemetry.Instrumentation.Hangfire.Implementation;
910

@@ -12,6 +13,15 @@ namespace OpenTelemetry.Instrumentation.Hangfire.Implementation;
1213
/// </summary>
1314
internal sealed class HangfireMetricsStateFilter : JobFilterAttribute, IApplyStateFilter
1415
{
16+
private readonly HangfireMetricsInstrumentationOptions options;
17+
18+
#pragma warning disable CA1019 // Define accessors for attribute arguments
19+
public HangfireMetricsStateFilter(HangfireMetricsInstrumentationOptions options)
20+
#pragma warning restore CA1019 // Define accessors for attribute arguments
21+
{
22+
this.options = options ?? throw new ArgumentNullException(nameof(options));
23+
}
24+
1525
public void OnStateApplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
1626
{
1727
var workflowState = HangfireTagBuilder.MapWorkflowState(context.NewState.Name);
@@ -22,7 +32,8 @@ public void OnStateApplied(ApplyStateContext context, IWriteOnlyTransaction tran
2232

2333
var errorType = GetErrorTypeFromNewState(context.NewState);
2434
var tags = HangfireTagBuilder.BuildStateTags(
25-
context.BackgroundJob.Job,
35+
context.BackgroundJob,
36+
this.options.DisplayNameFunc,
2637
workflowState,
2738
errorType);
2839

@@ -39,7 +50,8 @@ public void OnStateUnapplied(ApplyStateContext context, IWriteOnlyTransaction tr
3950

4051
var errorType = GetErrorTypeFromOldState(context);
4152
var tags = HangfireTagBuilder.BuildStateTags(
42-
context.BackgroundJob.Job,
53+
context.BackgroundJob,
54+
this.options.DisplayNameFunc,
4355
workflowState,
4456
errorType);
4557

src/OpenTelemetry.Instrumentation.Hangfire/Implementation/HangfirePendingDurationFilterAttribute.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using Hangfire.Common;
55
using Hangfire.Server;
66
using Hangfire.States;
7+
using OpenTelemetry.Metrics;
78
using DateTime = System.DateTime;
89

910
namespace OpenTelemetry.Instrumentation.Hangfire.Implementation;
@@ -19,6 +20,14 @@ namespace OpenTelemetry.Instrumentation.Hangfire.Implementation;
1920
internal sealed class HangfirePendingDurationFilterAttribute : JobFilterAttribute, IServerFilter, IElectStateFilter
2021
{
2122
private const string EnqueuedAtParameter = "OpenTelemetry.EnqueuedAt";
23+
private readonly HangfireMetricsInstrumentationOptions options;
24+
25+
#pragma warning disable CA1019 // Define accessors for attribute arguments
26+
public HangfirePendingDurationFilterAttribute(HangfireMetricsInstrumentationOptions options)
27+
#pragma warning restore CA1019 // Define accessors for attribute arguments
28+
{
29+
this.options = options ?? throw new ArgumentNullException(nameof(options));
30+
}
2231

2332
public void OnStateElection(ElectStateContext context)
2433
{
@@ -57,7 +66,8 @@ public void OnPerforming(PerformingContext performingContext)
5766

5867
// Record workflow.execution.duration with state="pending"
5968
var tags = HangfireTagBuilder.BuildExecutionTags(
60-
performingContext.BackgroundJob.Job,
69+
performingContext.BackgroundJob,
70+
this.options.DisplayNameFunc,
6171
exception: null,
6272
workflowState: HangfireTagBuilder.StatePending);
6373
HangfireMetrics.ExecutionDuration.Record(pendingDuration, tags);

src/OpenTelemetry.Instrumentation.Hangfire/Implementation/HangfireTagBuilder.cs

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
using System.Diagnostics;
5-
using Hangfire.Common;
5+
using Hangfire;
66
using Hangfire.Server;
77
using Hangfire.States;
88

@@ -48,11 +48,11 @@ internal static class HangfireTagBuilder
4848
/// </summary>
4949
/// <param name="job">The Hangfire job.</param>
5050
/// <returns>Tag list with common job tags.</returns>
51-
public static TagList BuildCommonTags(Job job)
51+
public static TagList BuildCommonTags(BackgroundJob backgroundJob, Func<BackgroundJob, string> displayNameFunc)
5252
{
5353
var tags = new TagList
5454
{
55-
GetTaskName(job),
55+
GetTaskName(backgroundJob, displayNameFunc),
5656
GetPlatformName(),
5757
};
5858
return tags;
@@ -68,11 +68,11 @@ public static TagList BuildCommonTags(Job job)
6868
/// <param name="workflowState">The workflow state value.</param>
6969
/// <param name="errorType">Optional error type to annotate failure states.</param>
7070
/// <returns>Tag list suitable for workflow.execution.status metric.</returns>
71-
public static TagList BuildStateTags(Job job, string workflowState, string? errorType)
71+
public static TagList BuildStateTags(BackgroundJob backgroundJob, Func<BackgroundJob, string> displayNameFunc, string workflowState, string? errorType)
7272
{
7373
var tags = new TagList
7474
{
75-
GetTaskName(job),
75+
GetTaskName(backgroundJob, displayNameFunc),
7676
GetPlatformName(),
7777
GetState(workflowState),
7878
};
@@ -125,11 +125,11 @@ public static TagList BuildStateTags(Job job, string workflowState, string? erro
125125
/// <param name="exception">The exception, if any occurred.</param>
126126
/// <param name="workflowState">The workflow state value (typically "executing" for execution duration).</param>
127127
/// <returns>Tag list with execution result tags.</returns>
128-
public static TagList BuildExecutionTags(Job job, Exception? exception, string workflowState)
128+
public static TagList BuildExecutionTags(BackgroundJob backgroundJob, Func<BackgroundJob, string> displayNameFunc, Exception? exception, string workflowState)
129129
{
130130
var tags = new TagList
131131
{
132-
GetTaskName(job),
132+
GetTaskName(backgroundJob, displayNameFunc),
133133
GetPlatformName(),
134134
GetExecutionOutcome(exception),
135135
GetState(workflowState),
@@ -154,11 +154,11 @@ public static TagList BuildExecutionTags(Job job, Exception? exception, string w
154154
/// <param name="job">The Hangfire job.</param>
155155
/// <param name="exception">The exception, if any occurred.</param>
156156
/// <returns>Tag list suitable for workflow.execution.count metric.</returns>
157-
public static TagList BuildExecutionCountTags(Job job, Exception? exception)
157+
public static TagList BuildExecutionCountTags(BackgroundJob backgroundJob, Func<BackgroundJob, string> displayNameFunc, Exception? exception)
158158
{
159159
var tags = new TagList
160160
{
161-
GetTaskName(job),
161+
GetTaskName(backgroundJob, displayNameFunc),
162162
GetPlatformName(),
163163
GetExecutionOutcome(exception),
164164
};
@@ -183,11 +183,11 @@ public static TagList BuildExecutionCountTags(Job job, Exception? exception)
183183
/// <param name="exception">The exception, if any occurred.</param>
184184
/// <param name="recurringJobId">Optional recurring job ID if this job was triggered by a recurring job.</param>
185185
/// <returns>Tag list suitable for workflow.count metric.</returns>
186-
public static TagList BuildWorkflowTags(Job job, Exception? exception, string? recurringJobId)
186+
public static TagList BuildWorkflowTags(BackgroundJob job, Func<BackgroundJob, string> displayNameFunc, Exception? exception, string? recurringJobId)
187187
{
188188
var tags = new TagList
189189
{
190-
GetDefinitionName(job),
190+
GetDefinitionName(job, displayNameFunc),
191191
GetPlatformName(),
192192
GetWorkflowOutcome(exception),
193193
GetTriggerType(recurringJobId),
@@ -210,24 +210,25 @@ public static TagList BuildWorkflowTags(Job job, Exception? exception, string? r
210210
/// <param name="job">The Hangfire job.</param>
211211
/// <param name="exception">The exception that occurred.</param>
212212
/// <returns>Tag list with error tags.</returns>
213-
public static TagList BuildErrorTags(Job job, Exception exception)
213+
public static TagList BuildErrorTags(BackgroundJob backgroundJob, Func<BackgroundJob, string> displayNameFunc, Exception exception)
214214
{
215215
var tags = new TagList
216216
{
217217
GetErrorType(exception),
218-
GetTaskName(job),
218+
GetTaskName(backgroundJob, displayNameFunc),
219219
GetPlatformName(),
220220
};
221221

222222
return tags;
223223
}
224224

225225
// Required workflow attributes
226-
private static KeyValuePair<string, object?> GetTaskName(Job job) =>
227-
new(TagWorkflowTaskName, job.ToString() ?? "unknown");
226+
private static KeyValuePair<string, object?> GetTaskName(BackgroundJob backgroundJob, Func<BackgroundJob, string> displayNameFunc) =>
227+
new(TagWorkflowTaskName, displayNameFunc(backgroundJob));
228+
229+
private static KeyValuePair<string, object?> GetDefinitionName(BackgroundJob backgroundJob, Func<BackgroundJob, string> displayNameFunc) =>
230+
new(TagWorkflowDefinitionName, displayNameFunc(backgroundJob));
228231

229-
private static KeyValuePair<string, object?> GetDefinitionName(Job job) =>
230-
new(TagWorkflowDefinitionName, job.ToString() ?? "unknown");
231232

232233
private static KeyValuePair<string, object?> GetExecutionOutcome(Exception? exception) =>
233234
new(TagWorkflowExecutionOutcome, exception is null ? OutcomeSuccess : OutcomeFailure);

0 commit comments

Comments
 (0)