Bagikan melalui


Pembuatan sumber pengelogan waktu kompilasi

.NET 6 memperkenalkan jenisnya LoggerMessageAttribute . Atribut ini adalah bagian Microsoft.Extensions.Logging dari namespace layanan, dan ketika digunakan, atribut ini menghasilkan API pengelogan berkinerja berkinerja. Dukungan pengelogan generasi sumber dirancang untuk memberikan solusi pengelogan yang sangat dapat digunakan dan berkinerja tinggi untuk aplikasi .NET modern. Kode sumber yang dihasilkan secara otomatis bergantung pada ILogger antarmuka bersama dengan LoggerMessage.Define fungsionalitas.

Generator sumber dipicu ketika LoggerMessageAttribute digunakan pada partial metode pengelogan. Ketika dipicu, ia dapat membuat otomatis implementasi partial metode yang didekorasinya, atau menghasilkan diagnostik waktu kompilasi dengan petunjuk tentang penggunaan yang tepat. Solusi pengelogan waktu kompilasi biasanya jauh lebih cepat pada waktu proses daripada pendekatan pengelogan yang ada. Ini mencapai ini dengan menghilangkan tinju, alokasi sementara, dan salinan sejauh mungkin.

Penggunaan dasar

Untuk menggunakan LoggerMessageAttribute, kelas dan metode yang mengonsumsi harus partial. Generator kode dipicu pada waktu kompilasi, dan menghasilkan implementasi partial metode .

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

Dalam contoh sebelumnya, metode pengelogan adalah static dan tingkat log ditentukan dalam definisi atribut. Saat menggunakan atribut dalam konteks statis, ILogger instans diperlukan sebagai parameter, atau ubah definisi untuk menggunakan this kata kunci untuk menentukan metode sebagai metode ekstensi.

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

Anda juga dapat memilih untuk menggunakan atribut dalam konteks non-statis. Pertimbangkan contoh berikut di mana metode pengelogan dinyatakan sebagai metode instans. Dalam konteks ini, metode pengelogan mendapatkan pencatat dengan mengakses ILogger bidang di kelas yang berisi.

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

Dimulai dengan .NET 9, metode pengelogan juga dapat mendapatkan pencatat dari ILogger parameter konstruktor utama di kelas yang berisi.

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

Jika ada ILogger bidang dan parameter konstruktor utama, metode pengelogan akan mendapatkan pencatat dari bidang .

Terkadang, tingkat log harus dinamis daripada secara statis dibangun ke dalam kode. Anda dapat melakukan ini dengan menghilangkan tingkat log dari atribut dan sebaliknya mengharuskannya sebagai parameter ke metode pengelogan.

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

Anda dapat menghilangkan pesan pengelogan dan String.Empty akan disediakan untuk pesan tersebut. Status akan berisi argumen, diformat sebagai pasangan kunci-nilai.

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

Pertimbangkan contoh output pengelogan saat menggunakan pemformat 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}."
  }
}

Batasan metode log

Saat menggunakan LoggerMessageAttribute metode pengelogan pada, beberapa batasan harus diikuti:

  • Metode pengelogan harus partial dan mengembalikan void.
  • Nama metode pengelogan tidak boleh dimulai dengan garis bawah.
  • Nama parameter metode pengelogan tidak boleh dimulai dengan garis bawah.
  • Metode pengelogan mungkin tidak ditentukan dalam jenis berlapis.
  • Metode pengelogan tidak boleh generik.
  • Jika metode pengelogan adalah static, ILogger instans diperlukan sebagai parameter.

Model pembuatan kode bergantung pada kode yang dikompilasi dengan pengkompilasi C# modern, versi 9 atau yang lebih baru. Compiler C# 9.0 tersedia dengan .NET 5. Untuk meningkatkan ke pengkompilasi C# modern, edit file proyek Anda ke target C# 9.0.

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

