Udostępnij za pośrednictwem


Rejestrowanie w 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. Podstawowi dostawcy logowania są wbudowani, a także dostępnych jest wielu dostawców zewnętrznych.

Rozpocznij

W tym pierwszym przykładzie przedstawiono podstawy, ale jest ona odpowiednia tylko dla trywialnych aplikacji konsolowych. Ta przykładowa aplikacja konsolowa korzysta z następujących pakietów NuGet:

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. ILoggerFactory przechowuje całą konfigurację, która określa, gdzie są wysyłane komunikaty dziennika. W takim przypadku należy skonfigurować dostawcę rejestrowania dla konsoli, aby komunikaty dziennika zostały zapisane na konsoli.
  • Tworzy element ILogger z kategorią o nazwie "Program". Kategoria to , która jest skojarzona z każdym komunikatem rejestrowanym przez obiekt . Służy do grupowania komunikatów dziennika z tej samej klasy (lub kategorii) podczas wyszukiwania lub filtrowania dzienników.
  • Wywołuje LogInformation w celu zarejestrowania komunikatu na poziomie Information. 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="9.0.3" />
    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.3" />
  </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

Należy rozważyć kilka zmian w poprzednim przykładzie podczas logowania się w mniej trywialnym scenariuszu:

  • Jeśli aplikacja używa wstrzykiwania zależności (DI) lub hosta, takiego jak ASP.NET WebApplication lub Generic Host, powinieneś używać ILoggerFactory i ILogger z odpowiednich kontenerów DI, a nie tworzyć ich bezpośrednio. Aby uzyskać więcej informacji, zobacz Integracja z DI i hostami.

  • Rejestrowanie generowania źródła czasu kompilacji jest zwykle lepszą alternatywą dla ILogger metod rozszerzenia, takich jak LogInformation. Generowanie źródła rejestrowania zapewnia lepszą wydajność, silniejsze wpisywanie i pozwala uniknąć rozkładania string stałych w różnych metodach. Kompromis polega na tym, że użycie tej techniki wymaga nieco więcej kodu.

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 Type, aby to nazewnictwo było łatwe do wykonania.
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 Twoja aplikacja używa wstrzykiwania zależności (DI) lub hosta, takiego jak WebApplication lub Generic Host w ASP.NET, powinieneś używać obiektów ILoggerFactory i ILogger z kontenera DI zamiast tworzyć je bezpośrednio.

Pobierz rejestrator ILogger z DI

Aby pobrać obiekt ILogger w hostowanej aplikacji, używamy ASP.NET Minimal APIs:

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 przyporządkowano przychodzące żądania internetowe do uruchomienia funkcji ExampleHandler.HandleRequest.
  • Linia 12 definiuje podstawowy konstruktor dla klasy ExampleHandler, funkcja dodana w języku C# 12. Użycie starszego stylu konstruktora języka C# działałoby równie dobrze, ale jest nieco bardziej rozbudowane.
  • 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 żaden ILogger w tej kategorii jeszcze nie istnieje, kontener DI automatycznie tworzy go w dostawcy usług.
  • Parametr logger odebrany w konstruktorze był używany do rejestrowania w funkcji HandleRequest.

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 zbudowaniem hosta można dostosować konfigurację rejestrowania za pomocą interfejsów API, takich jak HostApplicationBuilder.Logging, WebApplicationBuilder.Logging lub podobnych na innych hostach. Hosty używają również konfiguracji rejestrowania z domyślnych źródeł, takich jak appsettings.json i zmienne środowiskowe. Aby uzyskać więcej informacji, zobacz Konfiguracja na platformie .NET.

Ten przykład rozszerza poprzedni, aby dostosować ILoggerFactory, który został dostarczony przez WebApplicationBuilder program. Dodaje usługę OpenTelemetry jako dostawcę rejestrowania, przesyłającego 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();

Utwórz ILoggerFactory przy użyciu 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 zapisywania do konsoli
  • Dodano pojedynczy element ExampleService do kontenera
  • Utworzono wystąpienie ExampleService z kontenera DI, które automatycznie stworzyło także ILogger<ExampleService>, aby użyć go jako argumentu konstruktora.
  • Wywołano ExampleService.DoSomeWork, które używało elementu ILogger<ExampleService> do zalogowania wiadomości na konsolę.

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:

  • Określone są kategorie poziomów dziennika: "Default", "Microsoft" i "Microsoft.Hosting.Lifetime".
  • 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".
  • Kategoria "Microsoft" rejestruje zdarzenia na poziomie Warning i wyższym.
  • "Microsoft.Hosting.Lifetime" kategoria jest bardziej szczegółowa niż "Microsoft" kategoria, więc kategoria "Microsoft.Hosting.Lifetime" rejestruje na poziomie dziennika "Information" i wyższym.
  • 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ć właściwość LogLevel oraz właściwości dostawcy dziennika. LogLevel określa minimalny poziom rejestrowania dla wybranych kategorii. W poprzednim JSON Information i Warning poziomy logowania są określone. LogLevel wskazuje poziom istotności logu i ma wartość z zakresu od 0 do 6.

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

