Protokollierung in C# und .NET

.NET unterstützt über die ILogger-API leistungsstarke, strukturierte Protokollierung, um das Anwendungsverhalten zu überwachen und Probleme zu diagnostizieren. Protokolle können durch die Konfiguration verschiedener Protokollierungsanbieter in verschiedene Ziele geschrieben werden. Grundlegende Protokollierungsanbieter sind integriert. Zudem stehen viele Drittanbieter zur Verfügung.

Erste Schritte

Dieses erste Beispiel zeigt die Grundlagen, eignet sich jedoch nur für eine triviale Konsolen-App. Im nächsten Abschnitt erfahren Sie, wie Sie den Code unter Berücksichtigung von Skalierung, Leistung, Konfiguration und typischen Programmiermustern verbessern können.

using Microsoft.Extensions.Logging;

using ILoggerFactory factory = LoggerFactory.Create(builder => builder.AddConsole());
ILogger logger = factory.CreateLogger("Program");
logger.LogInformation("Hello World! Logging is {Description}.", "fun");

Für das vorherige Beispiel gilt Folgendes:

  • Erstellt ein ILoggerFactory. In ILoggerFactory wird die gesamte Konfiguration gespeichert, die bestimmt, wohin Protokollmeldungen gesendet werden. In diesem Fall konfigurieren Sie die Konsole Protokollierungsanbieter so, dass Protokollmeldungen auf die Konsole geschrieben werden.
  • Erstellt ein ILogger-Objekt mit einer Kategorie namens „Program“. Die Kategorie ist ein string, das jeder vom ILogger-Objekt protokollierten Meldung zugeordnet ist. Es wird verwendet, um Protokollmeldungen aus derselben Klasse (oder Kategorie) beim Durchsuchen oder Filtern von Protokollen zu gruppieren.
  • Ruft LogInformation auf, um eine Meldung auf der Ebene Information zu protokollieren. Die Protokollierungsebene gibt den Schweregrad des protokollierten Ereignisses an und wird verwendet, um weniger wichtige Protokollmeldungen herauszufiltern. Der Protokolleintrag enthält auch eine Nachrichtenvorlage"Hello World! Logging is {Description}." und ein Schlüssel-Wert-Paar Description = fun. Der Schlüsselname (oder Platzhalter) stammt aus dem Wort innerhalb der geschweiften Klammern in der Vorlage, und der Wert stammt aus dem restlichen Methodenargument.

Diese Projektdatei für dieses Beispiel enthält zwei NuGet-Pakete:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
  </ItemGroup>

</Project>

Tipp

Der gesamte Quellcode des Protokollierungsbeispiels steht im Beispielbrowser zum Download zur Verfügung. Weitere Informationen finden Sie unter Durchsuchen von Codebeispielen: Protokollierung in .NET.

Protokollierung in einer nicht trivialen App

Es gibt einige Änderungen, die Sie am vorherigen Beispiel vornehmen sollten, wenn Sie die Protokollierung in einem weniger trivialen Szenario verwenden:

  • Wenn Ihre Anwendung DI (Dependency Injection) oder einen Host wie WebApplication oder Generic Host von ASP.NET verwendet, dann sollten Sie dann ILoggerFactory- bzw. ILogger-Objekte aus den jeweiligen DI-Containern verwenden, anstatt sie direkt zu erstellen. Weitere Informationen finden Sie unter Integration in DI und Hosts.

  • Die Protokollierung der Quellengenerierung zur Kompilierungszeit ist in der Regel eine bessere Alternative als ILogger-Erweiterungsmethoden wie LogInformation. Die Protokollierung der Quellengenerierung bietet eine bessere Leistung und eine stärkere Typisierung, und sie verhindert, dassstring-Konstanten in den Methoden verteilt werden. Der Nachteil ist, dass diese Technik ein wenig mehr Code erfordert.

using Microsoft.Extensions.Logging;

internal partial class Program
{
    static void Main(string[] args)
    {
        using ILoggerFactory factory = LoggerFactory.Create(builder => builder.AddConsole());
        ILogger logger = factory.CreateLogger("Program");
        LogStartupMessage(logger, "fun");
    }

    [LoggerMessage(Level = LogLevel.Information, Message = "Hello World! Logging is {Description}.")]
    static partial void LogStartupMessage(ILogger logger, string description);
}
  • Es wird empfohlen, für die Namen der Protokollkategorien den vollständig qualifizierten Namen der Klasse zu verwenden, die die Protokollmeldung erstellt. Dies erleichtert es, Protokollmeldungen dem Code zuzuordnen, der sie erzeugt hat, und bietet beim Filtern von Protokollen ein gutes Maß an Kontrolle. CreateLogger akzeptiert einen Type, um diese Benennung zu vereinfachen.
using Microsoft.Extensions.Logging;

internal class Program
{
    static void Main(string[] args)
    {
        using ILoggerFactory factory = LoggerFactory.Create(builder => builder.AddConsole());
        ILogger logger = factory.CreateLogger<Program>();
        logger.LogInformation("Hello World! Logging is {Description}.", "fun");
    }
}
using Microsoft.Extensions.Logging;
using OpenTelemetry.Logs;

