Sdílet prostřednictvím


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

Protokolování generované zdrojem je navržené tak, aby poskytovalo vysoce použitelné a vysoce výkonné řešení protokolování pro moderní .NET aplikace. Automaticky generovaný zdrojový kód spoléhá na ILogger rozhraní ve spojení s funkcemi LoggerMessage.Define .

Generátor zdroje se aktivuje, když je LoggerMessageAttribute použito na metodách partial protokolování. Při aktivaci automaticky vygeneruje implementaci metod partial, které dekoruje. Pokud dojde k problému, vytvoří diagnostiku v době kompilace s nápovědou k správnému použití. Toto řešení protokolování v době kompilace je za běhu výrazně rychlejší než dříve dostupné přístupy k protokolování. Do maximální možné míry eliminuje boxování, dočasné alokace a kopie.

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í LoggerMessageAttribute ve statickém kontextu musí být tato instance ILogger předána jako argument. Nebo přidejte this modifikátor k parametru ILogger , který definuje metodu jako rozšiřující metodu.

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 použít atribut i v nestatickém 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á logger 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);
}

Počínaje .NET 9 může metoda protokolování dále získat protokolovací nástroj z parametru ILogger primárního 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 existuje pole ILogger i parametr primárního konstruktoru, metoda protokolování získá logger z pole.

Dynamická úroveň protokolu

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

Message vlastnost

Vlastnost MessageLoggerMessageAttribute je volitelná. Pokud ji vynecháte, String.Empty bude použito pro zprávu. Pokud však metoda protokolování obsahuje parametry bez odpovídajících zástupných symbolů šablony, kompilátor vygeneruje SYSLIB1015 upozornění. Tyto parametry jsou uložené ve stavu protokolu, ale nezobrazují se ve výstupu formátovaného protokolu. Pouze poskytovatelé strukturovaného protokolování, kteří vyčíslují stav protokolu, je zobrazí.

Omezení metody záznamu

Metody protokolování, které jsou zdobeny LoggerMessageAttribute musí splňovat následující požadavky:

  • Metody logová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í podporují parametry obecného typu, ale antikonstraint jazyka C# 13 allows ref struct není podporován.
  • Parametry metody protokolování nemohou používat modifikátory params, scoped nebo out a nemohou být typy ref struct.
  • 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#, tedy verze 9 nebo novější. Informace o změně jazykové verze najdete v tématu Správa verzí jazyka C#.

Anatomie metody logování

Podpis ILogger.Log přijímá LogLevel a volitelně i Exception, jak je znázorněno v následujícím příkladu kódu.

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, LogLevel, a Exception jsou zpracovávány speciálně v podpisu metody logovací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é

Varování poskytují podrobnosti o správném použití LoggerMessageAttribute. V předchozím příkladu WarningLogMethod hlásí DiagnosticSeverity.WarningSYSLIB0025.

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 provádí porovnání bez rozlišování velikosti písmen mezi položkami v šabloně zprávy a názvy argumentů v protokolové zprávě. To znamená, že při výčtu stavu ILogger je argumentu využito šablonou zprávy, což může znamenat, že protokoly budou lépe čitelné.

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

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

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

Návod

Pořadí parametrů v protokolové metodě nemusí odpovídat pořadí zástupných symbolů v šabloně. 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);
    }
}

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

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

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

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

Odstraňování citlivých informací z logů

Při protokolování citlivých dat je důležité zabránit náhodnému vystavení. I při generování metod protokolování v době kompilace může protokolování nezpracovaných citlivých hodnot vést k problémům s únikem dat a dodržováním předpisů.

Knihovna Microsoft.Extensions.Telemetry poskytuje pokročilé možnosti protokolování a rozšiřování telemetrie pro aplikace .NET. Rozšiřuje kanál protokolování tak, aby při zápisu protokolů automaticky použil redakci u klasifikovaných dat. Umožňuje vynucovat zásady ochrany dat v celé aplikaci integrací redakce do pracovního postupu protokolování. Je sestavená pro aplikace, které potřebují sofistikované přehledy telemetrie a protokolování.

Pokud chcete povolit redaction, použijte knihovnu Microsoft.Extensions.Compliance.Redaction . Tato knihovna poskytuje redaktory – komponenty, které transformují citlivá data (například vymazáním, maskováním nebo zatřiďováním) tak, aby byly bezpečné pro výstup. Redactory jsou vybrány na základě klasifikace dat, která umožňuje označovat data podle jejich citlivosti (například osobní, soukromé nebo veřejné).

Pokud chcete použít redakci se zdrojovými metodami protokolování, měli byste:

  1. Klasifikujte citlivá data pomocí systému klasifikace dat.
  2. Zaregistrujte a nakonfigurujte redaktory pro každou klasifikaci v kontejneru DI.
  3. Povolte redakci v protokolovacím kanálu.
  4. Zkontrolujte protokoly a ujistěte se, že nejsou vystavena žádná citlivá data.

Pokud máte například zprávu protokolu s parametrem, který je považován za soukromý:

[LoggerMessage(0, LogLevel.Information, "User SSN: {SSN}")]
public static partial void LogPrivateInformation(
    this ILogger logger,
    [MyTaxonomyClassifications.Private] string SSN);

Budete muset mít nastavení podobné tomuto:

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

Výstup by měl vypadat takto:

User SSN: *****

Tento přístup zajišťuje, že se protokolují pouze redactovaná data, a to i při použití rozhraní API pro protokolování vygenerované časem kompilace. Pro různé datové typy nebo klasifikace můžete použít různé redaktory a logiku redakce aktualizovat centrálně.

Další informace o klasifikaci dat najdete v tématu Data classification in .NET. Další informace o redakcích a redaktorech najdete v tématu Data redaction in .NET.

Shrnutí

S nástupem zdrojových generátorů jazyka C# je psaní vysoce výkonných rozhraní API protokolování 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 jsou, bez jakýchkoli komplikací ohledně způsobu jejich uložení, než se s nimi něco udělá (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 opakujícího se kódu.
  • 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ě záznamů To není možné jen s LoggerMessage.Define.

Viz také