Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Mit .NET 6 wird der Typ LoggerMessageAttribute eingeführt. Dieses Attribut ist Teil des Namespace Microsoft.Extensions.Logging und führt bei Verwendung eine Quellgenerierung von APIs zur leistungsbasierten Protokollierung durch. Die Protokollierungsunterstützung für die Quellgenerierung wurde entwickelt, um eine hochverwertbare und äußerst leistungsfähige Protokollierungslösung für moderne .NET-Anwendungen zu bieten. Der automatisch generierte Quellcode basiert auf der ILogger-Schnittstelle in Verbindung mit der LoggerMessage.Define-Funktionalität.
Der Quellgenerator wird ausgelöst, wenn LoggerMessageAttribute für partial-Protokollierungsmethoden verwendet wird. Wenn dies ausgelöst wird, kann sie entweder die Implementierung der partial zu erstellenden Methoden automatisch generieren oder kompilierte Zeitdiagnosen mit Hinweisen zur ordnungsgemäßen Verwendung erstellen. Die Kompilierungszeitprotokollierungslösung ist zur Laufzeit wesentlich schneller als vorhandene Protokollierungsansätze. Dies wird erreicht, indem Boxing, temporäre Zuordnungen und Kopien so weit wie möglich beseitigt werden.
Grundlegende Verwendung
Um LoggerMessageAttribute zu verwenden, müssen die nutzende Klasse und Methode partial sein. Der Codegenerator wird zur Kompilierzeit ausgelöst und generiert eine Implementierung der partial-Methode.
public static partial class Log
{
[LoggerMessage(
EventId = 0,
Level = LogLevel.Critical,
Message = "Could not open socket to `{HostName}`")]
public static partial void CouldNotOpenSocket(
ILogger logger, string hostName);
}
Im vorherigen Beispiel ist die Protokollierungsmethode static, und die Protokollebene wird in der Attributdefinition angegeben. Wenn Sie das Attribut in einem statischen Kontext verwenden, ist entweder die ILogger-Instanz als Parameter erforderlich, oder Sie ändern die Definition so, dass sie das this-Schlüsselwort verwendet, um die Methode als Erweiterungsmethode zu definieren.
public static partial class Log
{
[LoggerMessage(
EventId = 0,
Level = LogLevel.Critical,
Message = "Could not open socket to `{HostName}`")]
public static partial void CouldNotOpenSocket(
this ILogger logger, string hostName);
}
Sie können auch das Attribut in einem nicht statischen Kontext verwenden. Betrachten Sie das folgende Beispiel, in dem die Protokollierungsmethode als Instanzmethode deklariert wird. In diesem Kontext ruft die Protokollierungsmethode die Protokollierung ab, indem sie auf ein ILogger-Feld in der enthaltenden Klasse zugreift.
public partial class InstanceLoggingExample
{
private readonly ILogger _logger;
public InstanceLoggingExample(ILogger logger)
{
_logger = logger;
}
[LoggerMessage(
EventId = 0,
Level = LogLevel.Critical,
Message = "Could not open socket to `{HostName}`")]
public partial void CouldNotOpenSocket(string hostName);
}
Ab .NET 9 kann die Protokollierungsmethode zusätzlich den Logger aus einem primären ILogger-Konstruktorparameter in der enthaltenden Klasse abrufen.
public partial class InstanceLoggingExample(ILogger logger)
{
[LoggerMessage(
EventId = 0,
Level = LogLevel.Critical,
Message = "Could not open socket to `{HostName}`")]
public partial void CouldNotOpenSocket(string hostName);
}
Wenn sowohl ein ILogger Feld als auch ein primärer Konstruktorparameter vorhanden sind, ruft die Protokollierungsmethode den Logger aus dem Feld ab.
Manchmal muss die Protokollebene dynamisch sein, anstatt statisch in den Code integriert zu werden. Sie können dies erreichen, indem Sie den Protokolliergrad aus dem Attribut weglassen und stattdessen als erforderlichen Parameter für die Protokollierungsmethode festlegen.
public static partial class Log
{
[LoggerMessage(
EventId = 0,
Message = "Could not open socket to `{HostName}`")]
public static partial void CouldNotOpenSocket(
ILogger logger,
LogLevel level, /* Dynamic log level as parameter, rather than defined in attribute. */
string hostName);
}
Sie können die Protokollierungsnachricht weglassen und String.Empty ist für die Nachricht angegeben. Der Zustand enthält die Argumente, die als Schlüsselwertpaare formatiert sind.
using System.Text.Json;
using Microsoft.Extensions.Logging;
using ILoggerFactory loggerFactory = LoggerFactory.Create(
builder =>
builder.AddJsonConsole(
options =>
options.JsonWriterOptions = new JsonWriterOptions()
{
Indented = true
}));
ILogger<SampleObject> logger = loggerFactory.CreateLogger<SampleObject>();
logger.PlaceOfResidence(logLevel: LogLevel.Information, name: "Liana", city: "Seattle");
readonly file record struct SampleObject { }
public static partial class Log
{
[LoggerMessage(EventId = 23, Message = "{Name} lives in {City}.")]
public static partial void PlaceOfResidence(
this ILogger logger,
LogLevel logLevel,
string name,
string city);
}
Betrachten Sie die Beispielprotokollierungsausgabe bei Verwendung des JsonConsole-Formatierers.
{
"EventId": 23,
"LogLevel": "Information",
"Category": "\u003CProgram\u003EF...9CB42__SampleObject",
"Message": "Liana lives in Seattle.",
"State": {
"Message": "Liana lives in Seattle.",
"name": "Liana",
"city": "Seattle",
"{OriginalFormat}": "{Name} lives in {City}."
}
}
Einschränkungen für Protokollmethoden
Bei Verwendung von LoggerMessageAttribute für Protokollierungsmethoden müssen einige Einschränkungen beachtet werden:
- Protokollierungsmethoden müssen
partialsein undvoidzurückgeben. - Protokollierungsmethodennamen dürfen nicht mit einem Unterstrich beginnen.
- Parameternamen von Protokollierungsmethoden dürfen nicht mit einem Unterstrich beginnen.
- Protokollierungsmethoden können nicht generisch sein.
- Wenn eine Protokollierungsmethode ist
static, ist dieILogger-Instanz als Parameter erforderlich.
Das Codegenerierungsmodell erfordert, dass Code mit einem modernen C#-Compiler (Version 9 oder höher) kompiliert wird. Der C# 9.0-Compiler wurde mit .NET 5 verfügbar. Bearbeiten Sie Ihre Projektdatei so, dass sie auf C# 9.0 ausgerichtet ist, um ein Upgrade auf einen modernen C#-Compiler durchzuführen.
<PropertyGroup>
<LangVersion>9.0</LangVersion>
</PropertyGroup>
Weitere Informationen finden Sie unter C#-Sprachversionsverwaltung.
Struktur der Protokollierungsmethode
Die ILogger.Log-Signatur akzeptiert LogLevel und optional ein Exception, wie im folgenden Codebeispiel gezeigt.
public interface ILogger
{
void Log<TState>(
Microsoft.Extensions.Logging.LogLevel logLevel,
Microsoft.Extensions.Logging.EventId eventId,
TState state,
System.Exception? exception,
Func<TState, System.Exception?, string> formatter);
}
Als allgemeine Regel wird die erste Instanz von ILogger, LogLevel und Exception speziell in der Protokollierungsmethodensignatur des Quellgenerators behandelt. Nachfolgende Instanzen werden wie normale Parameter der Meldungsvorlage behandelt:
// This is a valid attribute usage
[LoggerMessage(
EventId = 110, Level = LogLevel.Debug, Message = "M1 {Ex3} {Ex2}")]
public static partial void ValidLogMethod(
ILogger logger,
Exception ex,
Exception ex2,
Exception ex3);
// This causes a warning
[LoggerMessage(
EventId = 0, Level = LogLevel.Debug, Message = "M1 {Ex} {Ex2}")]
public static partial void WarningLogMethod(
ILogger logger,
Exception ex,
Exception ex2);
Wichtig
Die ausgegebenen Warnungen enthalten Details zur richtigen Verwendung von LoggerMessageAttribute. Im vorherigen Beispiel meldet WarningLogMethod eine DiagnosticSeverity.Warning von SYSLIB0025.
Don't include a template for `ex` in the logging message since it is implicitly taken care of.
Unterstützung von Vorlagennamen ohne Unterscheidung nach Groß-/Kleinschreibung
Der Generator vergleicht Elemente in der Meldungsvorlage und Argumentnamen in der Protokollmeldung ohne Unterscheidung von Groß-/Kleinschreibung. Dies bedeutet, dass beim Aufzählen des Zustands durch ILogger das Argument von der Meldungsvorlage verwendet wird, wodurch die Protokolle besser verarbeitet werden können:
public partial class LoggingExample
{
private readonly ILogger _logger;
public LoggingExample(ILogger logger)
{
_logger = logger;
}
[LoggerMessage(
EventId = 10,
Level = LogLevel.Information,
Message = "Welcome to {City} {Province}!")]
public partial void LogMethodSupportsPascalCasingOfNames(
string city, string province);
public void TestLogging()
{
LogMethodSupportsPascalCasingOfNames("Vancouver", "BC");
}
}
Betrachten Sie die Beispielprotokollierungsausgabe bei Verwendung des JsonConsole-Formatierers:
{
"EventId": 13,
"LogLevel": "Information",
"Category": "LoggingExample",
"Message": "Welcome to Vancouver BC!",
"State": {
"Message": "Welcome to Vancouver BC!",
"City": "Vancouver",
"Province": "BC",
"{OriginalFormat}": "Welcome to {City} {Province}!"
}
}
Unbestimmte Parameterreihenfolge
Es gibt keine Einschränkungen für die Reihenfolge von Protokollierungsmethodenparametern. Ein Entwickler könnte den ILogger letzten Parameter definieren, obwohl er möglicherweise etwas ungünstig erscheint.
[LoggerMessage(
EventId = 110,
Level = LogLevel.Debug,
Message = "M1 {Ex3} {Ex2}")]
static partial void LogMethod(
Exception ex,
Exception ex2,
Exception ex3,
ILogger logger);
Tipp
Die Reihenfolge der Parameter in einer Protokollmethode ist nicht erforderlich, um der Reihenfolge der Vorlagenplatzhalter zu entsprechen. Stattdessen wird erwartet, dass die Platzhalternamen in der Vorlage mit den Parametern übereinstimmen. Betrachten Sie die folgende JsonConsole-Ausgabe und die Reihenfolge der Fehler.
{
"EventId": 110,
"LogLevel": "Debug",
"Category": "ConsoleApp.Program",
"Message": "M1 System.Exception: Third time's the charm. System.Exception: This is the second error.",
"State": {
"Message": "M1 System.Exception: Third time's the charm. System.Exception: This is the second error.",
"ex2": "System.Exception: This is the second error.",
"ex3": "System.Exception: Third time's the charm.",
"{OriginalFormat}": "M1 {Ex3} {Ex2}"
}
}
Weitere Protokollierungsbeispiele
In den folgenden Beispielen wird veranschaulicht, wie Sie den Ereignisnamen abrufen, den Protokolliergrad dynamisch festlegen und Protokollierungsparameter formatieren. Die Protokollierungsmethoden sind:
-
LogWithCustomEventName: Abrufen des Ereignisnamens über das AttributLoggerMessage -
LogWithDynamicLogLevel: Dynamisches Festlegen der Protokollebene, damit die Protokollebene basierend auf Konfigurationseingaben festgelegt werden kann -
UsingFormatSpecifier: Verwenden von Formatbezeichnern zum Formatieren von Protokollierungsparametern
public partial class LoggingSample
{
private readonly ILogger _logger;
public LoggingSample(ILogger logger)
{
_logger = logger;
}
[LoggerMessage(
EventId = 20,
Level = LogLevel.Critical,
Message = "Value is {Value:E}")]
public static partial void UsingFormatSpecifier(
ILogger logger, double value);
[LoggerMessage(
EventId = 9,
Level = LogLevel.Trace,
Message = "Fixed message",
EventName = "CustomEventName")]
public partial void LogWithCustomEventName();
[LoggerMessage(
EventId = 10,
Message = "Welcome to {City} {Province}!")]
public partial void LogWithDynamicLogLevel(
string city, LogLevel level, string province);
public void TestLogging()
{
LogWithCustomEventName();
LogWithDynamicLogLevel("Vancouver", LogLevel.Warning, "BC");
LogWithDynamicLogLevel("Vancouver", LogLevel.Information, "BC");
UsingFormatSpecifier(logger, 12345.6789);
}
}
Betrachten Sie die Beispielprotokollierungsausgabe bei Verwendung des SimpleConsole-Formatierers:
trce: LoggingExample[9]
Fixed message
warn: LoggingExample[10]
Welcome to Vancouver BC!
info: LoggingExample[10]
Welcome to Vancouver BC!
crit: LoggingExample[20]
Value is 1.234568E+004
Betrachten Sie die Beispielprotokollierungsausgabe bei Verwendung des JsonConsole-Formatierers:
{
"EventId": 9,
"LogLevel": "Trace",
"Category": "LoggingExample",
"Message": "Fixed message",
"State": {
"Message": "Fixed message",
"{OriginalFormat}": "Fixed message"
}
}
{
"EventId": 10,
"LogLevel": "Warning",
"Category": "LoggingExample",
"Message": "Welcome to Vancouver BC!",
"State": {
"Message": "Welcome to Vancouver BC!",
"city": "Vancouver",
"province": "BC",
"{OriginalFormat}": "Welcome to {City} {Province}!"
}
}
{
"EventId": 10,
"LogLevel": "Information",
"Category": "LoggingExample",
"Message": "Welcome to Vancouver BC!",
"State": {
"Message": "Welcome to Vancouver BC!",
"city": "Vancouver",
"province": "BC",
"{OriginalFormat}": "Welcome to {City} {Province}!"
}
}
{
"EventId": 20,
"LogLevel": "Critical",
"Category": "LoggingExample",
"Message": "Value is 1.234568E+004",
"State": {
"Message": "Value is 1.234568E+004",
"value": 12345.6789,
"{OriginalFormat}": "Value is {Value:E}"
}
}
Das Redigieren vertraulicher Informationen in Protokollen
Beim Protokollieren vertraulicher Daten ist es wichtig, versehentliche Gefährdungen zu verhindern. Auch bei kompilierungszeitgenerierten Protokollierungsmethoden kann die Protokollierung unformatierter vertraulicher Werte zu Datenlecks und Complianceproblemen führen.
Die Microsoft.Extensions.Telemetry-Bibliothek bietet erweiterte Protokollierungs- und Telemetrieanreicherungsfunktionen für .NET-Anwendungen. Die Protokollierungspipeline wird erweitert, um beim Schreiben von Protokollen klassifizierte Daten automatisch zu schwärzen. Dadurch können Sie Datenschutzrichtlinien in Ihrer gesamten Anwendung erzwingen, indem Sie Schwärzung in Ihren Protokollierungsworkflow integrieren. Es wurde für Anwendungen entwickelt, die anspruchsvolle Telemetrie- und Protokollierungserkenntnisse benötigen.
Verwenden Sie zum Aktivieren der Redaction die Microsoft.Extensions.Compliance.Redaction-Bibliothek . Diese Bibliothek bietet Redactors – Komponenten, die vertrauliche Daten transformieren (z. B. durch Löschen, Maskieren oder Hashing), damit sie sicher ausgegeben werden können. Redactors werden basierend auf der Datenklassifizierung ausgewählt, mit der Sie Daten entsprechend ihrer Vertraulichkeit (z. B. persönlich, privat oder öffentlich) bezeichnen können.
Um die Redaktion mit Methoden für die quellgenerierten Protokollierung zu verwenden, sollten Sie:
- Klassifizieren Sie Ihre vertraulichen Daten mithilfe eines Datenklassifizierungssystems.
- Registrieren und konfigurieren Sie Redactors für jede Klassifizierung in Ihrem DI-Container.
- Aktivieren Sie die Redaction in der Protokollierungspipeline.
- Überprüfen Sie Ihre Protokolle, um sicherzustellen, dass keine vertraulichen Daten verfügbar gemacht werden.
Wenn Sie beispielsweise über eine Protokollmeldung verfügen, die einen Parameter enthält, der als privat betrachtet wird:
[LoggerMessage(0, LogLevel.Information, "User SSN: {SSN}")]
public static partial void LogPrivateInformation(
this ILogger logger,
[MyTaxonomyClassifications.Private] string SSN);
Sie müssen eine Konfiguration ähnlich der folgenden haben:
using Microsoft.Extensions.Telemetry;
using Microsoft.Extensions.Compliance.Redaction;
var services = new ServiceCollection();
services.AddLogging(builder =>
{
// Enable redaction.
builder.EnableRedaction();
});
services.AddRedaction(builder =>
{
// configure redactors for your data classifications
builder.SetRedactor<StarRedactor>(MyTaxonomyClassifications.Private);
});
public void TestLogging()
{
LogPrivateInformation("MySSN");
}
Die Ausgabe sollte wie folgt aussehen:
User SSN: *****
Dieser Ansatz stellt sicher, dass nur redigierte Daten protokolliert werden, auch wenn bei der Kompilierung generierte Protokollierungs-APIs verwendet werden. Sie können unterschiedliche Redactors für unterschiedliche Datentypen oder Klassifizierungen verwenden und Ihre Redaction-Logik zentral aktualisieren.
Weitere Informationen zum Klassifizieren Ihrer Daten finden Sie unter "Datenklassifizierung" in .NET. Weitere Informationen zu Schwärzen und Schwärzungstools finden Sie unter Data redaction in .NET.
Zusammenfassung
Mit der Einführung von C#-Quellgeneratoren ist das Schreiben von leistungsstarken APIs einfacher. Die Verwendung des Quellgeneratoransatzes hat mehrere wichtige Vorteile:
- Ermöglicht das Beibehalten der Protokollierungsstruktur und die genaue Formatsyntax, die für Meldungsvorlagen erforderlich ist
- Ermöglicht das Angeben alternativer Namen für die Vorlagenplatzhalter und die Verwendung von Formatbezeichnern
- Ermöglicht die Übergabe aller unveränderten Originaldaten ohne Komplikationen bei der Art ihrer Speicherung, bevor sie in irgendeiner Weise verarbeitet werden (anders als das Erstellen einer
string). - Stellt protokollierungsspezifische Diagnosen bereit und gibt Warnungen für doppelte Ereignis-IDs aus.
Darüber hinaus gibt es Vorteile gegenüber der manuellen Verwendung von LoggerMessage.Define:
- Kürzere und einfachere Syntax: deklarative Attributverwendung anstelle von Codebausteinen
- Geführter Entwicklungsprozess: Der Generator gibt Warnungen aus, um Fehler bei der Entwicklung zu vermeiden.
- Unterstützung für eine beliebige Anzahl von Protokollierungsparametern
LoggerMessage.Defineunterstützt maximal sechs. - Unterstützung für dynamische Protokollebene Dies ist mit
LoggerMessage.Defineallein nicht möglich.