using ILoggerFactory factory = LoggerFactory.Create(builder =>
{
    builder.AddOpenTelemetry(logging =>
    {
        logging.AddOtlpExporter();
    });
});
ILogger logger = factory.CreateLogger("Program");
logger.LogInformation("Hello World! Logging is {Description}.", "fun");

Integration in Hosts und DI (Dependency Injection)

Wenn Ihre Anwendung DI (Dependency Injection) oder einen Host wie WebApplication oder Generic Host von ASP.NET verwendet, dann sollten Sie dann ILoggerFactory- bzw. ILogger-Objekte aus dem DI-Container verwenden, anstatt sie direkt zu erstellen.

Abrufen eines ILogger-Objekts von DI

In diesem Beispiel wird ein ILogger-Objekt in einer gehosteten App mithilfe von ASP.NET Minimal-APIs abgerufen:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSingleton<ExampleHandler>();

var app = builder.Build();

var handler = app.Services.GetRequiredService<ExampleHandler>();
app.MapGet("/", handler.HandleRequest);

app.Run();

partial class ExampleHandler(ILogger<ExampleHandler> logger)
{
    public string HandleRequest()
    {
        LogHandleRequest(logger);
        return "Hello World";
    }

    [LoggerMessage(LogLevel.Information, "ExampleHandler.HandleRequest was called")]
    public static partial void LogHandleRequest(ILogger logger);
}

Für das vorherige Beispiel gilt Folgendes:

  • Erstellte einen Singletondienst namens ExampleHandler und ordnete eingehende Webanforderungen zu, um die Funktion ExampleHandler.HandleRequest auszuführen.
  • In Zeile 8 wird ein primärer Konstruktor für den ExampleHandler definiert, ein Feature, das in C# 12 hinzugefügt wurde. Die Verwendung des älteren C#-Konstruktors würde genauso gut funktionieren, ist aber etwas länger.
  • Der Konstruktor definiert einen Parameter vom Typ ILogger<ExampleHandler>. ILogger<TCategoryName> ist von ILogger abgeleitet und gibt an, zu welcher Kategorie das ILogger-Objekt gehört. Der DI-Container sucht ein ILogger-Objekt der richtigen Kategorie und gibt es als Konstruktorargument an. Wenn noch kein ILogger-Objekt dieser Kategorie vorhanden ist, erstellt der DI-Container es automatisch aus ILoggerFactory im Dienstanbieter.
  • Der im Konstruktor empfangene Parameter logger wurde für die Protokollierung in der Funktion HandleRequest verwendet.

Vom Host bereitgestellte ILoggerFactory-Instanz

Host-Generatoren initialisieren die Standardkonfiguration und fügen dann ein konfiguriertes ILoggerFactory-Objekt dem DI-Container des Hosts hinzu, wenn der Host erstellt wird. Bevor der Host erstellt wird, können Sie die Protokollierungskonfiguration über HostApplicationBuilder.Logging, WebApplicationBuilder.Logging oder ähnliche APIs auf anderen Hosts anpassen. Hosts wenden auch die Protokollierungskonfiguration aus Standardkonfigurationsquellen wie appsettings.json und Umgebungsvariablen an. Weitere Informationen finden Sie unter Konfiguration in .NET.

In diesem Beispiel wird das vorige Beispiel erweitert, um die von WebApplicationBuilder bereitgestellte ILoggerFactory-Schnittstelle anzupassen. Hier wird OpenTelemetry als Protokollierungsanbieter hinzugefügt, der die Protokolle über OTLP (OpenTelemetry-Protokoll) überträgt:

var builder = WebApplication.CreateBuilder(args);
builder.Logging.AddOpenTelemetry(logging => logging.AddOtlpExporter());
builder.Services.AddSingleton<ExampleHandler>();
var app = builder.Build();

Erstellen einer ILoggerFactory-Instanz mit DI

Wenn Sie einen DI-Container ohne Host verwenden, verwenden Sie AddLogging, um ILoggerFactory zu konfigurieren und dem Container hinzuzufügen.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

// Add services to the container including logging
var services = new ServiceCollection();
services.AddLogging(builder => builder.AddConsole());
services.AddSingleton<ExampleService>();
IServiceProvider serviceProvider = services.BuildServiceProvider();

// Get the ExampleService object from the container
ExampleService service = serviceProvider.GetRequiredService<ExampleService>();

// Do some pretend work
service.DoSomeWork(10, 20);

class ExampleService(ILogger<ExampleService> logger)
{
    public void DoSomeWork(int x, int y)
    {
        logger.LogInformation("DoSomeWork was called. x={X}, y={Y}", x, y);
    }
}

Für das vorherige Beispiel gilt Folgendes:

  • Erstellt einen DI-Dienstcontainer, der eine ILoggerFactory-Instanz enthält, die für das Schreiben in die Konsole konfiguriert wurde
  • Dem Container wurde ein Singleton ExampleService hinzugefügt.
  • Es wurde eine Instanz von ExampleService vom DI-Container erstellt, wobei auch automatisch eine ILogger<ExampleService>-Instanz erstellt wurde, die als Konstruktorargument verwendet werden soll.
  • Aufruf von ExampleService.DoSomeWork, mit dem eine ILogger<ExampleService> Meldung auf der Konsole zu protokollieren.

Konfigurieren der Protokollierung

