Rejestrowanie w językach C# i .NET

Platforma .NET obsługuje rejestrowanie strukturalne o wysokiej wydajności za pośrednictwem interfejsu ILogger API, aby ułatwić monitorowanie zachowania aplikacji i diagnozowanie problemów. Dzienniki można zapisywać w różnych miejscach docelowych, konfigurując różnych dostawców rejestrowania. Dostawcy rejestrowania podstawowego są wbudowani i dostępnych jest również wielu dostawców innych firm.

Rozpocznij

W tym pierwszym przykładzie przedstawiono podstawy, ale jest ona odpowiednia tylko dla trywialnych aplikacji konsolowych. W następnej sekcji zobaczysz, jak poprawić kod biorąc pod uwagę skalowanie, wydajność, konfigurację i typowe wzorce programowania.

using Microsoft.Extensions.Logging;

using ILoggerFactory factory = LoggerFactory.Create(builder => builder.AddConsole());
ILogger logger = factory.CreateLogger("Program");
logger.LogInformation("Hello World! Logging is {Description}.", "fun");

Powyższy przykład:

  • Tworzy element ILoggerFactory. Przechowuje ILoggerFactory całą konfigurację, która określa, gdzie są wysyłane komunikaty dziennika. W takim przypadku należy skonfigurować dostawcę rejestrowania konsoli, aby komunikaty dziennika zostały zapisane w konsoli programu .
  • Tworzy element ILogger z kategorią o nazwie "Program". Kategoria jest skojarzona z każdym komunikatem string rejestrowanym ILogger przez obiekt. Służy do grupowania komunikatów dziennika z tej samej klasy (lub kategorii) podczas wyszukiwania lub filtrowania dzienników.
  • Wywołuje metodę LogInformation rejestrowania komunikatu na Information poziomie. Poziom dziennika wskazuje ważność zarejestrowanego zdarzenia i służy do filtrowania mniej ważnych komunikatów dziennika. Wpis dziennika zawiera również szablon komunikatu"Hello World! Logging is {Description}." i parę Description = funklucz-wartość . Nazwa klucza (lub symbol zastępczy) pochodzi od słowa wewnątrz nawiasów klamrowych w szablonie, a wartość pochodzi z pozostałego argumentu metody.

Ten plik projektu dla tego przykładu zawiera dwa pakiety NuGet:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
  </ItemGroup>

</Project>

Napiwek

Cały przykładowy kod źródłowy rejestrowania jest dostępny w przeglądarce Samples Browser do pobrania. Aby uzyskać więcej informacji, zobacz Przeglądanie przykładów kodu: Rejestrowanie na platformie .NET.

Rejestrowanie w aplikacji innej niż trywialna

Istnieje kilka zmian, które należy wziąć pod uwagę w poprzednim przykładzie podczas logowania w mniej prostym scenariuszu:

using Microsoft.Extensions.Logging;

internal partial class Program
{
    static void Main(string[] args)
    {
        using ILoggerFactory factory = LoggerFactory.Create(builder => builder.AddConsole());
        ILogger logger = factory.CreateLogger("Program");
        LogStartupMessage(logger, "fun");
    }

    [LoggerMessage(Level = LogLevel.Information, Message = "Hello World! Logging is {Description}.")]
    static partial void LogStartupMessage(ILogger logger, string description);
}
  • Zalecaną praktyką dla nazw kategorii dzienników jest użycie w pełni kwalifikowanej nazwy klasy tworzącej komunikat dziennika. Pomaga to powiązać komunikaty dzienników z powrotem z kodem, który je wygenerował, i oferuje dobry poziom kontroli podczas filtrowania dzienników. CreateLogger akceptuje element , Type aby to nazewnictwo było łatwe.
using Microsoft.Extensions.Logging;

internal class Program
{
    static void Main(string[] args)
    {
        using ILoggerFactory factory = LoggerFactory.Create(builder => builder.AddConsole());
        ILogger logger = factory.CreateLogger<Program>();
        logger.LogInformation("Hello World! Logging is {Description}.", "fun");
    }
}
using Microsoft.Extensions.Logging;
using OpenTelemetry.Logs;

using ILoggerFactory factory = LoggerFactory.Create(builder =>
{
    builder.AddOpenTelemetry(logging =>
    {
        logging.AddOtlpExporter();
    });
});
ILogger logger = factory.CreateLogger("Program");
logger.LogInformation("Hello World! Logging is {Description}.", "fun");

Integracja z hostami i wstrzykiwanie zależności

Jeśli aplikacja używa wstrzykiwania zależności (DI) lub hosta, takiego jak ASP. Usługa WebApplication lub host ogólny platformy NET powinny następnie używać obiektów ILoggerFactory i ILogger z kontenera DI zamiast tworzyć je bezpośrednio.

Pobieranie rejestratora ILogger z di