Kiedy LogLevel jest określony, rejestrowanie jest włączone dla komunikatów na wskazanym poziomie i wyżej. W poprzednim kodzie JSON kategoria jest rejestrowana dla Default i wyższych poziomów Information. Rejestrowane są na przykład komunikaty na poziomach Information, Warning, Error i Critical. Jeśli element LogLevel nie zostanie określony, rejestrowanie domyślnie odbywa się na poziomie Information. Aby uzyskać więcej informacji, zobacz Poziomy dziennika.

Właściwość dostawcy może wskazywać na 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 logowania 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" nadpisują ustawienia w Logging:LogLevel

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

  • Określeni dostawcy: 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 lub 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, sprawdź 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 logowania można ustawić za pomocą 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 utrwaloną zmienną środowiskową, biorąc pod uwagę wartość poziomu logu.

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

W nowym oknie 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 logowania konsoli i kilku 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 dostosowania poziomu logowania, który jest włączony dla różnych kategorii. AddConsole służy do dodawania dostawcy funkcji rejestrowania na konsoli. Domyślnie dzienniki o poziomie Debug nie są włączone, ale ponieważ konfiguracja dostosowała filtry, wiadomość debugowania "Witajcie wszyscy" zostaje wyświetlona w konsoli.

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ędniona 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 CreateLogger z użyciem w pełni kwalifikowanej nazwy typu T.

Poziom logowania

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

Poziom logowania 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 4 LogError W przypadku 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 Dla 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 stopień istotności logu. Zamiast wywoływać metodę Log(LogLevel, ...), większość deweloperów wywołuje metody rozszerzenia Log{LogLevel}. Metody rozszerzenia Log{LogLevel} wywołują metodę Log i określają 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 udostępnia różne nazwane stałe identyfikatorów i wyświetla się 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 poprzednim kodzie pierwszy Log{LogLevel} parametr , AppLogEvents.Readto 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 właściwe metody Log{LogLevel}, aby kontrolować ilość danych wyjściowych dziennika zapisywanych na określonym nośniku. 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 na poziomie Trace i Debug w tanim magazynie danych o dużej pojemności. Rozważ ograniczenie Trace i Debug do określonych kategorii.
    • Rejestrowanie na poziomach od Warning do Critical powinno generować bardzo mało komunikatów dziennika.
      • Koszty i limity magazynu zwykle nie są tu problemem.
      • Niewielka liczba dzienników zapewnia większą swobodę wyboru magazynu danych.
  • W trakcie rozwoju
    • 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 JSON Logging:Console:LogLevel:Microsoft:Information:

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

Identyfikator zdarzenia w dzienniku

Każdy dziennik może określić identyfikator zdarzenia, EventId jest strukturą z Id i opcjonalnymi Name właściwościami tylko do odczytu. 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 logowania przechowują identyfikator zdarzenia w polu, co umożliwia filtrowanie po identyfikatorze.

Szablon komunikatu dziennika

Każde API do logów używa szablonu wiadomości. 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 stosowaniu wielu symboli zastępczych w jednym szablonie wiadomości, ponieważ są one oparte na kolejności. Nazwy nie są używane do dopasowywania 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 wyodrębniania czasu z treści wiadomości.

Formatowanie szablonu komunikatu dziennika

Szablony komunikatów dziennika obsługują formatowanie symboli zastępczych. 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 jest instancją typu odpowiadającą typowi PlaceHolderName w szablonie komunikatu dziennika. Ta nazwa może być dowolna, ponieważ wartości są oparte na porządku. Format MMMM dd, yyyy jest prawidłowy dla DateTimeOffset typu.

Aby uzyskać więcej informacji na temat formatowania DateTime i DateTimeOffset, zobacz ciągi niestandardowego 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, pokazano przykład pominiecia składni symbolu zastępczego {} wraz z jego wynikiem. Na koniec pokazano również interpolację ciągów z symbolami zastępczymi 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.

Rejestrowanie wyjątkó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 logowania

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ę serwisową dla pracowników:

  • 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 logów

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 scopy dla dostawcy konsoli:

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

Poniższy kod aktywuje zakresy dla 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 loguje się w Main, uzyskując instancję 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 warto ponosić kosztu wydajności używania kodu asynchronicznego. Jeśli magazyn danych rejestrowania działa wolno, nie zapisuj do niego bezpośrednio. Rozważ zapisywanie wiadomości logu najpierw w szybkim magazynie, a później przenoszenie ich do wolniejszego 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

API do rejestrowania logów nie obejmuje scenariusza zmiany poziomów logów podczas działania aplikacji. Jednak niektórzy dostawcy konfiguracji mogą ponownie ładować konfigurację, co natychmiast wpływa na konfigurację rejestrowania. Na przykład dostawca konfiguracji plików domyślnie ponownie wczytuje konfigurację logowania. 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ą przywoływane w sposób domyślny.

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ż