Die Protokollierungskonfiguration wird im Code oder über externe Quellen festgelegt, z. B. Konfigurationsdateien und Umgebungsvariablen. Die Verwendung einer externer Konfiguration ist nach Möglichkeit von Vorteil, da sie geändert werden kann, ohne die Anwendung neu zu erstellen. Einige Aufgaben, z. B. das Festlegen der Protokollierungsanbieter, können jedoch nur über Code konfiguriert werden.

Konfigurieren der Protokollierung ohne Code

Für Apps, die einen Host verwenden, wird die Protokollierungskonfiguration meistens im Abschnitt "Logging" von appsettings.{Environment}.json-Dateien angegeben. Für Apps, die keinen Host verwenden, werden stattdessen externe Konfigurationsquellen explizit eingerichtet oder im Code konfiguriert.

Die folgende Datei appsettings.Development.json wird von den .NET-Workerdienstvorlagen generiert:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  }
}

Für den oben stehenden JSON-Code gilt:

  • Die Protokolliergradkategorien "Default", "Microsoft" und "Microsoft.Hosting.Lifetime" werden angegeben.
  • Der "Default"-Wert wird auf alle Kategorien angewendet, die nicht anderweitig angegeben sind, wodurch effektiv alle Standardwerte für alle Kategorien auf "Information" festgelegt werden. Sie können dieses Verhalten außer Kraft setzen, indem Sie einen Wert für eine Kategorie angeben.
  • Die Kategorie "Microsoft" gilt für alle Kategorien, die mit "Microsoft" beginnen.
  • Die Kategorie "Microsoft" protokolliert mit dem Protokolliergrad Warning oder höher.
  • Die Kategorie "Microsoft.Hosting.Lifetime" ist spezifischer als die Kategorie "Microsoft", sodass die Kategorie "Microsoft.Hosting.Lifetime" mit dem Protokolliergrad "Information" und höher protokolliert.
  • Ein bestimmter Protokollanbieter wird nicht angegeben, sodass LogLevel für alle aktivierten Protokollanbieter mit Ausnahme von Windows EventLog gilt.

Die Logging-Eigenschaft kann LogLevel und Protokollanbietereigenschaften beinhalten. LogLevel gibt den Mindestgrad an, der für ausgewählte Kategorien protokolliert werden soll. Im JSON-Code oben werden die Protokolliergrade Information und Warning angegeben. LogLevel gibt den Schweregrad des Protokolls an und liegt zwischen 0 und 6:

Trace = 0, Debug = 1, Information = 2, Warning = 3, Error = 4, Critical = 5 und None = 6.

Wenn LogLevel angegeben wird, wird Protokollierung für Meldungen mit dem angegebenen Protokolliergrad oder höher aktiviert. Im JSON-Code oben wird die Kategorie Default für Information oder höher protokolliert. Beispielsweise werden Information-, Warning-, Error- und Critical-Meldungen protokolliert. Wenn kein LogLevel angegeben wird, wird Protokollierung standardmäßig mit dem Protokolliergrad Information verwendet. Weitere Informationen finden Sie unter Protokolliergrade.

Eine Anbietereigenschaft kann eine LogLevel-Eigenschaft angeben. LogLevel unter einem Anbieter gibt die Protokolliergrade an, die für diesen Anbieter protokolliert werden sollen, und überschreibt die Nicht-Anbieterprotokolleinstellungen. Sehen Sie sich die nachfolgende Datei appsettings.json an:

{
    "Logging": {
        "LogLevel": {
            "Default": "Error",
            "Microsoft": "Warning"
        },
        "Debug": {
            "LogLevel": {
                "Default": "Information",
                "Microsoft.Hosting": "Trace"
            }
        },
        "EventSource": {
            "LogLevel": {
                "Default": "Warning"
            }
        }
    }
}

Die Einstellungen in Logging.{ProviderName}.LogLevel überschreiben die Einstellungen in Logging.LogLevel. Im JSON-Code oben wird der Standardprotokolliergrad Debug des Anbieters auf Information festgelegt:

Logging:Debug:LogLevel:Default:Information

Die Einstellung oben gibt den Protokolliergrad Information für jede Logging:Debug:-Kategorie mit Ausnahme von Microsoft.Hosting an. Wenn eine bestimmte Kategorie aufgelistet wird, überschreibt die jeweilige Kategorie die Standardkategorie. Im JSON-Code oben überschreiben die Logging:Debug:LogLevel-Kategorien "Microsoft.Hosting" und "Default" die Einstellungen in Logging:LogLevel.

Der Mindestprotokolliergrad kann für Folgendes angegeben werden:

  • Bestimmte Anbieter: Beispiel: Logging:EventSource:LogLevel:Default:Information
  • Bestimmte Kategorien: Beispiel: Logging:LogLevel:Microsoft:Warning
  • Alle Anbieter und alle Kategorien: Logging:LogLevel:Default:Warning

Alle Protokolle unterhalb des Mindestprotokolliergrads werden nicht:

  • An den Anbieter übergeben.
  • Protokolliert oder angezeigt.

Um alle Protokolle zu unterdrücken, geben Sie LogLevel.None an. LogLevel.None hat den Wert 6, der höher als LogLevel.Critical (5) ist.

Wenn ein Anbieter Protokollbereiche unterstützt, gibt IncludeScopes an, ob sie aktiviert sind. Weitere Informationen finden Sie unter Protokollierbereiche.