W tym przykładzie obiekt ILogger w hostowanej aplikacji jest pobierany przy użyciu ASP.NET minimalnych interfejsów API:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSingleton<ExampleHandler>();

var app = builder.Build();

var handler = app.Services.GetRequiredService<ExampleHandler>();
app.MapGet("/", handler.HandleRequest);

app.Run();

partial class ExampleHandler(ILogger<ExampleHandler> logger)
{
    public string HandleRequest()
    {
        LogHandleRequest(logger);
        return "Hello World";
    }

    [LoggerMessage(LogLevel.Information, "ExampleHandler.HandleRequest was called")]
    public static partial void LogHandleRequest(ILogger logger);
}

Powyższy przykład:

  • Utworzono pojedynczą usługę o nazwie ExampleHandler i zamapowane przychodzące żądania internetowe w celu uruchomienia ExampleHandler.HandleRequest funkcji.
  • Wiersz 8 definiuje podstawowy konstruktor dla programu ExampleHandler, funkcję dodaną w języku C# 12. Użycie starszego stylu konstruktora języka C# działałoby równie dobrze, ale jest nieco bardziej pełne.
  • Konstruktor definiuje parametr typu ILogger<ExampleHandler>. ILogger<TCategoryName> pochodzi z ILogger i wskazuje, która kategoria ILogger ma obiekt. Kontener DI lokalizuje obiekt ILogger z prawidłową kategorią i dostarcza go jako argument konstruktora. Jeśli nie ILogger ma jeszcze tej kategorii, kontener DI automatycznie tworzy go z ILoggerFactory poziomu dostawcy usług.
  • Parametr logger odebrany w konstruktorze był używany do rejestrowania HandleRequest w funkcji.

ILoggerFactory dostarczony przez hosta

Konstruktorzy hostów inicjują konfigurację domyślną, a następnie dodaj skonfigurowany ILoggerFactory obiekt do kontenera DI hosta podczas kompilowania hosta. Przed skompilowanie hosta można dostosować konfigurację rejestrowania za pomocą HostApplicationBuilder.Logginginterfejsów API , WebApplicationBuilder.Logginglub podobnych na innych hostach. Hosty stosują również konfigurację rejestrowania z domyślnych źródeł konfiguracji jako appsettings.json i zmiennych środowiskowych. Aby uzyskać więcej informacji, zobacz Konfiguracja na platformie .NET.

Ten przykład rozszerza poprzedni, aby dostosować ILoggerFactory element dostarczony przez WebApplicationBuilderprogram . Dodaje on usługę OpenTelemetry jako dostawca rejestrowania przesyłający dzienniki za pośrednictwem protokołu OTLP (protokół OpenTelemetry)::

var builder = WebApplication.CreateBuilder(args);
builder.Logging.AddOpenTelemetry(logging => logging.AddOtlpExporter());
builder.Services.AddSingleton<ExampleHandler>();
var app = builder.Build();

Tworzenie elementu ILoggerFactory za pomocą di

Jeśli używasz kontenera di bez hosta, użyj polecenia AddLogging , aby skonfigurować i dodać ILoggerFactory do kontenera.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

// Add services to the container including logging
var services = new ServiceCollection();
services.AddLogging(builder => builder.AddConsole());
services.AddSingleton<ExampleService>();
IServiceProvider serviceProvider = services.BuildServiceProvider();

// Get the ExampleService object from the container
ExampleService service = serviceProvider.GetRequiredService<ExampleService>();

// Do some pretend work
service.DoSomeWork(10, 20);

class ExampleService(ILogger<ExampleService> logger)
{
    public void DoSomeWork(int x, int y)
    {
        logger.LogInformation("DoSomeWork was called. x={X}, y={Y}", x, y);
    }
}

Powyższy przykład:

  • Utworzono kontener usługi DI zawierający ILoggerFactory skonfigurowany do zapisu w konsoli programu
  • Dodano pojedynczy element ExampleService do kontenera
  • Utworzono wystąpienie ExampleService obiektu z kontenera DI, które również automatycznie utworzyło ILogger<ExampleService> obiekt do użycia jako argument konstruktora.
  • Wywołano ExampleService.DoSomeWork wywołanie , które używało elementu ILogger<ExampleService> do rejestrowania komunikatu w konsoli programu .

Konfigurowanie rejestrowania

Konfiguracja rejestrowania jest ustawiana w kodzie lub za pośrednictwem źródeł zewnętrznych, takich jak pliki konfiguracji i zmienne środowiskowe. Korzystanie z konfiguracji zewnętrznej jest korzystne, jeśli jest to możliwe, ponieważ można ją zmienić bez ponownego kompilowania aplikacji. Jednak niektóre zadania, takie jak ustawianie dostawców rejestrowania, można skonfigurować tylko z poziomu kodu.

Konfigurowanie rejestrowania bez kodu

