Genereren van bronbron voor compileertijd

.NET 6 introduceert het LoggerMessageAttribute type. Dit kenmerk maakt deel uit van de Microsoft.Extensions.Logging naamruimte en wanneer dit wordt gebruikt, genereert het bron-API's voor performante logboekregistratie. De ondersteuning voor logboekregistratie van de brongeneratie is ontworpen om een zeer bruikbare en zeer goed presterende logboekregistratieoplossing te bieden voor moderne .NET-toepassingen. De automatisch gegenereerde broncode is afhankelijk van de ILogger interface in combinatie met LoggerMessage.Define functionaliteit.

De brongenerator wordt geactiveerd wanneer LoggerMessageAttribute deze wordt gebruikt voor partial logboekregistratiemethoden. Wanneer deze wordt geactiveerd, kan de implementatie automatisch worden gegenereerd van de partial methoden die het decoreert of compileertijddiagnose met hints over het juiste gebruik. De oplossing voor het compileren van logboekregistratie is doorgaans aanzienlijk sneller tijdens runtime dan bij bestaande logboekregistratiemethoden. Dit wordt bereikt door boksen, tijdelijke toewijzingen en kopieën zoveel mogelijk te elimineren.

Basaal gebruik

De verbruikende klasse en methode moeten zijn partialom de LoggerMessageAttributete gebruiken. De codegenerator wordt geactiveerd tijdens het compileren en genereert een implementatie van de 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);
}

In het voorgaande voorbeeld is static de methode voor logboekregistratie en wordt het logboekniveau opgegeven in de kenmerkdefinitie. Wanneer u het kenmerk in een statische context gebruikt, is het ILogger exemplaar vereist als parameter of wijzigt u de definitie om het this trefwoord te gebruiken om de methode te definiëren als een extensiemethode.

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);
}

U kunt er ook voor kiezen om het kenmerk in een niet-statische context te gebruiken. Bekijk het volgende voorbeeld waarbij de logboekregistratiemethode wordt gedeclareerd als een instantiemethode. In deze context haalt de logboekregistratiemethode de logboekregistratie op door toegang te krijgen tot een ILogger veld in de bevatde klasse.

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);
}

Soms moet het logboekniveau dynamisch zijn in plaats van statisch in de code te zijn ingebouwd. U kunt dit doen door het logboekniveau weg te laten van het kenmerk en in plaats daarvan te vereisen als parameter voor de logboekregistratiemethode.

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);
}

U kunt het logboekbericht weglaten en String.Empty wordt voor het bericht opgegeven. De status bevat de argumenten, opgemaakt als sleutel-waardeparen.

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);
}

Bekijk de voorbeelduitvoer van logboekregistratie wanneer u de JsonConsole formatter gebruikt.

{
  "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}."
  }
}

Beperkingen voor logboekmethoden

Wanneer u de LoggerMessageAttribute methoden voor logboekregistratie gebruikt, moeten enkele beperkingen worden gevolgd:

  • Logboekregistratiemethoden moeten worden gebruikt partial en moeten worden geretourneerd void.
  • Namen van logboekregistratiemethoden mogen niet beginnen met een onderstrepingsteken.
  • Parameternamen van logboekregistratiemethoden mogen niet beginnen met een onderstrepingsteken.
  • Logboekregistratiemethoden zijn mogelijk niet gedefinieerd in een genest type.
  • Logboekregistratiemethoden kunnen niet algemeen zijn.
  • Als een logboekregistratiemethode is static, is het ILogger exemplaar vereist als parameter.

Het model voor het genereren van code is afhankelijk van code die wordt gecompileerd met een moderne C#-compiler, versie 9 of hoger. De C# 9.0-compiler werd beschikbaar met .NET 5. Als u een upgrade wilt uitvoeren naar een moderne C#-compiler, bewerkt u het projectbestand naar C# 9.0.

<PropertyGroup>
  <LangVersion>9.0</LangVersion>
</PropertyGroup>

Zie C#-taalversiebeheer voor meer informatie.

Anatomie van logboekmethode

De ILogger.Log handtekening accepteert de LogLevel en eventueel een Exception, zoals hieronder wordt weergegeven.

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);
}

In de algemene regel wordt het eerste exemplaar van ILogger, LogLevelen Exception speciaal behandeld in de handtekening van de logboekmethode van de brongenerator. Volgende exemplaren worden behandeld als normale parameters voor de berichtsjabloon:

