1. Introduzione
Nel programming il logging non è semplicemente "stampare qualcosa sulla console". Il logging è la tua black box, il tuo GPS-tracker e il tuo indicatore tutto in uno. Senza log una grande applicazione diventa una scatola nera-misteriosa: "perché il servizio si è bloccato?", "perché l'utente non ha ricevuto la mail?", "ma qualcuno ha lanciato questo modulo negli ultimi 3 mesi?" — a tutte queste domande spesso si può rispondere solo se i log vengono conservati e sono accessibili.
Perché il logging serve nella realtà?
Cerca e diagnosi degli errori
Se il programma è crashato — i log diranno dove e perché. Se non è crashato ma si comporta in modo strano — i log indicheranno i passi che hanno portato a quel comportamento.
Monitoring dello stato
Attraverso i log puoi sapere se il sistema è operativo ora, quanti request arrivano al server, se ci sono errori, chi e cosa sta facendo.
Sicurezza
Logging dei tentativi di accesso non autorizzato, attività sospette, errori di autenticazione.
Audit
Chi, cosa e quando ha fatto qualcosa. Se se ne va un admin cattivo e cancella dei dati, dai log si può ricostruire tutto (o almeno capire cosa ha fatto).
Supporto nello sviluppo e nelle operations
I log non sono solo per il programmatore, ma anche per il tester, l'operatore, l'amministratore. Tra sei mesi ti ringrazierai: 'Grazie per aver aggiunto i log!'
Da Console.WriteLine al logging di produzione
La reazione iniziale del principiante: "Davvero, non basta solo Console.WriteLine?". Per piccoli programmi didattici spesso basta. Ma quando l'app gira su un server, opera in parallelo, o viene usata da decine o centinaia di utenti, la console non è più sufficiente. Serve:
- Separare le informazioni importanti da quelle di debug.
- Inviare i log non solo alla console, ma anche a file, database, sistemi centralizzati.
- Modificare il livello di dettaglio dei log (minimo — solo errori, massimo — tutto).
- Arricchire automaticamente le entry con data, ora e metadati.
- Configurare in modo flessibile.
- E soprattutto — non cambiare il codice per reindirizzare i log altrove.
Qui inizia l'era dei logger "veri".
2. Fondamenti di un sistema di logging moderno in .NET
Nell'ecosistema .NET esiste un framework standard, potente e flessibile per il logging — Microsoft.Extensions.Logging. Fa parte della "nuova ondata" di librerie .NET arrivate con ASP.NET Core, ma oggi si usa ovunque: server, desktop e persino mobile.
Perché questo framework è buono?
- Astrattazione — non ti lega a un'implementazione specifica (il logger può scrivere su file, console, cloud, o su tutto contemporaneamente).
- Supporto dei livelli di logging (Trace, Debug, Information, Warning, Error, Critical).
- Integrazione nel DI-container e supporto per tutte le moderne applicazioni .NET.
- Ecologia ricca di estensioni: supporto per logging strutturato, formatter avanzati e integrazioni.
Concetti e classi principali
Conosciamo gli oggetti chiave che ci serviranno per lavorare con Microsoft.Extensions.Logging:
| Classe/Interfaccia | Scopo |
|---|---|
|
Interfaccia per il logging, tipizzata per classe |
|
Interfaccia generica del logger |
|
Factory per creare istanze di logger |
|
Permette di configurare il logging nell'applicazione |
|
Enumerazione dei livelli di log (Trace, Debug, Information, ...) |
Livelli di logging
La separazione dei messaggi per livello di importanza permette di filtrare il "rumore" e trovare ciò che serve:
| Livello (LogLevel) | Quando usarlo? |
|---|---|
|
Debug estremamente dettagliato, "rumore", dati temporanei |
|
Informazioni principali di debugging |
|
Messaggi chiave di eventi per il normale funzionamento |
|
Avvisi su potenziali problemi, ma il sistema continua |
|
Errori che richiedono attenzione, ma l'app è ancora viva |
|
Guasti critici che minacciano l'intero sistema |
3. Pratica: aggiungiamo il logging alla nostra applicazione
Non scriviamo codice astratto: continuiamo con la nostra applicazione demo. Supponiamo di avere già una semplice classe Calculator che estendiamo durante il corso.
Esempio: calcolatore base
public class Calculator
{
public int Add(int a, int b)
{
return a + b;
}
// Altri metodi...
}
Ora integreremo il logging. Ci serve l'interfaccia ILogger<Calculator>, che riceveremo dall'esterno (per esempio via Dependency Injection, DI).
using Microsoft.Extensions.Logging;
public class Calculator
{
private readonly ILogger<Calculator> _logger;
public Calculator(ILogger<Calculator> logger)
{
_logger = logger;
}
public int Add(int a, int b)
{
int result = a + b;
_logger.LogInformation("Eseguita somma: {A} + {B} = {Result}", a, b, result);
return result;
}
}
Curiosità:
Invece di concatenare stringhe i logger supportano template e parametri nominati ({A}, {B}, {Result}), il che rende i log strutturati e comodi per l'elaborazione e la ricerca automatica.
4. Come creare e configurare il logger in un'app console
1. Aggiungiamo i pacchetti NuGet
Nel tuo progetto serviranno:
- Microsoft.Extensions.Logging
- Microsoft.Extensions.Logging.Console (se vuoi scrivere sulla console)
- (opzionale) altri provider, se necessari
2. Configuriamo il logger
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
// Creiamo il DI-container
var serviceProvider = new ServiceCollection()
.AddLogging(builder => {
builder.AddConsole(); // Output su console
builder.SetMinimumLevel(LogLevel.Debug); // Livello minimo dei log
})
.BuildServiceProvider();
// Otteniamo un'istanza del logger del tipo desiderato
var logger = serviceProvider.GetRequiredService<ILogger<Calculator>>();
var calculator = new Calculator(logger);
calculator.Add(5, 3); // Il messaggio finirà nei log
Errore tipico dei principianti
"Perché il log non appare in console?" — controlla che il provider sia stato aggiunto (.AddConsole()) e che il livello minimo sia impostato correttamente (SetMinimumLevel). Se il livello è più alto, i messaggi potrebbero semplicemente essere "filtrati"!
5. Dettagli utili
Logghiamo errori e situazioni non standard
Supponiamo di voler loggare una divisione per zero. Aggiungiamo il metodo corrispondente:
public int Divide(int a, int b)
{
if (b == 0)
{
_logger.LogError("Tentativo di divisione per zero! a={A}", a);
throw new DivideByZeroException();
}
int result = a / b;
_logger.LogInformation("Eseguita divisione: {A} / {B} = {Result}", a, b, result);
return result;
}
Perché serve?
In un'app reale, quando qualcosa va storto, i log con livello Error attirano solitamente attenzione speciale: vengono inviati automaticamente agli admin, evidenziati nel monitoring e usati per alert/notifiche.
Uso di categorie e scope
Il logger in .NET supporta gli cosiddetti scopes — metadati aggiuntivi che vengono automaticamente aggiunti a tutti i log durante un blocco di codice. Per esempio, quando gestisci una web request o una sessione utente, puoi aggiungere il suo id nello scope.
using (_logger.BeginScope("UserId: {UserId}", 42))
{
_logger.LogInformation("Inizio elaborazione dati utente");
// ...
}
Tutti i messaggi dentro il blocco avranno il tag aggiuntivo UserId: 42, che poi aiuta a cercare i log per utente o operazione.
Esempio: livelli di logging in azione
_logger.LogTrace("Questo è Trace — quasi nessuno lo vedrà");
_logger.LogDebug("Questo è Debug — per gli sviluppatori");
_logger.LogInformation("Questo è Information — per eventi di normale funzionamento");
_logger.LogWarning("Questo è Warning — avviso di possibile problema");
_logger.LogError("Questo è Error — errore che richiede attenzione");
_logger.LogCritical("Questo è Critical — il sistema sta bruciando, serve il pompiere!");
Se hai impostato SetMinimumLevel(LogLevel.Information), vedrai solo i messaggi di livello Information, Warning, Error, Critical.
Trucco:
Lascia i log Trace e Debug per diagnostica dettagliata durante lo sviluppo, e in produzione di solito attiva solo Information e superiori, per non gonfiare i log e non perdere il segnale nel rumore.
Schema visivo: architettura del logging moderno
graph TD
A[Codice dell'applicazione] --ILogger<YourClass>--> B[Microsoft.Extensions.Logging]
B --> C1[Console Provider]
B --> C2[File Provider]
B --> C3[Cloud/Database Provider]
C1 -.-> D1[Log sulla console]
C2 -.-> D2[Log su file]
C3 -.-> D3[Log nel sistema di monitoring]
subgraph Providers
C1
C2
C3
end
6. Funzionalità aggiuntive ed estensioni
Logging strutturato:
I valori dei parametri possono essere conservati non solo nella stringa ma come coppie chiave-valore, permettendo ricerca e aggregazione su questi campi (per esempio con Seq, ELK/ElasticSearch o Application Insights).
Provider di logging:
Puoi aggiungere decine di provider diversi: file, Windows EventLog, Azure ecc.
Configurare il logging tramite appsettings.json
In ASP.NET Core il logging si configura comodamente tramite file di configurazione, senza ricompilare l'applicazione.
Esempio di impostazione del livello minimo tramite config (appsettings.json):
{
"Logging": {
"LogLevel": {
"Default": "Information",
"MyApp.Calculator": "Debug",
"Microsoft": "Warning"
}
}
}
7. Errori tipici usando Microsoft.Extensions.Logging
Errore #1: Scelta sbagliata del livello di logging.
Usare LogInformation per errori invece di LogError o LogCritical rende difficile trovare i problemi in produzione.
Errore #2: Ignorare il logging strutturato.
Concatenare stringhe invece di usare template con placeholder ({Parameter}) toglie i benefici del logging strutturato, come la ricerca per parametro.
Errore #3: Configurazione sbagliata dei livelli di logging.
Se il livello minimo è impostato troppo alto (es. Warning invece di Debug), messaggi importanti possono essere filtrati.
Errore #4: Logging eccessivo o insufficiente.
Log troppo dettagliati (es. Trace in produzione) generano "rumore", mentre la mancanza di log rende difficile la diagnostica.
GO TO FULL VERSION