ASP.NET Core Logging


  1. Logging
  2. Log Level
  3. Log Category
  4. Log to File
  5. Factory Pattern (Multiple Logger)

筆記 ASP.NET Core Logging 的基本要點。

logo

Logging

We can use primary constructor to inject ILogger into our controller, and use it to log messages.

[Route("api/[controller]")]
public class LogController(ILogger<LogController> _logger) : ControllerBase
{
    [HttpGet]
    public IActionResult Get()
    {
        var message = "Get Method Invoked";
        _logger.LogInformation(message);
        return Ok(message);
    }
}

Kestrel Log will be console window.

info is Level, Controllers.LogController is Category, Get Method Invoked is Message, [0] is EventId.

IIS Express will log will be output window.

Debug Model will log will be Debug output window.

Log Level

Log Level Description
5 Critical
4 Error
3 Warning
2 Information
1 Debug
0 Trace

The default log level is Information, if we want to change it, we can do it in appsettings.json (in development environment, we should cahnge appsettings.Development.json).

{
  "Logging": {
    "LogLevel": {
      "Default": "Trace",
      "Microsoft.AspNetCore": "Warning"
    }
  }
}
[HttpGet("level")]
public IActionResult Level()
{
    var message = "GetLogLevel Method Invoked";
    _logger.LogCritical(message);
    _logger.LogError(message);
    _logger.LogWarning(message);
    _logger.LogInformation(message);
    _logger.LogDebug(message);
    _logger.LogTrace(message);
    return Ok(message);
}

We can custom message with multiple parameters.

var message = "GetLogLevel Method Invoked {}";
_logger.LogCritical(message, Request.Path);

Log Category

Instead of ILogger, we ca use logfactory, the benefit is we can use the same category for multiple controllers.

public class LogController(ILoggerFactory factory) : ControllerBase
{
    readonly ILogger _logger = factory.CreateLogger("MyLog");
    ...
}

With LogFactory to create logger, then we can use log filter to filter log messages in program.cs

builder.Logging.AddFilter((category, level) =>
{
    return category == "MyLog" && level >= LogLevel.Warning;
});

💡 However the best practice is to set log level in appsettings.json or appsettings.Development.json, by the logger name.

We disable Default log level, and set MyLog log level to Warning.

{
  "Logging": {
    "LogLevel": {
      "MyLog": "Warning",
      "Default": "None",
      "Microsoft.AspNetCore": "Warning"
    }
  }
}

We even can use namespace as category to filter log messages 😉

readonly ILogger _logger = factory.CreateLogger("MyNamespace.MyClass");
{
  "Logging": {
    "LogLevel": {
      "MyNamespace.MyClass": "Warning",
      "Default": "None",
    }
  }
}

Log to File

We can not log to file directly, but we can log to file by using NLog library.

Getting started with ASP.NET Core 6

<ItemGroup>
  <PackageReference Include="NLog.Web.AspNetCore" Version="5.*" />
  <PackageReference Include="NLog" Version="5.*" />
</ItemGroup>

And then add a nlog.config file to the root of the project.

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      autoReload="true"
      internalLogLevel="Info"
      internalLogFile="c:\temp\internal-nlog-AspNetCore.txt">

	<!-- enable asp.net core layout renderers -->
	<extensions>
		<add assembly="NLog.Web.AspNetCore"/>
	</extensions>

	<!-- the targets to write to -->
	<targets>
		<!-- File Target for all log messages with basic details -->
		<target xsi:type="File" name="allfile" fileName="c:\temp\nlog-AspNetCore-all-${shortdate}.log"
				layout="${longdate}|${event-properties:item=EventId:whenEmpty=0}|${level:uppercase=true}|${logger}|${message} ${exception:format=tostring}" />

		<!-- File Target for own log messages with extra web details using some ASP.NET core renderers -->
		<target xsi:type="File" name="ownFile-web" fileName="c:\temp\nlog-AspNetCore-own-${shortdate}.log"
				layout="${longdate}|${event-properties:item=EventId:whenEmpty=0}|${level:uppercase=true}|${logger}|${message} ${exception:format=tostring}|url: ${aspnet-request-url}|action: ${aspnet-mvc-action}" />

		<!--Console Target for hosting lifetime messages to improve Docker / Visual Studio startup detection -->
		<target xsi:type="Console" name="lifetimeConsole" layout="${MicrosoftConsoleLayout}" />
	</targets>

	<!-- rules to map from logger name to target -->
	<rules>
		<!--All logs, including from Microsoft-->
		<logger name="*" minlevel="Trace" writeTo="allfile" />

		<!--Output hosting lifetime messages to console target for faster startup detection -->
		<logger name="Microsoft.Hosting.Lifetime" minlevel="Info" writeTo="lifetimeConsole, ownFile-web" final="true" />

		<!--Skip non-critical Microsoft logs and so log only own logs (BlackHole) -->
		<logger name="Microsoft.*" maxlevel="Info" final="true" />
		<logger name="System.Net.Http.*" maxlevel="Info" final="true" />

		<logger name="*" minlevel="Trace" writeTo="ownFile-web" />
	</rules>
</nlog>

The default log path will be “c:\temp”.

program.cs

builder.Host.UseNLog(new NLogAspNetCoreOptions() { RemoveLoggerFactoryFilter = false });

Factory Pattern (Multiple Logger)

We can use factory pattern to create multiple loggers.

public class LoggerFactory
{
    private readonly IServiceProvider _serviceProvider;

    public LoggerFactory(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public ICustomLogger CreateLogger(string loggerType)
    {
        switch (loggerType.ToLower())
        {
            case "console":
                return _serviceProvider.GetRequiredService<ConsoleCustomLogger>();
            case "debug":
                return _serviceProvider.GetRequiredService<DebugCustomLogger>();
            default:
                throw new ArgumentException("Invalid logger type", nameof(loggerType));
        }
    }
}

program.cs

builder.Services.AddTransient<ConsoleCustomLogger>();
builder.Services.AddTransient<DebugCustomLogger>();
builder.Services.AddTransient<LoggerFactory>();