From cdd14908014932b61d63a572f7ffbab87c3ba45c Mon Sep 17 00:00:00 2001 From: Mia Winter Date: Tue, 9 Apr 2024 17:11:05 +0200 Subject: [PATCH] Added Serilog and extended logging, support for grafana loki (use `Loki` config key to set loki url) --- Wave/Program.cs | 61 +++++++++++++++++++++------ Wave/Wave.csproj | 2 + Wave/appsettings.Development.json | 7 --- Wave/appsettings.json | 7 --- Wave/log | 8 ++++ docker-compose.dcproj | 1 + docker-compose.yml | 42 ++++++++++++------ provisioning/datasources/auomatic.yml | 17 ++++++++ 8 files changed, 106 insertions(+), 39 deletions(-) create mode 100644 Wave/log create mode 100644 provisioning/datasources/auomatic.yml diff --git a/Wave/Program.cs b/Wave/Program.cs index ece5707..9f7520c 100644 --- a/Wave/Program.cs +++ b/Wave/Program.cs @@ -19,15 +19,27 @@ using Wave.Services; using Wave.Utilities; using Microsoft.IdentityModel.Protocols.OpenIdConnect; +using Serilog; +using Serilog.Events; +using Serilog.Sinks.Grafana.Loki; + +#region Version Information string humanReadableVersion = Assembly.GetEntryAssembly()? .GetCustomAttribute()? .InformationalVersion.Split("+", 2)[0] ?? "unknown"; -Console.WriteLine(@"Starting Wave " + humanReadableVersion); -var logMessages = new List(); +#endregion var builder = WebApplication.CreateBuilder(args); +Log.Logger = new LoggerConfiguration() + .Enrich.FromLogContext() + .WriteTo.Console() + .CreateBootstrapLogger(); +var logger = Log.Logger.ForContext(); +logger.Information("Starting Wave {WaveVersion}", humanReadableVersion); +builder.Services.AddCascadingValue("Version", _ => humanReadableVersion); + builder.Configuration .AddJsonFile(Path.Combine(FileSystemService.ConfigurationDirectory, "config.json"), true, false) .AddYamlFile(Path.Combine(FileSystemService.ConfigurationDirectory, "config.yml"), true, false) @@ -35,7 +47,28 @@ .AddIniFile( Path.Combine(FileSystemService.ConfigurationDirectory, "config.ini"), true, false) .AddXmlFile( Path.Combine(FileSystemService.ConfigurationDirectory, "config.xml"), true, false) .AddEnvironmentVariables("WAVE_"); -builder.Services.AddCascadingValue("Version", _ => humanReadableVersion); + +#region Logging + +builder.Services.AddSerilog((services, configuration) => { + configuration + .MinimumLevel.Verbose() + .ReadFrom.Services(services) + .Enrich.WithProperty("Application", "Wave") + .Enrich.WithProperty("WaveVersion", humanReadableVersion) + .Enrich.WithProperty("AppName", + builder.Configuration.GetSection(nameof(Customization))[nameof(Customization.AppName)]) + .Enrich.FromLogContext(); + if (builder.Configuration["loki"] is {} lokiConfiguration) { + configuration.WriteTo.GrafanaLoki(lokiConfiguration, null, [ + "Application", "WaveVersion", "AppName", "level", "SourceContext", "RequestId", "RequestPath" + ], restrictedToMinimumLevel:LogEventLevel.Debug); + } else { + configuration.WriteTo.Console(restrictedToMinimumLevel:LogEventLevel.Information); + } +}); + +#endregion builder.Services.AddRazorComponents().AddInteractiveServerComponents(); builder.Services.AddControllers(options => { @@ -63,12 +96,12 @@ }); } else { builder.Services.AddDataProtection() - .UseCryptographicAlgorithms(new AuthenticatedEncryptorConfiguration() { + .UseCryptographicAlgorithms(new AuthenticatedEncryptorConfiguration { EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC, ValidationAlgorithm = ValidationAlgorithm.HMACSHA256 }); builder.Services.AddDistributedMemoryCache(); - logMessages.Add("No Redis connection string found."); + logger.Warning("No Redis connection string found, running in-memory."); } #endregion @@ -197,7 +230,7 @@ builder.Services.AddScoped, IdentityEmailSender>(); } else { builder.Services.AddSingleton, IdentityNoOpEmailSender>(); - logMessages.Add("No 'live' email provider configured."); + logger.Warning("No 'live' email provider configured."); } if (emailConfig.Smtp.Keys.Any(k => k.Equals("bulk", StringComparison.CurrentCultureIgnoreCase))) { @@ -211,14 +244,17 @@ } else { builder.Services.AddSingleton(); builder.Services.AddSingleton, IdentityNoOpEmailSender>(); - logMessages.Add("No email provider configured."); + logger.Warning("No email provider configured."); } builder.Services.AddSingleton(); builder.Services.AddSingleton(); + #endregion +#region Localization + var customization = builder.Configuration.GetSection(nameof(Customization)).Get(); string[] cultures = ["en-US", "en-GB", "de-DE"]; @@ -232,6 +268,8 @@ .AddSupportedUICultures(cultures); }); +#endregion + var app = builder.Build(); // Configure the HTTP request pipeline. @@ -241,6 +279,7 @@ app.UseExceptionHandler("/Error", createScopeForErrors: true); } +app.UseSerilogRequestLogging(); app.UseStaticFiles(new StaticFileOptions { ContentTypeProvider = new FileExtensionContentTypeProvider { @@ -263,10 +302,6 @@ app.UseRequestLocalization(); -foreach (string message in logMessages) { - app.Logger.LogInformation("{message}", message); -} - { using var scope = app.Services.CreateScope(); await using var context = scope.ServiceProvider.GetRequiredService(); @@ -276,13 +311,13 @@ if (userManager.GetUsersInRoleAsync("Admin").Result.Any() is false) { var cache = app.Services.GetRequiredService(); - // Check first wheter the password exists already + // Check first whether the password exists already string? admin = await cache.GetStringAsync("admin_promote_key"); // If it does not exist, create a new one and save it to redis if (string.IsNullOrWhiteSpace(admin)){ admin = Guid.NewGuid().ToString("N")[..16]; - await cache.SetAsync("admin_promote_key", Encoding.UTF8.GetBytes(admin), new DistributedCacheEntryOptions{}); + await cache.SetAsync("admin_promote_key", Encoding.UTF8.GetBytes(admin), new DistributedCacheEntryOptions()); } app.Logger.LogWarning("There is currently no user in your installation with the admin role, " + diff --git a/Wave/Wave.csproj b/Wave/Wave.csproj index 7fdf8c8..80dcbb3 100644 --- a/Wave/Wave.csproj +++ b/Wave/Wave.csproj @@ -32,6 +32,8 @@ + + diff --git a/Wave/appsettings.Development.json b/Wave/appsettings.Development.json index 11181d8..7a73a41 100644 --- a/Wave/appsettings.Development.json +++ b/Wave/appsettings.Development.json @@ -1,9 +1,2 @@ { - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning", - "Microsoft.EntityFrameworkCore": "Information" - } - } } \ No newline at end of file diff --git a/Wave/appsettings.json b/Wave/appsettings.json index 7c03dd1..9b555b4 100644 --- a/Wave/appsettings.json +++ b/Wave/appsettings.json @@ -1,10 +1,3 @@ { - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning", - "Microsoft.EntityFrameworkCore": "Warning" - } - }, "AllowedHosts": "*" } \ No newline at end of file diff --git a/Wave/log b/Wave/log new file mode 100644 index 0000000..22932c7 --- /dev/null +++ b/Wave/log @@ -0,0 +1,8 @@ +2024-04-09 15:23:03.138 +02:00 [INF] Starting Wave 1.0.0 +2024-04-09 15:25:01.782 +02:00 [INF] Starting Wave 1.0.0 +2024-04-09 15:30:33.291 +02:00 [INF] Starting Wave 1.0.0 +2024-04-09 15:33:55.672 +02:00 [INF] Starting Wave 1.0.0 +2024-04-09 15:40:30.616 +02:00 [INF] Starting Wave 1.0.0 +2024-04-09 15:44:58.653 +02:00 [INF] Starting Wave 1.0.0 +2024-04-09 15:52:25.338 +02:00 [INF] Starting Wave 1.0.0 +2024-04-09 15:57:09.656 +02:00 [INF] Starting Wave 1.0.0 diff --git a/docker-compose.dcproj b/docker-compose.dcproj index 3dc5ea5..875fa3e 100644 --- a/docker-compose.dcproj +++ b/docker-compose.dcproj @@ -15,5 +15,6 @@ + \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 2b6069f..ba73bd0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,13 +16,15 @@ services: - "TZ=Europe/Berlin" - "WAVE_ConnectionStrings__DefaultConnection=Host=db; Username=wave; Password=development; Include Error Detail=true" - "WAVE_ConnectionStrings__Redis=redis,password=development" + - "WAVE_Loki=http://loki:3100" volumes: - wave-files:/app/files - wave-config:/configuration - networks: - - wave depends_on: - - database + database: + condition: service_started + loki: + condition: service_healthy database: image: postgres:16.1-alpine restart: unless-stopped @@ -32,16 +34,36 @@ services: - "POSTGRES_PASSWORD=development" volumes: - wave-db:/var/lib/postgresql/data - networks: - - wave redis: image: redis:7-alpine restart: unless-stopped command: redis-server --requirepass development --save 60 1 --loglevel warning volumes: - wave-redis:/data - networks: - - wave + + loki: + image: grafana/loki:master + ports: + - 3100:3100 + healthcheck: + test: wget -q --tries=1 -O- http://localhost:3100/ready + interval: 3s + timeout: 3s + retries: 10 + start_period: 10s + grafana: + image: grafana/grafana:master + ports: + - 3000:3000 + volumes: + - ./provisioning:/etc/grafana/provisioning + environment: + - GF_AUTH_ANONYMOUS_ENABLED=true + - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin + - GF_AUTH_DISABLE_LOGIN_FORM=true + depends_on: + loki: + condition: service_healthy mailhog: image: mailhog/mailhog:latest @@ -49,13 +71,9 @@ services: ports: - 8080:8025 profiles: ["smtp-debug"] - networks: - - wave volumes: wave-files: wave-config: wave-db: - wave-redis: -networks: - wave: \ No newline at end of file + wave-redis: \ No newline at end of file diff --git a/provisioning/datasources/auomatic.yml b/provisioning/datasources/auomatic.yml new file mode 100644 index 0000000..e2704d5 --- /dev/null +++ b/provisioning/datasources/auomatic.yml @@ -0,0 +1,17 @@ +apiVersion: 1 + +deleteDatasources: + - name: Wave + orgId: 1 + +datasources: + - name: Wave + type: loki + isDefault: true + orgId: 1 + access: proxy + url: http://loki:3100 + editable: true + jsonData: + timeout: 60 + maxLines: 1000