feat(api): integrate Serilog bootstrap and in-app log viewer

This commit is contained in:
2026-10-10 16:53:37 +02:00
parent 1754aa64a2
commit 600374a6f0

View File

@@ -2,65 +2,145 @@ using DigitalData.TaskFlow.Application;
using DigitalData.TaskFlow.API.Middleware;
using DigitalData.TaskFlow.API.Options;
using DigitalData.TaskFlow.Infrastructure;
using Microsoft.OpenApi;
using Microsoft.OpenApi.Models;
using Scalar.AspNetCore;
using Serilog;
using Serilog.Events;
using Serilog.Ui.Core.Extensions;
using Serilog.Ui.SqliteDataProvider.Extensions;
using Serilog.Ui.Web.Extensions;
var builder = WebApplication.CreateBuilder(args);
var config = builder.Configuration;
var tempConfig = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: false)
.Build();
// Add services to the container.
builder.Services.AddApplicationServices(config);
builder.Services.AddInfrastructureServices(config);
var logDirectoryRaw = tempConfig.GetValue<string>("Application:LogDirectory") ?? "logs";
var logDirectory = Path.IsPathRooted(logDirectoryRaw)
? logDirectoryRaw
: Path.Combine(AppContext.BaseDirectory, logDirectoryRaw);
Directory.CreateDirectory(logDirectory);
var sqliteDbPath = Path.Combine(logDirectory, "logs.db");
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)
.MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning)
.MinimumLevel.Override("System", LogEventLevel.Warning)
.Enrich.FromLogContext()
.WriteTo.File(
path: Path.Combine(logDirectory, "taskflow-.log"),
rollingInterval: RollingInterval.Day,
retainedFileCountLimit: 30,
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}")
.WriteTo.SQLite(sqliteDbPath, storeTimestampInUtc: true)
.CreateBootstrapLogger();
try
{
options.SwaggerDoc("v1", new OpenApiInfo
Log.Information("Starting TaskFlow API");
var builder = WebApplication.CreateBuilder(args);
var config = builder.Configuration;
builder.Host.UseSerilog((context, services, loggerConfiguration) =>
{
Title = "DigitalData TaskFlow API",
Version = "v1",
Description = "Integration API for taskFLOW workflow and document operations.",
Contact = new OpenApiContact
loggerConfiguration
.ReadFrom.Configuration(context.Configuration)
.ReadFrom.Services(services)
.Enrich.FromLogContext()
.WriteTo.File(
path: Path.Combine(logDirectory, "taskflow-.log"),
rollingInterval: RollingInterval.Day,
retainedFileCountLimit: 30,
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}")
.WriteTo.SQLite(sqliteDbPath, storeTimestampInUtc: true);
if (context.HostingEnvironment.IsDevelopment())
{
Name = "Digital Data GmbH",
Url = new Uri("https://digitaldata.works/"),
Email = "info-flow@digitaldata.works"
loggerConfiguration.WriteTo.Console(
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}");
}
});
});
builder.Services
.AddOptions<SwaggerOptions>()
.Bind(config.GetSection(SwaggerOptions.SectionName));
// Add services to the container.
builder.Services.AddApplicationServices(config);
builder.Services.AddInfrastructureServices(config);
var app = builder.Build();
var swaggerOptions = app.Services
.GetRequiredService<Microsoft.Extensions.Options.IOptions<SwaggerOptions>>()
.Value;
var enableSwagger = app.Environment.IsDevelopment() || swaggerOptions.EnabledInProduction;
// Configure the HTTP request pipeline.
app.UseMiddleware<ExceptionHandlingMiddleware>();
if (enableSwagger)
{
app.UseSwagger();
app.UseSwaggerUI();
app.MapScalarApiReference(options =>
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
options.Title = "DigitalData TaskFlow API";
options.Theme = ScalarTheme.BluePlanet;
options.SwaggerDoc("v1", new OpenApiInfo
{
Title = "DigitalData TaskFlow API",
Version = "v1",
Description = """
Integration API for taskFLOW workflow and document operations.
---
- [Serilog Log Viewer](/serilog-ui)
""",
Contact = new OpenApiContact
{
Name = "Digital Data GmbH",
Url = new Uri("https://digitaldata.works/"),
Email = "info-flow@digitaldata.works"
}
});
});
builder.Services
.AddOptions<SwaggerOptions>()
.Bind(config.GetSection(SwaggerOptions.SectionName));
builder.Services.AddSerilogUi(options =>
{
options.UseSqliteServer(databaseOptions =>
{
databaseOptions.WithConnectionString($"Data Source={sqliteDbPath}");
databaseOptions.WithTable("Logs");
});
});
var app = builder.Build();
var swaggerOptions = app.Services
.GetRequiredService<Microsoft.Extensions.Options.IOptions<SwaggerOptions>>()
.Value;
var enableSwagger = app.Environment.IsDevelopment() || swaggerOptions.EnabledInProduction;
app.UseMiddleware<ExceptionHandlingMiddleware>();
app.UseSerilogRequestLogging();
if (enableSwagger)
{
app.UseSwagger();
app.UseSwaggerUI();
app.MapScalarApiReference(options =>
{
options.Title = "DigitalData TaskFlow API";
options.Theme = ScalarTheme.BluePlanet;
});
}
app.UseHttpsRedirection();
app.UseSerilogUi();
app.UseAuthorization();
app.MapControllers();
Log.Information("TaskFlow API started successfully");
app.Run();
}
catch (Exception exception)
{
Log.Fatal(exception, "TaskFlow API failed to start");
throw;
}
finally
{
Log.CloseAndFlush();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();