Skip to content

Commit a90e614

Browse files
committed
Add custom logger documentaions (dotnet#11842)
* Add a section in the docs aspnetcore/fundamentals/logging/index.md * Add a sample at aspnetcore/fundamentals/logging/loggermessage/samples/3.1/CustomLogger
1 parent d40ef2d commit a90e614

36 files changed

+39836
-0
lines changed

aspnetcore/fundamentals/logging/index.md

+186
Original file line numberDiff line numberDiff line change
@@ -869,6 +869,192 @@ The following example shows how to register filter rules in code:
869869
* Log level `Information` and higher.
870870
* All categories starting with `"Microsoft"`.
871871

872+
## Create a custom logger
873+
874+
To add a custom logger to your application you need to add an own `ILoggerProvider` to the `ILoggerFactory`, that can be injected into the method `Configure` in the `Startup.cs`:
875+
876+
```csharp
877+
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
878+
public void Configure(
879+
IApplicationBuilder app,
880+
IWebHostEnvironment env,
881+
ILoggerFactory loggerFactory)
882+
{
883+
loggerFactory.AddProvider(new CustomLoggerProvider(new CustomLoggerConfiguration()));
884+
```
885+
886+
The `ILoggerProvider` creates one or more `ILogger` which are used by the framework to log the information.
887+
888+
### The custom logger configuration
889+
890+
The idea is, to create different colored console entries per log level and event ID. To configure this we need a configuration type like this:
891+
892+
~~~ csharp
893+
public class ColoredConsoleLoggerConfiguration
894+
{
895+
public LogLevel LogLevel { get; set; } = LogLevel.Warning;
896+
public int EventId { get; set; } = 0;
897+
public ConsoleColor Color { get; set; } = ConsoleColor.Yellow;
898+
}
899+
~~~
900+
901+
This sets the default level to `Warning` and the color to `Yellow`. If the `EventId` is set to 0, we will log all events.
902+
903+
### Creating the custom logger
904+
905+
The actual logger is an `ILogger` implementation gets a name and the configuration passed in via the constructor. The name is the category name, which usually is the logging source, eg. the type where the logger is created in:
906+
907+
~~~ csharp
908+
public class ColoredConsoleLogger : ILogger
909+
{
910+
private readonly string _name;
911+
private readonly ColoredConsoleLoggerConfiguration _config;
912+
913+
public ColoredConsoleLogger(string name, ColoredConsoleLoggerConfiguration config)
914+
{
915+
_name = name;
916+
_config = config;
917+
}
918+
919+
public IDisposable BeginScope<TState>(TState state)
920+
{
921+
return null;
922+
}
923+
924+
public bool IsEnabled(LogLevel logLevel)
925+
{
926+
return logLevel == _config.LogLevel;
927+
}
928+
929+
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
930+
{
931+
if (!IsEnabled(logLevel))
932+
{
933+
return;
934+
}
935+
936+
if (_config.EventId == 0 || _config.EventId == eventId.Id)
937+
{
938+
var color = Console.ForegroundColor;
939+
Console.ForegroundColor = _config.Color;
940+
Console.WriteLine($"{logLevel.ToString()} - {eventId.Id} - {_name} - {formatter(state, exception)}");
941+
Console.ForegroundColor = color;
942+
}
943+
}
944+
}
945+
~~~
946+
947+
This creates a logger instance per category name with the `ILoggerProvider`, that needs to be created in the next step.
948+
949+
### creating the custom LoggerProvider
950+
951+
The `LoggerProvider` is the class that creates the logger instances. Maybe it is not needed to create a logger instance per category, but this makes sense for some Loggers, like NLog or log4net. Doing this you are also able to choose different logging output targets per category if needed:
952+
953+
~~~ csharp
954+
public class ColoredConsoleLoggerProvider : ILoggerProvider
955+
{
956+
private readonly ColoredConsoleLoggerConfiguration _config;
957+
private readonly ConcurrentDictionary<string, ColoredConsoleLogger> _loggers = new ConcurrentDictionary<string, ColoredConsoleLogger>();
958+
959+
public ColoredConsoleLoggerProvider(ColoredConsoleLoggerConfiguration config)
960+
{
961+
_config = config;
962+
}
963+
964+
public ILogger CreateLogger(string categoryName)
965+
{
966+
return _loggers.GetOrAdd(categoryName, name => new ColoredConsoleLogger(name, _config));
967+
}
968+
969+
public void Dispose()
970+
{
971+
_loggers.Clear();
972+
}
973+
}
974+
~~~
975+
976+
There's no magic here. The method `CreateLogger` creates a single instance of the `ColoredConsoleLogger` per category name and stores it in the `ConcurrentDictionary`.
977+
978+
### Usage and registration of the custom logger
979+
980+
Now you are able to register the logger in the `Startup.cs`:
981+
982+
~~~ csharp
983+
public void Configure(
984+
IApplicationBuilder app,
985+
IHostingEnvironment env,
986+
ILoggerFactory loggerFactory)
987+
{
988+
// here is our CustomLogger
989+
loggerFactory.AddProvider(new ColoredConsoleLoggerProvider(new ColoredConsoleLoggerConfiguration
990+
{
991+
LogLevel = LogLevel.Information,
992+
Color = ConsoleColor.Blue
993+
}));
994+
loggerFactory.AddProvider(new ColoredConsoleLoggerProvider(new ColoredConsoleLoggerConfiguration
995+
{
996+
LogLevel = LogLevel.Debug,
997+
Color = ConsoleColor.Gray
998+
}));
999+
~~~
1000+
1001+
The loggers will automatically be used, if a log entry is crated.
1002+
1003+
But those registrations don't really look nice. You are able to encapsulate the registrations to provide an API like this:
1004+
1005+
```csharp
1006+
// using the custom registration with default values:
1007+
loggerFactory.AddColoredConsoleLogger();
1008+
1009+
// using the custom registration with a new configuration instance:
1010+
loggerFactory.AddColoredConsoleLogger(new ColoredConsoleLoggerConfiguration
1011+
{
1012+
LogLevel = LogLevel.Debug,
1013+
Color = ConsoleColor.Gray
1014+
});
1015+
1016+
// using the custom registration with a configuration object:
1017+
loggerFactory.AddColoredConsoleLogger(c =>
1018+
{
1019+
c.LogLevel = LogLevel.Information;
1020+
c.Color = ConsoleColor.Blue;
1021+
});
1022+
```
1023+
1024+
This means you need to write at least one extension method for the `ILoggerFactory`:
1025+
1026+
~~~ csharp
1027+
public static class ColoredConsoleLoggerExtensions
1028+
{
1029+
// custom registration with a configuration instance
1030+
public static ILoggerFactory AddColoredConsoleLogger(
1031+
this ILoggerFactory loggerFactory,
1032+
ColoredConsoleLoggerConfiguration config)
1033+
{
1034+
loggerFactory.AddProvider(new ColoredConsoleLoggerProvider(config));
1035+
return loggerFactory;
1036+
}
1037+
// custom registration with default values
1038+
public static ILoggerFactory AddColoredConsoleLogger(
1039+
this ILoggerFactory loggerFactory)
1040+
{
1041+
var config = new ColoredConsoleLoggerConfiguration();
1042+
return loggerFactory.AddColoredConsoleLogger(config);
1043+
}
1044+
// custom registration with a configuration object
1045+
public static ILoggerFactory AddColoredConsoleLogger(
1046+
this ILoggerFactory loggerFactory,
1047+
Action<ColoredConsoleLoggerConfiguration> configure)
1048+
{
1049+
var config = new ColoredConsoleLoggerConfiguration();
1050+
configure(config);
1051+
return loggerFactory.AddColoredConsoleLogger(config);
1052+
}
1053+
}
1054+
~~~
1055+
1056+
1057+
8721058
## Additional resources
8731059

8741060
* <xref:fundamentals/logging/loggermessage>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using System;
2+
using Microsoft.Extensions.Logging;
3+
4+
namespace CustomLogger.ColoredConsoleLogger
5+
{
6+
public class ColoredConsoleLogger : ILogger
7+
{
8+
private readonly string _name;
9+
private readonly ColoredConsoleLoggerConfiguration _config;
10+
11+
public ColoredConsoleLogger(string name, ColoredConsoleLoggerConfiguration config)
12+
{
13+
_name = name;
14+
_config = config;
15+
}
16+
17+
public IDisposable BeginScope<TState>(TState state)
18+
{
19+
return null;
20+
}
21+
22+
public bool IsEnabled(LogLevel logLevel)
23+
{
24+
return logLevel == _config.LogLevel;
25+
}
26+
27+
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
28+
{
29+
if (!IsEnabled(logLevel))
30+
{
31+
return;
32+
}
33+
34+
if (_config.EventId == 0 || _config.EventId == eventId.Id)
35+
{
36+
var color = Console.ForegroundColor;
37+
Console.ForegroundColor = _config.Color;
38+
Console.WriteLine($"{logLevel} - {eventId.Id} - {_name} - {formatter(state, exception)}");
39+
Console.ForegroundColor = color;
40+
}
41+
}
42+
}
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System;
2+
using Microsoft.Extensions.Logging;
3+
4+
namespace CustomLogger.ColoredConsoleLogger
5+
{
6+
public class ColoredConsoleLoggerConfiguration
7+
{
8+
public LogLevel LogLevel { get; set; } = LogLevel.Warning;
9+
public int EventId { get; set; } = 0;
10+
public ConsoleColor Color { get; set; } = ConsoleColor.Yellow;
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using System;
2+
using Microsoft.Extensions.Logging;
3+
4+
namespace CustomLogger.ColoredConsoleLogger
5+
{
6+
public static class ColoredConsoleLoggerExtensions
7+
{
8+
public static ILoggerFactory AddColoredConsoleLogger(this ILoggerFactory loggerFactory, ColoredConsoleLoggerConfiguration config)
9+
{
10+
loggerFactory.AddProvider(new ColoredConsoleLoggerProvider(config));
11+
return loggerFactory;
12+
}
13+
public static ILoggerFactory AddColoredConsoleLogger(this ILoggerFactory loggerFactory)
14+
{
15+
var config = new ColoredConsoleLoggerConfiguration();
16+
return loggerFactory.AddColoredConsoleLogger(config);
17+
}
18+
public static ILoggerFactory AddColoredConsoleLogger(this ILoggerFactory loggerFactory, Action<ColoredConsoleLoggerConfiguration> configure)
19+
{
20+
var config = new ColoredConsoleLoggerConfiguration();
21+
configure(config);
22+
return loggerFactory.AddColoredConsoleLogger(config);
23+
}
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System.Collections.Concurrent;
2+
using Microsoft.Extensions.Logging;
3+
4+
namespace CustomLogger.ColoredConsoleLogger
5+
{
6+
public class ColoredConsoleLoggerProvider : ILoggerProvider
7+
{
8+
private readonly ColoredConsoleLoggerConfiguration _config;
9+
private readonly ConcurrentDictionary<string, ColoredConsoleLogger> _loggers = new ConcurrentDictionary<string, ColoredConsoleLogger>();
10+
11+
public ColoredConsoleLoggerProvider(ColoredConsoleLoggerConfiguration config)
12+
{
13+
_config = config;
14+
}
15+
16+
public ILogger CreateLogger(string categoryName)
17+
{
18+
return _loggers.GetOrAdd(categoryName, name => new ColoredConsoleLogger(name, _config));
19+
}
20+
21+
public void Dispose()
22+
{
23+
_loggers.Clear();
24+
}
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using System.Diagnostics;
2+
using Microsoft.AspNetCore.Mvc;
3+
using Microsoft.Extensions.Logging;
4+
using CustomLogger.Models;
5+
6+
namespace CustomLogger.Controllers
7+
{
8+
public class HomeController : Controller
9+
{
10+
private readonly ILogger<HomeController> _logger;
11+
12+
public HomeController(ILogger<HomeController> logger)
13+
{
14+
_logger = logger;
15+
}
16+
17+
public IActionResult Index()
18+
{
19+
_logger.LogInformation("Start Index action in the HomeController");
20+
_logger.LogWarning("The Privacy action was just started");
21+
_logger.LogError("Not really an error. Just to show the custom logger");
22+
_logger.LogDebug("End Index action in the HomeController");
23+
return View();
24+
}
25+
26+
public IActionResult Privacy()
27+
{
28+
_logger.LogInformation("Start Privacy action in the HomeController");
29+
_logger.LogWarning("The Privacy action was just started");
30+
_logger.LogError("Not really an error. Just to show the custom logger");
31+
_logger.LogDebug("End Privacy action in the HomeController");
32+
return View();
33+
}
34+
35+
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
36+
public IActionResult Error()
37+
{
38+
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
39+
}
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netcoreapp3.1</TargetFramework>
5+
</PropertyGroup>
6+
7+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System;
2+
3+
namespace CustomLogger.Models
4+
{
5+
public class ErrorViewModel
6+
{
7+
public string RequestId { get; set; }
8+
9+
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading.Tasks;
5+
using Microsoft.AspNetCore.Hosting;
6+
using Microsoft.Extensions.Configuration;
7+
using Microsoft.Extensions.Hosting;
8+
using Microsoft.Extensions.Logging;
9+
10+
namespace CustomLogger
11+
{
12+
public class Program
13+
{
14+
public static void Main(string[] args)
15+
{
16+
CreateHostBuilder(args).Build().Run();
17+
}
18+
19+
public static IHostBuilder CreateHostBuilder(string[] args) =>
20+
Host.CreateDefaultBuilder(args)
21+
.ConfigureWebHostDefaults(webBuilder =>
22+
{
23+
webBuilder.UseStartup<Startup>();
24+
});
25+
}
26+
}

0 commit comments

Comments
 (0)