Skip to content

Commit 9ab5adf

Browse files
Rick-AndersonJuergenGutschBrennanConroy
authored
Custom/logger (#18258)
* Add custom logger documentaions (#11842) * Add a section in the docs aspnetcore/fundamentals/logging/index.md * Add a sample at aspnetcore/fundamentals/logging/loggermessage/samples/3.1/CustomLogger * Custom loggiing * Custom loggiing * Custom loggiing * Custom loggiing * Custom loggiing * Custom loggiing * Apply suggestions from code review Co-authored-by: Brennan <brecon@microsoft.com> * react to feedback * react to feedback * react to feedback Co-authored-by: Juergen Gutsch <juergen@gutsch-online.de> Co-authored-by: Brennan <brecon@microsoft.com>
1 parent 511573e commit 9ab5adf

36 files changed

Lines changed: 39801 additions & 1 deletion

aspnetcore/fundamentals/logging/index.md

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ uid: fundamentals/logging/index
1212

1313
::: moniker range=">= aspnetcore-3.0"
1414

15-
By [Kirk Larkin](https://twitter.com/serpent5), [Rick Anderson](https://twitter.com/RickAndMSFT), [Tom Dykstra](https://github.com/tdykstra), and [Steve Smith](https://ardalis.com/)
15+
By [Kirk Larkin](https://twitter.com/serpent5), [Juergen Gutsch](https://github.com/JuergenGutsch) and [Rick Anderson](https://twitter.com/RickAndMSFT)
1616

1717
.NET Core supports a logging API that works with a variety of built-in and third-party logging providers. This article shows how to use the logging API with built-in providers.
1818

@@ -869,6 +869,68 @@ 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, add an <xref:Microsoft.Extensions.Logging.ILoggerProvider> with <xref:Microsoft.Extensions.Logging.ILoggerFactory>:
875+
876+
```csharp
877+
public void Configure(
878+
IApplicationBuilder app,
879+
IWebHostEnvironment env,
880+
ILoggerFactory loggerFactory)
881+
{
882+
loggerFactory.AddProvider(new CustomLoggerProvider(new CustomLoggerConfiguration()));
883+
```
884+
885+
The `ILoggerProvider` creates one or more `ILogger` instances. The `ILogger` instances are used by the framework to log the information.
886+
887+
### Sample custom logger configuration
888+
889+
The sample:
890+
891+
* Is designed to be a very basic sample that sets the color of the log console by event ID and log level. Loggers generally don't change by event ID and are not specific to log level.
892+
* Creates different colored console entries per log level and event ID using the following configuration type:
893+
894+
[!code-csharp[](index/samples/3.x/CustomLogger/ColoredConsoleLogger/ColoredConsoleLoggerConfiguration.cs?name=snippet)]
895+
896+
The preceding code sets the default level to `Warning` and the color to `Yellow`. If the `EventId` is set to 0, we will log all events.
897+
898+
### Create the custom logger
899+
900+
The `ILogger` implementation category name is typically the logging source. For example, the type where the logger is created:
901+
902+
[!code-csharp[](index/samples/3.x/CustomLogger/ColoredConsoleLogger/ColoredConsoleLogger.cs?name=snippet)]
903+
904+
The preceding code:
905+
906+
* Creates a logger instance per category name.
907+
* Checks `logLevel == _config.LogLevel` in `IsEnabled`, so each `logLevel` has a unique logger. Generally, loggers should also be enabled for all higher log levels:
908+
909+
```csharp
910+
public bool IsEnabled(LogLevel logLevel)
911+
{
912+
return logLevel >= _config.LogLevel;
913+
}
914+
```
915+
916+
### Create the custom LoggerProvider
917+
918+
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:
919+
920+
[!code-csharp[](index/samples/3.x/CustomLogger/ColoredConsoleLogger/ColoredConsoleLoggerProvider.cs?name=snippet)]
921+
922+
In the preceding code, <xref:Microsoft.Build.Logging.LoggerDescription.CreateLogger*> creates a single instance of the `ColoredConsoleLogger` per category name and stores it in the [`ConcurrentDictionary<TKey,TValue>`](/dotnet/api/system.collections.concurrent.concurrentdictionary-2);
923+
924+
### Usage and registration of the custom logger
925+
926+
Register the logger in the `Startup.Configure`:
927+
928+
[!code-csharp[](index/samples/3.x/CustomLogger/Startup.cs?name=snippet)]
929+
930+
For the preceding code, provide at least one extension method for the `ILoggerFactory`:
931+
932+
[!code-csharp[](index/samples/3.x/CustomLogger/ColoredConsoleLogger/ColoredConsoleLoggerExtensions.cs?name=snippet)]
933+
872934
## Additional resources
873935

874936
* <xref:fundamentals/logging/loggermessage>
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using System;
2+
using Microsoft.Extensions.Logging;
3+
4+
namespace CustomLogger.ColoredConsoleLogger
5+
{
6+
#region snippet
7+
public class ColoredConsoleLogger : ILogger
8+
{
9+
private readonly string _name;
10+
private readonly ColoredConsoleLoggerConfiguration _config;
11+
12+
public ColoredConsoleLogger(string name, ColoredConsoleLoggerConfiguration config)
13+
{
14+
_name = name;
15+
_config = config;
16+
}
17+
18+
public IDisposable BeginScope<TState>(TState state)
19+
{
20+
return null;
21+
}
22+
23+
public bool IsEnabled(LogLevel logLevel)
24+
{
25+
return logLevel == _config.LogLevel;
26+
}
27+
28+
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
29+
Exception exception, Func<TState, Exception, string> formatter)
30+
{
31+
if (!IsEnabled(logLevel))
32+
{
33+
return;
34+
}
35+
36+
if (_config.EventId == 0 || _config.EventId == eventId.Id)
37+
{
38+
var color = Console.ForegroundColor;
39+
Console.ForegroundColor = _config.Color;
40+
Console.WriteLine($"{logLevel} - {eventId.Id} " +
41+
$"- {_name} - {formatter(state, exception)}");
42+
Console.ForegroundColor = color;
43+
}
44+
}
45+
}
46+
#endregion
47+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System;
2+
using Microsoft.Extensions.Logging;
3+
4+
namespace CustomLogger.ColoredConsoleLogger
5+
{
6+
#region snippet
7+
public class ColoredConsoleLoggerConfiguration
8+
{
9+
public LogLevel LogLevel { get; set; } = LogLevel.Warning;
10+
public int EventId { get; set; } = 0;
11+
public ConsoleColor Color { get; set; } = ConsoleColor.Yellow;
12+
}
13+
#endregion
14+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System;
2+
using Microsoft.Extensions.Logging;
3+
4+
namespace CustomLogger.ColoredConsoleLogger
5+
{
6+
#region snippet
7+
public static class ColoredConsoleLoggerExtensions
8+
{
9+
public static ILoggerFactory AddColoredConsoleLogger(
10+
this ILoggerFactory loggerFactory,
11+
ColoredConsoleLoggerConfiguration config)
12+
{
13+
loggerFactory.AddProvider(new ColoredConsoleLoggerProvider(config));
14+
return loggerFactory;
15+
}
16+
public static ILoggerFactory AddColoredConsoleLogger(
17+
this ILoggerFactory loggerFactory)
18+
{
19+
var config = new ColoredConsoleLoggerConfiguration();
20+
return loggerFactory.AddColoredConsoleLogger(config);
21+
}
22+
public static ILoggerFactory AddColoredConsoleLogger(
23+
this ILoggerFactory loggerFactory,
24+
Action<ColoredConsoleLoggerConfiguration> configure)
25+
{
26+
var config = new ColoredConsoleLoggerConfiguration();
27+
configure(config);
28+
return loggerFactory.AddColoredConsoleLogger(config);
29+
}
30+
}
31+
#endregion
32+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using System.Collections.Concurrent;
2+
using Microsoft.Extensions.Logging;
3+
4+
namespace CustomLogger.ColoredConsoleLogger
5+
{
6+
#region snippet
7+
public class ColoredConsoleLoggerProvider : ILoggerProvider
8+
{
9+
private readonly ColoredConsoleLoggerConfiguration _config;
10+
private readonly ConcurrentDictionary<string, ColoredConsoleLogger> _loggers = new ConcurrentDictionary<string, ColoredConsoleLogger>();
11+
12+
public ColoredConsoleLoggerProvider(ColoredConsoleLoggerConfiguration config)
13+
{
14+
_config = config;
15+
}
16+
17+
public ILogger CreateLogger(string categoryName)
18+
{
19+
return _loggers.GetOrAdd(categoryName, name => new ColoredConsoleLogger(name, _config));
20+
}
21+
22+
public void Dispose()
23+
{
24+
_loggers.Clear();
25+
}
26+
}
27+
#endregion
28+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using System.Diagnostics;
2+
using Microsoft.AspNetCore.Mvc;
3+
using Microsoft.Extensions.Logging;
4+
using CustomLogger.Models;
5+
using Microsoft.Docs.Samples;
6+
7+
namespace CustomLogger.Controllers
8+
{
9+
public class HomeController : Controller
10+
{
11+
private readonly ILogger<HomeController> _logger;
12+
13+
public HomeController(ILogger<HomeController> logger)
14+
{
15+
_logger = logger;
16+
}
17+
18+
public IActionResult Index()
19+
{
20+
var routeInfo = ControllerContext.ToCtxString();
21+
22+
_logger.LogInformation(routeInfo);
23+
_logger.LogWarning(routeInfo + "Testing LogWarning" );
24+
_logger.LogError("Not an error. Just to show the custom logger");
25+
26+
return View();
27+
}
28+
29+
public IActionResult Privacy()
30+
{
31+
var routeInfo = ControllerContext.ToCtxString();
32+
33+
_logger.LogInformation(routeInfo);
34+
_logger.LogWarning(routeInfo + "Testing LogWarning");
35+
_logger.LogError("Not an error. Just to show the custom logger");
36+
37+
return View();
38+
}
39+
40+
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
41+
public IActionResult Error()
42+
{
43+
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
44+
}
45+
}
46+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netcoreapp3.1</TargetFramework>
5+
</PropertyGroup>
6+
7+
<ItemGroup>
8+
<PackageReference Include="Rick.Docs.Samples.RouteInfo" Version="1.0.0.4" />
9+
</ItemGroup>
10+
11+
</Project>
Lines changed: 11 additions & 0 deletions
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+
}
Lines changed: 26 additions & 0 deletions
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+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
using System;
2+
using Microsoft.AspNetCore.Builder;
3+
using Microsoft.AspNetCore.Hosting;
4+
using Microsoft.Extensions.Configuration;
5+
using Microsoft.Extensions.DependencyInjection;
6+
using Microsoft.Extensions.Hosting;
7+
using Microsoft.Extensions.Logging;
8+
using CustomLogger.ColoredConsoleLogger;
9+
10+
namespace CustomLogger
11+
{
12+
public class Startup
13+
{
14+
public Startup(IConfiguration configuration)
15+
{
16+
Configuration = configuration;
17+
}
18+
19+
public IConfiguration Configuration { get; }
20+
21+
public void ConfigureServices(IServiceCollection services)
22+
{
23+
services.AddControllersWithViews();
24+
}
25+
26+
#region snippet
27+
public void Configure(IApplicationBuilder app, IWebHostEnvironment env,
28+
ILoggerFactory loggerFactory)
29+
{
30+
// Default registration.
31+
loggerFactory.AddProvider(new ColoredConsoleLoggerProvider(
32+
new ColoredConsoleLoggerConfiguration
33+
{
34+
LogLevel = LogLevel.Error,
35+
Color = ConsoleColor.Red
36+
}));
37+
38+
// Custom registration with default values.
39+
loggerFactory.AddColoredConsoleLogger();
40+
41+
// Custom registration with a new configuration instance.
42+
loggerFactory.AddColoredConsoleLogger(new ColoredConsoleLoggerConfiguration
43+
{
44+
LogLevel = LogLevel.Debug,
45+
Color = ConsoleColor.Gray
46+
});
47+
48+
// Custom registration with a configuration object.
49+
loggerFactory.AddColoredConsoleLogger(c =>
50+
{
51+
c.LogLevel = LogLevel.Information;
52+
c.Color = ConsoleColor.Blue;
53+
});
54+
55+
if (env.IsDevelopment())
56+
{
57+
app.UseDeveloperExceptionPage();
58+
}
59+
else
60+
{
61+
app.UseExceptionHandler("/Home/Error");
62+
app.UseHsts();
63+
}
64+
app.UseHttpsRedirection();
65+
app.UseStaticFiles();
66+
67+
app.UseRouting();
68+
69+
app.UseAuthorization();
70+
71+
app.UseEndpoints(endpoints =>
72+
{
73+
endpoints.MapControllerRoute(
74+
name: "default",
75+
pattern: "{controller=Home}/{action=Index}/{id?}");
76+
});
77+
}
78+
#endregion
79+
}
80+
}

0 commit comments

Comments
 (0)