Share via


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 unika ILogger<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är TCategoryName ä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 namngiven LogMessages, som är static så att den kan användas för att definiera tilläggsmetoder för ILogger typen.
  • Dekorerar en LogProductSaleDetails tilläggsmetod med LoggerMessage attributet och Message mallen.
  • Deklarerar LogProductSaleDetails, som utökar ILogger och accepterar en quantity och description.

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: