OOD Handboek

9Logging

Niet alle uitvoer is even belangrijk, en in productie wil je achteraf kunnen nagaan wat er gebeurde. Logging gaat via een interface, zodat je de bestemming (console, bestand) kunt wisselen zonder de gebruikende klasse aan te passen (Open/Closed + DIP).

#Eigen logger-interface

public interface ILogger
{
    void Info(string melding);
    void Fout(string melding);
}

public class ConsoleLogger : ILogger
{
    public void Info(string m) => Console.WriteLine($"[INFO] {m}");
    public void Fout(string m) => Console.WriteLine($"[FOUT] {m}");
}

// later erbij, Verhuurbedrijf blijft ongewijzigd:
public class BestandLogger : ILogger
{
    private readonly string _pad;
    public BestandLogger(string pad) => _pad = pad;
    public void Info(string m) => File.AppendAllText(_pad, $"{DateTime.Now:s} [INFO] {m}\n");
    public void Fout(string m) => File.AppendAllText(_pad, $"{DateTime.Now:s} [FOUT] {m}\n");
}

// gebruik
bedrijf.Logger = new BestandLogger("log.txt");
Uitleg

De klasse die logt kent alleen de interface ILogger. Daardoor kun je de console-logger vervangen door een bestand-logger zonder één regel in die klasse te wijzigen: dat is het Open/Closed-principe in actie.

Functies en keywords
interface
Contract: een lijst met leden die een klasse belooft te leveren, zonder implementatie.
File.AppendAllText
Voegt tekst toe aan het einde van een bestand (maakt het aan als het niet bestaat).
private
Alleen zichtbaar binnen deze klasse.
readonly
Veld mag alleen bij de declaratie of in de constructor een waarde krijgen.
=>
Korte schrijfwijze voor een methode of property die één expressie teruggeeft; in LINQ ook de pijl van een lambda.
$"...{x}"
String-interpolatie: zet de waarde van x direct in de tekst.

#.NET ILogger<T> (Microsoft.Extensions.Logging)

public class BestellingService
{
    private readonly ILogger<BestellingService> _logger;
    public BestellingService(ILogger<BestellingService> logger)
        => _logger = logger;

    public void Plaats(int klantId)
        => _logger.LogInformation("Bestelling geplaatst voor {KlantId}", klantId);
}
Uitleg

De standaard logger van .NET. Hij wordt via de constructor geïnjecteerd en het type tussen haakjes wordt de categorie, zodat je ziet waar een regel vandaan komt. Gebruik placeholders zoals {KlantId} in plaats van string-interpolatie, dan blijven de waarden doorzoekbaar.

Functies en keywords
LogInformation
Schrijft een logregel op niveau Information, met placeholders voor waarden.
private
Alleen zichtbaar binnen deze klasse.
readonly
Veld mag alleen bij de declaratie of in de constructor een waarde krijgen.
=>
Korte schrijfwijze voor een methode of property die één expressie teruggeeft; in LINQ ook de pijl van een lambda.
LevelWanneer
Traceextreem gedetailleerd (waarde in elke loop-iteratie)
Debugbug opsporen (cache-miss voor product 17)
Informationnormale gebeurtenis (gebruiker logt in, bestelling geplaatst)
Warningonverwacht maar herstelt (retry na time-out, mislukte login)
Erroractie mislukt (opslaan gooit exception)
Criticalapp/kernfunctie ligt eruit (database onbereikbaar bij opstart)
  • Message template, geen interpolatie: "Bestelling {Id}", id i.p.v. $"Bestelling {id}" (velden blijven doorzoekbaar, analyzer CA2254).
  • Exception als eerste argument: _logger.LogError(ex, "Opslaan mislukt voor {KlantId}", klantId);
  • Nooit wachtwoorden of gevoelige data loggen.
  • Naam botst? Eigen ILogger en die van Microsoft heten hetzelfde. In een kale console-app zonder dat package geen probleem.