W przypadku aplikacji korzystających z hosta konfiguracja rejestrowania jest często udostępniana przez "Logging" sekcję appsettings.{Environment}.json plików. W przypadku aplikacji, które nie korzystają z hosta, zewnętrzne źródła konfiguracji są konfigurowane jawnie lub konfigurowane w kodzie .

Następujące ustawienia aplikacji. Development.json plik jest generowany przez szablony usługi .NET Worker:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  }
}

W powyższym kodzie JSON:

  • "Default"Określone są kategorie na poziomie , "Microsoft"i "Microsoft.Hosting.Lifetime" dziennika.
  • Wartość "Default" jest stosowana do wszystkich kategorii, które nie są określone w inny sposób, skutecznie tworząc wszystkie wartości domyślne dla wszystkich kategorii "Information". To zachowanie można zastąpić, określając wartość dla kategorii.
  • Kategoria "Microsoft" dotyczy wszystkich kategorii rozpoczynających się ciągiem "Microsoft".
  • Dzienniki "Microsoft" kategorii na poziomie Warning dziennika i wyższe.
  • Kategoria jest bardziej szczegółowa "Microsoft.Hosting.Lifetime" niż "Microsoft" kategoria, więc "Microsoft.Hosting.Lifetime" dzienniki kategorii na poziomie "Information" dziennika i wyższe.
  • Nie określono konkretnego dostawcy dziennika, więc właściwość LogLevel dotyczy wszystkich włączonych dostawców rejestrowania z wyjątkiem dostawcy Windows EventLog.

Właściwość Logging może mieć wyliczenie LogLevel i właściwości dostawcy dziennika. Wyliczenie LogLevel określa minimalny poziom rejestrowania dla wybranych kategorii. W poprzednim formacie JSON Information i Warning określono poziomy dziennika. Wyliczenie LogLevel określa ważność dziennika i ma wartość z zakresu od 0 do 6:

Trace = 0, Debug = 1, Information = 2, Warning = 3, Error = 4, Critical = 5 i None = 6.

Gdy wyliczenie LogLevel jest określone, rejestrowanie jest włączone dla komunikatów na tym i wyższym poziomie. W poprzednim kodzie JSON kategoria jest rejestrowana Default i Information wyższa. Rejestrowane są na przykład komunikaty na poziomach Information, Warning, Error i Critical. Jeśli wyliczenie LogLevel nie zostanie określone, dla rejestrowania stosowany jest poziom domyślny Information. Aby uzyskać więcej informacji, zobacz Poziomy dziennika.

Właściwość dostawcy może określać właściwość LogLevel. Właściwość LogLevel w sekcji dostawcy określa poziomy, które mają być rejestrowane dla tego dostawcy, i zastępuje ustawienia dziennika bez określonego dostawcy. Rozważ następujący plik appsettings.json :

{
    "Logging": {
        "LogLevel": {
            "Default": "Error",
            "Microsoft": "Warning"
        },
        "Debug": {
            "LogLevel": {
                "Default": "Information",
                "Microsoft.Hosting": "Trace"
            }
        },
        "EventSource": {
            "LogLevel": {
                "Default": "Warning"
            }
        }
    }
}

Ustawienia w Logging.{ProviderName}.LogLevel zastępują ustawienia w Logging.LogLevel. W poprzednim kodzie JSON Debug domyślny poziom dziennika dostawcy ma wartość Information:

Logging:Debug:LogLevel:Default:Information

Poprzednie ustawienie określa poziom dziennika Information dla każdej kategorii Logging:Debug: z wyjątkiem kategorii Microsoft.Hosting. Gdy jest określona konkretna kategoria, zastępuje ona kategorię domyślną. W poprzednim kodzie JSON Logging:Debug:LogLevel kategorie "Microsoft.Hosting" i "Default" przesłonięć ustawienia w pliku Logging:LogLevel

Minimalny poziom dziennika można określić dla:

  • określonych dostawców, na przykład Logging:EventSource:LogLevel:Default:Information
  • określonych kategorii, na przykład: Logging:LogLevel:Microsoft:Warning
  • wszystkich dostawców i wszystkich kategorii: Logging:LogLevel:Default:Warning

Wszystkie dzienniki poniżej minimalnego poziomu nie są:

  • przekazywane do dostawcy,
  • rejestrowane ani wyświetlane.

Aby pominąć wszystkie dzienniki, określ wartość LogLevel.None. Poziom LogLevel.None ma wartość 6, która jest wyższa niż w przypadku poziomu LogLevel.Critical (5).

Jeśli dostawca obsługuje zakresy dzienników, właściwość IncludeScopes wskazuje, czy są one włączone. Aby uzyskać więcej informacji, zobacz zakresy dzienników

Poniższy plik appsettings.json zawiera ustawienia dla wszystkich wbudowanych dostawców:

