Forward Your Serilog Logs to Sawmills via OpenTelemetry: A Complete Guide

If you're already using Serilog for structured logging in your .NET applications, you're probably familiar with its powerful sink ecosystem. But what if you want to send those logs to a centralized telemetry management platform, such as Sawmills? In this guide, we'll show you how to seamlessly forward your existing Serilog logs through OpenTelemetry to Sawmills without disrupting your current logging setup.
Why OpenTelemetry + Sawmills?
Before diving into the implementation, let's understand why this combination is powerful:
- Centralized Telemetry Management: Sawmills provides a smart telemetry management platform for logs, metrics, and traces that leverages AI to help organizations route and filter telemetry data before it’s sent to the observability platform. No other solution in the market applies AI-based detection for noise to telemetry optimization, making Sawmills uniquely positioned to cut waste, improve data quality, and enforce governance at scale.
- Vendor Neutrality: OpenTelemetry is an industry standard, avoiding vendor lock-in
- Rich Context: Your existing structured Serilog data flows seamlessly into Sawmills
- Multi-Sink Architecture: Keep your existing console, file, and other sinks while adding Sawmills
Prerequisites
- Existing .NET application with Serilog configured
- Access to a Sawmills collector (we'll show you how to identify it)
- Basic knowledge of NuGet package management
Step 1: Add OpenTelemetry Packages
First, add the necessary NuGet packages to your existing project:
<PackageReference Include="Serilog.Sinks.OpenTelemetry" Version="4.1.0" />
<PackageReference Include="OpenTelemetry" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.9.0" />
Important: Update your Serilog version to 4.0.0+ for compatibility:
<PackageReference Include="Serilog" Version="4.0.0" />
Step 2: Find Your Sawmills Collector Endpoint
If you're running Sawmills in Kubernetes, locate your collector:
# List Sawmills collector pods
kubectl get pods -n sawmills -l app.kubernetes.io/name=sawmills-collector-chart
# Get the pod IP (you'll need this)
kubectl get pods -n sawmills -o wide | grep sawmills-collector
You should see output like:
sawmills-collector-69848894bc-6bqsw 3/3 Running 192.168.194.120
Note the IP address (192.168.194.120 in this example) - this is your collector endpoint.
Step 3: Configure the OpenTelemetry Sink
Option A: Configuration File Approach
Add the OpenTelemetry sink to your existing appsettings.json:
{
"Serilog": {
"WriteTo": [
{
"Name": "Console",
"Args": {
"outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}"
}
},
{
"Name": "File",
"Args": {
"path": "logs/app-.log",
"rollingInterval": "Day"
}
},
{
"Name": "OpenTelemetry",
"Args": {
"endpoint": "http://192.168.194.120:4318",
"protocol": "HttpProtobuf",
"headers": {
"X-Source": "YourApp-to-Sawmills"
},
"resourceAttributes": {
"service.name": "your-service-name",
"service.version": "1.0.0",
"service.instance.id": "instance-1",
"deployment.environment": "production"
},
"includeFormattedMessage": true,
"includeScopeInformation": true
}
}
]
}
}
Option B: Code-First Approach
If you prefer configuring in code, update your Program.cs:
using OpenTelemetry.Logs;
using OpenTelemetry.Resources;
using Serilog;
// Your existing Serilog configuration
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.WriteTo.File("logs/app.log")
.WriteTo.OpenTelemetry(opts =>
{
opts.Endpoint = "http://192.168.194.120:4318";
opts.Protocol = OtlpProtocol.HttpProtobuf;
opts.Headers = new Dictionary<string, string>
{
["X-Source"] = "YourApp-to-Sawmills"
};
opts.ResourceAttributes = new Dictionary<string, object>
{
["service.name"] = "your-service-name",
["service.version"] = "1.0.0",
["deployment.environment"] = "production"
};
})
.CreateLogger();
// Also configure OpenTelemetry logging for .NET's ILogger
var host = Host.CreateDefaultBuilder(args)
.UseSerilog()
.ConfigureServices(services =>
{
services.AddOpenTelemetry()
.WithLogging(logging =>
{
logging.SetResourceBuilder(ResourceBuilder.CreateDefault()
.AddService("your-service-name", "1.0.0"));
logging.AddOtlpExporter(options =>
{
options.Endpoint = new Uri("http://192.168.194.120:4318");
options.Protocol = OpenTelemetry.Exporter.OtlpExportProtocol.HttpProtobuf;
});
});
})
.Build();
Step 4: Make the Configuration Flexible
Hard-coding IP addresses isn't ideal for production. Here are better approaches:
Environment Variables
Update your code to use environment variables:
logging.AddOtlpExporter(options =>
{
var host = Environment.GetEnvironmentVariable("SAWMILLS_COLLECTOR_HOST") ?? "localhost";
var port = Environment.GetEnvironmentVariable("SAWMILLS_COLLECTOR_PORT") ?? "4318";
options.Endpoint = new Uri($"http://{host}:{port}");
options.Protocol = OpenTelemetry.Exporter.OtlpExportProtocol.HttpProtobuf;
});
Environment-Specific Configuration Files
Create appsettings.Production.json:
{
"Serilog": {
"WriteTo": [
{
"Name": "OpenTelemetry",
"Args": {
"endpoint": "http://sawmills-collector.sawmills.svc.cluster.local:4318"
}
}
]
}
}
Step 5: Test the Integration
Run your application and verify the connection:
# Set environment variables
export SAWMILLS_COLLECTOR_HOST=192.168.194.120
export SAWMILLS_COLLECTOR_PORT=4318
# Run your application
dotnet run
You should see your existing console/file logs plus successful transmission to Sawmills.
Step 6: Verify Logs in Sawmills
Check your Sawmills dashboard to confirm logs are flowing. Look for:
- Service name: your-service-name
- Custom headers: X-Source: YourApp-to-Sawmills
- All your existing structured log data
- Proper timestamps and log levels
Example Output
Here's what your logs will look like in Sawmills:
{
"timestamp": "2025-09-23T18:42:12.530Z",
"level": "Information",
"message": "User {UserId} placed order {OrderId} for {Amount:C}",
"properties": {
"UserId": 12345,
"OrderId": "8409a340-238a-4c53-ad58-3373d5b3c44e",
"Amount": 99.99,
"SourceContext": "YourApp.OrderService"
},
"resource": {
"service.name": "your-service-name",
"service.version": "1.0.0",
"deployment.environment": "production"
}
}
Conclusion
By following this guide, you've successfully:
- Added OpenTelemetry forwarding to your existing Serilog setup
- Configured flexible endpoint management
- Maintained all your existing logging functionality
- Gained centralized telemetry management through Sawmills
Your logs now flow seamlessly from Serilog → OpenTelemetry → Sawmills, giving you the best of structured logging and centralized observability.
Need help with your specific setup? The Sawmills team is here to help with implementation questions and best practices.