Die folgende Datei appsettings.json enthält Einstellungen für alle integrierten Anbieter:

{
    "Logging": {
        "LogLevel": {
            "Default": "Error",
            "Microsoft": "Warning",
            "Microsoft.Hosting.Lifetime": "Warning"
        },
        "Debug": {
            "LogLevel": {
                "Default": "Information"
            }
        },
        "Console": {
            "IncludeScopes": true,
            "LogLevel": {
                "Microsoft.Extensions.Hosting": "Warning",
                "Default": "Information"
            }
        },
        "EventSource": {
            "LogLevel": {
                "Microsoft": "Information"
            }
        },
        "EventLog": {
            "LogLevel": {
                "Microsoft": "Information"
            }
        },
        "AzureAppServicesFile": {
            "IncludeScopes": true,
            "LogLevel": {
                "Default": "Warning"
            }
        },
        "AzureAppServicesBlob": {
            "IncludeScopes": true,
            "LogLevel": {
                "Microsoft": "Information"
            }
        },
        "ApplicationInsights": {
            "LogLevel": {
                "Default": "Information"
            }
        }
    }
}

Im vorgehenden Beispiel:

  • Die Kategorien und Protokolliergrade sind keine vorgeschlagenen Werte. Das Beispiel wird bereitgestellt, um alle Standardanbieter zu zeigen.
  • Die Einstellungen in Logging.{ProviderName}.LogLevel überschreiben die Einstellungen in Logging.LogLevel. Beispielsweise überschreibt der Protokolliergrad in Debug.LogLevel.Default den Protokolliergrad in LogLevel.Default.
  • Der Alias der einzelnen Anbieter wird verwendet. Jeder Anbieter definiert einen Alias, der in der Konfiguration anstelle des vollqualifizierten Typnamens verwendet werden kann. Die folgenden Anbieteraliase sind integriert:
    • Console
    • Debug
    • EventSource
    • EventLog
    • AzureAppServicesFile
    • AzureAppServicesBlob
    • ApplicationInsights

Festlegen des Protokolliergrads über die Befehlszeile, Umgebungsvariablen und andere Konfigurationen

Der Protokolliergrad kann von einem beliebigen Konfigurationsanbieter festgelegt werden. Beispielsweise können Sie eine persistente Umgebungsvariable namens Logging:LogLevel:Microsoft mit dem Wert Information erstellen.

Erstellen Sie eine persistente Umgebungsvariable anhand des Werts für den Protokolliergrad.

:: Assigns the env var to the value
setx "Logging__LogLevel__Microsoft" "Information" /M

Lesen Sie die Umgebungsvariable in einer neuen Instanz der Eingabeaufforderung.

:: Prints the env var value
echo %Logging__LogLevel__Microsoft%

Die vorherige Umgebungseinstellung wird in der Umgebung persistent gespeichert. Um die Einstellungen bei Verwendung einer App zu testen, die mit den .NET-Workerdienstvorlagen erstellt wurde, verwenden Sie nach dem Zuweisen der Umgebungsvariablen den dotnet run-Befehl im Projektverzeichnis.

dotnet run

Tipp

Starten Sie nach dem Festlegen einer Umgebungsvariablen Ihre integrierte Entwicklungsumgebung (Integrated Development Environment, IDE) neu, um sicherzustellen, dass neu hinzugefügte Umgebungsvariablen verfügbar sind.

Klicken Sie in Azure App Service auf der Seite Einstellungen > Konfiguration auf Neue Anwendungseinstellung. Anwendungseinstellungen von Azure App Service werden:

  • Im Ruhezustand verschlüsselt und über einen verschlüsselten Kanal übermittelt.
  • Als Umgebungsvariablen verfügbar gemacht.

Weitere Informationen zum Festlegen von .NET-Konfigurationswerten mithilfe von Umgebungsvariablen finden Sie unter Umgebungsvariablen.

Konfigurieren der Protokollierung mit Code

Verwenden Sie die ILoggingBuilder-API, um die Protokollierung im Code zu konfigurieren. Von verschiedenen Orten aus kann darauf zugegriffen werden:

Dieses Beispiel zeigt das Festlegen des Protokollierungsanbieters „Konsole“ und mehrerer Filter.

using Microsoft.Extensions.Logging;

using var loggerFactory = LoggerFactory.Create(static builder =>
{
    builder
        .AddFilter("Microsoft", LogLevel.Warning)
        .AddFilter("System", LogLevel.Warning)
        .AddFilter("LoggingConsoleApp.Program", LogLevel.Debug)
        .AddConsole();
});

ILogger logger = loggerFactory.CreateLogger<Program>();
logger.LogDebug("Hello {Target}", "Everyone");

Im vorherigen Beispiel wird AddFilter verwendet, um die Protokollebene anzupassen, die für verschiedene Kategorien aktiviert ist. AddConsole wird verwendet, um den Konsolenprotokollierungsanbieter hinzuzufügen. Standardmäßig sind Protokolle mit dem Schweregrad Debug nicht aktiviert, aber da die Filter in der Konfiguration angepasst wurden, wird die Debugmeldung „Hello Everyone“ auf der Konsole angezeigt.

Anwendung von Filterregeln

Wenn ein ILogger<TCategoryName>-Objekt erstellt wird, wählt das ILoggerFactory-Objekt eine einzige Regel pro Anbieter aus, die auf diese Protokollierung angewendet wird. Alle von einer ILogger-Instanz geschriebenen Meldungen werden auf Grundlage der ausgewählten Regeln gefiltert. Aus den verfügbaren Regeln wird die für jeden Anbieter und jedes Kategoriepaar spezifischste Regel ausgewählt.

Beim Erstellen von ILogger für eine vorgegebene Kategorie wird für jeden Anbieter der folgende Algorithmus verwendet:

  • Es werden alle Regeln ausgewählt, die dem Anbieter oder dem zugehörigen Alias entsprechen. Wenn keine Übereinstimmung gefunden wird, werden alle Regeln mit einem leeren Anbieter ausgewählt.
  • Aus dem Ergebnis des vorherigen Schritts werden die Regeln mit dem längsten übereinstimmenden Kategoriepräfix ausgewählt. Wenn keine Übereinstimmung gefunden wird, werden alle Regeln ohne Angabe einer Kategorie ausgewählt.
  • Wenn mehrere Regeln ausgewählt sind, wird die letzte Regel verwendet.
  • Wenn keine Regeln ausgewählt sind, geben Sie über LoggingBuilderExtensions.SetMinimumLevel(ILoggingBuilder, LogLevel) den Mindestprotokolliergrad an.

Protokollkategorie

Wenn ein ILogger-Objekt erstellt wird, wird eine Kategorie angegeben. Diese Kategorie ist in jeder Protokollmeldung enthalten, die von dieser Instanz von ILogger erstellt wird. Die Kategoriezeichenfolge ist beliebig, aber die Konvention ist, den vollständig qualifizierten Klassennamen zu verwenden. Beispielsweise kann die Kategorie in einer Anwendung mit einem dem folgenden Objekt entsprechend definierten Dienst "Example.DefaultService" lauten:

namespace Example
{
    public class DefaultService : IService
    {
        private readonly ILogger<DefaultService> _logger;

        public DefaultService(ILogger<DefaultService> logger) =>
            _logger = logger;

        // ...
    }
}

Wenn eine weitere Kategorisierung erwünscht ist, besteht die Konvention darin, einen hierarchischen Namen zu verwenden, indem eine Unterkategorie an den voll qualifizierten Klassennamen angehängt und die Kategorie explizit mit LoggerFactory.CreateLogger angegeben wird:

namespace Example
{
    public class DefaultService : IService
    {
        private readonly ILogger _logger;

        public DefaultService(ILoggerFactory loggerFactory) =>
            _logger = loggerFactory.CreateLogger("Example.DefaultService.CustomCategory");

        // ...
    }
}

Das Aufrufen von CreateLogger mit einem festgelegten Namen kann bei Verwendung in mehreren Klassen/Typen nützlich sein, damit die Ereignisse nach Kategorie organisiert werden können.

ILogger<T> entspricht dem Aufruf von CreateLogger mit dem vollqualifizierten Typnamen T.

Protokolliergrad

In der folgenden Tabelle werden die LogLevel-Werte, die Erweiterungsmethode Log{LogLevel} und die empfohlene Syntax aufgeführt:

LogLevel Wert Methode BESCHREIBUNG
Ablaufverfolgung 0 LogTrace Enthält die ausführlichsten Meldungen. Diese Meldungen enthalten möglicherweise sensible App-Daten. Sie sind standardmäßig deaktiviert und sollten nicht in einer Produktionsumgebung aktiviert werden.
Debuggen 1 LogDebug Zum Debuggen und für die Entwicklung. Wegen des großen Volumens in der Produktion mit Vorsicht zu verwenden.
Information 2 LogInformation Verfolgt den allgemeinen Ablauf der App nach. Kann über einen langfristigen Wert verfügen.
Warnung 3 LogWarning Für ungewöhnliche oder unerwartete Ereignisse. Schließt in der Regel Fehler oder Bedingungen ein, die nicht bewirken, dass die App fehlschlägt.
Fehler 4 LogError Für Fehler und Ausnahmen, die nicht behandelt werden können. Diese Meldungen weisen auf einen Fehler im aktuellen Vorgang oder der aktuellen Anforderung und nicht auf einen anwendungsweiten Fehler hin.
Critical (Kritisch) 5 LogCritical Für Fehler, die sofortige Aufmerksamkeit erfordern. Beispiel: Szenarios mit Datenverlust, Speichermangel.
Keine 6 Gibt an, dass keine Meldungen geschrieben werden sollen.

In der Tabelle oben wird der LogLevel vom niedrigsten bis zum höchsten Schweregrad aufgelistet.

Der erste Parameter der Log-Methode (LogLevel) gibt den Schweregrad des Protokolls an. Anstatt Log(LogLevel, ...) aufzurufen, rufen die meisten Entwickler die Log{LogLevel}-Erweiterungsmethoden auf. Die Log{LogLevel}-Erweiterungsmethoden rufen die Log-Methode auf und geben den LogLevel an. Beispielsweise sind die folgenden beiden Protokollierungsaufrufe funktionell gleichwertig und generieren das gleiche Protokoll:

public void LogDetails()
{
    var logMessage = "Details for log.";

    _logger.Log(LogLevel.Information, AppLogEvents.Details, logMessage);
    _logger.LogInformation(AppLogEvents.Details, logMessage);
}

AppLogEvents.Details ist die Ereignis-ID, die implizit durch einen konstanten Int32-Wert dargestellt wird. AppLogEvents ist eine Klasse, die verschiedene benannte Bezeichnerkonstanten verfügbar macht und im Abschnitt Protokollereignis-ID angezeigt wird.

Der folgende Code erstellt Information- und Warning-Protokolle:

public async Task<T> GetAsync<T>(string id)
{
    _logger.LogInformation(AppLogEvents.Read, "Reading value for {Id}", id);

    var result = await _repository.GetAsync(id);
    if (result is null)
    {
        _logger.LogWarning(AppLogEvents.ReadNotFound, "GetAsync({Id}) not found", id);
    }

    return result;
}

Im Code oben ist der erste Log{LogLevel}-Parameter (AppLogEvents.Read) die Protokollereignis-ID. Der zweite Parameter ist eine Meldungsvorlage mit Platzhaltern für Argumentwerte, die von den verbleibenden Methodenparametern bereitgestellt werden. Die Methodenparameter werden im Abschnitt Meldungsvorlage weiter unten in diesem Artikel erläutert.

Konfigurieren Sie den geeigneten Protokolliergrad, und rufen Sie die richtigen Log{LogLevel}-Methoden auf, um die Menge der Protokollausgabe zu steuern, die in ein bestimmtes Speichermedium geschrieben werden. Zum Beispiel:

  • In einer Produktionsumgebung
    • Das Protokollieren mit den Protokolliergraden Trace oder Debug verursacht viele detaillierte Protokollmeldungen. Um die Kosten zu kontrollieren und die Datenspeichergrenzen nicht zu überschreiten, protokollieren Sie Meldungen der Protokolliergrade Trace und Debug in einem kostengünstigen Datenspeicher mit hohem Datenvolumen. Erwägen Sie eine Beschränkung von Trace und Debug auf bestimmte Kategorien.
    • Bei der Protokollierung mit den Protokolliergraden Warning bis Critical sollten nur wenige Protokollmeldungen generiert werden.
      • Kosten und Speichergrenzwerte stellen in der Regel kein Problem dar.
      • Wenige Protokolle ermöglichen eine größere Flexibilität bei der Auswahl von Datenspeichern.
  • Bei der Entwicklung:
    • Legen Sie Warning fest.
    • Fügen Sie bei der Problembehandlung Trace- oder Debug-Meldungen hinzu. Um die Ausgabe einzuschränken, legen Sie Trace oder Debug nur für die zu untersuchenden Kategorien fest.

Der folgende JSON-Code legt Logging:Console:LogLevel:Microsoft:Information fest:

{
    "Logging": {
        "LogLevel": {
            "Microsoft": "Warning"
        },
        "Console": {
            "LogLevel": {
                "Microsoft": "Information"
            }
        }
    }
}

Protokollereignis-ID

Jedes Protokoll kann einen Ereignisbezeichner angeben. EventId ist eine Struktur mit einer Id und optionalen schreibgeschützten Name-Eigenschaften. Im Beispielquellcode wird die AppLogEvents-Klasse zum Definieren von Ereignis-IDs verwendet:

using Microsoft.Extensions.Logging;

internal static class AppLogEvents
{
    internal static EventId Create = new(1000, "Created");
    internal static EventId Read = new(1001, "Read");
    internal static EventId Update = new(1002, "Updated");
    internal static EventId Delete = new(1003, "Deleted");

    // These are also valid EventId instances, as there's
    // an implicit conversion from int to an EventId
    internal const int Details = 3000;
    internal const int Error = 3001;

    internal static EventId ReadNotFound = 4000;
    internal static EventId UpdateNotFound = 4001;

    // ...
}

Tipp

Weitere Informationen zum Konvertieren eines int in eine EventId finden Sie unter EventId.Implicit(Int32 to EventId)-Operator.

Eine Ereignis-ID ordnet eine Gruppe von Ereignissen zu. Beispielsweise könnten alle Protokolle im Zusammenhang mit dem Lesen von Werten aus einem Repository 1001 lauten.

Der Protokollierungsanbieter kann die Ereignis-ID in einem ID-Feld, in der Protokollierungsmeldung oder gar nicht protokollieren. Der Debuganbieter zeigt keine Ereignis-IDs an. Der Konsolenanbieter zeigt Ereignis-IDs in Klammern hinter der Kategorie an:

info: Example.DefaultService.GetAsync[1001]
      Reading value for a1b2c3
warn: Example.DefaultService.GetAsync[4000]
      GetAsync(a1b2c3) not found

Einige Protokollierungsanbieter speichern die Ereignis-ID in einem Feld, das das Filtern nach der ID ermöglicht.

Protokollmeldungsvorlage

Jede Protokoll-API verwendet eine Meldungsvorlage. Die Meldungsvorlage kann Platzhalter enthalten, für die Argumente bereitgestellt werden. Verwenden Sie Namen für die Platzhalter, keine Zahlen. Die Reihenfolge der Platzhalter – nicht ihre Namen – bestimmt, welche Parameter verwendet werden, um ihre Werte zur Verfügung zu stellen. Im folgenden Code befinden sich die Parameternamen in der Meldungsvorlage nicht in der richtigen Reihenfolge:

