Understanding asp.net core - logging

xiaoxiaotank 2021-11-25 17:56:42

notes : This article belongs to 《 understand ASP.NET Core》 Series articles , Please check the top blog or Click here to view the full-text catalog

Quick start

Add log provider

In the article host (Host) in , speak of Host.CreateDefaultBuilder Method , The default is to call ConfigureLogging Method added ConsoleDebugEventSource and EventLog( only Windows) There are four logging providers (Logger Provider), And then on the mainframe Build In the process , adopt AddLogging() Registered log related services .

.ConfigureLogging((hostingContext, logging) =>
{
bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
if (isWindows)
{
logging.AddFilter<EventLogLoggerProvider>(level => level >= LogLevel.Warning);
}
// add to Logging To configure
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
// ConsoleLoggerProvider
logging.AddConsole();
// DebugLoggerProvider
logging.AddDebug();
// EventSourceLoggerProvider
logging.AddEventSourceLogger();
if (isWindows)
{
// stay Windows On the platform , add to EventLogLoggerProvider
logging.AddEventLog();
}
logging.Configure(options =>
{
options.ActivityTrackingOptions = ActivityTrackingOptions.SpanId
| ActivityTrackingOptions.TraceId
| ActivityTrackingOptions.ParentId;
});
})
public class HostBuilder : IHostBuilder
{
private void CreateServiceProvider()
{
var services = new ServiceCollection();
// ...
services.AddLogging();
// ...
}
}

If you don't want to use the log provider added by default , We can go through ClearProviders Clear all added logging providers , Then add what you want , Such as Console

public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging =>
{
logging.ClearProviders()
.AddConsole();
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});

Log

Logging providers implement interfaces ILoggerProvider, This interface can create ILogger example .

By injecting Services ILogger<TCategoryName>, It is very convenient to log .

The service needs to specify the log category , It could be any string , But we agreed to use the name of the class to which we belong , Through generics . for example , In the controller ValuesController in , The log category is ValuesController The fully qualified type name of the class .

public class ValuesController : ControllerBase
{
private readonly ILogger<ValuesController> _logger;
public ValuesController(ILogger<ValuesController> logger)
{
_logger = logger;
}
[HttpGet]
public string Get()
{
_logger.LogInformation("ValuesController.Get");
return "Ok";
}
}

When requested Get After the method , You can see the output in the console “ValuesController.Get”

If you want to explicitly specify the log category , You can use ILoggerFactory.CreateLogger Method :

public class ValuesController : ControllerBase
{
private readonly ILogger _logger1;
public ValuesController(ILoggerFactory loggerFactory)
{
_logger1 = loggerFactory.CreateLogger("MyCategory");
}
}

Configuration log

In the default template , The log configuration is as follows ( stay appsettings.{Environment}.json In file ):

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

Configure for all logging providers

LogLevel, seeing the name of a thing one thinks of its function , It refers to the lowest level of logs to be recorded ( That is to record logs greater than or equal to this level ), I think everyone is familiar with . Log levels are described in detail below .

LogLevel In the field , As in the example above “Default”、“Microsoft” etc. , Indicates the category of the log , That is, we inject ILogger Generic parameters specified when . You can set the minimum logging level for each category , That is, the values corresponding to these categories .

The three log categories in the example are explained in detail below .

Default

By default , If the classification is not specially configured ( That is, not in LogLevel Middle configuration ), The application of Default Configuration of .

Microsoft

All categories are in Microsoft All logs at the beginning should be applied Microsoft Configuration of . for example ,Microsoft.AspNetCore.Routing.EndpointMiddleware The configuration will be applied to the logs of the category .

Microsoft.Hosting.Lifetime

All categories are in Microsoft.Hosting.Lifetime All logs at the beginning should be applied Microsoft.Hosting.Lifetime Configuration of . for example , classification Microsoft.Hosting.Lifetime The configuration will be applied , It doesn't apply Microsoft, because Microsoft.Hosting.Lifetime Than Microsoft More specifically .

OK, That's all for the above three log categories .

Back to the example , You may not have noticed , There is no separate configuration for a logging provider ( Such as :Console Record only Error And above , and EventSource You need to record all levels of logs ). Like this , If not configured for a specific logging provider , This configuration will be applied to all logging providers .

Windows EventLog With the exception of .EventLog Must be explicitly configured , Otherwise, it will use its default LogLevel.Warning.

Configure for the specified logging provider

