Kompilering av källgenerering för loggning
.NET 6 introducerar LoggerMessageAttribute
typen. Det här attributet är en del av Microsoft.Extensions.Logging
namnområdet och när det används genererar det högpresterande loggnings-API:er. Stöd för källgenereringsloggning är utformat för att leverera en mycket användbar och mycket högpresterande loggningslösning för moderna .NET-program. Den automatiskt genererade källkoden ILogger förlitar sig på gränssnittet tillsammans med LoggerMessage.Define funktioner.
Källgeneratorn utlöses när LoggerMessageAttribute
den används på partial
loggningsmetoder. När den utlöses kan den antingen automatiskt generera implementeringen av de metoder som den partial
dekorerar eller producera kompileringstidsdiagnostik med tips om korrekt användning. Kompileringslösningen för tidsloggning är vanligtvis betydligt snabbare vid körning än befintliga loggningsmetoder. Det uppnår detta genom att eliminera boxning, tillfälliga allokeringar och kopior i största möjliga utsträckning.
Grundläggande användning
Om du vill använda LoggerMessageAttribute
måste den förbrukande klassen och metoden vara partial
. Kodgeneratorn utlöses vid kompileringstillfället och genererar en implementering av partial
metoden.
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);
}
I föregående exempel är static
loggningsmetoden och loggnivån anges i attributdefinitionen. När du använder attributet i en statisk kontext krävs antingen instansen ILogger
som en parameter eller ändrar definitionen för att använda nyckelordet this
för att definiera metoden som en tilläggsmetod.
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);
}
Du kan också välja att använda attributet i en icke-statisk kontext. Tänk dig följande exempel där loggningsmetoden deklareras som en instansmetod. I det här sammanhanget hämtar loggningsmetoden loggaren genom att komma åt ett ILogger
fält i den innehållande klassen.
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);
}
Från och med .NET 9 kan loggningsmetoden dessutom hämta loggningsverktyget från en ILogger
primär konstruktorparameter i den innehållande klassen.
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);
}
Om det finns både ett ILogger
fält och en primär konstruktorparameter hämtas loggningsmetoden från fältet.
Ibland måste loggnivån vara dynamisk snarare än statiskt inbyggd i koden. Du kan göra detta genom att utelämna loggnivån från attributet och i stället kräva det som en parameter till loggningsmetoden.
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);
}
Du kan utelämna loggningsmeddelandet och String.Empty kommer att tillhandahållas för meddelandet. Tillståndet innehåller argumenten, formaterade som nyckel/värde-par.
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);
}
Överväg exempelloggningsutdata när du använder formateringsfunktionen JsonConsole
.
{
"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}."
}
}
Begränsningar för loggmetod
När du använder loggningsmetoderna LoggerMessageAttribute
måste vissa begränsningar följas:
- Loggningsmetoder måste vara
partial
och returneravoid
. - Namn på loggningsmetod får inte börja med ett understreck.
- Parameternamn för loggningsmetoder får inte börja med ett understreck.
- Loggningsmetoder kanske inte definieras i en kapslad typ.
- Loggningsmetoder kan inte vara generiska.
- Om en loggningsmetod är
static
krävs instansenILogger
som en parameter.
Kodgenereringsmodellen beror på att kod kompileras med en modern C#-kompilator, version 9 eller senare. C# 9.0-kompilatorn blev tillgänglig med .NET 5. Om du vill uppgradera till en modern C#-kompilator redigerar du projektfilen till mål C# 9.0.
<PropertyGroup>
<LangVersion>9.0</LangVersion>
</PropertyGroup>
Mer information finns i C#-språkversioner.
Loggmetodsanatomi
Signaturen ILogger.Log LogLevel accepterar och eventuellt en Exception, enligt nedan.
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);
}
Som en allmän regel behandlas den första instansen av ILogger
, LogLevel
och Exception
särskilt i loggmetodsignaturen för källgeneratorn. Efterföljande instanser behandlas som normala parametrar för meddelandemallen:
// 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);
Viktigt!
Varningarna som genereras ger information om rätt användning av LoggerMessageAttribute
. I föregående exempel WarningLogMethod
rapporterar will en DiagnosticSeverity.Warning
av SYSLIB0025
.
Don't include a template for `ex` in the logging message since it is implicitly taken care of.
Stöd för skiftlägesokänsliga mallnamn
Generatorn gör en skiftlägeskänslig jämförelse mellan objekt i meddelandemallen och argumentnamnen i loggmeddelandet. Det innebär att när ILogger
tillståndet räknas upp hämtas argumentet av meddelandemallen, vilket kan göra loggarna snyggare att använda:
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");
}
}
Överväg exempelloggningsutdata när du använder formateringsfunktionen 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}!"
}
}
Obestämd parameterordning
Det finns inga begränsningar för ordningen på loggmetodparametrar. En utvecklare kan definiera ILogger
som den sista parametern, även om det kan verka lite besvärligt.
[LoggerMessage(
EventId = 110,
Level = LogLevel.Debug,
Message = "M1 {Ex3} {Ex2}")]
static partial void LogMethod(
Exception ex,
Exception ex2,
Exception ex3,
ILogger logger);
Dricks
Ordningen på parametrarna på en loggmetod krävs inte för att motsvara ordningen på mallplatshållarna. Platshållarnamnen i mallen förväntas i stället matcha parametrarna. Överväg följande JsonConsole
utdata och ordningen på felen.
{
"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}"
}
}
Ytterligare loggningsexempel
Följande exempel visar hur du hämtar händelsenamnet, anger loggnivån dynamiskt och formaterar loggningsparametrar. Loggningsmetoderna är:
LogWithCustomEventName
: Hämta händelsenamn viaLoggerMessage
attribut.LogWithDynamicLogLevel
: Ange loggnivå dynamiskt för att tillåta att loggnivån anges baserat på konfigurationsindata.UsingFormatSpecifier
: Använd formatspecificerare för att formatera loggningsparametrar.
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);
}
}
Överväg exempelloggningsutdata när du använder formateringsfunktionen 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
Överväg exempelloggningsutdata när du använder formateringsfunktionen 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}"
}
}
Sammanfattning
Med tillkomsten av C#-källgeneratorer är det mycket enklare att skriva högpresterande loggnings-API:er. Att använda källgeneratormetoden har flera viktiga fördelar:
- Tillåter att loggningsstrukturen bevaras och aktiverar den exakta formatsyntax som krävs av meddelandemallar.
- Tillåter att du anger alternativa namn för mallplatshållarna och använder formatspecificerare.
- Tillåter att alla ursprungliga data skickas som de är, utan någon komplikation kring hur de lagras innan något görs med dem (förutom att skapa en
string
). - Tillhandahåller loggningsspecifik diagnostik och genererar varningar för duplicerade händelse-ID:n.
Dessutom finns det fördelar jämfört med att använda LoggerMessage.Define:
- Kortare och enklare syntax: Deklarativ attributanvändning i stället för kodning av pannplåt.
- Interaktiv utvecklarupplevelse: Generatorn varnar för att hjälpa utvecklare att göra det rätta.
- Stöd för ett godtyckligt antal loggningsparametrar.
LoggerMessage.Define
stöder högst sex. - Stöd för dynamisk loggnivå. Detta är inte möjligt med
LoggerMessage.Define
ensam.