diff --git a/ECMJobRunner.WebCron/ECMJobRunner.WebCron.csproj b/ECMJobRunner.WebCron/ECMJobRunner.WebCron.csproj index 271364e..71515d1 100644 --- a/ECMJobRunner.WebCron/ECMJobRunner.WebCron.csproj +++ b/ECMJobRunner.WebCron/ECMJobRunner.WebCron.csproj @@ -13,6 +13,9 @@ + + + diff --git a/ECMJobRunner.WebCron/ProfileManager.cs b/ECMJobRunner.WebCron/ProfileManager.cs index 62661d8..2524de5 100644 --- a/ECMJobRunner.WebCron/ProfileManager.cs +++ b/ECMJobRunner.WebCron/ProfileManager.cs @@ -14,49 +14,57 @@ namespace ECMJobRunner.WebCron protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - - while (!stoppingToken.IsCancellationRequested) + try { - if (Logger.IsEnabled(LogLevel.Information)) + + while (!stoppingToken.IsCancellationRequested) { - Logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now); + if (Logger.IsEnabled(LogLevel.Information)) + { + Logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now); + } + + // Create a scope to resolve scoped services (ISQLExecutor used by MediatR pipeline) + using var scope = ScopeFactory.CreateScope(); + var mediator = scope.ServiceProvider.GetRequiredService(); + + var profiles = await mediator.Send(new GetProfileQuery() + { + Active = true, + IncludeSqlJobs = true + }, stoppingToken); + + foreach (var profile in profiles) + { + if (Profiles.TryGetValue(profile.JobId(), out var currentProfile) + && currentProfile.Schedule == profile.Schedule) + continue; + + // Add or update recurring job using MediatR command + JobManager.AddOrUpdate( + profile.JobId(), + mediator => mediator.Send(profile.ToJob(), CancellationToken.None), + profile.Schedule, + new RecurringJobOptions + { + TimeZone = TimeZoneInfo.Local + } + ); + + // Store/update in local cache + Profiles[profile.JobId()] = profile; + + Logger.LogInformation("Job {JobId} registered with schedule: {Schedule}", + profile.JobId(), profile.Schedule); + } + + await Task.Delay(1000, stoppingToken); } - // Create a scope to resolve scoped services (ISQLExecutor used by MediatR pipeline) - using var scope = ScopeFactory.CreateScope(); - var mediator = scope.ServiceProvider.GetRequiredService(); - - var profiles = await mediator.Send(new GetProfileQuery() - { - Active = true, - IncludeSqlJobs = true - }, stoppingToken); - - foreach (var profile in profiles) - { - if (Profiles.TryGetValue(profile.JobId(), out var currentProfile) - && currentProfile.Schedule == profile.Schedule) - continue; - - // Add or update recurring job using MediatR command - JobManager.AddOrUpdate( - profile.JobId(), - mediator => mediator.Send(profile.ToJob(), CancellationToken.None), - profile.Schedule, - new RecurringJobOptions - { - TimeZone = TimeZoneInfo.Local - } - ); - - // Store/update in local cache - Profiles[profile.JobId()] = profile; - - Logger.LogInformation("Job {JobId} registered with schedule: {Schedule}", - profile.JobId(), profile.Schedule); - } - - await Task.Delay(1000, stoppingToken); + } + catch (Exception ex) + { + Logger.LogError(ex, "An error occurred in ProfileManager."); } } } diff --git a/ECMJobRunner.WebCron/Program.cs b/ECMJobRunner.WebCron/Program.cs index 6e9490b..fedb01b 100644 --- a/ECMJobRunner.WebCron/Program.cs +++ b/ECMJobRunner.WebCron/Program.cs @@ -4,14 +4,30 @@ using ECMJobRunner.WebCron; using Hangfire; using Hangfire.SqlServer; using Microsoft.Data.SqlClient; +using Serilog; -var builder = WebApplication.CreateBuilder(args); +// Configure Serilog from appsettings.json +Log.Logger = new LoggerConfiguration() + .ReadFrom.Configuration(new ConfigurationBuilder() + .AddJsonFile("appsettings.json") + .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true) + .Build()) + .CreateLogger(); -// Configure Windows Service hosting if enabled -if (builder.Configuration.GetValue("HostingOptions:UseWindowsService")) +try { - builder.Host.UseWindowsService(); -} + Log.Information("Starting ECMJobRunner.WebCron application"); + + var builder = WebApplication.CreateBuilder(args); + + // Use Serilog for logging + builder.Host.UseSerilog(); + + // Configure Windows Service hosting if enabled + if (builder.Configuration.GetValue("HostingOptions:UseWindowsService")) + { + builder.Host.UseWindowsService(); + } // Add services to the container. builder.Services.AddControllers(); @@ -84,3 +100,12 @@ app.UseHangfireDashboard("/hangfire", new DashboardOptions app.MapControllers(); app.Run(); +} +catch (Exception ex) +{ + Log.Fatal(ex, "Application terminated unexpectedly"); +} +finally +{ + Log.CloseAndFlush(); +} diff --git a/ECMJobRunner.WebCron/appsettings.Development.json b/ECMJobRunner.WebCron/appsettings.Development.json index 0c208ae..1db0dc9 100644 --- a/ECMJobRunner.WebCron/appsettings.Development.json +++ b/ECMJobRunner.WebCron/appsettings.Development.json @@ -1,8 +1,22 @@ { - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } + "Serilog": { + "Using": [ "Serilog.Sinks.Console" ], + "MinimumLevel": { + "Default": "Debug", + "Override": { + "Microsoft.AspNetCore": "Warning", + "Microsoft.EntityFrameworkCore": "Warning", + "Hangfire": "Information" + } + }, + "WriteTo": [ + { + "Name": "Console", + "Args": { + "outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}" + } + } + ], + "Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ] } } diff --git a/ECMJobRunner.WebCron/appsettings.json b/ECMJobRunner.WebCron/appsettings.json index 97662c5..320dc21 100644 --- a/ECMJobRunner.WebCron/appsettings.json +++ b/ECMJobRunner.WebCron/appsettings.json @@ -1,4 +1,27 @@ { + "Serilog": { + "Using": [ "Serilog.Sinks.File" ], + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft.AspNetCore": "Warning", + "Microsoft.EntityFrameworkCore": "Warning", + "Hangfire": "Information" + } + }, + "WriteTo": [ + { + "Name": "File", + "Args": { + "path": "Logs/log-.txt", + "rollingInterval": "Day", + "outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] {Message:lj}{NewLine}{Exception}", + "retainedFileCountLimit": 30 + } + } + ], + "Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ] + }, "Logging": { "LogLevel": { "Default": "Information",