Next, let's look at how to configure for the specified logging provider , Let's start with an example :

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

It's like appsettings.{Environment}.json and appsettings.json The relationship between them is the same ,Logging.{Provider}.LogLevel The configuration in will override Logging.LogLevel Configuration in .

for example Logging.Console.LogLevel.Default Will overwrite Logging.LogLevel.Default,Console The logger will record by default Error And above .

I just mentioned ,Windows EventLog A special , It will not inherit Logging.LogLevel Configuration of .EventLog The default log level is LogLevel.Warning, If you want to modify , Must be specified explicitly , Such as :

{
"Logging": {
"EventLog": {
"LogLevel": {
"Default": "Information"
}
}
}
}

Configured filtering principle

When creating a ILogger<TCategoryName> Object instance of ,ILoggerFactory According to different logging providers , will :

  1. Find a configuration that matches the logging provider . If you can't find it , General configuration is used .
  2. Then match the configuration category with the longest prefix . If you can't find it , Then use Default To configure .
  3. If multiple configurations are matched , Then use the last .
  4. If no configuration matches , Then use MinimumLevel, This is a configuration item , The default is LogLevel.Information.

Can be in ConfigureLogging In the extension SetMinimumLevel Method setting MinimumLevel.

Log Level

The log level indicates the severity of the log , Divided into 7 etc. , From light to heavy ( final None More special ):

The level of logging value describe
Trace 0 Tracking level , Contains the most detailed information . This information may contain sensitive data , Disabled by default , And must not appear in the production environment .
Debug 1 Debug level , For developers to develop and debug . The amount of information is generally large , Be sure to use with caution in the production environment .
Information 2 Information level , This level is often used .
Warning 3 Warning level , Some unexpected events , But these events do not cause program errors .
Error 4 error level , Some unhandled errors or exceptions , These events cause the current operation or request to fail , But it will not cause errors in the whole application .
Critical 5 Fatal error level , These errors will cause errors in the whole application . For example, there is not enough memory .
None 6 Indicates that no logs are logged

Logging provider

Console

The log will be output to the console .

Debug

The log will be sent through System.Diagnostics.Debug Class to output , Can pass VS Output window view .

stay Linux On , Can be in /var/log/message or /var/log/syslog Find below

EventSource

Cross platform logging , stay Windows I use ETW

Windows EventLog

Only in Windows Effective under the system , It can be done by “ Event viewer ” Log view .

By default

  • LogName by “Application”
  • SourceName by “NET Runtime”
  • MachineName Is the name of the local computer .

These fields can be accessed through EventLogSettings Make changes :

public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging =>
{
logging.AddEventLog(settings =>
{
settings.LogName = "My App";
settings.SourceName = "My Log";
settings.MachineName = "My Computer";
})
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});

Logging filters

Through the logging filter , Allows you to write complex logic , To control whether to log .

public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging =>
{
logging
// For all LoggerProvider Set up Microsoft Minimum log level , It is recommended to configure through the configuration file
.AddFilter("Microsoft", LogLevel.Trace)
// in the light of ConsoleLoggerProvider Set up Microsoft Minimum log level , It is recommended to configure through the configuration file
.AddFilter<ConsoleLoggerProvider>("Microsoft", LogLevel.Debug)
// For all LoggerProvider Filter configuration
.AddFilter((provider, category, logLevel) =>
{
// Because the following is for ConsoleLoggerProvider Added filtering configuration , therefore ConsoleLoggerProvider Will not enter the method
if (provider == typeof(ConsoleLoggerProvider).FullName
&& category == typeof(ValuesController).FullName
&& logLevel <= LogLevel.Warning)
{
// false: Do not log
return false;
}
// true: Log
return true;
})
// in the light of ConsoleLoggerProvider Filter configuration
.AddFilter<ConsoleLoggerProvider>((category, logLevel) =>
{
if (category == typeof(ValuesController).FullName
&& logLevel <= LogLevel.Warning)
{
// false: Do not log
return false;
}
// true: Log
return true;
});
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});

Log message template

In the process of application development , For a certain type of log , We want their message formats to be consistent , Just some parameters change . This requires the log message template .

for instance :

[HttpGet("{id}")]
public int Get(int id)
{
_logger.LogInformation("Get {Id}", id);
return id;
}

among Get {Id} Is a log message template ,{Id} Is the template parameter ( Be careful , Please write your name in it , Not Numbers , This makes it easier to understand the meaning of parameters ).

