Sdílet prostřednictvím


Generování zdroje protokolování v čase kompilace

.NET 6 zavádí LoggerMessageAttribute typ. Tento atribut je součástí Microsoft.Extensions.Logging oboru názvů a při použití generuje výkonná rozhraní API protokolování. Podpora protokolování generování zdrojů je navržená tak, aby poskytovala vysoce použitelné a vysoce výkonné řešení protokolování pro moderní aplikace .NET. Automaticky generovaný zdrojový kód spoléhá na ILogger rozhraní ve spojení s funkcemi LoggerMessage.Define .

Zdrojový generátor se aktivuje při LoggerMessageAttribute použití metod partial protokolování. Při aktivaci je buď schopen automaticky vygenerovat implementaci partial metod, které dekóduje, nebo vytvořit diagnostiku v době kompilace s radami o správném použití. Řešení protokolování v době kompilace je obvykle výrazně rychlejší za běhu než existující přístupy k protokolování. Dosahuje toho odstraněním boxingu, dočasných přidělení a kopií do maximálního možného rozsahu.

Základní použití

Chcete-li použít LoggerMessageAttribute, spotřebová třída a metoda musí být partial. Generátor kódu se aktivuje v době kompilace a vygeneruje implementaci partial metody.

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

V předchozím příkladu je static metoda protokolování a úroveň protokolu je zadána v definici atributu. Při použití atributu ve statickém kontextu se buď ILogger jako parametr vyžaduje instance, nebo upravte definici tak, aby používala this klíčové slovo k definování metody jako rozšiřující metody.

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

Můžete také použít atribut v nestatického kontextu. Představte si následující příklad, ve kterém je metoda protokolování deklarována jako metoda instance. V tomto kontextu metoda protokolování získá protokolovací nástroj přístupem k ILogger poli v obsahující třídě.

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

Od rozhraní .NET 9 může metoda protokolování navíc získat protokolovací nástroj z parametru primárního ILogger konstruktoru v obsahující třídě.

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

Pokud je ILogger pole i primární parametr konstruktoru, metoda protokolování získá protokolovací nástroj z pole.

Někdy musí být úroveň protokolu dynamická, nikoli staticky integrovaná do kódu. Můžete to udělat tak, že z atributu vynecháte úroveň protokolu a místo toho ji potřebujete jako parametr metody protokolování.

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

Zprávu protokolování můžete vynechat a String.Empty zpráva bude k dispozici. Stav bude obsahovat argumenty formátované jako páry klíč-hodnota.

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

Při použití formátovače zvažte příklad výstupu JsonConsole protokolování.

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

Omezení metody protokolu

Při použití LoggerMessageAttribute metod protokolování je potřeba dodržovat některá omezení:

  • Metody protokolování musí být partial a vracet void.
  • Názvy metod protokolování nesmí začínat podtržítkem.
  • Názvy parametrů metod protokolování nesmí začínat podtržítkem.
  • Metody protokolování nemusí být definovány vnořeném typu.
  • Metody protokolování nemohou být obecné.
  • Pokud je staticmetoda protokolování , ILogger instance je vyžadována jako parametr.

Model generování kódu závisí na kompilaci kódu pomocí moderního kompilátoru jazyka C#, verze 9 nebo novější. Kompilátor C# 9.0 byl dostupný v .NET 5. Pokud chcete upgradovat na moderní kompilátor jazyka C#, upravte soubor projektu tak, aby cílil na C# 9.0.

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

Další informace najdete v tématu Správa verzí jazyka C#.

Anatomie metody protokolu

Podpis ILogger.Log přijme a volitelně i LogLevel znak Exception, jak je znázorněno níže.

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

Obecně platí, že první instance ILogger, a LogLevelException jsou zpracovávány speciálně v protokolu metody podpis zdrojového generátoru. Další instance se považují za normální parametry šablony zprávy:

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

Důležité

Varovná upozornění poskytují podrobnosti o správném použití LoggerMessageAttribute. V předchozím příkladu WarningLogMethod bude zpráva o DiagnosticSeverity.Warning SYSLIB0025.

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

Podpora názvů šablon bez rozlišování malých a velkých písmen

Generátor nerozlišuje malá a velká písmena mezi položkami v šabloně zprávy a názvy argumentů ve zprávě protokolu. To znamená, že při výčtu ILogger stavu se argument vyzvedne šablonou zprávy, která může znamenat, že protokoly budou příjemnější:

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

Při použití JsonConsole formátovače vezměte v úvahu příklad výstupu protokolování:

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

Neurčité pořadí parametrů

Řazení parametrů metody protokolu není nijak omezené. Vývojář může definovat ILogger jako poslední parametr, i když se může zdát trochu divný.

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

Tip

Pořadí parametrů metody protokolu není nutné, aby odpovídalo pořadí zástupných symbolů šablony. Místo toho se očekává, že zástupné názvy v šabloně odpovídají parametrům. Vezměte v úvahu následující JsonConsole výstup a pořadí chyb.

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

Další příklady protokolování

Následující ukázky ukazují, jak načíst název události, dynamicky nastavit úroveň protokolu a formátovat parametry protokolování. Metody protokolování jsou:

  • LogWithCustomEventName: Načtení názvu události prostřednictvím LoggerMessage atributu
  • LogWithDynamicLogLevel: Nastavte úroveň protokolu dynamicky, aby bylo možné nastavit úroveň protokolu na základě vstupu konfigurace.
  • UsingFormatSpecifier: K formátování parametrů protokolování použijte specifikátory formátu.
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);
    }
}

Při použití SimpleConsole formátovače vezměte v úvahu příklad výstupu protokolování:

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

Při použití JsonConsole formátovače vezměte v úvahu příklad výstupu protokolování:

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

Shrnutí

S nástupem zdrojových generátorů jazyka C# je psaní vysoce výkonných rozhraní API protokolování mnohem jednodušší. Použití přístupu ke zdrojovému generátoru má několik klíčových výhod:

  • Umožňuje zachovat strukturu protokolování a povolit přesnou syntaxi formátu vyžadovanou šablonami zpráv.
  • Umožňuje zadat alternativní názvy zástupných symbolů šablony a použít specifikátory formátu.
  • Umožňuje předávání všech původních dat tak, jak je, bez jakýchkoli komplikací, které by se ukládaly před tím, než se s ním něco děje (kromě vytvoření string).
  • Poskytuje diagnostiku specifickou pro protokolování a generuje upozornění pro duplicitní ID událostí.

Kromě toho existují výhody oproti ručnímu použití LoggerMessage.Define:

  • Kratší a jednodušší syntaxe: Použití deklarativního atributu místo kódování často používaného atributu
  • Prostředí pro vývojáře s asistencí: Generátor poskytuje upozornění, která vývojářům pomůžou udělat správnou věc.
  • Podpora libovolného počtu parametrů protokolování LoggerMessage.Define podporuje maximálně šest.
  • Podpora dynamické úrovně protokolu To není možné s osaměm LoggerMessage.Define .

Viz také