Kestrun Logging
🚧 Work in Progress
This page is currently under development. Content will be expanded with guides, examples, and best practices soon.
Thank you for your patience while we build it out.
Overview
Kestrun’s logging system builds on Serilog to give you:
- Multiple named loggers — each with its own minimum level, enrichers, and sinks.
- Full Serilog flexibility — use any sink (console, file, Seq, HTTP, syslog…), any enricher, any output template.
- Global fallback — the library itself and any PowerShell scripts that don’t specify a logger still write to
Serilog.Log
. - Global fallback – when no named logger is specified, log events go through
Serilog.Log
. - PowerShell support — configure and invoke named loggers directly from PS scripts.
Under the hood we provide:
C# Type / PS Module | Purpose |
---|---|
KestrunLogConfigurator | Static registry; creates, stores and re-configures loggers. |
KestrunLoggerBuilder | Fluent builder that wraps Serilog.LoggerConfiguration & adds helpers (Minimum , WithProperty , With<TEnricher> , Sink(...) , Register(...) ). |
PowerShell | Cmdlets that surface the builder pattern in PS pipelines (New-KrLogger , Add-KrProperty , Add-KrSink , …). |
1. Initializing a Logger
// 1. Start a builder for a named logger “api”.
// If not seen before, creates new LoggerConfiguration with FromLogContext().
var builder = KestrunLogConfigurator.Configure("api");
2. Setting the Minimum Level
builder.Minimum(LogEventLevel.Debug);
// Events at Debug or above will be emitted; Verbose events are dropped.
3. Adding Enrichment
// A. Static properties:
builder.WithProperty("Subsystem", "API");
// B. Built-in enrichers (parameter-less):
builder.With<Serilog.Enrichers.Thread.ThreadIdEnricher>();
// C. Custom enrichers with ctor args:
builder.With<MyCustomEnricher>("someSetting");
Under the hood WithProperty
and With<TEnricher>
invoke the same Serilog enrichment pipeline your normal Serilog code uses.
4. Adding Sinks
You can add as many sinks as you like. Each event will be duplicated to every sink.
// Console sink with custom template
builder.Sink(w => w.Console(
outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message}{NewLine}{Exception}"));
// Rolling file sink
builder.Sink(w => w.File(
path: "logs/api-.log",
rollingInterval: RollingInterval.Day));
// HTTP sink (example)
builder.Sink(w => w.DurableHttpUsingFileSizeRolledBuffers(
requestUri: "https://logs.mycompany.com/api/logs"));
5. Finalizing the Logger
// Build and register under “api”. Does NOT replace Serilog.Log by default.
var apiLog = builder.Register();
// If you want this logger to become the new global default:
builder.Register(setAsDefault: true);
Once applied, you can immediately call:
apiLog.Information("User {UserId} requested {Path}", userId, req.Path);
Complete C# Examples
Example A: Basic “api” Logger
KestrunLogConfigurator.Configure("api")
.Minimum(LogEventLevel.Information)
.WithProperty("Subsystem", "API")
.Sink(w => w.Console())
.Register(); // Does not change Serilog.Log.Logger
Example B: Two Loggers, Different Levels & Sinks
// 1️⃣ Audit log: only warnings and above, writes JSON files
KestrunLogConfigurator.Configure("audit")
.Minimum(LogEventLevel.Warning)
.Sink(w => w.File(
path: "logs/audit-.json",
rollingInterval: RollingInterval.Day,
formatter: new Serilog.Formatting.Json.JsonFormatter()))
.Register();
// 2️⃣ Debug log: verbose output to console + Seq
KestrunLogConfigurator.Configure("debug")
.Minimum(LogEventLevel.Verbose)
.Sink(w => w.Console(outputTemplate: "[{Level:u3}] {Message}"))
.Sink(w => w.Seq(serverUrl: "http://localhost:5341"))
.Register();
// 3️⃣ Use them:
var audit = KestrunLogConfigurator.Get("audit")!;
audit.Error("Failed to process order {OrderId}", orderId);
var debug = KestrunLogConfigurator.Get("debug")!;
debug.Verbose("Processing {StepName}", stepName);
Example C: Promote One as Global Default
// Replace the built-in Serilog.Log.Logger with this custom logger
KestrunLogConfigurator.Configure("default")
.Minimum(LogEventLevel.Debug)
.WithProperty("App", "KestrunService")
.Sink(w => w.Console())
.Register(setAsDefault: true);
// Anywhere else, Serilog.Log.Information(...) now goes through “default”
Log.Information("Service started at {Start}", DateTime.UtcNow);
PowerShell Usage
# 1. Create a named logger “ps”
New-KrLogger -Name "ps" -Level Debug |
Add-KrSink -Type File -Options @{ Path = "logs/ps-.log" } |
Add-KrSink -Type Console -Options @{} |
Register-KrLogger
# 2. Write to it anywhere in your PS routes
Write-KrLog -Name "ps" -Level Information `
-Message "Handled {Method} {Path}" -Args $Context.Request.Method, $Context.Request.Path
Write-KrLog -Name "ps" -Level Information `
-Message "Handled {Method} {Path}" `
-Arguments $Context.Request.Method, $Context.Request.Path `
-Properties @{ CorrelationId = $cid }
If you pass -Default
to Apply-KrLogger
, it swaps in your PS logger as Serilog.Log.Logger
so any C# or framework logs go to your sinks too.
Resetting & Hot-Reload
When you need to tear down and rebuild all loggers (e.g. upon config-file change or between tests), call:
csharp
KestrunLogConfigurator.Reset();
powershell
ReSet-KrDefaultLogger -Name 'ps'
This will:
- Flush all pending events.
- Dispose each custom logger.
- Clear internal registries.
- Restore a minimal console logger as
Serilog.Log.Logger
.
After Reset()
you can re-run your Configure(…)…Register()
calls or (in PS) your New-KrLogger … | Register-KrLogger
pipelines to spin loggers back up.
3. PowerShell cmdlet reference
Cmdlet | What it does | Typical pipeline position |
---|---|---|
New-KrLogger -Name string[-Level] Verbose∣Debug∣… | Starts a new builder for the named logger (or returns existing builder to mutate). | 1st |
Add-KrProperty -Builder Builder-Name string -Value object | Adds a static property. | anywhere |
Add-KrEnricher -TypeName “Namespace.Type” [-Arguments] object[] | Adds an ILogEventEnricher . | anywhere |
Add-KrSink -Type Console∣File∣Seq∣Http∣Syslog∣Custom[-Options] hashtable[-CustomSink] ILogEventSink | Attaches a sink. | anywhere |
Register-KrLogger [-Default] | Builds the ILogger , stores it, optionally makes it global. | last |
Write-KrLog [-Name] string-Level Verbose∣…-Message string[-Arguments] object[][-Exception] [Exception][-Properties] hashtable[-Object] psobject | Emits an event through a named logger or Serilog.Log . Supports extra properties and arbitrary PS objects (-Object serialises to JSON). | n/a |
Update-KrLogger -Name string-ScriptBlock { param($cfg) … }[-Default] | Re-builds the logger by replaying its recipe plus your script-block delta. | n/a |
Get-KrLogger -Name | Returns the live ILogger (or throws). | n/a |
Test-KrLogger -Name | True/False – does the logger exist? | n/a |
ReSet-KrDefaultLogger -Name | Reset the Logger (or THrow) | n/a |
4. Complete C# examples
A. Basic “api” logger
KestrunLogConfigurator.Configure("api")
.Minimum(LogEventLevel.Information)
.WithProperty("Subsystem", "API")
.Sink(w => w.Console())
.Register();
B. Two loggers, different levels & sinks
// Audit – JSON files, warnings+
KestrunLogConfigurator.Configure("audit")
.Minimum(LogEventLevel.Warning)
.Sink(w => w.File("logs/audit-.json",
rollingInterval: RollingInterval.Day,
formatter: new JsonFormatter()))
.Register();
// Debug – verbose console + Seq
KestrunLogConfigurator.Configure("debug")
.Minimum(LogEventLevel.Verbose)
.Sink(w => w.Console("[{Level:u3}] {Message}"))
.Sink(w => w.Seq("http://localhost:5341"))
.Register();
C. Promote one as global default
KestrunLogConfigurator.Configure("default")
.Minimum(LogEventLevel.Debug)
.Sink(w => w.Console())
.Register(setAsDefault: true);
Available Enrichers
How to use
.WithProperty("Key", value) // custom .With<YourEnricher>() // parameter-less constructor .With<YourEnricher>(arg1, arg2) // ctor args
Enricher | Description | Example |
---|---|---|
WithProperty(name, value) | Injects a static property on every event. | .WithProperty("Module", "API") |
WithThreadId() | Adds the current thread’s managed ID (ThreadId property). | .With<Serilog.Enrichers.Thread.ThreadIdEnricher>() |
WithMachineName() | Adds the host machine’s name (MachineName property). | .With<Serilog.Enrichers.Environment.MachineNameEnricher>() |
WithProcessId() | Adds the current process ID (ProcessId property). | .With<Serilog.Enrichers.Process.ProcessIdEnricher>() |
WithExceptionDetails() | Captures full exception details (stack, inner exceptions) for richer error logs. Requires the [Serilog.Exceptions] package. | .With<Serilog.Exceptions.Destructuring.DestructuringEnricher>() |
FromLogContext() | (Applied by default) allows ambient context (via LogContext.PushProperty ) to flow into events. | implicit on every builder |
Custom ILogEventEnricher | Any custom enricher you write — implement ILogEventEnricher and attach with With<T>() . | .With<YourApp.UserNameEnricher>("claimType") |
Tip: You can mix and match any enrichers in a single chain:
.WithProperty("Subsystem", "Auth") .WithThreadId() .WithMachineName() .With<CustomRegionEnricher>(regionConfig)
Common Sinks
How to use
.Sink(w => w.Console(...)) .Sink(w => w.File(...)) .Sink(w => w.Seq(...)) .Sink(w => w.DurableHttpUsingFileSizeRolledBuffers(...)) .Sink(w => w.UdpSyslog(...)) // …or any Serilog sink extension you reference
Sink | Package / Namespace | Description | Example |
---|---|---|---|
Console | Serilog.Sinks.Console | Writes events to standard output or error streams. | .Sink(w => w.Console(outputTemplate: "[{Level:u3}] {Message}{NewLine}{Exception}")) |
File | Serilog.Sinks.File | Appends events to a file (supports rolling by time/size). | .Sink(w => w.File("logs/app-.log", rollingInterval: RollingInterval.Day)) |
Seq | Serilog.Sinks.Seq | Sends events to a Seq server over HTTP for structured log storage and querying. | .Sink(w => w.Seq("http://localhost:5341")) |
Durable HTTP | Serilog.Sinks.Http | Buffers events to disk and posts in batches to an HTTP endpoint. | .Sink(w => w.DurableHttpUsingFileSizeRolledBuffers("https://logs.mycompany.com/api/logs")) |
UDP Syslog | Serilog.Sinks.SyslogMessages | Sends events over UDP to a syslog server (RFC 5424). | .Sink(w => w.UdpSyslog("syslog.company.com", 514)) |
Rolling File with JSON Formatter | Same as File sink but formats each event as JSON | .Sink(w => w.File("logs/audit-.json", rollingInterval: RollingInterval.Day, formatter: new Serilog.Formatting.Json.JsonFormatter())) | |
Custom Sink | any ILogEventSink you implement or reference | Drop events anywhere you like—e.g. Azure Tables, Elasticsearch, AWS CloudWatch, etc. | .Sink(w => w.Sink(new MyCustomSink())) |
Note: Because
Sink(...)
takes aFunc<LoggerSinkConfiguration, LoggerConfiguration>
,you can invoke any extension method provided by the sinks you reference—even ones not listed here.