Skip to content

Commit c7a1681

Browse files
committed
chore(diagnostics): update netcore example with activity correlation
Update NetCoreSample with an OpenTelemetry based listener to enable the Hangfire activity source. Add initial activity creation along with logging of TraceId to show job correlation in log output. Add background job examples, including error examples.
1 parent 8caa078 commit c7a1681

File tree

4 files changed

+187
-12
lines changed

4 files changed

+187
-12
lines changed

NuGet.config

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
<trustedSigners>
1414
<repository name="nuget.org" serviceIndex="https://api.nuget.org/v3/index.json">
15-
<owners>Microsoft;aspnet;dotnetframework;HangfireIO;xunit;jamesnk;kzu;castleproject;psake;ILRepack;davidebbo;StackExchange;Dapper;brady.holt;dwhelan;raboof;damianh;</owners>
15+
<owners>Microsoft;aspnet;dotnetframework;HangfireIO;xunit;jamesnk;kzu;castleproject;psake;ILRepack;davidebbo;StackExchange;Dapper;brady.holt;dwhelan;raboof;damianh;OpenTelemetry;</owners>
1616
<certificate fingerprint="5a2901d6ada3d18260b9c6dfe2133c95d74b9eef6ae0e5dc334c8454d1477df4" hashAlgorithm="SHA256" allowUntrustedRoot="false" />
1717
<certificate fingerprint="0e5f38f57dc1bcc806d8494f4f90fbcedd988b46760709cbeec6f4219aa6157d" hashAlgorithm="SHA256" allowUntrustedRoot="false" />
1818
<certificate fingerprint="1f4b311d9acc115c8dc8018b5a49e00fce6da8e2855f9f014ca6f34570bc482d" hashAlgorithm="SHA256" allowUntrustedRoot="false" />

samples/NetCoreSample/NetCoreSample.csproj

+3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
<ItemGroup>
1717
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
1818
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.1" />
19+
<PackageReference Include="Hangfire.InMemory" Version="1.0.0" />
20+
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.9.0" />
21+
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.9.0" />
1922
</ItemGroup>
2023

2124
</Project>

samples/NetCoreSample/Program.cs

+128-11
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,42 @@
11
using System;
22
using System.Data;
3+
using System.Diagnostics;
34
using System.Threading;
45
using System.Threading.Tasks;
56
using Hangfire;
67
using Hangfire.Annotations;
78
using Hangfire.Client;
89
using Hangfire.Common;
10+
using Hangfire.InMemory;
911
using Hangfire.Server;
1012
using Hangfire.SqlServer;
1113
using Hangfire.States;
14+
using Microsoft.AspNetCore.Mvc.Infrastructure;
15+
using Microsoft.Extensions.Configuration;
1216
using Microsoft.Extensions.DependencyInjection;
1317
using Microsoft.Extensions.DependencyInjection.Extensions;
1418
using Microsoft.Extensions.Hosting;
1519
using Microsoft.Extensions.Logging;
20+
using OpenTelemetry.Trace;
1621