{
    "Logging": {
        "LogLevel": {
            "Default": "Error",
            "Microsoft": "Warning",
            "Microsoft.Hosting.Lifetime": "Warning"
        },
        "Debug": {
            "LogLevel": {
                "Default": "Information"
            }
        },
        "Console": {
            "IncludeScopes": true,
            "LogLevel": {
                "Microsoft.Extensions.Hosting": "Warning",
                "Default": "Information"
            }
        },
        "EventSource": {
            "LogLevel": {
                "Microsoft": "Information"
            }
        },
        "EventLog": {
            "LogLevel": {
                "Microsoft": "Information"
            }
        },
        "AzureAppServicesFile": {
            "IncludeScopes": true,
            "LogLevel": {
                "Default": "Warning"
            }
        },
        "AzureAppServicesBlob": {
            "IncludeScopes": true,
            "LogLevel": {
                "Microsoft": "Information"
            }
        },
        "ApplicationInsights": {
            "LogLevel": {
                "Default": "Information"
            }
        }
    }
}

W powyższym przykładzie:

  • Kategorie i poziomy nie mają sugerowanych wartości. Przykład ma na celu pokazanie wszystkich dostawców domyślnych.
  • Ustawienia w Logging.{ProviderName}.LogLevel zastępują ustawienia w Logging.LogLevel. Na przykład poziom w sekcji Debug.LogLevel.Default zastępuje poziom w sekcji LogLevel.Default.
  • Każdy alias dostawcy jest używany. Każdy dostawca ma zdefiniowany alias, którego można używać w konfiguracji zamiast w pełni kwalifikowanej nazwy typu. Aliasy wbudowanych dostawców to:
    • Console
    • Debug
    • EventSource
    • EventLog
    • AzureAppServicesFile
    • AzureAppServicesBlob
    • ApplicationInsights

Ustawianie poziomu dziennika za pośrednictwem wiersza polecenia, zmiennych środowiskowych i innej konfiguracji

Poziom dziennika można ustawić za pośrednictwem dowolnego dostawcy konfiguracji. Można na przykład utworzyć utrwałą zmienną środowiskową o nazwie Logging:LogLevel:Microsoft z wartością Information.

Utwórz i przypisz utrwałą zmienną środowiskową, biorąc pod uwagę wartość poziomu dziennika.

:: Assigns the env var to the value
setx "Logging__LogLevel__Microsoft" "Information" /M

W nowym wystąpieniu wiersza polecenia odczytaj zmienną środowiskową.

:: Prints the env var value
echo %Logging__LogLevel__Microsoft%

Poprzednie ustawienie środowiska jest utrwalane w środowisku. Aby przetestować ustawienia podczas korzystania z aplikacji utworzonej za pomocą szablonów usługi .NET Worker, użyj dotnet run polecenia w katalogu projektu po przypisaniu zmiennej środowiskowej.

dotnet run

Napiwek

Po ustawieniu zmiennej środowiskowej ponownie uruchom zintegrowane środowisko projektowe (IDE), aby upewnić się, że nowo dodane zmienne środowiskowe są dostępne.

W usłudze Azure App Service wybierz pozycję Nowe ustawienie aplikacji na stronie Ustawienia > Konfiguracja. Ustawienia aplikacji usługi Azure App Service są:

  • szyfrowane podczas przechowywania i przesyłane za pośrednictwem zaszyfrowanego kanału,
  • Uwidaczniane jako zmienne środowiskowe.

Aby uzyskać więcej informacji na temat ustawiania wartości konfiguracji platformy .NET przy użyciu zmiennych środowiskowych, zobacz zmienne środowiskowe.

Konfigurowanie rejestrowania przy użyciu kodu

Aby skonfigurować logowanie w kodzie, użyj interfejsu ILoggingBuilder API. Dostęp do nich można uzyskać z różnych miejsc:

W tym przykładzie pokazano ustawienie dostawcy rejestrowania konsoli i kilka filtrów.

using Microsoft.Extensions.Logging;

using var loggerFactory = LoggerFactory.Create(static builder =>
{
    builder
        .AddFilter("Microsoft", LogLevel.Warning)
        .AddFilter("System", LogLevel.Warning)
        .AddFilter("LoggingConsoleApp.Program", LogLevel.Debug)
        .AddConsole();
});

ILogger logger = loggerFactory.CreateLogger<Program>();
logger.LogDebug("Hello {Target}", "Everyone");

W poprzednim przykładzie AddFilter służy do dostosowywania poziomu dziennika, który jest włączony dla różnych kategorii. AddConsole służy do dodawania dostawcy rejestrowania konsoli. Domyślnie dzienniki o Debug ważności nie są włączone, ale ponieważ konfiguracja dostosowała filtry, w konsoli jest wyświetlany komunikat debugowania "Hello Everyone".

Jak są stosowane reguły filtrowania

