Derleme zamanı günlüğü kaynak oluşturma

.NET 6 türü tanıtır LoggerMessageAttribute . Bu öznitelik ad alanının bir parçasıdır Microsoft.Extensions.Logging ve kullanıldığında kaynak olarak performans gösteren günlük API'leri oluşturur. Kaynak oluşturma günlük desteği, modern .NET uygulamaları için yüksek oranda kullanılabilir ve yüksek performanslı bir günlük çözümü sunmak üzere tasarlanmıştır. Otomatik olarak oluşturulan kaynak kodu, işlevlerle LoggerMessage.Define birlikte arabirime ILogger dayanır.

Kaynak oluşturucu, günlüğe kaydetme yöntemlerinde partial kullanıldığında tetikleniyorLoggerMessageAttribute. Tetiklendiğinde, süslediği yöntemlerin partial uygulanmasını otomatik olarak oluşturabilir veya doğru kullanımla ilgili ipuçları içeren derleme zamanı tanılamaları oluşturabilir. Derleme zamanı günlüğe kaydetme çözümü genellikle çalışma zamanında mevcut günlük yaklaşımlarından çok daha hızlıdır. Bu, mümkün olan en yüksek ölçüde kutulama, geçici ayırmalar ve kopyalar ortadan kaldırarak bunu başarır.

Temel kullanım

kullanmak LoggerMessageAttributeiçin, tüketen sınıfın ve yönteminin olması partialgerekir. Kod oluşturucu derleme zamanında tetikler ve yönteminin partial bir uygulamasını oluşturur.

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

Yukarıdaki örnekte günlük yöntemidir static ve günlük düzeyi öznitelik tanımında belirtilir. Özniteliği statik bağlamda kullanırken, ILogger örneğin parametre olarak kullanılması gerekir veya yöntemi bir uzantı yöntemi olarak tanımlamak için anahtar sözcüğünü kullanacak this şekilde tanımı değiştirin.

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

Özniteliği statik olmayan bir bağlamda da kullanmayı seçebilirsiniz. Günlüğe kaydetme yönteminin örnek yöntemi olarak bildirildiği aşağıdaki örneği göz önünde bulundurun. Bu bağlamda günlük yöntemi, içeren sınıftaki bir ILogger alana erişerek günlükçüye sahip olur.

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

Bazen günlük düzeyinin kodda statik olarak yerleşik olarak değil dinamik olması gerekir. Bunu, günlük düzeyini özniteliğinden çıkararak ve bunun yerine günlük yöntemine parametre olarak gerektirerek yapabilirsiniz.

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

Günlük iletisini atlayabilirsiniz ve String.Empty ileti için sağlanacaktır. Durum, anahtar-değer çiftleri olarak biçimlendirilmiş bağımsız değişkenleri içerir.

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

Biçimlendiriciyi kullanırken örnek günlüğe kaydetme çıkışını JsonConsole göz önünde bulundurun.

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

Günlük yöntemi kısıtlamaları

Günlük oluşturma yöntemlerini kullanırken LoggerMessageAttribute bazı kısıtlamalara uyulmalıdır:

  • Günlük yöntemleri olmalıdır partial ve döndürmelidir void.
  • Günlüğe kaydetme yöntemi adları alt çizgiyle başlamamalıdır.
  • Günlük yöntemlerinin parametre adları alt çizgiyle başlamamalıdır.
  • Günlük yöntemleri iç içe yerleştirilmiş bir türde tanımlanamaz .
  • Günlük yöntemleri genel olamaz .
  • Günlük yöntemi isestaticILogger, örnek parametre olarak gereklidir.

Kod oluşturma modeli, kodun modern bir C# derleyicisi, sürüm 9 veya üzeri ile derlendiğine bağlıdır. C# 9.0 derleyicisi .NET 5 ile kullanılabilir hale geldi. Modern bir C# derleyicisine yükseltmek için proje dosyanızı C# 9.0 hedefine göre düzenleyin.

<PropertyGroup>
  <LangVersion>9.0</LangVersion>
</PropertyGroup>

Daha fazla bilgi için bkz . C# dil sürümü oluşturma.

Log yöntemi anatomisi

İmza, ILogger.Log aşağıda gösterildiği gibi ve isteğe bağlı olarak bir Exceptionkabul ederLogLevel.

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

Genel bir kural olarak, ilk , LogLevelve Exception örneğiILogger, kaynak oluşturucunun günlük yöntemi imzasında özel olarak işlenir. Sonraki örnekler, ileti şablonunda normal parametreler olarak değerlendirilir:

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

