Gå til indholdet

Introduktion til log

I denne sektion vil der blive gennemgået betydningen af log i C#, hvorfor det er vigtigt, hvordan det kan implementeres, og de mest benyttede pakker og metoder til at arbejde med log.

Hvorfor logge

Logging er en vigtig del af softwareudvikling og har flere formål:

  • Fejlfinding: Logging gør det lettere at finde og diagnosticere fejl og problemer i koden under udvikling og vedligeholdelse.
  • Overvågning: Logging giver indsigt i, hvordan applikationen fungerer, og kan bruges til at identificere ydeevne- eller ressourceproblemer.
  • Sikkerhed: Logging kan hjælpe med at spore og analysere sikkerhedshændelser og potentielle angreb.
Information til undervisere

Viden om log generelt er nødvendigt - al professionel udvikling benytter log. Kursisten skal kende til både log levels og targets/sinks, og selv prøve det. På denne side er der et par repos med eksempler men måske skulle i sammen kigge på En udvidet konsol applikation som indeholder et eksempel på brug af Serilog. Husk at give besked (footer) hvis noget mod forventning ikke virker. Pakker bliver opdateret og det kan nogle gange slå et eller andet i stykker.

Hvad er forskellen på Logs, Metrics og Audits?

Når man “logger noget,” er det afgørende at overveje hvad formålet er, hvor data skal gemmes, og hvor længe det skal opbevares. Dette fører til tre forskellige typer af information:

  • Logs

    • Formål: Primært udviklerfokuserede til fejlfinding og diagnose af tekniske problemer. Bruges til at spore undtagelser (med stack traces), API-svar og processtrin.
    • Karakteristika:
      • Bør bruge struktureret logning for lettere søgning og analyse.
      • Indeholder ofte nøgleinformation som bruger-ID, korrelations-ID, request-URL og applikationsversion.
      • Logniveauer skal bruges korrekt: Debug (individuelle trin), Information (opsamlinger), Warning (potentielle problemer i samlet form), Error (ubehandlede undtagelser) og Critical/Fatal (applikationen kan ikke starte).
      • Typisk bufferes i hukommelsen, hvilket betyder at logs kan gå tabt ved uventet nedlukning af applikationen.
      • Opbevaring er ofte kort (f.eks. 30 dage) og er ikke egnet til historiske data.
    • Anvendelse: Fejlfinding og overvågning af applikationens tekniske funktion.
  • Metrics

    • Formål: Data, der aggregeres over tid for at kvantificere applikationens opførsel eller forretningsmæssige resultater.
    • Typer:
      • Applikationsniveau: CPU-brug, hukommelse, netværk.
      • Forretningsmæssige: Antal ordrer, sidevisninger, klik på knapper.
    • Karakteristika:
      • Opbevaringsperiode besluttes af forretningen.
      • Det kan være acceptabelt at miste nogle data (f.eks. en dags CPU-data), men ikke alle (f.eks. ordre-data).
      • Almindelige logningssystemer er ofte ikke det rette sted at gemme metrics.
    • Anvendelse: Ydeevneovervågning, trendanalyse og forretningsindsigt.
  • Audits

    • Formål: Registrering af “hvem gjorde hvad og hvornår” i applikationen, ofte på grund af juridiske krav, compliance eller sporbarhed.
    • Karakteristika:
      • Tab af data er ikke acceptabelt. Dataintegritet er kritisk.
      • Anbefales at gemmes i samme datastore som de data, de auditerer (f.eks. SQL Server). Dette sikrer atomiske transaktioner (enten lykkes alt, eller også fejler alt).
      • Almindelige logningsframeworks er ikke egnede til audit-formål på grund af risiko for datatab.
    • Anvendelse: Overholdelse af lovkrav, sikkerhedssporing og ufejlbarlig sporbarhed af vigtige handlinger.

Brug af System.Diagnostics

Der er flere måder at implementere logging i C# applikationer.

En simpel måde er at bruge System.Diagnostics.Trace og System.Diagnostics.Debug klasserne, som er en del af .NET framework:

using System.Diagnostics;

// Skriver en logbesked til Trace
Trace.WriteLine("Dette er en trace besked.");