Po utworzeniu obiektu ILogger<TCategoryName> obiekt ILoggerFactory wybiera jedną regułę dla każdego dostawcy, która ma być stosowana do tego rejestratora. Wszystkie komunikaty zapisywane przez wystąpienie ILogger są filtrowane na podstawie wybranych reguł. Z dostępnych reguł wybierana jest najbardziej konkretna reguła dla każdej pary dostawcy i kategorii.

Następujący algorytm jest używany dla każdego dostawcy podczas tworzenia obiektu ILogger dla danej kategorii:

  • Wybierz wszystkie reguły zgodne z dostawcą lub jego aliasem. Jeśli nie zostanie znalezione żadne dopasowanie, wybierz wszystkie reguły z pustym dostawcą.
  • Z wyników poprzedniego kroku wybierz reguły z najdłuższym pasującym prefiksem kategorii. Jeśli nie zostanie znalezione żadne dopasowanie, wybierz wszystkie reguły, które nie określają kategorii.
  • Jeśli wybrano wiele reguł, użyj ostatniej.
  • Jeśli nie wybrano żadnych reguł, użyj polecenia LoggingBuilderExtensions.SetMinimumLevel(ILoggingBuilder, LogLevel) , aby określić minimalny poziom rejestrowania.

Kategoria dziennika

Po utworzeniu obiektu ILogger jest określana kategoria. Ta kategoria jest uwzględniania w każdym komunikacie dziennika utworzonym przez to wystąpienie ILogger. Ciąg kategorii jest dowolny, ale konwencja polega na użyciu w pełni kwalifikowanej nazwy klasy. Na przykład w aplikacji z usługą zdefiniowaną tak jak następujący obiekt kategoria może mieć wartość "Example.DefaultService":

namespace Example
{
    public class DefaultService : IService
    {
        private readonly ILogger<DefaultService> _logger;

        public DefaultService(ILogger<DefaultService> logger) =>
            _logger = logger;

        // ...
    }
}

Jeśli wymagana jest dalsza kategoryzacja, konwencja polega na użyciu nazwy hierarchicznej przez dołączenie podkategorii do w pełni kwalifikowanej nazwy klasy i jawne określenie kategorii przy użyciu polecenia LoggerFactory.CreateLogger:

namespace Example
{
    public class DefaultService : IService
    {
        private readonly ILogger _logger;

        public DefaultService(ILoggerFactory loggerFactory) =>
            _logger = loggerFactory.CreateLogger("Example.DefaultService.CustomCategory");

        // ...
    }
}

Wywołanie CreateLogger przy użyciu stałej nazwy może być przydatne w przypadku użycia w wielu klasach/typach, dzięki czemu zdarzenia mogą być zorganizowane według kategorii.

ILogger<T> jest równoważne wywołaniu metody CreateLogger z w pełni kwalifikowaną nazwą typu T.

Poziom dziennika

Poniższa tabela zawiera wartości LogLevel, wygodną metodę rozszerzenia Log{LogLevel} i sugerowane użycie:

PoziomRejestrowania Wartość Metoda opis
Śledzenie 0 LogTrace Obejmuje najbardziej szczegółowe komunikaty. Te komunikaty mogą zawierać poufne dane aplikacji. Komunikaty są domyślnie wyłączone i nie powinny być włączane w środowisku produkcyjnym.
Debug 1 LogDebug Na potrzeby debugowania i programowania. Należy zachować ostrożność w środowisku produkcyjnym ze względu na dużą pojemność.
Informacje 2 LogInformation Śledzi ogólny przepływ aplikacji. Może mieć wartość długoterminową.
Ostrzeżenie 3 LogWarning Na potrzeby nietypowych lub nieoczekiwanych zdarzeń. Zazwyczaj obejmuje błędy lub warunki, które nie powodują awarii aplikacji.
Błąd 100 LogError Na potrzeby błędów i wyjątków, których nie można obsłużyć. Te komunikaty wskazują na błąd w bieżącej operacji lub żądaniu, a nie awarię całej aplikacji.
Krytyczne 5 LogCritical Na potrzeby awarii wymagających natychmiastowej uwagi. Przykłady: scenariusze utraty danych, brak miejsca na dysku.
Brak 6 Określa, że nie należy zapisywać żadnych komunikatów.

W powyższej tabeli obiekty LogLevel uporządkowano w kolejności od najniższej do najwyższej ważności.

Pierwszy parametr metody Log, LogLevel, wskazuje ważność dziennika. Zamiast wywoływać metodę Log(LogLevel, ...), większość deweloperów wywołuje metody rozszerzenia Log{LogLevel}. Metody Log{LogLevel} rozszerzenia wywołają metodę Log i określ parametr LogLevel. Na przykład dwa poniższe wywołania rejestrowania działają tak samo i tworzą ten sam dziennik:

public void LogDetails()
{
    var logMessage = "Details for log.";

    _logger.Log(LogLevel.Information, AppLogEvents.Details, logMessage);
    _logger.LogInformation(AppLogEvents.Details, logMessage);
}