Önemli

Gösterilen uyarılar, doğru kullanımıyla LoggerMessageAttributeilgili ayrıntıları sağlar. Yukarıdaki örnekte, WarningLogMethod bir değerini SYSLIB0025raporlayacaktırDiagnosticSeverity.Warning.

Don't include a template for `ex` in the logging message since it is implicitly taken care of.

Büyük/küçük harfe duyarlı olmayan şablon adı desteği

Oluşturucu, ileti şablonundaki öğelerle günlük iletisindeki bağımsız değişken adları arasında büyük/küçük harfe duyarlı olmayan bir karşılaştırma yapar. Bu, durum numaralandırıldığında ILogger bağımsız değişkenin ileti şablonu tarafından alınıp günlüklerin daha iyi bir şekilde tüketilebileceği anlamına gelir:

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

Biçimlendiriciyi kullanırken örnek günlüğe kaydetme çıkışını JsonConsole göz önünde bulundurun:

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

Belirsiz parametre sırası

Günlük yöntemi parametrelerinin sıralanmasında herhangi bir kısıtlama yoktur. Geliştirici son ILogger parametre olarak öğesini tanımlayabilir, ancak biraz garip görünebilir.

[LoggerMessage(
    EventId = 110,
    Level = LogLevel.Debug,
    Message = "M1 {Ex3} {Ex2}")]
static partial void LogMethod(
    Exception ex,
    Exception ex2,
    Exception ex3,
    ILogger logger);

İpucu

Bir günlük yöntemindeki parametrelerin sırası, şablon yer tutucularının sırasına karşılık getirmek için gerekli değildir . Bunun yerine, şablondaki yer tutucu adlarının parametrelerle eşleşmesi beklenir. Aşağıdaki JsonConsole çıkışı ve hataların sırasını göz önünde bulundurun.

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

Ek günlük örnekleri

Aşağıdaki örneklerde olay adını alma, günlük düzeyini dinamik olarak ayarlama ve günlüğe kaydetme parametrelerini biçimlendirme işlemleri gösterilmektedir. Günlüğe kaydetme yöntemleri şunlardır:

  • LogWithCustomEventName: Öznitelik aracılığıyla LoggerMessage olay adını alın.
  • LogWithDynamicLogLevel: Günlük düzeyinin yapılandırma girişine göre ayarlanmasına izin vermek için günlük düzeyini dinamik olarak ayarlayın.
  • UsingFormatSpecifier: Günlük parametrelerini biçimlendirmek için biçim tanımlayıcılarını kullanın.
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);
    }
}

Biçimlendiriciyi kullanırken örnek günlüğe kaydetme çıkışını SimpleConsole göz önünde bulundurun:

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

Biçimlendiriciyi kullanırken örnek günlüğe kaydetme çıkışını JsonConsole göz önünde bulundurun:

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

Özet

C# kaynak oluşturucularının ortaya çıkmasıyla birlikte, yüksek performanslı günlük API'leri yazmak çok daha kolaydır. Kaynak oluşturucu yaklaşımını kullanmanın çeşitli temel avantajları vardır:

  • Günlük yapısının korunmasına izin verir ve İleti Şablonları için gereken tam biçim söz dizimini etkinleştirir.
  • Şablon yer tutucuları için alternatif adlar sağlamanıza ve biçim tanımlayıcılarını kullanmanıza izin verir.
  • Tüm özgün verilerin olduğu gibi geçirilmesine izin verir. Bu veriler, bir işlem yapılmadan önce nasıl depolandığıyla ilgili herhangi bir stringsorun yaşamadan (oluşturma dışında).
  • Günlüğe özgü tanılamalar sağlar ve yinelenen olay kimlikleri için uyarılar yayar.

Ayrıca, el ile kullanmanın LoggerMessage.Defineavantajları da vardır:

  • Daha kısa ve daha basit söz dizimi: Ortak kodlama yerine bildirim temelli öznitelik kullanımı.
  • Destekli geliştirici deneyimi: Oluşturucu, geliştiricilerin doğru şeyi yapmalarına yardımcı olmak için uyarılar verir.
  • Rastgele sayıda günlük parametresi desteği. LoggerMessage.Define en fazla altıyı destekler.
  • Dinamik günlük düzeyi desteği. Bu tek başına mümkün LoggerMessage.Define değildir.

Ayrıca bkz.