// 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);

Belangrijk

De verzonden waarschuwingen bevatten details over het juiste gebruik van de LoggerMessageAttribute. In het voorgaande voorbeeld wordt een WarningLogMethodDiagnosticSeverity.Warning van SYSLIB0025.

Don't include a template for `ex` in the logging message since it is implicitly taken care of.

Ondersteuning voor naam van niet-hoofdlettergevoelige sjabloon

De generator voert een niet-hoofdlettergevoelige vergelijking uit tussen items in de berichtsjabloon en argumentnamen in het logboekbericht. Dit betekent dat wanneer de ILogger status wordt opgesomd, het argument wordt opgehaald door de berichtsjabloon, waardoor de logboeken mooier kunnen worden gebruikt:

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");
    }
}

Bekijk de voorbeelduitvoer van logboekregistratie wanneer u de JsonConsole formatter gebruikt:

{
  "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}!"
  }
}

Onbepaalde parametervolgorde

Er zijn geen beperkingen voor de volgorde van parameters voor de logboekmethode. Een ontwikkelaar kan de ILogger als laatste parameter definiëren, hoewel het een beetje onhandig kan lijken.

[LoggerMessage(
    EventId = 110,
    Level = LogLevel.Debug,
    Message = "M1 {Ex3} {Ex2}")]
static partial void LogMethod(
    Exception ex,
    Exception ex2,
    Exception ex3,
    ILogger logger);

Tip

De volgorde van de parameters voor een logboekmethode is niet vereist om overeen te komen met de volgorde van de tijdelijke aanduidingen voor de sjabloon. In plaats daarvan komen de namen van de tijdelijke aanduidingen in de sjabloon naar verwachting overeen met de parameters. Houd rekening met de volgende JsonConsole uitvoer en de volgorde van de fouten.

{
  "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}"
  }
}

Aanvullende voorbeelden van logboekregistratie

In de volgende voorbeelden ziet u hoe u de gebeurtenisnaam ophaalt, het logboekniveau dynamisch instelt en logboekregistratieparameters opmaken. De logboekregistratiemethoden zijn:

  • LogWithCustomEventName: Gebeurtenisnaam ophalen via LoggerMessage kenmerk.
  • LogWithDynamicLogLevel: het logboekniveau dynamisch instellen, zodat het logboekniveau kan worden ingesteld op basis van configuratie-invoer.
  • UsingFormatSpecifier: Gebruik opmaakaanduidingen om logboekregistratieparameters op te maken.
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);
    }
}

Bekijk de voorbeelduitvoer van logboekregistratie wanneer u de SimpleConsole formatter gebruikt:

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

Bekijk de voorbeelduitvoer van logboekregistratie wanneer u de JsonConsole formatter gebruikt:

{
  "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}"
  }
}

Samenvatting

Met de komst van C#-brongeneratoren is het schrijven van zeer presterende logboekregistratie-API's veel eenvoudiger. Het gebruik van de brongeneratorbenadering heeft verschillende belangrijke voordelen:

  • Hiermee kan de structuur voor logboekregistratie behouden blijven en wordt de exacte notatiesyntaxis ingeschakeld die vereist is voor berichtsjablonen.
  • Hiermee kunt u alternatieve namen opgeven voor de tijdelijke aanduidingen voor de sjabloon en opmaakaanduidingen gebruiken.
  • Hiermee wordt het doorgeven van alle oorspronkelijke gegevens naar behoren toegestaan, zonder enige complicatie rond de manier waarop deze worden opgeslagen voordat er iets mee wordt gedaan (behalve het maken van een string).
  • Biedt logboekspecifieke diagnostische gegevens en verzendt waarschuwingen voor dubbele gebeurtenis-id's.

Daarnaast zijn er voordelen ten opzichte van het handmatig gebruik van LoggerMessage.Define:

  • Kortere en eenvoudigere syntaxis: declaratief kenmerkgebruik in plaats van het coderen van standaard.
  • Begeleide ontwikkelaarservaring: de generator geeft waarschuwingen om ontwikkelaars te helpen het juiste te doen.
  • Ondersteuning voor een willekeurig aantal logboekregistratieparameters. LoggerMessage.Define ondersteunt maximaal zes.
  • Ondersteuning voor dynamisch logboekniveau. Dit is niet mogelijk met LoggerMessage.Define alleen.

Zie ook