string p1 = "param1";
string p2 = "param2";
_logger.LogInformation("Parameter values: {p2}, {p1}", p1, p2);

Dieser Code oben erstellt eine Protokollierungsmeldung mit den Parameterwerten in der richtigen Reihenfolge:

Parameter values: param1, param2

Hinweis

Gehen Sie bei der Verwendung mehrerer Platzhalter innerhalb einer einzelnen Nachrichtenvorlage sorgfältig vor, da diese ordnungszahlbasiert sind. Die Namen werden nicht verwendet, um die Argumente an den Platzhaltern auszurichten.

Dieser Ansatz ermöglicht es Protokollierungsanbietern, semantische oder strukturierte Protokollierung zu implementieren. Die Argumente selbst (nicht nur die formatierte Meldungsvorlage) werden an das Protokollierungssystem übergeben. Dies ermöglicht es Protokollierungsanbietern, die Parameterwerte als Felder zu speichern. Sehen Sie sich die folgende Protokollierungsmethode an:

_logger.LogInformation("Getting item {Id} at {RunTime}", id, DateTime.Now);

Beispielsweise bei der Protokollierung in Azure Table Storage:

  • Jede Azure Table-Entität kann über ID- und RunTime-Eigenschaften verfügen.
  • Tabellen mit Eigenschaften vereinfachen Abfragen für protokollierte Daten. Beispielsweise kann eine Abfrage alle Protokolle innerhalb eines bestimmten RunTime-Bereichs ermitteln, ohne die Zeitangabe aus der Textnachricht analysieren zu müssen.

Formatierung von Protokollnachrichtenvorlagen

Protokollnachrichtenvorlagen unterstützen Platzhalterformatierung. Vorlagen können ein beliebiges gültiges Format für das vorgegebene Typargument angeben. Sehen Sie sich z. B. die folgende Information-Protokollierungsnachrichtenvorlage an:

_logger.LogInformation("Logged on {PlaceHolderName:MMMM dd, yyyy}", DateTimeOffset.UtcNow);
// Logged on January 06, 2022

Im vorherigen Beispiel ist die DateTimeOffset-Instanz der Typ, der dem PlaceHolderName in der Protokollierungsnachrichtenvorlage entspricht. Dieser Name kann beliebig sein, da die Werte ordnungszahlbasiert sind. Das MMMM dd, yyyy-Format ist für den DateTimeOffset-Typ gültig.

Weitere Informationen zur DateTime- und DateTimeOffset-Formatierung finden Sie unter Benutzerdefinierte Datums- und Uhrzeitformatzeichenfolgen.

Beispiele

Die folgenden Beispiele zeigen, wie eine Nachrichtenvorlage mithilfe der {}-Platzhaltersyntax formatiert wird. Außerdem wird ein Beispiel für das Escapen der {}-Platzhaltersyntax zusammen mit seiner Ausgabe gezeigt. Schließlich wird auch die Zeichenfolgeninterpolation mit Vorlagenerstellungsplatzhaltern gezeigt:

logger.LogInformation("Number: {Number}", 1);               // Number: 1
logger.LogInformation("{{Number}}: {Number}", 3);           // {Number}: 3
logger.LogInformation($"{{{{Number}}}}: {{Number}}", 5);    // {Number}: 5

Tipp

  • In den meisten Fällen sollten Sie bei der Protokollierung die Formatierung der Protokollnachrichtenvorlage verwenden. Die Verwendung der Zeichenfolgeninterpolation kann zu Leistungsproblemen führen.
  • Die Codeanalyseregel CA2254: Vorlage sollte ein statischer Ausdruck sein hilft Ihnen bei der Warnung vor Stellen, an denen Ihre Protokollnachrichten nicht die richtige Formatierung verwenden.

Protokollieren von Ausnahmen

Die Protokollierungsmethoden verfügen über Überladungen, die einen Ausnahmeparameter annehmen:

public void Test(string id)
{
    try
    {
        if (id is "none")
        {
            throw new Exception("Default Id detected.");
        }
    }
    catch (Exception ex)
    {
        _logger.LogWarning(
            AppLogEvents.Error, ex,
            "Failed to process iteration: {Id}", id);
    }
}

Ausnahmeprotokollierung ist anbieterspezifisch.

Standardprotokolliergrad

Wenn der Standardprotokolliergrad nicht festgelegt ist, ist der Standardwert des Standardprotokolliergrads Information.

Sehen Sie sich zum Beispiel die folgende Workerdienst-App an:

  • Sie wurde mit den .NET-Workervorlagen erstellt.
  • appsettings.json und appsettings.Development.json wurden gelöscht oder umbenannt.

Mit dem oben beschriebenen Setup generiert die Navigation zur Datenschutz- oder Homepage viele Trace-, Debug- und Information-Meldungen mit Microsoft im Kategorienamen.

Mit dem folgenden Code wird der Standardprotokolliergrad festgelegt, wenn der Standardprotokolliergrad nicht in der Konfiguration festgelegt ist:

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Logging.SetMinimumLevel(LogLevel.Warning);

using IHost host = builder.Build();

await host.RunAsync();

Filterfunktion

