diff --git a/AccessQueuePlayground/AccessQueuePlayground.csproj b/AccessQueuePlayground/AccessQueuePlayground.csproj index 4a7df85..a8e0738 100644 --- a/AccessQueuePlayground/AccessQueuePlayground.csproj +++ b/AccessQueuePlayground/AccessQueuePlayground.csproj @@ -8,6 +8,9 @@ + + + diff --git a/AccessQueuePlayground/Program.cs b/AccessQueuePlayground/Program.cs index 9ad2124..f00ab40 100644 --- a/AccessQueuePlayground/Program.cs +++ b/AccessQueuePlayground/Program.cs @@ -2,9 +2,20 @@ using AccessQueuePlayground.Components; using AccessQueuePlayground.Services; using AccessQueueService.Data; using AccessQueueService.Services; +using Serilog; var builder = WebApplication.CreateBuilder(args); +// Add Serilog configuration for console logging only +builder.Host.UseSerilog((context, services, configuration) => +{ + configuration + .WriteTo.Console() + .ReadFrom.Configuration(context.Configuration) + .ReadFrom.Services(services) + .Enrich.FromLogContext(); +}); + // Add services to the container. builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); diff --git a/AccessQueueService/AccessQueueService.csproj b/AccessQueueService/AccessQueueService.csproj index 5419ef0..9c50d6d 100644 --- a/AccessQueueService/AccessQueueService.csproj +++ b/AccessQueueService/AccessQueueService.csproj @@ -7,6 +7,10 @@ + + + + diff --git a/AccessQueueService/Program.cs b/AccessQueueService/Program.cs index b6ef6a4..3e6dbd5 100644 --- a/AccessQueueService/Program.cs +++ b/AccessQueueService/Program.cs @@ -1,8 +1,18 @@ using AccessQueueService.Data; using AccessQueueService.Services; +using Serilog; var builder = WebApplication.CreateBuilder(args); +// Add Serilog configuration from appsettings and serilog.json +builder.Host.UseSerilog((context, services, configuration) => +{ + configuration + .ReadFrom.Configuration(context.Configuration) + .ReadFrom.Services(services) + .Enrich.FromLogContext(); +}); + builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); diff --git a/AccessQueueService/Services/AccessCleanupBackgroundService.cs b/AccessQueueService/Services/AccessCleanupBackgroundService.cs index 2cb7e93..5a9f470 100644 --- a/AccessQueueService/Services/AccessCleanupBackgroundService.cs +++ b/AccessQueueService/Services/AccessCleanupBackgroundService.cs @@ -1,15 +1,16 @@ - -namespace AccessQueueService.Services +namespace AccessQueueService.Services { public class AccessCleanupBackgroundService : BackgroundService { private readonly IAccessService _accessService; private readonly IConfiguration _config; + private readonly ILogger _logger; - public AccessCleanupBackgroundService(IAccessService accessService, IConfiguration config) + public AccessCleanupBackgroundService(IAccessService accessService, IConfiguration config, ILogger logger) { _accessService = accessService; _config = config; + _logger = logger; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) @@ -17,7 +18,18 @@ namespace AccessQueueService.Services var cleanupIntervalMillis = _config.GetValue("AccessQueue:CleanupIntervalSeconds") * 1000; while (!stoppingToken.IsCancellationRequested) { - await _accessService.DeleteExpiredTickets(); + try + { + var removed = await _accessService.DeleteExpiredTickets(); + if (removed > 0) + { + _logger.LogInformation("Background cleanup removed {Count} expired tickets.", removed); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Exception occurred during background cleanup."); + } await Task.Delay(cleanupIntervalMillis, stoppingToken); } } diff --git a/AccessQueueService/Services/AccessService.cs b/AccessQueueService/Services/AccessService.cs index d0ca150..65d0f9a 100644 --- a/AccessQueueService/Services/AccessService.cs +++ b/AccessQueueService/Services/AccessService.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using AccessQueueService.Data; using AccessQueueService.Models; +using Microsoft.Extensions.Logging; namespace AccessQueueService.Services { @@ -8,16 +9,18 @@ namespace AccessQueueService.Services { private readonly IConfiguration _configuration; private readonly IAccessQueueRepo _accessQueueRepo; + private readonly ILogger _logger; private readonly SemaphoreSlim _queueLock = new(1, 1); private readonly int EXP_SECONDS; private readonly int ACT_SECONDS; private readonly int CAPACITY_LIMIT; private readonly bool ROLLING_EXPIRATION; - public AccessService(IConfiguration configuration, IAccessQueueRepo accessQueueRepo) + public AccessService(IConfiguration configuration, IAccessQueueRepo accessQueueRepo, ILogger logger) { _configuration = configuration; _accessQueueRepo = accessQueueRepo; + _logger = logger; EXP_SECONDS = _configuration.GetValue("AccessQueue:ExpirationSeconds"); ACT_SECONDS = _configuration.GetValue("AccessQueue:ActivitySeconds"); CAPACITY_LIMIT = _configuration.GetValue("AccessQueue:CapacityLimit"); @@ -47,6 +50,7 @@ namespace AccessQueueService.Services ExpiresOn = expiresOn, LastActive = DateTime.UtcNow }); + _logger.LogInformation("User {UserId} already has access. Expires on {ExpiresOn}.", userId, expiresOn); return new AccessResponse { ExpiresOn = expiresOn @@ -62,6 +66,7 @@ namespace AccessQueueService.Services LastActive = DateTime.UtcNow }; _accessQueueRepo.UpsertTicket(accessTicket); + _logger.LogInformation("User {UserId} granted access. Expires on {ExpiresOn}.", userId, accessTicket.ExpiresOn); return new AccessResponse { ExpiresOn = accessTicket.ExpiresOn, @@ -80,6 +85,7 @@ namespace AccessQueueService.Services LastActive = DateTime.UtcNow, ExpiresOn = DateTime.MaxValue, }); + _logger.LogInformation("User {UserId} added to queue. Requests ahead: {RequestsAhead}.", userId, requestsAhead); } return new AccessResponse { @@ -88,6 +94,11 @@ namespace AccessQueueService.Services }; } } + catch (Exception ex) + { + _logger.LogError(ex, "Exception occurred while processing access request for user {UserId}.", userId); + throw; + } finally { _queueLock.Release(); @@ -99,7 +110,17 @@ namespace AccessQueueService.Services await _queueLock.WaitAsync(); try { - return _accessQueueRepo.RemoveUser(userId); + var removed = _accessQueueRepo.RemoveUser(userId); + if (removed) + { + _logger.LogInformation("User {UserId} access revoked.", userId); + } + return removed; + } + catch (Exception ex) + { + _logger.LogError(ex, "Exception occurred while revoking access for user {UserId}.", userId); + throw; } finally { @@ -112,7 +133,17 @@ namespace AccessQueueService.Services await _queueLock.WaitAsync(); try { - return _accessQueueRepo.DeleteExpiredTickets(); + var removed = _accessQueueRepo.DeleteExpiredTickets(); + if (removed > 0) + { + _logger.LogInformation("Cleaned up {Count} expired tickets.", removed); + } + return removed; + } + catch (Exception ex) + { + _logger.LogError(ex, "Exception occurred during expired ticket cleanup."); + throw; } finally { diff --git a/AccessQueueService/appsettings.json b/AccessQueueService/appsettings.json index 7a7cdb3..42f1ca9 100644 --- a/AccessQueueService/appsettings.json +++ b/AccessQueueService/appsettings.json @@ -1,16 +1,34 @@ { - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "AccessQueue": { - "CapacityLimit": 100, // Maximum number of active users - "ActivitySeconds": 900, // Time since last access before a user is considered inactive - "ExpirationSeconds": 43200, // 12 hours - Time before a user access is revoked - "RollingExpiration": true, // Whether to extend expiration time on access - "CleanupIntervalSeconds": 60 // Interval for cleaning up expired users + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "Serilog": { + "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ], + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "System": "Warning" + } }, + "WriteTo": [ + { "Name": "Console" }, + { "Name": "File", "Args": { "path": "Logs/log-.txt", "rollingInterval": "Day", "outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}" } } + ], + "Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ], + "Properties": { + "Application": "AccessQueueService" + } + }, + "AccessQueue": { + "CapacityLimit": 100, + "ActivitySeconds": 900, + "ExpirationSeconds": 43200, + "RollingExpiration": true, + "CleanupIntervalSeconds": 60 + }, "AllowedHosts": "*" } diff --git a/AccessQueueService/serilog.json b/AccessQueueService/serilog.json new file mode 100644 index 0000000..58f3655 --- /dev/null +++ b/AccessQueueService/serilog.json @@ -0,0 +1,20 @@ +{ + "Serilog": { + "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ], + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "System": "Warning" + } + }, + "WriteTo": [ + { "Name": "Console" }, + { "Name": "File", "Args": { "path": "Logs/log-.txt", "rollingInterval": "Day", "outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}" } } + ], + "Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ], + "Properties": { + "Application": "AccessQueueService" + } + } +} diff --git a/AccessQueueServiceTests/AccessServiceTests.cs b/AccessQueueServiceTests/AccessServiceTests.cs index 97764b5..c44df63 100644 --- a/AccessQueueServiceTests/AccessServiceTests.cs +++ b/AccessQueueServiceTests/AccessServiceTests.cs @@ -3,6 +3,7 @@ namespace AccessQueueServiceTests using global::AccessQueueService.Data; using global::AccessQueueService.Services; using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Threading.Tasks; @@ -31,8 +32,9 @@ namespace AccessQueueServiceTests .AddInMemoryCollection(inMemorySettings) .Build(); var accessQueueRepo = new TakeANumberAccessQueueRepo(); - - _accessService = new AccessService(configuration, accessQueueRepo); + var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole()); + var logger = loggerFactory.CreateLogger(); + _accessService = new AccessService(configuration, accessQueueRepo, logger); } [Fact]