AppLogEvents.Details jest identyfikatorem zdarzenia i jest niejawnie reprezentowany przez wartość stałą Int32 . AppLogEvents jest klasą, która uwidacznia różne nazwane stałe identyfikatora i jest wyświetlana w sekcji Identyfikator zdarzenia dziennika.

Poniższy kod tworzy dzienniki Information i Warning:

public async Task<T> GetAsync<T>(string id)
{
    _logger.LogInformation(AppLogEvents.Read, "Reading value for {Id}", id);

    var result = await _repository.GetAsync(id);
    if (result is null)
    {
        _logger.LogWarning(AppLogEvents.ReadNotFound, "GetAsync({Id}) not found", id);
    }

    return result;
}

W powyższym kodzie pierwszym parametrem metody Log{LogLevel}, AppLogEvents.Read, jest identyfikator zdarzenia dziennika. Drugi parametr to szablon komunikatu z symbolami zastępczymi dla wartości argumentów dostarczanych przez pozostałe parametry metody. Parametry metody zostały wyjaśnione w sekcji szablonu komunikatu w dalszej części tego artykułu.

Skonfiguruj odpowiedni poziom dziennika i wywołaj prawidłowe Log{LogLevel} metody, aby kontrolować ilość danych wyjściowych dziennika zapisywanych na określonym nośniku magazynu. Na przykład:

  • W środowisku produkcyjnym:
    • Rejestrowanie na poziomach Trace i Debug generuje dużą ilość szczegółowych komunikatów dziennika. Aby kontrolować koszty i nie przekraczać limitów magazynowania danych, rejestruj komunikaty dziennika na poziomie Trace i Debug w magazynie danych o dużej pojemności i niskiej cenie. Rozważ ograniczenie dzienników Trace i Debug do określonych kategorii.
    • Rejestrowanie na poziomach od Warning do Critical powinno generować kilka komunikatów dziennika.
      • Koszty i limity magazynu zwykle nie są tu problemem.
      • Niewielka liczba dzienników zapewnia większą swobodę wyboru magazynu danych.
  • W programowania:
    • Ustaw wartość Warning.
    • Dodaj komunikaty Trace lub Debug podczas rozwiązywania problemów. Aby ograniczyć ilość danych wyjściowych, poziom Trace lub Debug ustawiaj tylko dla kategorii, które są badane.

Następujące zestawy Logging:Console:LogLevel:Microsoft:InformationJSON:

{
    "Logging": {
        "LogLevel": {
            "Microsoft": "Warning"
        },
        "Console": {
            "LogLevel": {
                "Microsoft": "Information"
            }
        }
    }
}

Identyfikator zdarzenia dziennika

Każdy dziennik może określić identyfikator zdarzenia, EventId jest strukturą z opcjonalnymi Id właściwościami odczytu i.Name Przykładowy kod źródłowy używa AppLogEvents klasy do definiowania identyfikatorów zdarzeń:

using Microsoft.Extensions.Logging;

internal static class AppLogEvents
{
    internal static EventId Create = new(1000, "Created");
    internal static EventId Read = new(1001, "Read");
    internal static EventId Update = new(1002, "Updated");
    internal static EventId Delete = new(1003, "Deleted");

    // These are also valid EventId instances, as there's
    // an implicit conversion from int to an EventId
    internal const int Details = 3000;
    internal const int Error = 3001;

    internal static EventId ReadNotFound = 4000;
    internal static EventId UpdateNotFound = 4001;

    // ...
}

Napiwek

Aby uzyskać więcej informacji na temat konwertowania elementu int na element EventId, zobacz EventId.Implicit(Int32 to EventId) Operator.

Identyfikator zdarzenia kojarzy zestaw zdarzeń. Na przykład wszystkie dzienniki związane z odczytywaniem wartości z repozytorium mogą mieć wartość 1001.

Dostawca rejestrowania może rejestrować identyfikator zdarzenia w polu identyfikatora, w komunikacie rejestrowania lub w ogóle nie. Dostawca debugowania nie pokazuje identyfikatorów zdarzeń. Dostawca konsoli wyświetla identyfikatory zdarzeń w nawiasach kwadratowych po kategorii:

info: Example.DefaultService.GetAsync[1001]
      Reading value for a1b2c3
warn: Example.DefaultService.GetAsync[4000]
      GetAsync(a1b2c3) not found

Niektórzy dostawcy rejestrowania przechowują identyfikator zdarzenia w polu, co umożliwia filtrowanie po identyfikatorze.

Szablon komunikatu dziennika

Każdy interfejs API dziennika używa szablonu komunikatu. Szablon komunikatu może zawierać symbole zastępcze, dla których są podawane argumenty. Dla symboli zastępczych należy używać nazw, a nie liczb. Kolejność symboli zastępczych, a nie ich nazw, określa, które parametry są używane do podawania ich wartości. W poniższym kodzie nazwy parametrów nie są sekwencyjne w szablonie komunikatu:

