Serilog.Sinks.Loki.YetAnother
4.0.5
dotnet add package Serilog.Sinks.Loki.YetAnother --version 4.0.5
NuGet\Install-Package Serilog.Sinks.Loki.YetAnother -Version 4.0.5
<PackageReference Include="Serilog.Sinks.Loki.YetAnother" Version="4.0.5" />
<PackageVersion Include="Serilog.Sinks.Loki.YetAnother" Version="4.0.5" />
<PackageReference Include="Serilog.Sinks.Loki.YetAnother" />
paket add Serilog.Sinks.Loki.YetAnother --version 4.0.5
#r "nuget: Serilog.Sinks.Loki.YetAnother, 4.0.5"
#:package Serilog.Sinks.Loki.YetAnother@4.0.5
#addin nuget:?package=Serilog.Sinks.Loki.YetAnother&version=4.0.5
#tool nuget:?package=Serilog.Sinks.Loki.YetAnother&version=4.0.5
Serilog.Sinks.Loki.YetAnother
A performance-oriented Serilog sink that sends log events to Grafana Loki using the HTTP push API. Designed to minimize memory allocations and GC pressure, making it a great fit for high-throughput .NET applications.
Features
- Low allocation design — writes JSON directly to a UTF-8 stream via
Utf8JsonWriterand pooled buffers instead of intermediate strings - Broad framework support — targets
net481,netstandard2.0,net8.0,net9.0, andnet10.0 - Batching with back-pressure — configurable batch size, period, queue limit, and retry policy
- Distributed tracing — optional
TraceId/SpanIdenrichment fromLogEventcontext - Flexible labeling — global static labels, property-to-label promotion, and automatic log-level labels
- Multi-tenant support — set the
X-Scope-OrgIDheader per sink via theTenantproperty - Custom exception formatting — plug in your own
ILokiExceptionFormatterimplementation - Serilog.Settings.Configuration — full support for
appsettings.json-based setup
Installation
dotnet add package Serilog.Sinks.Loki.YetAnother
Or add a PackageReference directly:
<PackageReference Include="Serilog.Sinks.Loki.YetAnother" Version="*" />
Quick Start
using Serilog;
using Serilog.Sinks.Loki;
Log.Logger = new LoggerConfiguration()
.WriteTo.Loki(new LokiSinkConfigurations
{
Url = new Uri("https://loki.example.com"),
Labels =
[
new LokiLabel("app", "my-service"),
new LokiLabel("environment", "production"),
]
})
.CreateLogger();
Log.Information("Hello from {App}!", "my-service");
Configuration Reference
Sink Parameters
These are passed directly to .WriteTo.Loki(...):
| Parameter | Type | Default | Description |
|---|---|---|---|
configurations |
LokiSinkConfigurations |
required | Core sink settings (see table below) |
batchSizeLimit |
int |
1000 |
Maximum number of events in a single batch |
period |
TimeSpan |
2 seconds |
Time between batch flush attempts |
queueLimit |
int |
100000 |
Maximum events held in the internal queue. When full, new events are dropped |
eagerlyEmitFirstEvent |
bool |
true |
Flush immediately when the first event arrives (useful during debugging) |
httpClient |
HttpClient? |
null |
Supply your own HttpClient for proxy, compression, or custom headers |
exceptionFormatter |
ILokiExceptionFormatter? |
null |
Custom exception formatter (falls back to the built-in recursive formatter) |
retryTimeLimit |
TimeSpan |
10 minutes |
How long the sink retries a failed batch before discarding it |
LokiSinkConfigurations Properties
| Property | Type | Default | Description |
|---|---|---|---|
Url |
Uri |
required | Base URL of the Loki server |
Labels |
LokiLabel[] |
[] |
Global labels added to every log stream |
Credentials |
LokiCredentials? |
null |
Basic-auth username and password |
PropertiesAsLabels |
string[] |
[] |
Log event property names to promote to Loki labels. Matching is case-sensitive. |
HandleLogLevelAsLabel |
bool |
true |
Add the Serilog Level as a level label |
Tenant |
string? |
null |
When set, an X-Scope-OrgID header is sent with every request |
EnrichTraceId |
bool |
false |
Include TraceId from LogEvent.TraceId in the JSON payload |
EnrichSpanId |
bool |
false |
Include SpanId from LogEvent.SpanId in the JSON payload |
Programmatic Setup (all options)
Log.Logger = new LoggerConfiguration()
.WriteTo.Loki(new LokiSinkConfigurations
{
Url = new Uri("https://loki.example.com"),
Credentials = new LokiCredentials("username", "password"),
HandleLogLevelAsLabel = true,
PropertiesAsLabels = ["userId"],
Labels =
[
new LokiLabel("app", "my-service"),
],
Tenant = "team-a",
EnrichTraceId = true,
EnrichSpanId = true,
},
batchSizeLimit: 500,
period: TimeSpan.FromSeconds(2),
queueLimit: 50_000,
eagerlyEmitFirstEvent: true,
retryTimeLimit: TimeSpan.FromMinutes(5))
.CreateLogger();
Serilog.Settings.Configuration (appsettings.json)
appsettings.json
{
"Serilog": {
"Using": ["Serilog.Sinks.Loki"],
"WriteTo": [
{
"Name": "Loki",
"Args": {
"configurations": {
"Url": "https://loki.example.com",
"Labels": [
{ "key": "app", "value": "my-service" }
],
"Credentials": {
"Username": "username",
"Password": "password"
},
"PropertiesAsLabels": ["app", "environment"],
"HandleLogLevelAsLabel": true,
"EnrichTraceId": true,
"EnrichSpanId": true,
"Tenant": "team-a"
},
"batchSizeLimit": 500
}
}
]
}
}
Program.cs
using Microsoft.Extensions.Configuration;
using Serilog;
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration)
.CreateLogger();
A runnable sample project is available under
samples/SettingsConfigurations.
Advanced Usage
Custom HttpClient
Supply your own HttpClient to configure proxies, compression, timeouts, or custom headers:
var httpClient = new HttpClient(new HttpClientHandler
{
Proxy = new WebProxy("http://proxy.example.com:8080"),
AutomaticDecompression = DecompressionMethods.GZip,
});
httpClient.Timeout = TimeSpan.FromSeconds(30);
Log.Logger = new LoggerConfiguration()
.WriteTo.Loki(new LokiSinkConfigurations
{
Url = new Uri("https://loki.example.com"),
},
httpClient: httpClient)
.CreateLogger();
Note: When you supply your own
HttpClient, you are responsible for its lifecycle (disposal).
Custom Exception Formatting
Implement ILokiExceptionFormatter to control how exceptions are serialized:
public class SimpleExceptionFormatter : ILokiExceptionFormatter
{
public void Format(Utf8JsonWriter writer, Exception exception)
{
writer.WriteStringValue($"{exception.GetType().Name}: {exception.Message}");
}
}
Log.Logger = new LoggerConfiguration()
.WriteTo.Loki(new LokiSinkConfigurations
{
Url = new Uri("https://loki.example.com"),
},
exceptionFormatter: new SimpleExceptionFormatter())
.CreateLogger();
Label Strategy
Loki indexes labels, not log content. Choosing the right labels is critical for query performance and cluster health.
Do:
- Use a small, fixed set of labels (e.g.,
app,environment,level). - Promote log properties to labels only when their cardinality is low and bounded.
Avoid:
- High-cardinality values as labels (user IDs, request IDs, trace IDs). These should stay in the log message JSON and be queried with LogQL filters.
- Adding many labels — Loki performs best with fewer than ~15 labels per stream.
PropertiesAsLabels matching is case-sensitive — "userId" will only match a property named exactly userId.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| No logs appear in Loki | Incorrect Url or network connectivity |
Verify the Loki push endpoint is reachable from the application host |
401 Unauthorized |
Missing or wrong credentials | Check Credentials username/password; verify basic auth is enabled on the Loki gateway |
| Logs are delayed or missing under load | Queue full — events are dropped when queueLimit is exceeded |
Increase queueLimit, decrease period, or increase batchSizeLimit |
| Retries consuming memory | Default retryTimeLimit is 10 minutes |
Lower retryTimeLimit for high-load scenarios to reduce buffering |
| Labels not appearing | Property name casing mismatch in PropertiesAsLabels |
Ensure the property name matches exactly (case-sensitive) |
| Multi-tenant header not sent | Tenant not set |
Set the Tenant property on LokiSinkConfigurations |
Why Another Loki Sink?
Widely used Loki sinks work well, but under high log volume they allocate many intermediate objects, increasing GC pressure.
This sink is designed from the ground up for low memory overhead:
Utf8JsonWriterwrites log events directly to the HTTP request stream — no intermediatestringorStringBuilderallocations.- Custom
TextWriterrenders Serilog message templates straight into the UTF-8 stream, bypassing the usualStringWriterpath. - Pooled buffers reuse memory across batches, reducing both allocation count and average allocation size.
The result: fewer GC cycles and lower memory footprint, especially at scale.
Inspiration and Credits
License
This project is licensed under the MIT License.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 is compatible. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETFramework 4.8.1
- Microsoft.Bcl.HashCode (>= 6.0.0)
- Serilog (>= 4.3.1)
- System.Net.Http (>= 4.3.4)
- System.Text.Json (>= 10.0.7)
- System.Text.RegularExpressions (>= 4.3.1)
-
.NETStandard 2.0
- Microsoft.Bcl.HashCode (>= 6.0.0)
- Serilog (>= 4.3.1)
- System.Text.Json (>= 10.0.7)
- System.Text.RegularExpressions (>= 4.3.1)
-
net10.0
- Serilog (>= 4.3.1)
-
net8.0
- Serilog (>= 4.3.1)
-
net9.0
- Serilog (>= 4.3.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 4.0.5 | 15,969 | 4/26/2026 |
| 3.1.4-alpha | 437 | 9/26/2025 |
| 3.1.2-alpha | 1,561 | 3/29/2025 |
| 3.0.8 | 14,178 | 9/26/2025 |
| 3.0.7 | 3,205 | 3/29/2025 |
| 3.0.6-alpha | 211 | 3/29/2025 |
| 3.0.5-alpha | 581 | 1/25/2025 |
| 3.0.3-alpha | 433 | 1/11/2025 |
| 2.0.2 | 1,272 | 12/4/2024 |
| 1.3.4-v1-0003-0000 | 243 | 4/28/2024 |
| 1.2.9 | 2,865 | 2/8/2024 |
| 1.2.4 | 247 | 2/8/2024 |
| 1.1.4 | 274 | 1/20/2024 |