Logging
Il logging in ASP.NET Core si basa sull'astrazione Microsoft.Extensions.Logging. Serilog è il backend raccomandato: ricco di sink, configurabile da appsettings.json, e supporta log strutturati nativamente.
Vedi anche: regole/logging.
Dipendenze
dotnet add package Serilog.AspNetCore
dotnet add package Serilog.Settings.Configuration
dotnet add package Serilog.Sinks.Console
dotnet add package Serilog.Sinks.File
dotnet add package Serilog.Sinks.Seq # opzionale, sviluppo locale
dotnet add package Serilog.Sinks.ApplicationInsights # opzionale, Azure
Configurazione in Program.cs
builder.Host.UseSerilog((context, configuration) =>
configuration.ReadFrom.Configuration(context.Configuration));
Tutta la configurazione vive in appsettings.json — nessun hardcoding nel codice:
{
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"Microsoft.EntityFrameworkCore": "Warning",
"System": "Warning"
}
},
"WriteTo": [
{ "Name": "Console" },
{
"Name": "File",
"Args": {
"path": "logs/app-.log",
"rollingInterval": "Day",
"retainedFileCountLimit": 30
}
}
],
"Enrich": ["FromLogContext", "WithMachineName", "WithThreadId"]
}
}
Utilizzo con ILogger<T>
public class CreaOrdine
{
private readonly ILogger<CreaOrdine> _logger;
private readonly AppDbContext _db;
public CreaOrdine(ILogger<CreaOrdine> logger, AppDbContext db)
{
_logger = logger;
_db = db;
}
public async Task<Result<OrdineId>> ExecuteAsync(CreaOrdineCommand command)
{
_logger.LogInformation(
"Creazione ordine per cliente {ClienteId} con {NumeroRighe} righe",
command.ClienteId, command.Righe.Count);
try
{
// ...
_logger.LogInformation("Ordine {OrdineId} creato", ordine.Id);
return Result.Ok(ordine.Id);
}
catch (Exception ex)
{
_logger.LogError(ex,
"Errore durante la creazione dell'ordine per cliente {ClienteId}",
command.ClienteId);
throw;
}
}
}
Log strutturati: la regola fondamentale
I valori si passano come parametri, mai con interpolazione di stringa:
// ✅ Log strutturato — il valore è una proprietà ricercabile
_logger.LogInformation("Ordine {OrdineId} confermato per {ClienteId}", ordineId, clienteId);
// ❌ Interpolazione — perde la struttura, alloca inutilmente
_logger.LogInformation($"Ordine {ordineId} confermato per {clienteId}");
Decorare le eccezioni per troubleshooting
Catturare e rethrow con contesto aggiunto è buona pratica quando si vuole arricchire l'eccezione con informazioni di dominio:
catch (DbUpdateException ex)
{
throw new InvalidOperationException(
$"Errore nel salvataggio dell'ordine per cliente {command.ClienteId}. " +
$"Righe: {command.Righe.Count}", ex);
}
Log request/response con middleware
Per loggare le richieste HTTP in ingresso:
// Program.cs
app.UseSerilogRequestLogging(options =>
{
options.MessageTemplate =
"HTTP {RequestMethod} {RequestPath} risposto {StatusCode} in {Elapsed:0.0000}ms";
options.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
{
diagnosticContext.Set("UserAgent", httpContext.Request.Headers.UserAgent);
diagnosticContext.Set("RemoteIP", httpContext.Connection.RemoteIpAddress);
diagnosticContext.Set("UserId",
httpContext.User.FindFirst(ClaimTypes.NameIdentifier)?.Value);
};
});
Sink comuni
Seq (sviluppo locale)
{
"WriteTo": [
{
"Name": "Seq",
"Args": { "serverUrl": "http://localhost:5341" }
}
]
}
Application Insights (Azure)
dotnet add package Serilog.Sinks.ApplicationInsights
{
"WriteTo": [
{
"Name": "ApplicationInsights",
"Args": {
"connectionString": "InstrumentationKey=...",
"telemetryConverter": "Serilog.Sinks.ApplicationInsights.TelemetryConverters.TraceTelemetryConverter, Serilog.Sinks.ApplicationInsights"
}
}
]
}
OpenTelemetry (OTLP)
dotnet add package Serilog.Sinks.OpenTelemetry
{
"WriteTo": [
{
"Name": "OpenTelemetry",
"Args": {
"endpoint": "http://localhost:4317",
"protocol": "Grpc"
}
}
]
}
Livelli in produzione
In produzione si innalza il livello minimo a Warning per ridurre il volume, lasciando Information solo per i componenti critici:
{
"Serilog": {
"MinimumLevel": {
"Default": "Warning",
"Override": {
"NomeSoluzione": "Information"
}
}
}
}