This class library contains a lightweight implementation of the Microsoft.Extensions.Logging.ILoggerProvider interface for file logging. Runs on all .NET platforms which implement .NET Standard 2.0+ including .NET Core 2 (ASP.NET Core 2.1+), .NET Core 3 and .NET 5+.
The code is based on ConsoleLogger whose full feature set is implemented (including log scopes and configuration reloading). The library has no 3rd party dependencies. No I/O blocking occurs as processing of log messages is done in the background. File system access is implemented on top of the Microsoft.Extensions.FileProviders.IFileProvider abstraction so it's even possible to use a custom backing storage.
As of version 3.3.0 JSON structured logging (following the format established by the JSON formatter of the built-in console logger) is also available.
As of version 3.6.0 the self-contained trimmed and Native AOT deployments models are also supported (in applications running on .NET 8 or newer).
Version 3.0 is a major revision with many improvements involving some breaking changes:
Thus, version 3.0 is not backward compatible with previous versions. If you want to upgrade from older versions, please read up on the new configuration system to be able to make the necessary adjustments.
However, you may stay with version 2.1 as it continues to work on .NET Core 3+ according to my tests (but please note that it isn't developed actively any more).
Add the Karambolo.Extensions.Logging.File NuGet package to your application project:
dotnet add package Karambolo.Extensions.Logging.File
or, if you want structured logging, add Karambolo.Extensions.Logging.File.Json NuGet package instead:
dotnet add package Karambolo.Extensions.Logging.File.Json
If you have a .NET Core/.NET 5+ project other than an ASP.NET Core web application (e.g. a console application), you should also consider adding explicit references to the following NuGet packages with the version matching your .NET runtime. For example, if your project targets .NET 8:
dotnet add package Microsoft.Extensions.FileProviders.Physical -v 8.0.*
dotnet add package Microsoft.Extensions.Logging.Configuration -v 8.0.*
dotnet add package Microsoft.Extensions.Options.ConfigurationExtensions -v 8.0.*
Explanation why this is recommended
The Karambolo.Extensions.Logging.File package depends on some framework libraries and references the lowest possible versions of these dependencies (e.g. the build targeting .NET 8 references Microsoft.Extensions.Logging.Configuration 8.0.0). These versions may not (mostly do not) align with the version of your application's target platform since that may be a newer patch, minor or even major version (e.g. .NET 9). Thus, referencing Karambolo.Extensions.Logging.File in itself may result in referencing outdated framework libraries on that particular platform (sticking to the previous example, Microsoft.Extensions.Logging.Configuration 8.0.0 instead of 9.0.0).
Luckily, in the case of ASP.NET Core this is resolved automatically as ASP.NET Core projects already reference the correct (newer) versions of the framework libraries in question (by means of the Microsoft.AspNetCore.App metapackage).
However, in other cases (like a plain .NET Core/.NET 5+ console application) you may end up with outdated dependencies, which is usually undesired (even can lead to issues like this), so you want to resolve this situation by adding the explicit package references listed above.
For more details, see NuGet package dependency resolution.
If you've chosen structured logging, replace calls to AddFile(...)
with AddJsonFile(...)
in the following samples.
AddJsonFile
is just a convenience method which sets the TextBuilder
setting to the default JSON formatter (JsonFileLogEntryTextBuilder
) globally. You can achieve the same effect by using AddFile
and setting TextBuilder
(or TextBuilderType
) to the aforementioned formatter manually. For details, see the Settings section.
It also follows from the above that you can still override this setting in your configuration (appsettings.json
or configure callback) and use other formatters regardless the defaults set by AddJsonFile
.
var builder = WebApplication.CreateBuilder(args); builder.Logging.AddFile(o => o.RootPath = builder.Environment.ContentRootPath); var app = builder.Build(); // ...
public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder .ConfigureLogging((ctx, builder) => { builder.AddConfiguration(ctx.Configuration.GetSection("Logging")); builder.AddFile(o => o.RootPath = ctx.HostingEnvironment.ContentRootPath); }) .UseStartup<Startup>(); }); }
// build configuration var configuration = /* ... */; // configure DI var services = new ServiceCollection(); services.AddLogging(builder => { builder.AddConfiguration(configuration.GetSection("Logging")); builder.AddFile(o => o.RootPath = AppContext.BaseDirectory); }); // create logger factory await using (var sp = services.BuildServiceProvider()) { var loggerFactory = sp.GetService<ILoggerFactory>(); // ... }
public class Program { public static void Main(string[] args) { CreateWebHostBuilder(args).Build().Run(); } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .ConfigureLogging((ctx, builder) => { builder.AddConfiguration(ctx.Configuration.GetSection("Logging")); builder.AddFile(o => o.RootPath = ctx.HostingEnvironment.ContentRootPath); }) .UseStartup<Startup>(); }
// build configuration var configuration = /* ... */; // configure DI var services = new ServiceCollection(); services.AddLogging(builder => { builder.AddConfiguration(configuration.GetSection("Logging")); builder.AddFile(o => o.RootPath = AppContext.BaseDirectory); }); // create logger factory using (var sp = services.BuildServiceProvider()) { var loggerFactory = sp.GetService<ILoggerFactory>(); // ... }Using multiple providers with different settings
First of all, you need a little bit of boilerplate code:
[ProviderAlias("File2")] class AltFileLoggerProvider : FileLoggerProvider { public AltFileLoggerProvider(FileLoggerContext context, IOptionsMonitor<FileLoggerOptions> options, string optionsName) : base(context, options, optionsName) { } }
And a setup like this:
services.AddLogging(builder => { builder.AddConfiguration(config.GetSection("Logging")); builder.AddFile(o => o.RootPath = AppContext.BaseDirectory); builder.AddFile<AltFileLoggerProvider>(configure: o => o.RootPath = AppContext.BaseDirectory); });
Now, you have two independent file logger providers. One of them picks up its configuration from the standard configuration section "File" while the other one from section "File2" as specified by the ProviderAlias attribute.
You may check out this demo application which shows a complete example of this advanced setup.
Customizing/extending the logging logicThe implementation of the file logger provides many extension points (mostly, in the form of overridable virtual methods), so you can customize its behavior and/or implement features that are not available out of the box.
For example, see this sample application, which extends the file logger with the ability to rotate log files.
There are some settings which are configured on provider level only (FileLoggerOptions):
Description Default value Notes FileAppender Specifies the object responsible for appending log messages. PhysicalFileAppender instance with root path set to Environment.CurrentDirectory The RootPath shortcut property is also available for setting a PhysicalFileAppender with a custom root path. (This path must point to an existing directory.) BasePath Path to the base directory of log files. "" (none) Base path is relative to (but cannot point outside of) the root path of the underlying file provider (FileAppender.FileProvider). (If this path does not exist, it will be created automatically.) Files An array of LogFileOptions which define the settings of the individual log files. There is an important change compared to older (2.x or earlier) versions: you must explicitly define at least one log file, otherwise the provider won't log anything.These settings can be configured on log file level only (LogFileOptions):
Description Default value Notes Path Path of the log file relative to FileLoggerOptions.BasePath. Can be a simple path or a path template.The log file settings below can be specified globally (per provider) and individually (per log file) as well. File-level settings always override provider-level settings. (In practice this means if a setting property is null for a given file, the value of the same property of the provider-level settings applies. If that is null too, a default value is used.)
Description Default value Notes FileAccessMode Strategy for accessing log files. LogFileAccessMode. KeepOpenAndAutoFlush{ "Logging": { // global filter settings "LogLevel": { "Default": "Information" }, // provider level settings "File": { "BasePath": "Logs", "FileAccessMode": "KeepOpenAndAutoFlush", "FileEncodingName": "utf-8", "DateFormat": "yyyyMMdd", "CounterFormat": "000", "MaxFileSize": 10485760, "TextBuilderType": "MyApp.CustomLogEntryTextBuilder, MyApp", // first-level filters "LogLevel": { "MyApp": "Information", "Default": "Debug" // first-level filters can loosen the levels specified by the global filters }, "IncludeScopes": true, "MaxQueueSize": 100, "Files": [ // a simple log file definition which inherits all settings from the provider (will produce files like "default-000.log") { "Path": "default-<counter>.log" }, // another log file definition which defines extra filters and overrides the Counter property (will produce files like "2019/08/other-00.log") { "Path": "<date:yyyy>/<date:MM>/other-<counter>.log", // second-level filters "MinLevel": { "MyApp.SomeClass": "Warning", "Default": "Trace" // this has no effect as second-level filters can only be more restrictive than first-level filters! }, "CounterFormat": "00" } ] } } }
If you have added the right NuGet package and configured logging in your application by the above but the application outputs no log files, check the following points:
Files
collection? If so, have you specified the Path
property of that file? (See also this issue.)Path
properties of the defined log files valid paths on the operating system you use? If you use path templates (that is, paths containing placeholders like <date>
or <counter>
), are they resolved to valid paths?RootPath
(or more precisely, inside the root path of FileAppender.FileProvider
)? (See also this issue.)RootPath
\BasePath
? (See also this issue.)If none of these helps, since version 3.2.0 you can track down the problem by observing the file logger's diagnostic events:
// this subscription should happen before anything is logged, // so place it in your code early enough (preferably, before configuration of logging) FileLoggerContext.Default.DiagnosticEvent += e => { // examine the diagnostic event here: // print it to the debug window, set a breakpoint and inspect internal state on break, etc. Debug.WriteLine(e); };
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4