Skip to main content

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"
}
VersionProperties on the event
v10.1.0 and earlierrequest-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:

  1. Confirm the client or upstream proxy sends x-request-id.
  2. Check that no middleware, proxy, or gateway strips it before it reaches ASP.NET Core.
  3. If you need the value echoed back or auto-generated when missing, use DefaultHeadersMiddleware from RA.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"
}
}
}
]
}
}
tip

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​

  • RequestIdEnricher needs IHttpContextAccessor for the request id: Outside an HTTP request the enricher cannot read x-request-id or TraceIdentifier. It then resolves TraceId from Activity.Current when one exists, and otherwise adds the property with a null value.
  • TraceId is always present (v10.1.1+): The property is added unconditionally, so non-request events carry TraceId: null. Dashboards and alerts that group by TraceId should exclude null values.
  • No automatic request-id generation: The enricher reads the x-request-id header; it never creates one. Use DefaultHeadersMiddleware from RA.Utilities.Api if 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 Filter configuration or a custom enricher.
  • appsettings.serilog.json is a sample, not a default: The file appsettings.serilog.json in the package repository is a usage example. It is not automatically loaded — merge its contents into your own appsettings.json, or load it explicitly with builder.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 in Program.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.Async for production: The sample config in appsettings.serilog.json wraps all sinks in the Async sink. This moves I/O off the request thread — strongly recommended for any non-trivial workload.
  • Keep outputTemplate simple: 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.AspNetCore to Warning in production to suppress noisy framework logs without losing your application's Information-level messages.
  • Forward x-request-id from the edge: Because the enricher prefers the request header, propagating that one header end-to-end makes every service log the same TraceId — correlation queries stay cheap and stable across the whole call chain.