Bagikan melalui


Pembuatan kode sumber pencatatan saat kompilasi

Pengelogan yang dihasilkan 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, secara otomatis menghasilkan implementasi dari metode partial yang didekorasinya. Jika ada masalah, ia menghasilkan diagnostik waktu kompilasi dengan petunjuk tentang penggunaan yang tepat. Solusi pengelogan waktu kompilasi ini jauh lebih cepat pada runtime daripada pendekatan pengelogan yang tersedia sebelumnya. Ini menghilangkan pembungkusan, alokasi sementara, dan salinan sebisa 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 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 disediakan untuk pesan. Status 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

Metode pengelogan yang dihiasi LoggerMessageAttribute harus memenuhi persyaratan berikut:

  • 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 tidak boleh generik.
  • Jika metode pengelogan adalah static, ILogger instans diperlukan sebagai parameter.

Model pembuatan kode tergantung pada kode yang dikompilasi dengan pengkompilasi C# modern, yaitu versi 9 atau yang lebih baru. Untuk informasi tentang mengubah versi bahasa, lihat penerapan versi bahasa C#.

Anatomi metode log

Tanda tangan ILogger.Log menerima LogLevel dan secara opsional Exception, seperti yang ditunjukkan dalam contoh kode berikut.

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

Petunjuk / Saran

Urutan parameter pada metode log tidak harus sesuai dengan urutan placeholder 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 lainnya

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

Meredaksi informasi sensitif dalam log

Saat mencatat data sensitif, penting untuk mencegah paparan yang tidak disengaja. Bahkan dengan metode pengelogan yang dihasilkan waktu kompilasi, mencatat nilai sensitif mentah dapat menyebabkan kebocoran data dan masalah kepatuhan.

Pustaka Microsoft.Extensions.Telemetry menyediakan kemampuan pengelogan dan pengayaan telemetri tingkat lanjut untuk aplikasi .NET. Ini memperluas alur pengelogan untuk secara otomatis menerapkan redaksi ke data rahasia saat menulis log. Ini memungkinkan Anda untuk menerapkan kebijakan perlindungan data di seluruh aplikasi Anda dengan mengintegrasikan redaksi ke dalam alur kerja pengelogan Anda. Ini dibuat untuk aplikasi yang membutuhkan telemetri canggih dan wawasan pengelogan.

Untuk mengaktifkan redaksi, gunakan pustaka Microsoft.Extensions.Compliance.Redaction . Pustaka ini menyediakan redactor—komponen yang mengubah data sensitif (misalnya, dengan menghapus, menutupi, atau hashing) sehingga aman untuk dihasilkan. Redactor dipilih berdasarkan klasifikasi data, yang memungkinkan Anda memberi label data sesuai dengan sensitivitasnya (seperti pribadi, privat, atau publik).

Untuk menggunakan penyuntingan dengan metode pencatatan yang dihasilkan secara otomatis dari sumber, Anda harus:

  1. Klasifikasikan data sensitif Anda menggunakan sistem klasifikasi data.
  2. Daftarkan dan konfigurasikan redactor untuk setiap klasifikasi dalam kontainer DI Anda.
  3. Aktifkan penyensoran dalam alur log.
  4. Periksa log Anda untuk memastikan tidak ada data sensitif yang terekspos.

Misalnya, jika Anda memiliki pesan log yang memiliki parameter yang dianggap privat:

[LoggerMessage(0, LogLevel.Information, "User SSN: {SSN}")]
public static partial void LogPrivateInformation(
    this ILogger logger,
    [MyTaxonomyClassifications.Private] string SSN);

Anda harus memiliki pengaturan yang mirip dengan ini:

using Microsoft.Extensions.Telemetry;
using Microsoft.Extensions.Compliance.Redaction;

var services = new ServiceCollection();
services.AddLogging(builder =>
{
    // Enable redaction.
    builder.EnableRedaction();
});

services.AddRedaction(builder =>
{
    // configure redactors for your data classifications
    builder.SetRedactor<StarRedactor>(MyTaxonomyClassifications.Private);
});

public void TestLogging()
{
    LogPrivateInformation("MySSN");
}

Outputnya harus seperti ini:

User SSN: *****

Pendekatan ini memastikan bahwa hanya data yang diredaksi yang dicatat, bahkan saat menggunakan API pengelogan yang dihasilkan waktu kompilasi. Anda dapat menggunakan redactor yang berbeda untuk berbagai jenis atau klasifikasi data, dan memperbarui logika redaksi Anda secara terpusat.

Untuk informasi selengkapnya tentang cara mengklasifikasikan data Anda, lihat Klasifikasi data di .NET. Untuk informasi selengkapnya tentang penyuntingan dan penyunting, lihat Penyuntingan data di .NET.

Ringkasan

Dengan munculnya generator sumber C#, menulis API pengelogan yang sangat berkinerja tinggi 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 untuk pencatatan log dan mengeluarkan 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 dimungkinkan dengan LoggerMessage.Define saja.

Lihat juga