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 = fun
klucz-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
iILogger
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 jakLogInformation
. Generowanie źródła rejestrowania zapewnia lepszą wydajność, silniejsze wpisywanie i pozwala uniknąć rozkładaniastring
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");
}
}
- Jeśli nie używasz dzienników konsoli jako jedynego rozwiązania do monitorowania produkcyjnego, dodaj dostawców rejestrowania, których planujesz użyć. Na przykład można użyć metody OpenTelemetry do wysyłania dzienników za pośrednictwem protokołu OTLP (OpenTelemetry protocol):
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 funkcjiExampleHandler.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 kategoriaILogger
ma obiekt. Kontener DI lokalizuje obiektILogger
z prawidłową kategorią i dostarcza go jako argument konstruktora. Jeśli żadenILogger
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 funkcjiHandleRequest
.
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żeILogger<ExampleService>
, aby użyć go jako argumentu konstruktora. - Wywołano
ExampleService.DoSomeWork
, które używało elementuILogger<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 poziomieWarning
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 wLogging.LogLevel
. Na przykład poziom w sekcjiDebug.LogLevel.Default
zastępuje poziom w sekcjiLogLevel.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:
- Podczas tworzenia obiektu
ILoggerFactory
bezpośrednio skonfiguruj element w programie LoggerFactory.Create. - Gdy używasz DI bez hosta, skonfiguruj to w LoggingServiceCollectionExtensions.AddLogging.
- W przypadku korzystania z hosta należy skonfigurować za pomocą HostApplicationBuilder.Logging, WebApplicationBuilder.Logging lub innych interfejsów API specyficznych dla hosta.
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.Read
to 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
iDebug
generuje dużą ilość szczegółowych komunikatów dziennika. Aby kontrolować koszty i nie przekraczać limitów magazynowania danych, rejestruj komunikaty na poziomieTrace
iDebug
w tanim magazynie danych o dużej pojemności. Rozważ ograniczenieTrace
iDebug
do określonych kategorii. - Rejestrowanie na poziomach od
Warning
doCritical
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.
- Rejestrowanie na poziomach
- W trakcie rozwoju
- Ustaw wartość
Warning
. - Dodaj komunikaty
Trace
lubDebug
podczas rozwiązywania problemów. Aby ograniczyć ilość danych wyjściowych, poziomTrace
lubDebug
ustawiaj tylko dla kategorii, które są badane.
- Ustaw wartość
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
iRunTime
. - 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:
- jest typem IDisposable zwracanym przez metodę BeginScope,
- trwa do chwili jego usunięcia.
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.
- Interfejsy znajdują się w pliku Microsoft.Extensions.Logging.Abstractions.
- Domyślne implementacje znajdują się w pliku Microsoft.Extensions.Logging.
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ż
- Dostawcy logowania w .NET
- Implementowanie niestandardowego dostawcy rejestrowania na platformie .NET
- Formatowanie dziennika konsoli
- Wysokowydajne rejestrowanie na platformie .NET
- Wskazówki dotyczące rejestrowania dla autorów bibliotek platformy .NET
- Błędy dotyczące logowania powinny zostać utworzone w repozytorium github.com/dotnet/runtime