Skip to main content
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:
app.UseFoveus();
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.
FieldDescription
ServiceNameFrom Foveus configuration. Defaults to the project assembly name if not set.
TimestampThe log event timestamp.
LevelThe mapped log level.
MessageThe rendered log message.
TraceIdCurrent trace ID, current activity trace ID, or generated fallback.
SpanIdCurrent span ID, current activity span ID, or generated fallback.
ExecutionIdCurrent execution ID or generated fallback.
ParentExecutionIdCurrent parent execution ID if available.
ExceptionException type, message, stack trace, and inner exception details.
PropertiesSerilog structured properties.
SourceFrom Foveus configuration, or unknown.

Log level mapping

Foveus maps Serilog levels to Foveus log levels.
Serilog levelFoveus level
VerboseTRACE
DebugDEBUG
InformationINFO
WarningWARN
ErrorERROR
FatalFATAL

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:
FieldValue
Rendered messageOrder ord_123 created
Message templateOrder {OrderId} created
Structured propertyOrderId = ord_123
CategoryLogger category or Serilog
LevelINFO
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:
  1. the current Foveus metrics/execution context
  2. System.Diagnostics.Activity.Current
  3. 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.

Logs are not correlated to executions

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