// Skriver en logbesked til Debug (kun i debug mode)
Debug.WriteLine("Dette er en debug besked.");

Forskellen mellem Trace og Debug

Trace og Debug er begge klasser i System.Diagnostics-navnerummet og bruges til at logge information i C# applikationer. Forskellen mellem de to ligger i deres formål og anvendelsesområde:

  • Trace: Trace klassen er designet til at logge information både i udviklings- og produktionsmiljøer. Det betyder, at Trace logbeskeder vil blive genereret og logget uanset om applikationen kører i Debug- eller Release-mode.
  • Debug: Debug klassen er beregnet til at logge information kun i udviklingsmiljøet. Debug logbeskeder vil kun blive genereret og logget, når applikationen kører i Debug-mode.

For at se Trace og Debug logbeskeder i Visual Studio skal Output-vinduet åbnes. Dette kan gøres ved at vælge “View” > “Output” i menuen, eller ved at trykke “Ctrl + Alt + O”. I Output-vinduet skal der vælges “Debug” i rullemenuen for at se logbeskederne.

Helt simpel fil-log

Du skal ikke selv kode et log-system – for det første er det ret komplekst at gøre rigtigt, og for det andet er det gjort mange gange. Du kan eventuelt benytte (open source) systemer som Serilog, NLog eller Log4Net, men hvis du gerne vil have en smule log, kan du eventuelt tage udgangspunkt i følgende metode – men du må ikke bruge den i produktion:

using System;

namespace Demo
{
    class Program
    {
        static void Main(string[] args)
        {

            Log("Test #1");
            Log("Test #2");

        }

                        // Simpel log-metode der skriver til en fil.
        // Metoden tilføjer tid og fjernet eventuelle linjeskift i tekst
        static void Log(string tekst) {
            System.IO.File.AppendAllText(
                @"c:\temp\log.txt",
                $"{DateTime.Now:dd-MM-yy HH:mm:ss:ffff} " +
                $"{tekst.Replace("\r\n", "")} \r\n");
                        }
    }
}

Koden vil skabe en log-fil i c:\temp (som skal eksistere) kaldet log.txt med følgende indhold:

13-01-20 16:43:37:5347 Test #1 
13-01-20 16:43:37:5620 Test #2

Med denne simple log-metode vil en typisk try/catch struktur se således ud:

try
{
    Console.WriteLine("Indtast tal");
    string talTekst = Console.ReadLine();
    int resultat = LægEnTil(talTekst);
    Console.WriteLine($"Resultatet er {resultat}");
}
catch (Exception ex)
{
    // ex.ToString() vil returnere al info til en streng
    Log(ex.ToString());     
    Console.WriteLine($"Ups - følgende fejl er opstået: {ex.Message}");
}

Nu vil fejl blive fanget, der udskrives en besked til brugeren, og fejlen bliver gemt i en log-fil til eventuelt senere gennemsyn.

Warning

Brug altid et gennemtestet Log-system til log - ikke din egen kode.

Professionel logning

Der er flere populære tredjeparts logging biblioteker, der tilbyder mere avancerede funktioner og bedre logningsmekanismer. Nogle af de mest populære logging biblioteker inkluderer:

Disse biblioteker tilbyder forskellige funktioner og konfigurationsmuligheder, så det anbefales at undersøge, hvilken løsning der passer bedst til applikationens behov.

Forskellige log targets