Untuk informasi selengkapnya lihat pembuatan versi bahasa C#.

Anatomi metode log

Tanda ILogger.Log tangan menerima dan secara opsional , Exceptionseperti yang ditunjukkan LogLevel di bawah ini.

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

Sebagai aturan umum, instans pertama , ILoggerLogLevel, dan Exception diperlakukan khusus dalam tanda tangan metode log generator sumber. Instans berikutnya diperlakukan seperti parameter normal ke templat pesan:

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

Penting

Peringatan yang dipancarkan memberikan detail tentang penggunaan yang benar dari LoggerMessageAttribute. Dalam contoh sebelumnya, WarningLogMethod akan melaporkan DiagnosticSeverity.Warning dari SYSLIB0025.

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

Dukungan nama templat yang tidak peka huruf besar/kecil

Generator melakukan perbandingan yang tidak peka huruf besar/kecil antara item dalam templat pesan dan nama argumen dalam pesan log. Ini berarti bahwa ketika ILogger menghitung status, argumen diambil oleh templat pesan, yang dapat membuat log lebih baik untuk digunakan:

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

Pertimbangkan contoh output pengelogan saat menggunakan pemformat 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}!"
  }
}

Urutan parameter yang tidak ditentukan

Tidak ada batasan pada urutan parameter metode log. Pengembang dapat mendefinisikan ILogger sebagai parameter terakhir, meskipun mungkin tampak agak canggung.

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

Tip

Urutan parameter pada metode log tidak diperlukan untuk sesuai dengan urutan tempat penampung templat. Sebagai gantinya, nama tempat penampung dalam templat diharapkan cocok dengan parameter. Pertimbangkan output berikut JsonConsole dan urutan kesalahan.

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

Contoh pengelogan tambahan

Sampel berikut menunjukkan cara mengambil nama peristiwa, mengatur tingkat log secara dinamis, dan memformat parameter pengelogan. Metode pengelogan adalah:

  • LogWithCustomEventName: Ambil nama peristiwa melalui LoggerMessage atribut.
  • LogWithDynamicLogLevel: Atur tingkat log secara dinamis, untuk memungkinkan tingkat log diatur berdasarkan input konfigurasi.
  • UsingFormatSpecifier: Gunakan penentu format untuk memformat parameter pengelogan.
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);
    }
}

Pertimbangkan contoh output pengelogan saat menggunakan pemformat 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

Pertimbangkan contoh output pengelogan saat menggunakan pemformat 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}"
  }
}

Ringkasan

Dengan munculnya generator sumber C#, menulis API pengelogan yang sangat berkinerja tinggi jauh lebih mudah. Menggunakan pendekatan generator sumber memiliki beberapa manfaat utama:

  • Memungkinkan struktur pengelogan dipertahankan dan mengaktifkan sintaks format yang tepat yang diperlukan oleh Templat Pesan.
  • Memungkinkan penyediaan nama alternatif untuk tempat penampung templat dan menggunakan penentu format.
  • Memungkinkan lolosnya semua data asli apa adanya, tanpa komplikasi apa pun tentang bagaimana data disimpan sebelum sesuatu dilakukan dengannya (selain membuat string).
  • Menyediakan diagnostik khusus pengelogan, dan memancarkan peringatan untuk ID peristiwa duplikat.

Selain itu, ada manfaat daripada secara manual menggunakan LoggerMessage.Define:

  • Sintaks yang lebih pendek dan sederhana: Penggunaan atribut deklaratif daripada pengodean boilerplate.
  • Pengalaman pengembang terpandu: Generator memberikan peringatan untuk membantu pengembang melakukan hal yang benar.
  • Dukungan untuk jumlah parameter pengelogan yang semena-mena. LoggerMessage.Define mendukung maksimal enam.
  • Dukungan untuk tingkat log dinamis. Ini tidak mungkin dilakukan sendirian LoggerMessage.Define .

Lihat juga