Loggningsvägledning för .NET-biblioteksförfattare
Som biblioteksförfattare är avslöjande av loggning ett bra sätt att ge konsumenterna insyn i bibliotekets inre funktioner. Den här vägledningen hjälper dig att exponera loggning på ett sätt som är konsekvent med andra .NET-bibliotek och ramverk. Det hjälper dig också att undvika vanliga flaskhalsar i prestanda som kanske inte annars är uppenbara.
När gränssnittet ILoggerFactory
ska användas
När du skriver ett bibliotek som genererar loggar behöver du ett ILogger objekt för att registrera loggarna. För att hämta objektet kan ditt API antingen acceptera en ILogger<TCategoryName> parameter eller acceptera ett ILoggerFactory som du anropar ILoggerFactory.CreateLoggerefter . Vilken metod bör föredras?
När du behöver ett loggningsobjekt som kan skickas till flera klasser så att alla kan generera loggar använder du
ILoggerFactory
. Vi rekommenderar att varje klass skapar loggar med en separat kategori med samma namn som klassen. För att göra detta behöver du fabriken för att skapa unikaILogger<TCategoryName>
objekt för varje klass som genererar loggar. Vanliga exempel är offentliga startpunkts-API:er för ett bibliotek eller offentliga konstruktorer av typer som kan skapa hjälpklasser internt.När du behöver ett loggningsobjekt som bara används i en klass och aldrig delas använder du
ILogger<TCategoryName>
, därTCategoryName
är den typ som skapar loggarna. Ett vanligt exempel på detta är en konstruktor för en klass som skapats av beroendeinmatning.
Om du utformar ett offentligt API som måste vara stabilt över tid bör du tänka på att du kanske vill omstrukturera din interna implementering i framtiden. Även om en klass inte skapar några interna hjälptyper från början kan det ändras när koden utvecklas. Med hjälp av ILoggerFactory
kan du skapa nya ILogger<TCategoryName>
objekt för alla nya klasser utan att ändra det offentliga API:et.
Mer information finns i Så här tillämpas filtreringsregler.
Föredrar källgenererad loggning
API:et ILogger
stöder två metoder för att använda API:et. Du kan antingen anropa metoder som LoggerExtensions.LogError och LoggerExtensions.LogInformation, eller så kan du använda loggningskällans generator för att definiera starkt skrivna loggningsmetoder. I de flesta fall rekommenderas källgeneratorn eftersom den ger överlägsen prestanda och starkare typning. Det isolerar även loggningsspecifika problem som meddelandemallar, ID:er och loggnivåer från den anropande koden. Den icke-källgenererade metoden är främst användbar för scenarier där du är villig att ge upp dessa fördelar för att göra koden mer koncis.
using Microsoft.Extensions.Logging;
namespace Logging.LibraryAuthors;
internal static partial class LogMessages
{
[LoggerMessage(
Message = "Sold {Quantity} of {Description}",
Level = LogLevel.Information)]
internal static partial void LogProductSaleDetails(
this ILogger logger,
int quantity,
string description);
}
Koden ovan:
- Definierar en
partial class
namngivenLogMessages
, som ärstatic
så att den kan användas för att definiera tilläggsmetoder förILogger
typen. - Dekorerar en
LogProductSaleDetails
tilläggsmetod medLoggerMessage
attributet ochMessage
mallen. - Deklarerar
LogProductSaleDetails
, som utökarILogger
och accepterar enquantity
ochdescription
.
Dricks
Du kan gå in i den källgenererade koden under felsökningen eftersom den ingår i samma sammansättning som koden som anropar den.
Använd IsEnabled
för att undvika dyr parameterutvärdering
Det kan finnas situationer där det är dyrt att utvärdera parametrar. Om du expanderar i föregående exempel kan du tänka dig att parametern description
är en string
som är dyr att beräkna. Kanske får produkten som säljs en användarvänlig produktbeskrivning och förlitar sig på en databasfråga eller läsning från en fil. I dessa situationer kan du instruera källgeneratorn att hoppa över IsEnabled
skyddet och manuellt lägga till IsEnabled
skyddet på anropsplatsen. Detta gör att användaren kan avgöra var skyddet anropas och ser till att parametrar som kan vara dyra att beräkna endast utvärderas när det verkligen behövs. Ta följande kod som exempel:
using Microsoft.Extensions.Logging;
namespace Logging.LibraryAuthors;
internal static partial class LogMessages
{
[LoggerMessage(
Message = "Sold {Quantity} of {Description}",
Level = LogLevel.Information,
SkipEnabledCheck = true)]
internal static partial void LogProductSaleDetails(
this ILogger logger,
int quantity,
string description);
}
LogProductSaleDetails
När tilläggsmetoden anropas IsEnabled
anropas skyddet manuellt och den dyra parameterutvärderingen begränsas till när den behövs. Ta följande kod som exempel:
if (_logger.IsEnabled(LogLevel.Information))
{
// Expensive parameter evaluation
var description = product.GetFriendlyProductDescription();
_logger.LogProductSaleDetails(
quantity,
description);
}
Mer information finns i Kompilera tidsloggningskällans generering och Loggning med höga prestanda i .NET.
Undvik stränginterpolation i loggning
Ett vanligt misstag är att använda stränginterpolation för att skapa loggmeddelanden. Stränginterpolation i loggning är problematiskt för prestanda, eftersom strängen utvärderas även om motsvarande LogLevel
inte är aktiverat. I stället för stränginterpolering använder du loggmeddelandemallen, formateringen och argumentlistan. Mer information finns i Loggning i .NET: Loggmeddelandemall.
Använd standardinställningar för no-op-loggning
Det kan finnas tillfällen när du använder ett bibliotek som exponerar loggnings-API:er som förväntar sig antingen en ILogger
eller ILoggerFactory
, som du inte vill ange en logger. I dessa fall innehåller NuGet-paketet Microsoft.Extensions.Logging.Abstractions standardinställningar för no-op-loggning.
Bibliotekskonsumenter kan som standard logga null om inget ILoggerFactory
anges. Användningen av null-loggning skiljer sig från att definiera typer som nullbara (ILoggerFactory?
), eftersom typerna inte är null. Dessa bekvämlighetsbaserade typer loggar ingenting och är i princip inga ops. Överväg att använda någon av de tillgängliga abstraktionstyperna i förekommande fall: