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",