Troubleshooting & FAQ
Common Issues
After upgrading, LoggingExtensions no longer compiles
Cause: In v10.1.1 LoggingExtensions was split into WebApplicationBuilderExtensions (AddLoggingWithConfiguration()) and LoggerEnrichmentConfigurationExtensions (WithRequestIdEnricher()), and ActivityExtensions was converted to a C# 14 extension member. All three classes remain in the RA.Utilities.Logging.Core.Extensions namespace.
Fix: Call sites need no changes — extension members resolve by receiver type, not by class name. Only code that references the class name directly (typeof(...), reflection, unit-test assertions) must be updated:
// ❌ v10.1.0 and earlier
typeof(LoggingExtensions)
// ✅ v10.1.1
typeof(WebApplicationBuilderExtensions) // AddLoggingWithConfiguration()
typeof(LoggerEnrichmentConfigurationExtensions) // WithRequestIdEnricher()
typeof(ActivityExtensions) // GetActivityId()
See the migration guide for the full before/after table.
XRequestId is no longer added to my log events
Cause: In v10.1.1 the enricher was consolidated. The request id from the x-request-id header is now folded into the single TraceId property, and no separate request-id property is emitted. Before that change the enricher added a request-id property (stored under the dashed name x-request-id, which output templates could not reference) alongside TraceId.
Fix: Query TraceId instead. The value is the same one the header carried:
{
"Timestamp": "2026-10-15T19:40:21.1330580Z",
"Level": "Information",
"TraceId": "abc-123",
"MessageTemplate": "Handling request"
}
| Version | Properties on the event |
|---|---|
| v10.1.0 and earlier | request-id property (stored as x-request-id) and TraceId |
| v10.1.1+ | TraceId only — the header value takes precedence over TraceIdentifier |
TraceId does not match the x-request-id header my client sent
Cause: The enricher uses the header only when it is present on the incoming request. When the header is missing, it falls back to HttpContext.TraceIdentifier, which is generated by the server — so TraceId differs from the value the caller generated.
Fix: Make sure the header actually reaches the application:
- Confirm the client or upstream proxy sends
x-request-id. - Check that no middleware, proxy, or gateway strips it before it reaches ASP.NET Core.
- If you need the value echoed back or auto-generated when missing, use
DefaultHeadersMiddlewarefromRA.Utilities.Api.
TraceId is null in background jobs and startup logs
Cause: This is by design. Outside an HTTP request there is no x-request-id header and no HttpContext.TraceIdentifier; when there is also no Activity.Current, the enricher adds TraceId with a null value. It never invents a random identifier — in v10.0.0 every non-request log event received a unique random GUID, which actively defeated log correlation.
Fix: If you need meaningful trace ids in background jobs, create an Activity in your job code:
using System.Diagnostics;
using var activity = new Activity("background-job").Start();
// All log events within this scope now carry a TraceId
Or use ActivitySource with OpenTelemetry for automatic trace-context propagation. If you would rather omit the property entirely than log it as null, register a custom enricher that skips it.
AddRaSerilog() does not exist — which method should I use?
Cause: Some older documentation and release notes referenced a method called AddRaSerilog. This name was never shipped — the actual method is AddLoggingWithConfiguration.
Fix: Use builder.AddLoggingWithConfiguration() in your Program.cs:
using RA.Utilities.Logging.Core.Extensions;
var builder = WebApplication.CreateBuilder(args);
builder.AddLoggingWithConfiguration();
My Console / File sinks aren't writing any logs
Cause: AddLoggingWithConfiguration() does not configure sinks in code — it reads configuration from appsettings.json through ReadFrom.Configuration. If your appsettings.json has no Serilog section with sinks, logs have nowhere to go.
Fix: Add a Serilog section to your appsettings.json that configures at least one sink. The package includes Serilog.Sinks.Console, Serilog.Sinks.File, and Serilog.Sinks.Async as dependencies, so no additional NuGet installs are needed:
{
"Serilog": {
"MinimumLevel": {
"Default": "Information"
},
"WriteTo:Async": {
"Name": "Async",
"Args": {
"configure": [
{
"Name": "Console",
"Args": {
"outputTemplate": "{Timestamp:G} [{Level}] ({TraceId}) {Message:lj}{NewLine}{Exception}"
}
}
]
}
}
}
}
GetActivityId() returns null after upgrading to v10.0.1
Cause: The return type changed from string to string? in v10.0.1. When no Activity is present or the format is Unknown, the method returns null instead of a random Guid.
Fix: Update any code that assigns the result to a non-nullable string:
// ❌ v10.0.0
string traceId = Activity.Current?.GetActivityId(); // may be a random GUID
// ✅ v10.0.1+
string? traceId = Activity.Current?.GetActivityId(); // null when no trace context
RequestIdEnricher does not see an HTTP context
Cause: AddLoggingWithConfiguration() no longer registers IHttpContextAccessor automatically as of v10.1.0. The enricher is null-safe, so it does not throw; without an HTTP context, TraceId is resolved from Activity.Current instead — or added as null when there is no activity either.
Fix: Register the accessor yourself whenever you activate the enricher, whether through configuration or the fluent extension:
builder.Services.AddHttpContextAccessor();
builder.AddLoggingWithConfiguration();
For manual Serilog configuration, keep the same service registration before building the application:
builder.Services.AddHttpContextAccessor();
Log.Logger = new LoggerConfiguration()
.Enrich.WithRequestIdEnricher()
.WriteTo.Console()
.CreateLogger();
Sensitive data masking isn't working
Cause: Serilog.Enrichers.Sensitive is included as a package dependency, but it must be configured in your appsettings.json — it is not enabled automatically by AddLoggingWithConfiguration().
Fix: Add the WithSensitiveDataMasking enricher to your Serilog → Enrich section:
{
"Serilog": {
"Enrich": [
{
"Name": "WithSensitiveDataMasking",
"Args": {
"options": {
"MaskValue": "***SECRET***",
"Mode": "Globally"
}
}
}
]
}
}
The package ships a ready-to-copy sample at Logging/RA.Utilities.Logging.Core/appsettings.serilog.json that combines the Async, File, Console, exception, sensitive-data, and request-id settings.
Known Limitations
RequestIdEnricherneedsIHttpContextAccessorfor the request id: Outside an HTTP request the enricher cannot readx-request-idorTraceIdentifier. It then resolvesTraceIdfromActivity.Currentwhen one exists, and otherwise adds the property with anullvalue.TraceIdis always present (v10.1.1+): The property is added unconditionally, so non-request events carryTraceId: null. Dashboards and alerts that group byTraceIdshould exclude null values.- No automatic request-id generation: The enricher reads the
x-request-idheader; it never creates one. UseDefaultHeadersMiddlewarefromRA.Utilities.Apiif you need the header generated when it is missing. - No built-in log sampling or filtering: The package does not provide log sampling, filtering, or level-override-per-endpoint. Configure these through Serilog's
Filterconfiguration or a custom enricher. appsettings.serilog.jsonis a sample, not a default: The fileappsettings.serilog.jsonin the package repository is a usage example. It is not automatically loaded — merge its contents into your ownappsettings.json, or load it explicitly withbuilder.Configuration.AddJsonFile("appsettings.serilog.json").- Package dependency versions are centralised: Sink and enricher versions are managed by the package; consumers cannot upgrade individual Serilog packages independently without potential compatibility issues.
Performance Tips
- Call
AddLoggingWithConfiguration()early inProgram.cs: Place it as one of the first calls so startup logs (including DI registration failures) are captured by Serilog rather than the default .NET logger. - Use
Serilog.Sinks.Asyncfor production: The sample config inappsettings.serilog.jsonwraps all sinks in the Async sink. This moves I/O off the request thread — strongly recommended for any non-trivial workload. - Keep
outputTemplatesimple: Every template token is resolved once per event. Adding many enrichers with complex logic can affect throughput in high-volume scenarios — enrich only with what you need. - Use configuration-driven log levels per namespace: Override
Microsoft.AspNetCoretoWarningin production to suppress noisy framework logs without losing your application'sInformation-level messages. - Forward
x-request-idfrom the edge: Because the enricher prefers the request header, propagating that one header end-to-end makes every service log the sameTraceId— correlation queries stay cheap and stable across the whole call chain.