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 vracetvoid
. - 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
static
metoda 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 LogLevel
Exception
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ímLoggerMessage
atributuLogWithDynamicLogLevel
: 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
.