string p1 = "param1";
string p2 = "param2";
_logger.LogInformation("Parameter values: {p2}, {p1}", p1, p2);

Powyższy kod tworzy komunikat dziennika z wartościami parametrów w sekwencji:

Parameter values: param1, param2

Uwaga

Należy pamiętać o używaniu wielu symboli zastępczych w ramach jednego szablonu wiadomości, ponieważ są one oparte na porządkowi. Nazwy nieużywane do wyrównywania argumentów do symboli zastępczych.

Takie podejście umożliwia dostawcom rejestrowania zaimplementowanie rejestrowania semantycznego lub strukturalnego. Same argumenty są przekazywane do systemu rejestrowania, a nie tylko do sformatowanego szablonu komunikatu. Dzięki temu dostawcy rejestrowania mogą przechowywać wartości parametrów jako pola. Rozważmy następującą metodę rejestratora:

_logger.LogInformation("Getting item {Id} at {RunTime}", id, DateTime.Now);

Na przykład podczas rejestrowania w usłudze Azure Table Storage:

  • Każda jednostka tabeli platformy Azure może mieć właściwości ID i RunTime.
  • Tabele z właściwościami upraszczają wykonywanie zapytań dotyczących zarejestrowanych danych. Na przykład zapytanie może znaleźć wszystkie dzienniki w określonym zakresie RunTime bez konieczności analizowania czasu z komunikatu tekstowego.

Formatowanie szablonu komunikatu dziennika

Szablony komunikatów dziennika obsługują formatowanie zastępcze. Szablony mogą określać dowolny prawidłowy format dla danego argumentu typu. Rozważmy na przykład następujący Information szablon komunikatu rejestratora:

_logger.LogInformation("Logged on {PlaceHolderName:MMMM dd, yyyy}", DateTimeOffset.UtcNow);
// Logged on January 06, 2022

W poprzednim przykładzie DateTimeOffset wystąpienie jest typem odpowiadającym PlaceHolderName szablonowi komunikatu rejestratora. Ta nazwa może być niczym, ponieważ wartości są oparte na porządkowi. Format MMMM dd, yyyy jest prawidłowy dla DateTimeOffset typu.

Aby uzyskać więcej informacji na DateTime temat formatowania i DateTimeOffset formatowania, zobacz Niestandardowe ciągi formatu daty i godziny.

Przykłady

W poniższych przykładach pokazano, jak sformatować szablon wiadomości przy użyciu składni symbolu zastępczego {} . Ponadto przykład ucieczki składni symbolu zastępczego {} jest wyświetlany z jego danymi wyjściowymi. Na koniec pokazano również interpolację ciągów z symbolami zastępczymi tworzenia szablonów:

logger.LogInformation("Number: {Number}", 1);               // Number: 1
logger.LogInformation("{{Number}}: {Number}", 3);           // {Number}: 3
logger.LogInformation($"{{{{Number}}}}: {{Number}}", 5);    // {Number}: 5

Napiwek

  • W większości przypadków podczas rejestrowania należy używać formatowania szablonu komunikatów dziennika. Użycie interpolacji ciągów może powodować problemy z wydajnością.
  • Reguła analizy kodu CA2254: Szablon powinien być wyrażeniem statycznym, które ułatwia powiadamianie o miejscach, w których komunikaty dziennika nie używają odpowiedniego formatowania.

Wyjątki dzienników

Metody rejestratora mają przeciążenia, które przyjmują parametr wyjątku:

public void Test(string id)
{
    try
    {
        if (id is "none")
        {
            throw new Exception("Default Id detected.");
        }
    }
    catch (Exception ex)
    {
        _logger.LogWarning(
            AppLogEvents.Error, ex,
            "Failed to process iteration: {Id}", id);
    }
}

Rejestrowanie wyjątków jest specyficzne dla dostawcy.

Domyślny poziom dziennika

Jeśli domyślny poziom dziennika nie jest ustawiony, domyślną wartością poziomu dziennika jest Information.

Rozważmy na przykład następującą aplikację usługi procesu roboczego:

  • Utworzono przy użyciu szablonów procesów roboczych platformy .NET.
  • appsettings.json i ustawienia aplikacji. Development.json usunięte lub zmienione.

W przypadku poprzedniej konfiguracji przejście do strony prywatności lub strony głównej powoduje wygenerowanie wielu komunikatów Trace, Debug i Information z ciągiem Microsoft w nazwie kategorii.

Poniższy kod ustawia domyślny poziom dziennika, kiedy domyślny poziom dziennika nie jest ustawiony w konfiguracji:

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Logging.SetMinimumLevel(LogLevel.Warning);

using IHost host = builder.Build();

await host.RunAsync();

Funkcja filtru