Eine Filterfunktion wird für alle Anbieter und Kategorien aufgerufen, denen keine Regeln durch Konfiguration oder Code zugewiesen sind:

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Logging.AddFilter((provider, category, logLevel) =>
{
    return provider.Contains("ConsoleLoggerProvider")
        && (category.Contains("Example") || category.Contains("Microsoft"))
        && logLevel >= LogLevel.Information;
});

using IHost host = builder.Build();

await host.RunAsync();

Der Code oben zeigt Konsolenprotokolle an, wenn die Kategorie Example oder Microsoft enthält und der Protokolliergrad Information oder höher ist.

Protokollbereiche

Ein Bereich gruppiert eine Reihe von logischen Vorgängen. Diese Gruppierung kann verwendet werden, um an jedes Protokoll, das als Teil einer Gruppe erstellt wird, die gleichen Daten anzufügen. So kann beispielsweise jedes Protokoll, das im Rahmen der Verarbeitung einer Transaktion erstellt wird, die Transaktions-ID enthalten.

Ein Bereich:

  • Ist ein IDisposable-Typ, der von der BeginScope-Methode zurückgegeben wird.
  • Er bleibt bestehen, bis er verworfen wird.

Die folgenden Anbieter unterstützen Bereiche:

Verwenden Sie einen Bereich, indem Sie Protokollierungsaufrufe mit einem using-Block umschließen, der als Wrapper verwendet wird:

public async Task<T> GetAsync<T>(string id)
{
    T result;
    var transactionId = Guid.NewGuid().ToString();

    using (_logger.BeginScope(new List<KeyValuePair<string, object>>
        {
            new KeyValuePair<string, object>("TransactionId", transactionId),
        }))
    {
        _logger.LogInformation(
            AppLogEvents.Read, "Reading value for {Id}", id);

        var result = await _repository.GetAsync(id);
        if (result is null)
        {
            _logger.LogWarning(
                AppLogEvents.ReadNotFound, "GetAsync({Id}) not found", id);
        }
    }

    return result;
}

Der folgende JSON-Code aktiviert Bereiche für den Konsolenanbieter:

{
    "Logging": {
        "Debug": {
            "LogLevel": {
                "Default": "Information"
            }
        },
        "Console": {
            "IncludeScopes": true,
            "LogLevel": {
                "Microsoft": "Warning",
                "Default": "Information"
            }
        },
        "LogLevel": {
            "Default": "Debug"
        }
    }
}

Der folgende Code aktiviert Bereiche für den Konsolenanbieter:

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Logging.ClearProviders();
builder.Logging.AddConsole(options => options.IncludeScopes = true);

using IHost host = builder.Build();

await host.RunAsync();

Erstellen von Protokollen in Main

Der folgende Code führt Protokollierung in Main aus, indem nach dem Erstellen des Hosts eine ILogger-Instanz von der Abhängigkeitsinjektion abgerufen wird:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

using IHost host = Host.CreateApplicationBuilder(args).Build();

var logger = host.Services.GetRequiredService<ILogger<Program>>();
logger.LogInformation("Host created.");

await host.RunAsync();

Der vorangehende Code basiert auf zwei NuGet-Paketen:

Die Projektdatei sähe in etwa wie folgt aus:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net7.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
    <PackageReference Include="Microsoft.Extensions.Logging" Version="7.0.0" />
  </ItemGroup>

</Project>

Keine asynchronen Protokollierungsmethoden

Die Protokollierung sollte so schnell erfolgen, dass sich die Leistungskosten von asynchronem Code nicht lohnen. Wenn ein Protokollierungsdatenspeicher langsam ist, schreiben Sie nicht direkt in diesen. Erwägen Sie, die Protokollnachrichten zunächst in einen schnellen Speicher zu schreiben und sie dann später in den langsamen Speicher zu verschieben. Wenn Sie beispielsweise in SQL Server protokollieren, verwenden Sie nicht direkt eine Log-Methode, da die Log-Methoden synchron sind. Fügen Sie stattdessen die Protokollmeldungen synchron zu einer Warteschlange im Arbeitsspeicher hinzu, und rufen Sie mithilfe eines Hintergrundworkers die Nachrichten aus der Warteschlange ab, um sie dann asynchron per Pushvorgang an SQL Server zu übertragen.

Ändern von Protokolliergraden in einer aktuell ausgeführten App

Die Protokollierungs-API umfasst kein Szenario zum Ändern der Protokollebene, während eine App ausgeführt wird. Einige Konfigurationsanbieter können jedoch die Konfiguration erneut laden, was sich unmittelbar auf die Protokollierungskonfiguration auswirkt. Beispielsweise lädt der Dateikonfigurationsanbieter die Protokollierungskonfiguration standardmäßig neu. Wenn die Konfiguration im Code geändert wird, während eine App ausgeführt wird, kann die App IConfigurationRoot.Reload aufrufen, um Protokollierungskonfiguration der App zu aktualisieren.

NuGet-Pakete

Die Schnittstellen ILogger<TCategoryName> und ILoggerFactory sowie Implementierungen sind in den meisten .NET-SDKs als impliziter Paketverweis enthalten. Sie sind auch explizit in den folgenden NuGet-Paketen verfügbar, wenn nicht anderweitig implizit auf sie verwiesen wird:

Weitere Informationen zu den .NET SDKs, die implizite Paketverweise enthalten, finden Sie in der Tabelle mit den impliziten Namespaces für .NET SDKs.

Siehe auch