Added Serilog and extended logging, support for grafana loki (use Loki config key to set loki url)

This commit is contained in:
Mia Rose Winter 2024-04-09 17:11:05 +02:00
parent 61e31ec2e1
commit cdd1490801
Signed by: miawinter
GPG key ID: 4B6F6A83178F595E
8 changed files with 106 additions and 39 deletions

View file

@ -19,15 +19,27 @@
using Wave.Services; using Wave.Services;
using Wave.Utilities; using Wave.Utilities;
using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Serilog;
using Serilog.Events;
using Serilog.Sinks.Grafana.Loki;
#region Version Information
string humanReadableVersion = Assembly.GetEntryAssembly()? string humanReadableVersion = Assembly.GetEntryAssembly()?
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()? .GetCustomAttribute<AssemblyInformationalVersionAttribute>()?
.InformationalVersion.Split("+", 2)[0] ?? "unknown"; .InformationalVersion.Split("+", 2)[0] ?? "unknown";
Console.WriteLine(@"Starting Wave " + humanReadableVersion);
var logMessages = new List<string>(); #endregion
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.WriteTo.Console()
.CreateBootstrapLogger();
var logger = Log.Logger.ForContext<Program>();
logger.Information("Starting Wave {WaveVersion}", humanReadableVersion);
builder.Services.AddCascadingValue("Version", _ => humanReadableVersion);
builder.Configuration builder.Configuration
.AddJsonFile(Path.Combine(FileSystemService.ConfigurationDirectory, "config.json"), true, false) .AddJsonFile(Path.Combine(FileSystemService.ConfigurationDirectory, "config.json"), true, false)
.AddYamlFile(Path.Combine(FileSystemService.ConfigurationDirectory, "config.yml"), true, false) .AddYamlFile(Path.Combine(FileSystemService.ConfigurationDirectory, "config.yml"), true, false)
@ -35,7 +47,28 @@
.AddIniFile( Path.Combine(FileSystemService.ConfigurationDirectory, "config.ini"), true, false) .AddIniFile( Path.Combine(FileSystemService.ConfigurationDirectory, "config.ini"), true, false)
.AddXmlFile( Path.Combine(FileSystemService.ConfigurationDirectory, "config.xml"), true, false) .AddXmlFile( Path.Combine(FileSystemService.ConfigurationDirectory, "config.xml"), true, false)
.AddEnvironmentVariables("WAVE_"); .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.AddRazorComponents().AddInteractiveServerComponents();
builder.Services.AddControllers(options => { builder.Services.AddControllers(options => {
@ -63,12 +96,12 @@
}); });
} else { } else {
builder.Services.AddDataProtection() builder.Services.AddDataProtection()
.UseCryptographicAlgorithms(new AuthenticatedEncryptorConfiguration() { .UseCryptographicAlgorithms(new AuthenticatedEncryptorConfiguration {
EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC, EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
ValidationAlgorithm = ValidationAlgorithm.HMACSHA256 ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
}); });
builder.Services.AddDistributedMemoryCache(); builder.Services.AddDistributedMemoryCache();
logMessages.Add("No Redis connection string found."); logger.Warning("No Redis connection string found, running in-memory.");
} }
#endregion #endregion
@ -197,7 +230,7 @@
builder.Services.AddScoped<IEmailSender<ApplicationUser>, IdentityEmailSender>(); builder.Services.AddScoped<IEmailSender<ApplicationUser>, IdentityEmailSender>();
} else { } else {
builder.Services.AddSingleton<IEmailSender<ApplicationUser>, IdentityNoOpEmailSender>(); builder.Services.AddSingleton<IEmailSender<ApplicationUser>, 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))) { if (emailConfig.Smtp.Keys.Any(k => k.Equals("bulk", StringComparison.CurrentCultureIgnoreCase))) {
@ -211,14 +244,17 @@
} else { } else {
builder.Services.AddSingleton<IEmailService, NoOpEmailService>(); builder.Services.AddSingleton<IEmailService, NoOpEmailService>();
builder.Services.AddSingleton<IEmailSender<ApplicationUser>, IdentityNoOpEmailSender>(); builder.Services.AddSingleton<IEmailSender<ApplicationUser>, IdentityNoOpEmailSender>();
logMessages.Add("No email provider configured."); logger.Warning("No email provider configured.");
} }
builder.Services.AddSingleton<IMessageDisplay, MessageService>(); builder.Services.AddSingleton<IMessageDisplay, MessageService>();
builder.Services.AddSingleton<FileSystemService>(); builder.Services.AddSingleton<FileSystemService>();
#endregion #endregion
#region Localization
var customization = builder.Configuration.GetSection(nameof(Customization)).Get<Customization>(); var customization = builder.Configuration.GetSection(nameof(Customization)).Get<Customization>();
string[] cultures = ["en-US", "en-GB", "de-DE"]; string[] cultures = ["en-US", "en-GB", "de-DE"];
@ -232,6 +268,8 @@
.AddSupportedUICultures(cultures); .AddSupportedUICultures(cultures);
}); });
#endregion
var app = builder.Build(); var app = builder.Build();
// Configure the HTTP request pipeline. // Configure the HTTP request pipeline.
@ -241,6 +279,7 @@
app.UseExceptionHandler("/Error", createScopeForErrors: true); app.UseExceptionHandler("/Error", createScopeForErrors: true);
} }
app.UseSerilogRequestLogging();
app.UseStaticFiles(new StaticFileOptions { app.UseStaticFiles(new StaticFileOptions {
ContentTypeProvider = new FileExtensionContentTypeProvider { ContentTypeProvider = new FileExtensionContentTypeProvider {
@ -263,10 +302,6 @@
app.UseRequestLocalization(); app.UseRequestLocalization();
foreach (string message in logMessages) {
app.Logger.LogInformation("{message}", message);
}
{ {
using var scope = app.Services.CreateScope(); using var scope = app.Services.CreateScope();
await using var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>(); await using var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
@ -276,13 +311,13 @@
if (userManager.GetUsersInRoleAsync("Admin").Result.Any() is false) { if (userManager.GetUsersInRoleAsync("Admin").Result.Any() is false) {
var cache = app.Services.GetRequiredService<IDistributedCache>(); var cache = app.Services.GetRequiredService<IDistributedCache>();
// Check first wheter the password exists already // Check first whether the password exists already
string? admin = await cache.GetStringAsync("admin_promote_key"); string? admin = await cache.GetStringAsync("admin_promote_key");
// If it does not exist, create a new one and save it to redis // If it does not exist, create a new one and save it to redis
if (string.IsNullOrWhiteSpace(admin)){ if (string.IsNullOrWhiteSpace(admin)){
admin = Guid.NewGuid().ToString("N")[..16]; 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, " + app.Logger.LogWarning("There is currently no user in your installation with the admin role, " +

View file

@ -32,6 +32,8 @@
<PackageReference Include="Mjml.Net" Version="3.8.0" /> <PackageReference Include="Mjml.Net" Version="3.8.0" />
<PackageReference Include="NetEscapades.Configuration.Yaml" Version="3.1.0" /> <PackageReference Include="NetEscapades.Configuration.Yaml" Version="3.1.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0" /> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
<PackageReference Include="Serilog.Sinks.Grafana.Loki" Version="8.3.0" />
<PackageReference Include="System.ServiceModel.Syndication" Version="8.0.0" /> <PackageReference Include="System.ServiceModel.Syndication" Version="8.0.0" />
<PackageReference Include="Tomlyn.Extensions.Configuration" Version="1.0.5" /> <PackageReference Include="Tomlyn.Extensions.Configuration" Version="1.0.5" />
</ItemGroup> </ItemGroup>

View file

@ -1,9 +1,2 @@
{ {
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.EntityFrameworkCore": "Information"
}
}
} }

View file

@ -1,10 +1,3 @@
{ {
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.EntityFrameworkCore": "Warning"
}
},
"AllowedHosts": "*" "AllowedHosts": "*"
} }

8
Wave/log Normal file
View file

@ -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

View file

@ -15,5 +15,6 @@
</None> </None>
<None Include="docker-compose.yml" /> <None Include="docker-compose.yml" />
<None Include=".dockerignore" /> <None Include=".dockerignore" />
<None Include="provisioning\datasources\auomatic.yml" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -16,13 +16,15 @@ services:
- "TZ=Europe/Berlin" - "TZ=Europe/Berlin"
- "WAVE_ConnectionStrings__DefaultConnection=Host=db; Username=wave; Password=development; Include Error Detail=true" - "WAVE_ConnectionStrings__DefaultConnection=Host=db; Username=wave; Password=development; Include Error Detail=true"
- "WAVE_ConnectionStrings__Redis=redis,password=development" - "WAVE_ConnectionStrings__Redis=redis,password=development"
- "WAVE_Loki=http://loki:3100"
volumes: volumes:
- wave-files:/app/files - wave-files:/app/files
- wave-config:/configuration - wave-config:/configuration
networks:
- wave
depends_on: depends_on:
- database database:
condition: service_started
loki:
condition: service_healthy
database: database:
image: postgres:16.1-alpine image: postgres:16.1-alpine
restart: unless-stopped restart: unless-stopped
@ -32,16 +34,36 @@ services:
- "POSTGRES_PASSWORD=development" - "POSTGRES_PASSWORD=development"
volumes: volumes:
- wave-db:/var/lib/postgresql/data - wave-db:/var/lib/postgresql/data
networks:
- wave
redis: redis:
image: redis:7-alpine image: redis:7-alpine
restart: unless-stopped restart: unless-stopped
command: redis-server --requirepass development --save 60 1 --loglevel warning command: redis-server --requirepass development --save 60 1 --loglevel warning
volumes: volumes:
- wave-redis:/data - 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: mailhog:
image: mailhog/mailhog:latest image: mailhog/mailhog:latest
@ -49,13 +71,9 @@ services:
ports: ports:
- 8080:8025 - 8080:8025
profiles: ["smtp-debug"] profiles: ["smtp-debug"]
networks:
- wave
volumes: volumes:
wave-files: wave-files:
wave-config: wave-config:
wave-db: wave-db:
wave-redis: wave-redis:
networks:
wave:

View file

@ -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