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. 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. 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
rejestrowanymILogger
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 = 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.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.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:
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ć
ILoggerFactory
obiektów iILogger
z odpowiednich kontenerów DI, a nie tworzyć ich bezpośrednio. Aby uzyskać więcej informacji, zobacz Integracja z di i hostami.Generowanie źródła czasu kompilacji jest zwykle lepszym rozwiązaniem dla
ILogger
metod rozszerzeń, 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 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");
}
}
- 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 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 uruchomieniaExampleHandler.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 kategoriaILogger
ma obiekt. Kontener DI lokalizuje obiektILogger
z prawidłową kategorią i dostarcza go jako argument konstruktora. Jeśli nieILogger
ma jeszcze tej kategorii, kontener DI automatycznie tworzy go zILoggerFactory
poziomu dostawcy usług. - Parametr
logger
odebrany w konstruktorze był używany do rejestrowaniaHandleRequest
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 WebApplicationBuilder
program . 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łoILogger<ExampleService>
obiekt do użycia jako argument konstruktora. - Wywołano
ExampleService.DoSomeWork
wywołanie , które używało elementuILogger<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 poziomieWarning
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 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 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:
- Podczas tworzenia obiektu
ILoggerFactory
bezpośrednio skonfiguruj element w programie LoggerFactory.Create. - W przypadku korzystania z di bez hosta skonfiguruj w programie LoggingServiceCollectionExtensions.AddLogging.
- W przypadku korzystania z hosta należy skonfigurować za pomocą HostApplicationBuilder.LoggingWebApplicationBuilder.Logging interfejsu API specyficznych dla hosta lub innych interfejsów API.
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 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 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
iDebug
generuje dużą ilość szczegółowych komunikatów dziennika. Aby kontrolować koszty i nie przekraczać limitów magazynowania danych, rejestruj komunikaty dziennika na poziomieTrace
iDebug
w magazynie danych o dużej pojemności i niskiej cenie. Rozważ ograniczenie dziennikówTrace
iDebug
do określonych kategorii. - Rejestrowanie na poziomach od
Warning
doCritical
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.
- Rejestrowanie na poziomach
- W programowania:
- 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 Logging:Console:LogLevel:Microsoft:Information
JSON:
{
"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 nie są uż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
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 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:
- jest typem IDisposable zwracanym przez metodę BeginScope,
- trwa do momentu 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 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:
- 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 rejestrowania na platformie .NET
- Implementowanie niestandardowego dostawcy rejestrowania na platformie .NET
- Formatowanie dziennika konsoli
- Rejestrowanie o wysokiej wydajności na platformie .NET
- Wskazówki dotyczące rejestrowania dla autorów bibliotek platformy .NET
- Błędy rejestrowania powinny zostać utworzone w repozytorium github.com/dotnet/runtime