Funkcja filtru jest wywoływana dla wszystkich dostawców i kategorii, dla których nie przypisano reguł za pomocą konfiguracji lub kodu:

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Logging.AddFilter((provider, category, logLevel) =>
{
    return provider.Contains("ConsoleLoggerProvider")
        && (category.Contains("Example") || category.Contains("Microsoft"))
        && logLevel >= LogLevel.Information;
});

using IHost host = builder.Build();

await host.RunAsync();

Powyższy kod wyświetla dzienniki konsoli, gdy kategoria zawiera parametr Example lub Microsoft, a poziom dziennika ma wartość Information lub wyższą.

Zakresy dziennika

Zakres grupuje zestaw operacji logicznych. To grupowanie może służyć do dołączania tych samych danych do każdego dziennika utworzonego w ramach zestawu. Na przykład każdy dziennik utworzony w ramach przetwarzania transakcji może zawierać identyfikator transakcji.

Zakres:

Następujący dostawcy obsługują zakresy:

Użyj zakresu, opakowując wywołania rejestratora za pomocą bloku using:

public async Task<T> GetAsync<T>(string id)
{
    T result;
    var transactionId = Guid.NewGuid().ToString();

    using (_logger.BeginScope(new List<KeyValuePair<string, object>>
        {
            new KeyValuePair<string, object>("TransactionId", transactionId),
        }))
    {
        _logger.LogInformation(
            AppLogEvents.Read, "Reading value for {Id}", id);

        var result = await _repository.GetAsync(id);
        if (result is null)
        {
            _logger.LogWarning(
                AppLogEvents.ReadNotFound, "GetAsync({Id}) not found", id);
        }
    }

    return result;
}

Poniższy kod JSON włącza zakresy dla dostawcy konsoli:

{
    "Logging": {
        "Debug": {
            "LogLevel": {
                "Default": "Information"
            }
        },
        "Console": {
            "IncludeScopes": true,
            "LogLevel": {
                "Microsoft": "Warning",
                "Default": "Information"
            }
        },
        "LogLevel": {
            "Default": "Debug"
        }
    }
}

Poniższy kod umożliwia zakresy dostawcy konsoli:

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Logging.ClearProviders();
builder.Logging.AddConsole(options => options.IncludeScopes = true);

using IHost host = builder.Build();

await host.RunAsync();

Tworzenie dzienników w pliku Main

Poniższy kod rejestruje w pliku Main, uzyskując wystąpienie ILogger z DI po utworzeniu hosta:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

using IHost host = Host.CreateApplicationBuilder(args).Build();

var logger = host.Services.GetRequiredService<ILogger<Program>>();
logger.LogInformation("Host created.");

await host.RunAsync();

Powyższy kod opiera się na dwóch pakietach NuGet:

Jego plik projektu będzie wyglądać podobnie do następującego:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net7.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
    <PackageReference Include="Microsoft.Extensions.Logging" Version="7.0.0" />
  </ItemGroup>

</Project>

Brak metod rejestratora asynchronicznego

Rejestrowanie powinno być tak szybkie, że nie jest warte kosztu wydajności kodu asynchronicznego. Jeśli magazyn danych rejestrowania działa wolno, nie zapisuj go bezpośrednio. Rozważ zapisywanie komunikatów dziennika najpierw w szybkim magazynie, a później przenoszenie ich do powolnego magazynu. Na przykład w przypadku rejestrowania w programie SQL Server nie rób tego bezpośrednio w metodzie Log, ponieważ metody Log są synchroniczne. Zamiast tego synchronicznie dodaj komunikaty dziennika do kolejki w pamięci, a następnie za pomocą procesu roboczego w tle ściągaj komunikaty z kolejki, aby wykonać asynchroniczne zadanie wypychania danych do programu SQL Server.

Zmienianie poziomów dziennika w uruchomionej aplikacji

Interfejs API rejestrowania nie obejmuje scenariusza zmieniania poziomów dziennika podczas działania aplikacji. Jednak niektórzy dostawcy konfiguracji mogą ponownie ładować konfigurację, która jest natychmiast stosowna w konfiguracji rejestrowania. Na przykład dostawca konfiguracji plików domyślnie ponownie ładuje konfigurację rejestrowania. Jeśli konfiguracja zostanie zmieniona w kodzie podczas działania aplikacji, aplikacja może wywołać element IConfigurationRoot.Reload , aby zaktualizować konfigurację rejestrowania aplikacji.

Pakiety NuGet

Interfejsy ILogger<TCategoryName> i ILoggerFactory i implementacje są uwzględniane w większości zestawów SDK platformy .NET jako niejawne odwołania do pakietu. Są one również jawnie dostępne w następujących pakietach NuGet, gdy nie są w inny sposób niejawnie przywoływali:

Aby uzyskać więcej informacji na temat tego, który zestaw .NET SDK zawiera niejawne odwołania do pakietów, zobacz .NET SDK: table to implicit namespace (Zestaw SDK platformy .NET: tabela do niejawnej przestrzeni nazw).

Zobacz też