Når der logges gennem de store NuGet pakker i en C# applikation, kan logbeskederne sendes til forskellige mål eller destinationskilder. Her er nogle af de mest almindelige log targets:

  • Filer: Logbeskeder kan skrives til tekstfiler, så de er lette at gennemgå og analysere. Dette er en almindelig metode, der bruges i mange applikationer.
  • Databaser: Logbeskeder kan gemmes i databaser for at gøre det nemmere at søge, filtrere og analysere logdata.
  • Event Log: Windows Event Log er en centraliseret logningsmekanisme, der gør det muligt at overvåge og analysere systemhændelser og applikationshændelser.
  • ElasticSearch: ElasticSearch er en søgemaskine med fuld tekst, der er baseret på Apache Lucene. Det er en meget populær løsning til at indeksere og søge i store mængder logdata. Der er nuget-pakker som Serilog.Sinks.Elasticsearch og NLog.Targets.ElasticSearch til at logge direkte til ElasticSearch.
  • Application Insights: Application Insights er en tjeneste fra Microsoft Azure, der giver overvågning og diagnostik for applikationer. Det er integreret med Visual Studio og kan bruges til at logge og analysere logdata fra C# applikationer. Se Microsoft.ApplicationInsights for at komme i gang.
  • Seq: Seq er en centraliseret logserver, der er designet specifikt til strukturerede logs. Det gør det nemt at søge, analysere og visualisere logdata ved hjælp af et webbaseret grænseflade. Serilog har en NuGet-pakke Serilog.Sinks.Seq for at logge direkte til en Seq server.
  • Email: I nogle tilfælde kan logbeskeder sendes via e-mail, især når der opstår kritiske fejl eller alarmer.

Professionelle applikationer benytter ofte en kombination af disse log targets for at opnå den bedste balance mellem ydeevne og analysekapacitet. Valget af log targets afhænger af applikationens krav og mål.

Log niveauer

Log niveauer er en vigtig del af logningsstrategien i en C# applikation. De hjælper med at kontrollere, hvilken type information der logges, og gør det nemmere at filtrere og analysere logdata. De mest almindelige log niveauer er:

  • Trace: Dette niveau bruges til at logge meget detaljerede og diagnostiske oplysninger, der kan være nyttige ved fejlfinding og analyse. Trace logbeskeder er normalt for omfattende til at blive brugt i en produktionsmiljø.
  • Debug: Dette niveau bruges til at logge oplysninger, der hjælper udviklere med at fejlfinde og forstå, hvad der sker inde i applikationen. Debug logbeskeder er typisk kun aktiveret i udviklingsmiljøet.
  • Information: Dette niveau bruges til at logge generelle oplysninger om, hvad applikationen gør. Disse logbeskeder er ofte aktiveret både i udviklings- og produktionsmiljøer og kan hjælpe med at forstå applikationsforløbet og opdage eventuelle problemer.
  • Warning: Dette niveau bruges til at logge potentielle problemer eller situationer, der kan kræve opmærksomhed, men ikke nødvendigvis betragtes som fejl. Warning logbeskeder er vigtige for at overvåge applikationens sundhed og ydeevne.
  • Error: Dette niveau bruges til at logge fejl og problemer, der forhindrer korrekt funktion af applikationen. Error logbeskeder er vigtige for at finde og rette fejl i applikationen.
  • Critical/Fatal: Dette niveau bruges til at logge alvorlige fejl og problemer, der kan forårsage, at applikationen går ned eller bliver utilgængelig. Critical logbeskeder skal håndteres hurtigt for at undgå tab af data eller yderligere problemer.

Ved at bruge disse log niveauer kan applikationen logge de nødvendige oplysninger, samtidig med at mængden af logdata holdes på et håndterbart niveau. Log niveauer kan også tilpasses, så de passer til applikationens specifikke krav og mål.

Log rotation og opbevaring

Log rotation er en vigtig praksis for at opretholde en sund applikation og forhindre, at logfiler eller tabeller bliver for store og besværlige at administrere. Log rotation indebærer at opdele logfiler i mindre, tidsbegrænsede segmenter og muligvis arkivere eller slette gamle logfiler. Flere logrammer og biblioteker, som NLog og Serilog, understøtter log rotation ud af boksen.

Log opbevaring er en anden vigtig overvejelse, når man arbejder med logfiler. At bestemme, hvor længe logdata skal opbevares, afhænger af virksomhedens politikker, juridiske krav og ressourcer. Overvej om der er behov for arkivering af logdata og hvordan dette kan gøres effektivt.

Log analyse og overvågning

Når logdata er opsamlet, er det vigtigt at analysere og overvåge det for at identificere problemer, fejl og ydeevne flaskehalse. Log analyse værktøjer som ElasticSearch, Application Insights og Seq, der er nævnt tidligere, kan hjælpe med at analysere logdata og give indsigt i applikationens sundhed. Overvej at opsætte alarmer og notifikationer baseret på bestemte logniveauer eller hændelser for at sikre, at teamet bliver informeret om kritiske problemer.

