Lightweight observable logging with file output, Serilog-style fluent configuration, and Microsoft.Extensions.Logging integration—fewer moving parts, predictable behavior for desktop and library scenarios.
- Fluent
LoggerConfiguration— chainWriteTo,ObserveTo,Global,MinimumLevel, andDispatchersimilar to common Serilog-style APIs. Microsoft.Extensions.Logging— register viaAddLiteObservableLogs(with optional options callback or prebuiltObservableLoggerOptions).- Static
Logfacade —Log.Information(...),Log.Received, and assignableLog.Loggerfor quick apps. - Multiple sinks — file (rolling by time and/or size), console or
Debug, in-processLog.Received, andObserveTo.Callbackwith optional per-sink output templates. - Global output template —
Global.OutputTemplate(...)applies when a sink omits its own template (same idea as shared format strings). - Runtime tuning —
Log.Logger.MinimumLevelandLog.Logger.OutputTemplateare get/set and affect subsequent writes without rebuilding the logger. - Retention & collision handling — optional retained file count/age; size rolling can back up a conflicting target path to
*.bakwhen the file name template would collide (e.g. missing{Count}).
using LiteObservableLogs;
using System.IO;
string logFile = Path.Combine(AppContext.BaseDirectory, "log", "observable_{Timestamp:yyyyMMdd}_{Count:d5}.log");
Log.Logger = new LoggerConfiguration()
.WriteTo.File(
logFile,
rollingInterval: RollingInterval.Day,
retainedFileCountLimit: 31,
retainedFileTimeLimit: TimeSpan.FromDays(21),
rollingSize: 10240L) // KB threshold for size-based roll
.WriteTo.Console(target: ConsoleTarget.Debug)
.ObserveTo.Event()
.ObserveTo.Callback(e => { /* handle ObservableLogEvent */ }, outputTemplate: null)
.Global.OutputTemplate("{Timestamp:yyyy-MM-dd HH:mm:ss.fff}|{Level:u5}|{Message}{NewLine}{Exception}")
.Dispatcher.Async()
.MinimumLevel.Debug()
.CreateLogger();
Log.Information("Application started");using LiteObservableLogs;
Log.Received += (_, e) => Console.WriteLine(e.RenderedText);
Log.Warning("Something happened");
Log.CloseAndFlush();using LiteObservableLogs.Providers;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
Host.CreateDefaultBuilder(args)
.ConfigureLogging(builder =>
{
builder.ClearProviders();
builder.AddLiteObservableLogs(options =>
{
options.LogFolder = Path.Combine(AppContext.BaseDirectory, "logs");
options.FileName = "app.log";
options.MinLevel = LogLevel.Information;
});
})
.Build()
.Run();Optional compatibility helper (name only): builder.AddSerilog() maps to current Log options when you already use that pattern elsewhere.
- Per sink — pass
outputTemplateonWriteTo.File,WriteTo.Console,ObserveTo.Event, orObserveTo.Callback. - Global fallback —
Global.OutputTemplate("...")is used when a sink-specific template is omitted or null. - Tokens — supports placeholders such as
{Timestamp},{Level},{Message},{Exception},{SourceContext},{NewLine},{StackFrames}, caller and thread tokens, etc.
ObserveTo.Event— raisesLog.Receivedwith the formattedObservableLogEvent(same isolation semantics as multicast handlers).ObserveTo.Callback— registers anAction<ObservableLogEvent>; remove withLog.Logger.RemoveCallback(action)when you no longer need it.
Log.Logger.MinimumLevel = LogLevel.Warning;
Log.Logger.OutputTemplate = "{Timestamp:O}|{Level:u3}|{Message}";Changes apply to subsequent log writes (including async formatting where applicable).
- Use
{Timestamp:format}and{Count:format}inWriteTo.Filepaths for time- and size-based rotation. - If size rolling produces the same resolved file name as the active file (for example when
{Count}is absent from the template), the library moves the existing file aside asoriginal.log.bak(overwriting an existing.bak) before continuing.
- Target frameworks —
net48,netstandard2.0(seeLiteObservableLogs.csproj). - Dependencies —
Microsoft.Extensions.Logging/Abstractions(versions as referenced by the project).