however , It should be noted that ,{Id} This template parameter , It is only used to make its meaning easy to understand , It has nothing to do with the following parameter name , Template values care about the order of parameters . for example :

[HttpGet("{id}")]
public int Get(int id)
{
_logger.LogInformation("Get {Id} at {Time}", DateTime.Now, id);
return id;
}

Suppose you pass in id = 1, Its output is :Get 11/02/2021 11:42:14 at 1

Log message template is a very important function , Among many open source logging middleware , All of them are used .

Logging during host build

ASP.NET Core The framework does not directly support logging during host build . However, you can log through a separate logging provider , for example , Use a third-party logging provider :Serilog

install Nuget package :Install-Package Serilog.AspNetCore

public static void Main(string[] args)
{
// from appsettings.json And read the configuration from the command line parameters
var config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddCommandLine(args)
.Build();
// establish Logger
Log.Logger = new LoggerConfiguration()
.WriteTo.Console() // Output to console
.WriteTo.File(config["Logging:File:Path"]) // Output to specified file
.CreateLogger();
try
{
CreateHostBuilder(args).Build().Run();
}
catch(Exception ex)
{
Log.Fatal(ex, "Host terminated unexpectedly");
throw;
}
finally
{
Log.CloseAndFlush();
}
}

appsettings.json

{
"Logging": {
"File": {
"Path": "logs/host.log"
}
}
}

Console log format configuration

The console logging provider is essential in our development process , From the above, we have learned that we can pass AddConsole() Add . However, it has great limitations , We can't customize the log format .

therefore , stay .NET 5 in , The console logging provider has been extended , Three log output formats are preset :Json、Simple、Systemd.

actually , There were enumerations before ConsoleLoggerFormat Provides Simple and Systemd Format , However, it cannot be customized , It has been abandoned .

these Formatter All inherited from abstract classes ConsoleFormatter, The abstract class constructor receives a “ name ” Parameters , It is required that its implementation class must have a name . You can use static classes ConsoleFormatterNames Get the names of the three built-in formats .

public abstract class ConsoleFormatter
{
protected ConsoleFormatter(string name)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
}
public string Name { get; }
public abstract void Write<TState>(in LogEntry<TState> logEntry, IExternalScopeProvider scopeProvider, TextWriter textWriter);
}
public static class ConsoleFormatterNames
{
public const string Simple = "simple";
public const string Json = "json";
public const string Systemd = "systemd";
}

You can use AddConsole() when , To configure ConsoleLoggerOptions Of FormatterName attribute , To achieve the purpose of custom format , The default value is “simple”. however , For ease of use ,.NET The framework has encapsulated the three built-in formats for us .

these Formatter All option classes inherit from the option class ConsoleFormatterOptions, The option class contains the following three properties :

public class ConsoleFormatterOptions
{
// Enable scope , Default false
public bool IncludeScopes { get; set; }
// Format the timestamp , Appears at the beginning of the log message
// The default is null, Don't show timestamp
public string TimestampFormat { get; set; }
// Whether to set the timestamp time zone to UTC, The default is false, Local time zone
public bool UseUtcTimestamp { get; set; }
}

SimpleConsoleFormatter

By extending the method AddSimpleConsole() Support can be added Simple Console logging provider in , Default behavior vs AddConsole() Agreement .

