Use Foveus.Serilog to send structured Serilog logs to Foveus.
This integration is for applications that already use Serilog.
If your application does not use Serilog, you do not need Foveus.Serilog. Use Foveus.SDK alone.
How it works
Foveus.SDK captures executions, context, metrics, batching, transport, and correlation.
Foveus.Serilog adds a Serilog sink so logs written through Serilog can be sent to Foveus.
Use both packages together for Serilog-first applications.
Foveus.SDK → core telemetry, execution capture, batching, transport
Foveus.Serilog → Serilog sink bridge
Install packages
Install both packages:
dotnet add package Foveus.SDK
dotnet add package Foveus.Serilog
During private beta, your package names or package source may be different. Use the package names and feed URL provided during onboarding.
Basic setup
Import the Foveus Serilog extension.
using Foveus.Serilog;
using Serilog;
Configure Foveus first:
builder.Services.AddFoveus(builder.Configuration);
Then configure Serilog and add the Foveus sink:
builder.Host.UseSerilog((context, services, loggerConfiguration) =>
{
loggerConfiguration
.ReadFrom.Configuration(context.Configuration)
.ReadFrom.Services(services)
.WriteTo.Foveus(services);
});
Then add Foveus middleware if this is an ASP.NET Core API:
Recommended setup
For Serilog-first applications, disable the built-in Foveus ILogger provider so logs are not captured twice.
{
"Foveus": {
"ApiKey": "fov_test_...",
"EnableFoveusLoggerProvider": false
}
}
Then configure Foveus and Serilog:
using Foveus.Serilog;
using Serilog;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddFoveus(builder.Configuration);
builder.Host.UseSerilog((context, services, loggerConfiguration) =>
{
loggerConfiguration
.ReadFrom.Configuration(context.Configuration)
.ReadFrom.Services(services)
.WriteTo.Foveus(services);
});
var app = builder.Build();
app.UseFoveus();
app.MapGet("/orders", (ILogger<Program> logger) =>
{
logger.LogInformation("Orders endpoint called");
return Results.Ok(new
{
status = "ok"
});
});
app.Run();
Keep using ILogger
You can keep using the standard .NET ILogger<T> APIs in a Serilog application.
public sealed class OrderService
{
private readonly ILogger<OrderService> _logger;
public OrderService(ILogger<OrderService> logger)
{
_logger = logger;
}
public void ProcessOrder(string orderId)
{
_logger.LogInformation("Order {OrderId} processed", orderId);
}
}
In a Serilog-first app, those logs flow through Serilog’s normal .NET logging pipeline.
Foveus captures them through the Serilog sink:
.WriteTo.Foveus(services)
The key point:
Disable the Foveus logger provider, not ILogger.
Why disable the built-in logger provider?
In Serilog-first applications, you should usually disable the built-in Foveus ILogger provider.
This does not mean you stop using ILogger<T>.
You can keep writing normal .NET logs:
_logger.LogInformation("Payment completed for {PaymentId}", paymentId);
The difference is the path the log takes into Foveus.
Without Serilog
If you are not using Serilog, Foveus can capture ILogger<T> logs through the SDK logger provider.
{
"Foveus": {
"ApiKey": "fov_test_...",
"EnableFoveusLoggerProvider": true
}
}
With Serilog
If your app uses Serilog, ILogger<T> logs can flow through Serilog’s normal .NET logging pipeline.
Foveus then captures them through the Serilog sink.
{
"Foveus": {
"ApiKey": "fov_test_...",
"EnableFoveusLoggerProvider": false
}
}
Then configure the Serilog sink:
builder.Services.AddFoveus(builder.Configuration);
builder.Host.UseSerilog((context, services, loggerConfiguration) =>
{
loggerConfiguration
.ReadFrom.Configuration(context.Configuration)
.ReadFrom.Services(services)
.WriteTo.Foveus(services);
});
This avoids duplicate log capture.
Why AddFoveus must run first
WriteTo.Foveus(services) uses dependency injection to get the Foveus services it needs.
It pulls dependencies such as:
FoveusClient
FoveusOptions
IMetricsCollector
That means AddFoveus(...) must be registered before the Serilog sink is configured.
Correct order:
builder.Services.AddFoveus(builder.Configuration);
builder.Host.UseSerilog((context, services, loggerConfiguration) =>
{
loggerConfiguration
.WriteTo.Foveus(services);
});
Do not build a separate service provider just for Serilog.
Appsettings example
A typical Serilog-first setup looks like this:
{
"Foveus": {
"ApiKey": "fov_test_...",
"ServiceName": "orders-api",
"CaptureProfile": "Balanced",
"EnableFoveusLoggerProvider": false,
"EnableDebugLogging": false,
"Source": "web"
},
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"System": "Warning"
}
}
}
}
Then configure the sink in code:
builder.Services.AddFoveus(builder.Configuration);
builder.Host.UseSerilog((context, services, loggerConfiguration) =>
{
loggerConfiguration
.ReadFrom.Configuration(context.Configuration)
.ReadFrom.Services(services)
.WriteTo.Foveus(services);
});
What gets captured
For each Serilog log event, Foveus captures structured log data.
| Field | Description |
|---|
ServiceName | From Foveus configuration. Defaults to the project assembly name if not set. |
Timestamp | The log event timestamp. |
Level | The mapped log level. |
Message | The rendered log message. |
TraceId | Current trace ID, current activity trace ID, or generated fallback. |
SpanId | Current span ID, current activity span ID, or generated fallback. |
ExecutionId | Current execution ID or generated fallback. |
ParentExecutionId | Current parent execution ID if available. |
Exception | Exception type, message, stack trace, and inner exception details. |
Properties | Serilog structured properties. |
Source | From Foveus configuration, or unknown. |
Log level mapping
Foveus maps Serilog levels to Foveus log levels.
| Serilog level | Foveus level |
|---|
Verbose | TRACE |
Debug | DEBUG |
Information | INFO |
Warning | WARN |
Error | ERROR |
Fatal | FATAL |
Structured logging
Serilog structured properties are sent to Foveus.
This works whether the log call is written through Serilog directly or through ILogger<T> in a Serilog-configured application.
Using ILogger<T>:
_logger.LogInformation("Order {OrderId} created", orderId);
Using Serilog directly:
Log.Information("Order {OrderId} created", orderId);
Foveus captures:
| Field | Value |
|---|
| Rendered message | Order ord_123 created |
| Message template | Order {OrderId} created |
| Structured property | OrderId = ord_123 |
| Category | Logger category or Serilog |
| Level | INFO |
Use structured properties for values you want to inspect or correlate later.
Exception logging
Serilog exceptions are captured with the log event.
Using ILogger<T>:
try
{
ProcessOrder(orderId);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to process order {OrderId}", orderId);
}
Using Serilog directly:
try
{
ProcessOrder(orderId);
}
catch (Exception ex)
{
Log.Error(ex, "Failed to process order {OrderId}", orderId);
}
Foveus captures:
- exception type
- exception message
- stack trace
- inner exception chain
- rendered message
- structured properties such as
OrderId
Correlation
Foveus.Serilog tries to attach logs to the same execution, trace, and span as the rest of the telemetry.
It gets correlation from:
- the current Foveus metrics/execution context
System.Diagnostics.Activity.Current
- generated fallback IDs
When used with Foveus.SDK, logs can appear connected to requests, executions, traces, and errors in Foveus.
Using Serilog with ASP.NET Core
A typical setup for an ASP.NET Core API:
using Foveus.Serilog;
using Serilog;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddFoveus(builder.Configuration);
builder.Host.UseSerilog((context, services, loggerConfiguration) =>
{
loggerConfiguration
.ReadFrom.Configuration(context.Configuration)
.ReadFrom.Services(services)
.WriteTo.Foveus(services);
});
var app = builder.Build();
app.UseFoveus();
app.MapPost("/orders", (
CreateOrderRequest request,
ILogger<Program> logger) =>
{
logger.LogInformation("Creating order for customer {CustomerId}", request.CustomerId);
return Results.Ok(new
{
orderId = "ord_123",
status = "created"
});
});
app.Run();
Using Serilog without HTTP middleware
You can use Foveus.Serilog in applications that do not receive HTTP requests.
For example:
- console apps
- background jobs
- workers
- queue consumers
In these cases, Foveus can still receive structured logs.
Full worker-service execution capture is not yet fully supported, but Serilog logs can still be sent when configured.
using Foveus.Serilog;
using Serilog;
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddFoveus(builder.Configuration);
builder.Services.AddSerilog((services, loggerConfiguration) =>
{
loggerConfiguration
.ReadFrom.Configuration(builder.Configuration)
.ReadFrom.Services(services)
.WriteTo.Foveus(services);
});
var app = builder.Build();
Log.Information("Worker started");
await app.RunAsync();
Worker service support is evolving. Foveus.Serilog can send structured logs from workers today, but full worker execution capture is not yet the same as HTTP execution capture.
For full worker setup details, see Worker services.
Redaction and safety
Log events can contain sensitive data.
Avoid logging secrets, credentials, tokens, card data, or highly sensitive personal data.
Use structured properties carefully.
// Good
_logger.LogInformation("Order {OrderId} created", orderId);
// Avoid
_logger.LogInformation("Payment card {CardNumber} used", cardNumber);
If sensitive values are logged, they may be sent to Foveus.
Use Serilog destructuring and filtering policies where appropriate, and keep Foveus redaction settings updated.
{
"Foveus": {
"ApiKey": "fov_test_...",
"RedactedFields": ["nationalId", "accountNumber"]
}
}
Troubleshooting
Logs are duplicated
If the same logs appear twice, both Foveus logging paths may be enabled:
- the Foveus SDK logger provider
- the Foveus Serilog sink
In Serilog-first apps, keep using ILogger<T>, but disable the Foveus SDK logger provider.
{
"Foveus": {
"ApiKey": "fov_test_...",
"EnableFoveusLoggerProvider": false
}
}
Then route logs to Foveus through:
.WriteTo.Foveus(services)
Sink throws a setup error
Make sure you call AddFoveus(...) before configuring the sink.
builder.Services.AddFoveus(builder.Configuration);
builder.Host.UseSerilog((context, services, loggerConfiguration) =>
{
loggerConfiguration.WriteTo.Foveus(services);
});
The sink needs Foveus services from dependency injection.
Check that:
app.UseFoveus() is registered for HTTP services
- logs are written during the request or operation
- Activity/correlation context is available
WriteTo.Foveus(services) uses the app service provider
- you are not building a separate service provider for Serilog
ILogger logs are not appearing
Check that your app is actually routing ILogger<T> logs through Serilog.
In ASP.NET Core, make sure Serilog is configured on the host:
builder.Host.UseSerilog((context, services, loggerConfiguration) =>
{
loggerConfiguration
.ReadFrom.Configuration(context.Configuration)
.ReadFrom.Services(services)
.WriteTo.Foveus(services);
});
Then this should still work:
_logger.LogInformation("Payment completed for {PaymentId}", paymentId);
Logs do not appear
Check that:
- both
Foveus.SDK and Foveus.Serilog are installed
AddFoveus(...) is called
WriteTo.Foveus(services) is configured
- the API key is valid
- the dashboard is showing the right mode
- the Serilog minimum level allows the event
- the service can reach Foveus
Too many logs are sent
Adjust Serilog minimum levels.
{
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"System": "Warning"
}
}
}
}
You can also use Serilog filters to exclude noisy categories.
What to do next