1722
namespace NetCoreSample
1823
{
1924
class Program
2025
{
26+
// To use in-memory store instead of database:
27+
// dotnet run -- --UseInMemory true
28+
29+
// To show trace console exporter output:
30+
// dotnet run -- --TraceConsoleExporter true
31+
32+
public static readonly ActivitySource ActivitySource = new ActivitySource(nameof(NetCoreSample));
33+
2134
static async Task Main(string[] args)
2235
{
23-
var host = new HostBuilder()
24-
.ConfigureLogging(x => x.AddConsole().SetMinimumLevel(LogLevel.Information))
36+
var host = Host.CreateDefaultBuilder(args)
37+
.ConfigureLogging(x => x
38+
.AddSimpleConsole()
39+
.SetMinimumLevel(LogLevel.Information))
2540
.ConfigureServices((hostContext, services) =>
2641
{
2742
services.Configure<HostOptions>(option =>
@@ -49,19 +64,40 @@ static async Task Main(string[] args)
4964
services.TryAddSingleton<IBackgroundJobStateChanger>(x => new CustomBackgroundJobStateChanger(
5065
new BackgroundJobStateChanger(x.GetRequiredService<IJobFilterProvider>())));
5166

52-
services.AddHangfire((provider, configuration) => configuration
67+
var useInMemory = hostContext.Configuration.GetValue<bool>("UseInMemory");
68+
services.AddHangfire((provider, configuration) => {
69+
configuration
5370
.SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
54-
.UseSimpleAssemblyNameTypeSerializer()
55-
.UseSqlServerStorage(
56-
@"Server=.\;Database=Hangfire.Sample;Trusted_Connection=True;",
57-
provider.GetRequiredService<SqlServerStorageOptions>()));
71+
.UseSimpleAssemblyNameTypeSerializer();
72+
if (useInMemory) {
73+
configuration.UseInMemoryStorage();
74+
}
75+
else
76+
{
77+
configuration.UseSqlServerStorage(
78+
@"Server=.\;Database=Hangfire.Sample;Trusted_Connection=True;",
79+
provider.GetRequiredService<SqlServerStorageOptions>());
80+
}
81+
});
5882

5983
services.AddHostedService<RecurringJobsService>();
84+
services.AddHostedService<BackgroundJobsService>();
6085
services.AddHangfireServer(options =>
6186
{
6287
options.StopTimeout = TimeSpan.FromSeconds(15);
6388
options.ShutdownTimeout = TimeSpan.FromSeconds(30);
6489
});
90+
91+
var traceConsoleExporter = hostContext.Configuration.GetValue<bool>("TraceConsoleExporter");
92+
services.AddOpenTelemetry()
93+
.WithTracing(tracing => {
94+
tracing.AddSource(DiagnosticsActivityFilter.DefaultListenerName);
95+
tracing.AddSource(nameof(NetCoreSample));
96+
if (traceConsoleExporter)
97+
{
98+
tracing.AddConsoleExporter();
99+
}
100+
});
65101
})
66102
.Build();
67103

@@ -123,24 +159,39 @@ internal class RecurringJobsService : BackgroundService
123159
{
124160
private readonly IBackgroundJobClient _backgroundJobs;
125161
private readonly IRecurringJobManager _recurringJobs;
126-
private readonly ILogger<RecurringJobScheduler> _logger;
162+
private readonly ILogger<RecurringJobsService> _logger;
163+
private readonly ILoggerFactory _loggerFactory;
127164

128165
public RecurringJobsService(
129166
[NotNull] IBackgroundJobClient backgroundJobs,
130167
[NotNull] IRecurringJobManager recurringJobs,
131-
[NotNull] ILogger<RecurringJobScheduler> logger)
168+
[NotNull] ILogger<RecurringJobsService> logger,
169+
ILoggerFactory loggerFactory)
132170
{
133171
_backgroundJobs = backgroundJobs ?? throw new ArgumentNullException(nameof(backgroundJobs));
134172
_recurringJobs = recurringJobs ?? throw new ArgumentNullException(nameof(recurringJobs));
135173
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
174+
_loggerFactory = loggerFactory;
136175
}
137176

138177
protected override Task ExecuteAsync(CancellationToken stoppingToken)
139178
{
140179
try
141180
{
142-
_recurringJobs.AddOrUpdate("seconds", () => Console.WriteLine("Hello, seconds!"), "*/15 * * * * *");
143-
_recurringJobs.AddOrUpdate("minutely", () => Console.WriteLine("Hello, world!"), Cron.Minutely);
181+
_logger.LogInformation("Creating recurring jobs");
182+
183+
using (var activity = Program.ActivitySource.StartActivity("enqueue seconds"))
184+
{
185+
_logger.LogInformation("Creating job seconds, trace_id={ActivityTraceId}", activity.TraceId);
186+
_recurringJobs.AddOrUpdate("seconds", () => Hello("seconds"), "*/15 * * * * *");
187+
}
188+
189+
using (var activity = Program.ActivitySource.StartActivity("enqueue minutely"))
190+
{
191+
_logger.LogInformation("Creating job minutely (hello world), trace_id={ActivityTraceId}", activity.TraceId);
192+
_recurringJobs.AddOrUpdate("minutely", () => Hello("world"), Cron.Minutely);
193+
}
194+
144195
_recurringJobs.AddOrUpdate("hourly", () => Console.WriteLine("Hello"), "25 15 * * *");
145196
_recurringJobs.AddOrUpdate("neverfires", () => Console.WriteLine("Can only be triggered"), "0 0 31 2 *");
146197

@@ -161,5 +212,71 @@ protected override Task ExecuteAsync(CancellationToken stoppingToken)
161212

162213
return Task.CompletedTask;
163214
}
215+
216+
public void Hello(string name)
217+
{
218+
Console.WriteLine($"Hello, {name}!");
219+
var logger = _loggerFactory.CreateLogger<RecurringJobsService>();
220+
logger.LogInformation("Hello, {Name}! trace_id={ActivityTraceId}", name, Activity.Current?.TraceId);
221+
}
164222
}
223+
224+
internal class BackgroundJobsService : BackgroundService
225+
{
226+
private readonly IBackgroundJobClient _backgroundJobs;
227+
private readonly ILogger _logger;
228+
private readonly ILoggerFactory _loggerFactory;
229+
230+
public BackgroundJobsService(
231+
[NotNull] IBackgroundJobClient backgroundJobs,
232+
[NotNull] ILogger<BackgroundJobsService> logger,
233+
ILoggerFactory loggerFactory)
234+
{
235+
_backgroundJobs = backgroundJobs ?? throw new ArgumentNullException(nameof(backgroundJobs));
236+
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
237+
_loggerFactory = loggerFactory;
238+
}
239+
240+
protected override Task ExecuteAsync(CancellationToken stoppingToken)
241+
{
242+
try
243+
{
244+
_logger.LogInformation("Creating backgriound jobs");
245+
246+
using (var activity = Program.ActivitySource.StartActivity("enqueue"))
247+
{
248+
_logger.LogInformation("Creating job 10, trace_id={ActivityTraceId}", activity.TraceId);
249+
var jobId1 = _backgroundJobs.Enqueue(() => Job(10));
250+
}
251+
using (var activity = Program.ActivitySource.StartActivity("schedule"))
252+
{
253+
_logger.LogInformation("Scheduling job 20, continue with 30, trace_id={ActivityTraceId}", activity.TraceId);
254+
var jobId2 = _backgroundJobs.Schedule(() => Job(20), TimeSpan.FromSeconds(30));
255+
var jobId3 = _backgroundJobs.ContinueJobWith(jobId2, () => Job(30));
256+
}
257+
using (var activity = Program.ActivitySource.StartActivity("error"))
258+
{
259+
_logger.LogInformation("Scheduling error job 40, trace_id={ActivityTraceId}", activity.TraceId);
260+
var jobId4 = _backgroundJobs.Schedule(() => Job(40), TimeSpan.FromSeconds(60));
261+
}
262+
}
263+
catch (Exception e)
264+
{
265+
_logger.LogError(e, "An exception occurred while creating recurring jobs.");
266+
}
267+
268+
return Task.CompletedTask;
269+
}
270+
271+
public void Job(int counter) {
272+
Console.WriteLine("Hello, job {0}!", counter);
273+
var logger = _loggerFactory.CreateLogger<BackgroundJobsService>();
274+
logger.LogInformation("Hello, job {Counter} trace_id={ActivityTraceId}", counter, Activity.Current?.TraceId);
275+
if (counter == 40)
276+
{
277+
throw new InvalidOperationException("Counter 40 is invalid.");
278+
}
279+
}
280+
}
281+
165282
}

samples/NetCoreSample/packages.lock.json

+55
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22
"version": 1,
33
"dependencies": {
44
"net6.0": {
5+
"Hangfire.InMemory": {
6+
"type": "Direct",
7+
"requested": "[1.0.0, )",
8+
"resolved": "1.0.0",
9+
"contentHash": "56H71lfcqn5sN/8Bjj9hOLGTG5HIERLRuMsRJTFpw0Tsq5ck5OUkNvtUw92s7bwD3PRKOo4PkDGqNs9KugaqoQ==",
10+
"dependencies": {
11+
"Hangfire.Core": "1.8.0"
12+
}
13+
},
514
"Microsoft.Extensions.Hosting": {
615
"type": "Direct",
716
"requested": "[8.0.1, )",
@@ -47,6 +56,25 @@
4756
"System.Text.Json": "8.0.5"
4857
}
4958
},
59+
"OpenTelemetry.Exporter.Console": {
60+
"type": "Direct",
61+
"requested": "[1.9.0, )",
62+
"resolved": "1.9.0",
63+
"contentHash": "TbScDLSc6kcji+/wZYIf8/HBV2SnttzN7PNxr3TYczlmGlU4K2ugujp6seSktEO4OaAvKRd7Y3CG3SKNj0C+1Q==",
64+
"dependencies": {
65+
"OpenTelemetry": "1.9.0"
66+
}
67+
},
68+
"OpenTelemetry.Extensions.Hosting": {
69+
"type": "Direct",
70+
"requested": "[1.9.0, )",
71+
"resolved": "1.9.0",
72+
"contentHash": "QBQPrKDVCXxTBE+r8tgjmFNKKHi4sKyczmip2XGUcjy8kk3quUNhttnjiMqC4sU50Hemmn4i5752Co26pnKe3A==",
73+
"dependencies": {
74+
"Microsoft.Extensions.Hosting.Abstractions": "8.0.0",
75+
"OpenTelemetry": "1.9.0"
76+
}
77+
},
5078
"Cronos": {
5179
"type": "Transitive",
5280
"resolved": "0.8.3",
@@ -346,6 +374,33 @@
346374
"resolved": "11.0.1",
347375
"contentHash": "pNN4l+J6LlpIvHOeNdXlwxv39NPJ2B5klz+Rd2UQZIx30Squ5oND1Yy3wEAUoKn0GPUj6Yxt9lxlYWQqfZcvKg=="
348376
},
377+
"OpenTelemetry": {
378+
"type": "Transitive",
379+
"resolved": "1.9.0",
380+
"contentHash": "7scS6BUhwYeSXEDGhCxMSezmvyCoDU5kFQbmfyW9iVvVTcWhec+1KIN33/LOCdBXRkzt2y7+g03mkdAB0XZ9Fw==",
381+
"dependencies": {
382+
"Microsoft.Extensions.Diagnostics.Abstractions": "8.0.0",
383+
"Microsoft.Extensions.Logging.Configuration": "8.0.0",
384+
"OpenTelemetry.Api.ProviderBuilderExtensions": "1.9.0"
385+
}
386+
},
387+
"OpenTelemetry.Api": {
388+
"type": "Transitive",
389+
"resolved": "1.9.0",
390+
"contentHash": "Xz8ZvM1Lm0m7BbtGBnw2JlPo++YKyMp08zMK5p0mf+cIi5jeMt2+QsYu9X6YEAbjCxBQYwEak5Z8sY6Ig2WcwQ==",
391+
"dependencies": {
392+
"System.Diagnostics.DiagnosticSource": "8.0.0"
393+
}
394+
},
395+
"OpenTelemetry.Api.ProviderBuilderExtensions": {
396+
"type": "Transitive",
397+
"resolved": "1.9.0",
398+
"contentHash": "L0D4LBR5JFmwLun5MCWVGapsJLV0ANZ+XXu9NEI3JE/HRKkRuUO+J2MuHD5DBwiU//QMYYM4B22oev1hVLoHDQ==",
399+
"dependencies": {
400+
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
401+
"OpenTelemetry.Api": "1.9.0"
402+
}
403+
},
349404
"StackTraceFormatter.Source": {
350405
"type": "Transitive",
351406
"resolved": "1.1.0",

0 commit comments

Comments
 (0)