diff --git a/NuGet.config b/NuGet.config index 282d0d55f..27572e8da 100644 --- a/NuGet.config +++ b/NuGet.config @@ -12,7 +12,7 @@ - Microsoft;aspnet;dotnetframework;HangfireIO;xunit;jamesnk;kzu;castleproject;psake;ILRepack;davidebbo;StackExchange;Dapper;brady.holt;dwhelan;raboof;damianh; + Microsoft;aspnet;dotnetframework;HangfireIO;xunit;jamesnk;kzu;castleproject;psake;ILRepack;davidebbo;StackExchange;Dapper;brady.holt;dwhelan;raboof;damianh;OpenTelemetry; diff --git a/samples/NetCoreSample/NetCoreSample.csproj b/samples/NetCoreSample/NetCoreSample.csproj index 596115eb3..81f72e30a 100644 --- a/samples/NetCoreSample/NetCoreSample.csproj +++ b/samples/NetCoreSample/NetCoreSample.csproj @@ -16,6 +16,9 @@ + + + diff --git a/samples/NetCoreSample/Program.cs b/samples/NetCoreSample/Program.cs index 170afb4c0..2330057a4 100644 --- a/samples/NetCoreSample/Program.cs +++ b/samples/NetCoreSample/Program.cs @@ -1,27 +1,42 @@ using System; using System.Data; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Hangfire; using Hangfire.Annotations; using Hangfire.Client; using Hangfire.Common; +using Hangfire.InMemory; using Hangfire.Server; using Hangfire.SqlServer; using Hangfire.States; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using OpenTelemetry.Trace; namespace NetCoreSample { class Program { + // To use in-memory store instead of database: + // dotnet run -- --UseInMemory true + + // To show trace console exporter output: + // dotnet run -- --TraceConsoleExporter true + + public static readonly ActivitySource ActivitySource = new ActivitySource(nameof(NetCoreSample)); + static async Task Main(string[] args) { - var host = new HostBuilder() - .ConfigureLogging(x => x.AddConsole().SetMinimumLevel(LogLevel.Information)) + var host = Host.CreateDefaultBuilder(args) + .ConfigureLogging(x => x + .AddSimpleConsole() + .SetMinimumLevel(LogLevel.Information)) .ConfigureServices((hostContext, services) => { services.Configure(option => @@ -49,19 +64,40 @@ static async Task Main(string[] args) services.TryAddSingleton(x => new CustomBackgroundJobStateChanger( new BackgroundJobStateChanger(x.GetRequiredService()))); - services.AddHangfire((provider, configuration) => configuration + var useInMemory = hostContext.Configuration.GetValue("UseInMemory"); + services.AddHangfire((provider, configuration) => { + configuration .SetDataCompatibilityLevel(CompatibilityLevel.Version_170) - .UseSimpleAssemblyNameTypeSerializer() - .UseSqlServerStorage( - @"Server=.\;Database=Hangfire.Sample;Trusted_Connection=True;", - provider.GetRequiredService())); + .UseSimpleAssemblyNameTypeSerializer(); + if (useInMemory) { + configuration.UseInMemoryStorage(); + } + else + { + configuration.UseSqlServerStorage( + @"Server=.\;Database=Hangfire.Sample;Trusted_Connection=True;", + provider.GetRequiredService()); + } + }); services.AddHostedService(); + services.AddHostedService(); services.AddHangfireServer(options => { options.StopTimeout = TimeSpan.FromSeconds(15); options.ShutdownTimeout = TimeSpan.FromSeconds(30); }); + + var traceConsoleExporter = hostContext.Configuration.GetValue("TraceConsoleExporter"); + services.AddOpenTelemetry() + .WithTracing(tracing => { + tracing.AddSource(DiagnosticsActivityFilter.DefaultListenerName); + tracing.AddSource(nameof(NetCoreSample)); + if (traceConsoleExporter) + { + tracing.AddConsoleExporter(); + } + }); }) .Build(); @@ -123,24 +159,39 @@ internal class RecurringJobsService : BackgroundService { private readonly IBackgroundJobClient _backgroundJobs; private readonly IRecurringJobManager _recurringJobs; - private readonly ILogger _logger; + private readonly ILogger _logger; + private readonly ILoggerFactory _loggerFactory; public RecurringJobsService( [NotNull] IBackgroundJobClient backgroundJobs, [NotNull] IRecurringJobManager recurringJobs, - [NotNull] ILogger logger) + [NotNull] ILogger logger, + ILoggerFactory loggerFactory) { _backgroundJobs = backgroundJobs ?? throw new ArgumentNullException(nameof(backgroundJobs)); _recurringJobs = recurringJobs ?? throw new ArgumentNullException(nameof(recurringJobs)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _loggerFactory = loggerFactory; } protected override Task ExecuteAsync(CancellationToken stoppingToken) { try { - _recurringJobs.AddOrUpdate("seconds", () => Console.WriteLine("Hello, seconds!"), "*/15 * * * * *"); - _recurringJobs.AddOrUpdate("minutely", () => Console.WriteLine("Hello, world!"), Cron.Minutely); + _logger.LogInformation("Creating recurring jobs"); + + using (var activity = Program.ActivitySource.StartActivity("enqueue seconds")) + { + _logger.LogInformation("Creating job seconds, trace_id={ActivityTraceId}", activity.TraceId); + _recurringJobs.AddOrUpdate("seconds", () => Hello("seconds"), "*/15 * * * * *"); + } + + using (var activity = Program.ActivitySource.StartActivity("enqueue minutely")) + { + _logger.LogInformation("Creating job minutely (hello world), trace_id={ActivityTraceId}", activity.TraceId); + _recurringJobs.AddOrUpdate("minutely", () => Hello("world"), Cron.Minutely); + } + _recurringJobs.AddOrUpdate("hourly", () => Console.WriteLine("Hello"), "25 15 * * *"); _recurringJobs.AddOrUpdate("neverfires", () => Console.WriteLine("Can only be triggered"), "0 0 31 2 *"); @@ -161,5 +212,71 @@ protected override Task ExecuteAsync(CancellationToken stoppingToken) return Task.CompletedTask; } + + public void Hello(string name) + { + Console.WriteLine($"Hello, {name}!"); + var logger = _loggerFactory.CreateLogger(); + logger.LogInformation("Hello, {Name}! trace_id={ActivityTraceId}", name, Activity.Current?.TraceId); + } } + + internal class BackgroundJobsService : BackgroundService + { + private readonly IBackgroundJobClient _backgroundJobs; + private readonly ILogger _logger; + private readonly ILoggerFactory _loggerFactory; + + public BackgroundJobsService( + [NotNull] IBackgroundJobClient backgroundJobs, + [NotNull] ILogger logger, + ILoggerFactory loggerFactory) + { + _backgroundJobs = backgroundJobs ?? throw new ArgumentNullException(nameof(backgroundJobs)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _loggerFactory = loggerFactory; + } + + protected override Task ExecuteAsync(CancellationToken stoppingToken) + { + try + { + _logger.LogInformation("Creating backgriound jobs"); + + using (var activity = Program.ActivitySource.StartActivity("enqueue")) + { + _logger.LogInformation("Creating job 10, trace_id={ActivityTraceId}", activity.TraceId); + var jobId1 = _backgroundJobs.Enqueue(() => Job(10)); + } + using (var activity = Program.ActivitySource.StartActivity("schedule")) + { + _logger.LogInformation("Scheduling job 20, continue with 30, trace_id={ActivityTraceId}", activity.TraceId); + var jobId2 = _backgroundJobs.Schedule(() => Job(20), TimeSpan.FromSeconds(30)); + var jobId3 = _backgroundJobs.ContinueJobWith(jobId2, () => Job(30)); + } + using (var activity = Program.ActivitySource.StartActivity("error")) + { + _logger.LogInformation("Scheduling error job 40, trace_id={ActivityTraceId}", activity.TraceId); + var jobId4 = _backgroundJobs.Schedule(() => Job(40), TimeSpan.FromSeconds(60)); + } + } + catch (Exception e) + { + _logger.LogError(e, "An exception occurred while creating recurring jobs."); + } + + return Task.CompletedTask; + } + + public void Job(int counter) { + Console.WriteLine("Hello, job {0}!", counter); + var logger = _loggerFactory.CreateLogger(); + logger.LogInformation("Hello, job {Counter} trace_id={ActivityTraceId}", counter, Activity.Current?.TraceId); + if (counter == 40) + { + throw new InvalidOperationException("Counter 40 is invalid."); + } + } + } + } diff --git a/samples/NetCoreSample/packages.lock.json b/samples/NetCoreSample/packages.lock.json index ecdf6adb2..708a1d9e6 100644 --- a/samples/NetCoreSample/packages.lock.json +++ b/samples/NetCoreSample/packages.lock.json @@ -2,6 +2,15 @@ "version": 1, "dependencies": { "net6.0": { + "Hangfire.InMemory": { + "type": "Direct", + "requested": "[1.0.0, )", + "resolved": "1.0.0", + "contentHash": "56H71lfcqn5sN/8Bjj9hOLGTG5HIERLRuMsRJTFpw0Tsq5ck5OUkNvtUw92s7bwD3PRKOo4PkDGqNs9KugaqoQ==", + "dependencies": { + "Hangfire.Core": "1.8.0" + } + }, "Microsoft.Extensions.Hosting": { "type": "Direct", "requested": "[8.0.1, )", @@ -47,6 +56,25 @@ "System.Text.Json": "8.0.5" } }, + "OpenTelemetry.Exporter.Console": { + "type": "Direct", + "requested": "[1.9.0, )", + "resolved": "1.9.0", + "contentHash": "TbScDLSc6kcji+/wZYIf8/HBV2SnttzN7PNxr3TYczlmGlU4K2ugujp6seSktEO4OaAvKRd7Y3CG3SKNj0C+1Q==", + "dependencies": { + "OpenTelemetry": "1.9.0" + } + }, + "OpenTelemetry.Extensions.Hosting": { + "type": "Direct", + "requested": "[1.9.0, )", + "resolved": "1.9.0", + "contentHash": "QBQPrKDVCXxTBE+r8tgjmFNKKHi4sKyczmip2XGUcjy8kk3quUNhttnjiMqC4sU50Hemmn4i5752Co26pnKe3A==", + "dependencies": { + "Microsoft.Extensions.Hosting.Abstractions": "8.0.0", + "OpenTelemetry": "1.9.0" + } + }, "Cronos": { "type": "Transitive", "resolved": "0.8.3", @@ -346,6 +374,33 @@ "resolved": "11.0.1", "contentHash": "pNN4l+J6LlpIvHOeNdXlwxv39NPJ2B5klz+Rd2UQZIx30Squ5oND1Yy3wEAUoKn0GPUj6Yxt9lxlYWQqfZcvKg==" }, + "OpenTelemetry": { + "type": "Transitive", + "resolved": "1.9.0", + "contentHash": "7scS6BUhwYeSXEDGhCxMSezmvyCoDU5kFQbmfyW9iVvVTcWhec+1KIN33/LOCdBXRkzt2y7+g03mkdAB0XZ9Fw==", + "dependencies": { + "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.0", + "Microsoft.Extensions.Logging.Configuration": "8.0.0", + "OpenTelemetry.Api.ProviderBuilderExtensions": "1.9.0" + } + }, + "OpenTelemetry.Api": { + "type": "Transitive", + "resolved": "1.9.0", + "contentHash": "Xz8ZvM1Lm0m7BbtGBnw2JlPo++YKyMp08zMK5p0mf+cIi5jeMt2+QsYu9X6YEAbjCxBQYwEak5Z8sY6Ig2WcwQ==", + "dependencies": { + "System.Diagnostics.DiagnosticSource": "8.0.0" + } + }, + "OpenTelemetry.Api.ProviderBuilderExtensions": { + "type": "Transitive", + "resolved": "1.9.0", + "contentHash": "L0D4LBR5JFmwLun5MCWVGapsJLV0ANZ+XXu9NEI3JE/HRKkRuUO+J2MuHD5DBwiU//QMYYM4B22oev1hVLoHDQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "OpenTelemetry.Api": "1.9.0" + } + }, "StackTraceFormatter.Source": { "type": "Transitive", "resolved": "1.1.0",