Centraliseret logning

I større applikationer eller applikationer, der kører i distribuerede miljøer, kan det være nyttigt at centralisere logdata. Centraliseret logning gør det nemmere at søge og analysere logdata på tværs af flere tjenester eller servere. Flere log targets, som ElasticSearch, Seq og Application Insights, understøtter centraliseret logning.

Overvejelserne om logning i C# applikationer strækker sig ud over de grundlæggende koncepter og teknikker, der er beskrevet i denne sektion. Men at forstå betydningen af logning og hvordan man arbejder med forskellige logrammer, logniveauer og log targets vil hjælpe med at opbygge mere robuste og vedligeholdelige applikationer.

Eksempler

Prøv dette mini-eksempel på Serilog i en konsol applikation med dependency injection (DI):

dotnet new console -n SerilogDemo
cd SerilogDemo
dotnet add package Microsoft.Extensions.Hosting
dotnet add package Serilog
dotnet add package Serilog.Extensions.Hosting
dotnet add package Serilog.Sinks.Console
dotnet add package Serilog.Sinks.File
dotnet add package Serilog.Sinks.Debug

og så tilret program.cs til:

// Program.cs
// Kommentarer på dansk
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Serilog;
using Serilog.Events;

internal class Program
{
    private static async Task Main(string[] args)
    {
        // Serilog konfiguration: Console, fil og Debug
        Log.Logger = new LoggerConfiguration()
            .MinimumLevel.Debug() // juster ved behov
            .Enrich.FromLogContext()
            .WriteTo.Console()
            .WriteTo.File("logs/app-.log", rollingInterval: RollingInterval.Day)
            .WriteTo.Debug() // ses i Debug Console når du kører med debugger
            .CreateLogger();

        try
        {
            Log.Information("Starter host");

            using IHost host = Host.CreateDefaultBuilder(args)
                .UseSerilog() // kobler Serilog på Microsoft ILogger
                .ConfigureServices(services =>
                {
                    services.AddTransient<Greeter>(); // registrér din klasse i DI
                })
                .Build();

            // Eksempel på log i Program.cs
            Log.Debug("Program.Main er i gang");

            // Hent klasse via DI og kør
            var greeter = host.Services.GetRequiredService<Greeter>();
            greeter.Run();

            await host.StopAsync();
        }
        catch (Exception ex)
        {
            Log.Fatal(ex, "Programmet stoppede pga. en kritisk fejl");
        }
        finally
        {
            Log.CloseAndFlush();
        }
    }

    // Greeter.cs

    public class Greeter
    {
        private readonly ILogger<Greeter> _logger;

        public Greeter(ILogger<Greeter> logger)
        {
            _logger = logger;
        }

        public void Run()
        {
            _logger.LogInformation("Hej fra Greeter.Run()");
            _logger.LogDebug("Noget detaljeret info til fejlsøgning");

            try
            {
                // Demo-fejl for at vise Error-log
                throw new InvalidOperationException("Demo-fejl");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Noget gik galt i Greeter.Run()");
            }
        }
    }
}

Så har du et lille program, der logger til konsol, fil og debug output. Kør med dotnet run og prøv at sætte et breakpoint i Greeter.Run() for at se Debug loggen. Brug lidt hjælp fra AI hvis du vil have splittet konfigurationen ud i en appsettings.json fil.

Se Udvidet konsol applikation for et eksempel på en konsol applikation med log.

Spørgsmål til AI

For at få mest muligt ud af AI-værktøjer som ChatGPT, er det vigtigt at stille klare og præcise spørgsmål (og skabe det rigtige kontekst - se her). Her er nogle spørgsmål til denne side:

Grundlæggende spørgsmål til AI

  • Hvad er logging og hvorfor er det vigtigt?
  • Hvad er forskellen mellem Console.WriteLine og proper logging?
  • Hvilke logging frameworks findes til C#?
  • Hvad er log levels og hvornår bruger jeg dem?
  • Hvordan strukturerer jeg mine log beskeder?
  • Hvad er struktureret logging?
  • Hvordan konfigurerer jeg logging i min applikation?