.ConfigureLogging(logging =>
{
logging.ClearProviders()
.AddSimpleConsole();
}

Sample output :

info: Microsoft.Hosting.Lifetime[0]
Now listening on: http://localhost:5000
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: C:\Repos\WebApplication

in addition , You can go through SimpleConsoleFormatterOptions Make some custom configuration :

.ConfigureLogging(logging =>
{
logging.ClearProviders()
.AddSimpleConsole(options =>
{
// A log message is displayed on the same line
options.SingleLine = true;
options.IncludeScopes = true;
options.TimestampFormat = "yyyy-MM-dd HH:mm:ss ";
options.UseUtcTimestamp = false;
});
}

Sample output :

2021-11-02 15:53:33 info: Microsoft.Hosting.Lifetime[0] Now listening on: http://localhost:5000
2021-11-02 15:53:33 info: Microsoft.Hosting.Lifetime[0] Application started. Press Ctrl+C to shut down.
2021-11-02 15:53:33 info: Microsoft.Hosting.Lifetime[0] Hosting environment: Development
2021-11-02 15:53:33 info: Microsoft.Hosting.Lifetime[0] Content root path: C:\Repos\WebApplication

SystemdConsoleFormatter

By extending the method AddSystemdConsole() Support can be added Systemd Console logging provider in . If you are familiar with Linux, Then you must be no stranger to it .

.ConfigureLogging(logging =>
{
logging.ClearProviders()
.AddSystemdConsole();
}

Sample output :

<6>Microsoft.Hosting.Lifetime[0] Now listening on: http://localhost:5000
<6>Microsoft.Hosting.Lifetime[0] Application started. Press Ctrl+C to shut down.
<6>Microsoft.Hosting.Lifetime[0] Hosting environment: Development
<6>Microsoft.Hosting.Lifetime[0] Content root path: C:\Repos\WebApplication

Ahead <6> Indicates the log level info, If you are interested in understanding Systemd, You can visit Mr. Ruan Yifeng's Systemd Introductory tutorial : Command

JsonConsoleFormatter

By extending the method AddJsonConsole() Support can be added Json Console logging provider in .

.ConfigureLogging(logging =>
{
logging.ClearProviders()
.AddJsonConsole(options =>
{
options.JsonWriterOptions = new JsonWriterOptions
{
// Enable indent , Looks more comfortable
Indented = true
};
});
}

Sample output :

{
"EventId": 0,
"LogLevel": "Information",
"Category": "Microsoft.Hosting.Lifetime",
"Message": "Now listening on: http://localhost:5000",
"State": {
"Message": "Now listening on: http://localhost:5000",
"address": "http://localhost:5000",
"{OriginalFormat}": "Now listening on: {address}"
}
}
{
"EventId": 0,
"LogLevel": "Information",
"Category": "Microsoft.Hosting.Lifetime",
"Message": "Application started. Press Ctrl\u002BC to shut down.",
"State": {
"Message": "Application started. Press Ctrl\u002BC to shut down.",
"{OriginalFormat}": "Application started. Press Ctrl\u002BC to shut down."
}
}
{
"EventId": 0,
"LogLevel": "Information",
"Category": "Microsoft.Hosting.Lifetime",
"Message": "Hosting environment: Development",
"State": {
"Message": "Hosting environment: Development",
"envName": "Development",
"{OriginalFormat}": "Hosting environment: {envName}"
}
}
{
"EventId": 0,
"LogLevel": "Information",
"Category": "Microsoft.Hosting.Lifetime",
"Message": "Content root path: C:\\Repos\\WebApplication",
"State": {
"Message": "Content root path: C:\\Repos\\WebApplication",
"contentRoot": "C:\\Repos\\WebApplication",
"{OriginalFormat}": "Content root path: {contentRoot}"
}
}

If you add a console recorder in multiple formats at the same time , Then only the last addition will take effect .

The above describes how to set the console logging provider through code , However, I think you should prefer to set the logging provider through configuration . Here is a simple configuration example :

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
},
"Console": {
"FormatterName": "json",
"FormatterOptions": {
"SingleLine": true,
"IncludeScopes": true,
"TimestampFormat": "yyyy-MM-dd HH:mm:ss ",
"UseUtcTimestamp": false,
"JsonWriterOptions": {
"Indented": true
}
}
}
}
}

ILogger<TCategoryName> Object instance creation

Here we are. , I wonder if you will be right ILogger<TCategoryName> There are doubts about the creation of object instances : How it was new What comes out? ?

To solve this problem , Let's start with AddLogging() Start with the extension method :

public static class LoggingServiceCollectionExtensions
{
public static IServiceCollection AddLogging(this IServiceCollection services)
{
return AddLogging(services, builder => { });
}
public static IServiceCollection AddLogging(this IServiceCollection services, Action<ILoggingBuilder> configure)
{
services.AddOptions();
// Registration form example ILoggerFactory
services.TryAdd(ServiceDescriptor.Singleton<ILoggerFactory, LoggerFactory>());
// Registration form example ILogger<>
services.TryAdd(ServiceDescriptor.Singleton(typeof(ILogger<>), typeof(Logger<>)));
// Batch registration single example IConfigureOptions<LoggerFilterOptions>
services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<LoggerFilterOptions>>(
new DefaultLoggerLevelConfigureOptions(LogLevel.Information)));
configure(new LoggingBuilder(services));
return services;
}
}

You may have guessed , This Logger<> Will not be LoggerFactory Create it ? Why else sign up for this thing ?

take it easy , Let's take a look at ILogger<> The implementation class of the service Logger<>

public interface ILogger
{
void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter);
// Check whether logs of this log level can be recorded
bool IsEnabled(LogLevel logLevel);
IDisposable BeginScope<TState>(TState state);
}
public interface ILogger<out TCategoryName> : ILogger
{
}
public class Logger<T> : ILogger<T>
{
// This instance is used for operation inside the interface implementation
private readonly ILogger _logger;
// Sure enough , Yes ILoggerFactory example
public Logger(ILoggerFactory factory)
{
// Do you remember? ? As mentioned above, when explicitly specifying the log category , The same is true for creating ILogger Example of
_logger = factory.CreateLogger(TypeNameHelper.GetTypeDisplayName(typeof(T), includeGenericParameters: false, nestedTypeDelimiter: '.'));
}
// ...
}

you 're right , You guessed it , Let's take a look at this LoggerFactory Well ( Just list the core code ):

public interface ILoggerFactory : IDisposable
{
ILogger CreateLogger(string categoryName);
void AddProvider(ILoggerProvider provider);
}
public class LoggerFactory : ILoggerFactory
{
// For singleton Logger<>
private readonly Dictionary<string, Logger> _loggers = new Dictionary<string, Logger>(StringComparer.Ordinal);
// Deposit ILoggerProviderRegistrations
private readonly List<ProviderRegistration> _providerRegistrations = new List<ProviderRegistration>();
private readonly object _sync = new object();
public LoggerFactory(IEnumerable<ILoggerProvider> providers, IOptionsMonitor<LoggerFilterOptions> filterOption, IOptions<LoggerFactoryOptions> options = null)
{
// ...
// register ILoggerProviders
foreach (ILoggerProvider provider in providers)
{
AddProviderRegistration(provider, dispose: false);
}
// ...
}
public ILogger CreateLogger(string categoryName)
{
lock (_sync)
{
// If it doesn't exist , be new
if (!_loggers.TryGetValue(categoryName, out Logger logger))
{
logger = new Logger
{
Loggers = CreateLoggers(categoryName),
};
(logger.MessageLoggers, logger.ScopeLoggers) = ApplyFilters(logger.Loggers);
// Uniinstantiation Logger<>
_loggers[categoryName] = logger;
}
return logger;
}
}
private void AddProviderRegistration(ILoggerProvider provider, bool dispose)
{
_providerRegistrations.Add(new ProviderRegistration
{
Provider = provider,
ShouldDispose = dispose
});
// ...
}
private LoggerInformation[] CreateLoggers(string categoryName)
{
var loggers = new LoggerInformation[_providerRegistrations.Count];
// Loop through all ILoggerProvider
for (int i = 0; i < _providerRegistrations.Count; i++)
{
loggers[i] = new LoggerInformation(_providerRegistrations[i].Provider, categoryName);
}
return loggers;
}
}

Be careful

  • If you want to be in Startup.Configure Method , Inject... Directly on the parameters ILogger<Startup> that will do .
  • Does not support the Startup.ConfigureServices Method used in ILogger, Because at this time DI The container has not been configured .
  • There is no asynchronous logging method . The logging action should be executed quickly , Use asynchronous methods at the expense of performance . If the logging action is time-consuming , As recorded MSSQL in , Then please do not write directly MSSQL. You should consider writing logs to fast storage media first , Such as memory queue , Then it is dumped from memory to... Through the background worker thread MSSQL in .
  • Cannot use logging API Change the logging configuration while the application is running . however , Some configuration providers ( Such as file configuration provider ) Reloads the configuration , This can immediately update the logging configuration .

Summary

  • Host.CreateDefaultBuilder In the method , Default added ConsoleDebugEventSource and EventLog( only Windows) There are four logging providers (Logger Provider).
  • By injecting Services ILogger<TCategoryName>, It is convenient to log .
  • The logging provider can be set by code or configuration , Such as LogLevelFormatterName etc. .
  • You can extend the method AddFilter Add logging filter , Allows you to write complex logic , To control whether to log .
  • Support log message templates .
  • For console logging programs ,.NET Frame built in Simple( Default )、SystemdJson Three log output formats .
  • .NET 6 In the preview version, a new one called “ Compile time logging source generation ” The function of , This function is very practical , If you are interested, you can go first Get to know .
  • Last , Give you some common log open source middleware :
Please bring the original link to reprint ,thank
Similar articles

2021-11-25

2021-11-25

2021-11-25

2021-11-25

2021-11-25

2021-11-25