A Serilog enricher that adds call stack information to log events in an exception-like format. This enricher helps with debugging and tracing by providing detailed context about where log events originated, displaying the call stack in an intuitive format similar to exception stack traces.
Last updated: August 2025
- Exception-like Format: Display call stack in familiar format:
Method:Line --> Method:Line --> Method:Line - Single Property: Consolidates call stack into one
CallStackproperty for cleaner logs - Configurable Depth: Control the number of frames to include (default: 5)
- Method Parameters: Optional parameter information in method names
- Type Information: Include declaring type names with optional namespace
- Line Numbers: Precise source code line numbers for exact location tracking
- Backward Compatibility: Legacy format with individual properties still available
- Frame Filtering: Skip specific namespaces or types when walking the call stack
- Exception Handling: Configurable exception handling to prevent logging failures
- Flexible Configuration: Extensive configuration options for customization
dotnet add package Serilog.Enrichers.CallStackusing Serilog;
using Serilog.Enrichers.CallStack;
var logger = new LoggerConfiguration()
.Enrich.WithCallStack()
.WriteTo.Console()
.CreateLogger();
logger.Information("Hello, world!");This will produce log output similar to:
[15:30:45 INF] Hello, world! {CallStack="Program.Main:12 --> Program.<Main>$:8"}
var logger = new LoggerConfiguration()
.Enrich.WithCallStack(config => config
.WithCallStackFormat(useExceptionLikeFormat: true, maxFrames: 3)
.WithMethodParameters(includeParameters: true)
.WithFullNames(fullTypeName: false)
.SkipNamespace("System")
.SkipNamespace("Microsoft"))
.WriteTo.Console(outputTemplate:
"[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} | {CallStack}{NewLine}{Exception}")
.CreateLogger();var logger = new LoggerConfiguration()
.Enrich.WithCallStack(config => config
.WithCallStackFormat(useExceptionLikeFormat: false) // Use individual properties
.WithIncludes(methodName: true, typeName: true, fileName: true, lineNumber: true)
.WithFullNames(fullTypeName: true)
.WithMethodParameters(includeParameters: true))
.WriteTo.Console()
.CreateLogger();Choose between the new exception-like format or legacy individual properties:
var config = new CallStackEnricherConfiguration()
.WithCallStackFormat(
useExceptionLikeFormat: true, // Default: true
maxFrames: 5, // Default: 5, -1 for unlimited
callStackPropertyName: "CallStack"); // Default: "CallStack"Exception-like Format Output:
CallStack: "UserService.ProcessUser:45 --> UserController.CreateUser:23 --> Program.Main:12"
Legacy Format Output:
MethodName: "ProcessUser", TypeName: "UserService", FileName: "UserService.cs", LineNumber: 45
var config = new CallStackEnricherConfiguration()
.WithIncludes(
methodName: true, // Include method names
typeName: true, // Include type names
fileName: true, // Include file names
lineNumber: true, // Include line numbers
columnNumber: false, // Include column numbers
assemblyName: false); // Include assembly namesCustomize the property names used in log events:
var config = new CallStackEnricherConfiguration()
.WithPropertyNames(
methodName: "Method",
typeName: "Class",
fileName: "File",
lineNumber: "Line",
columnNumber: "Column",
assemblyName: "Assembly");Control whether to use full names (with namespaces/paths) or short names:
var config = new CallStackEnricherConfiguration()
.WithFullNames(
fullTypeName: true, // Use "MyApp.Services.UserService" vs "UserService"
fullFileName: true, // Use full path vs just filename
fullParameterTypes: true); // Use full type names in parametersInclude method parameter information in the method name:
var config = new CallStackEnricherConfiguration()
.WithMethodParameters(
includeParameters: true,
useFullParameterTypes: false);
// Results in: "ProcessUser(String name, Int32 id)" instead of just "ProcessUser"Skip specific namespaces or types when walking the call stack:
var config = new CallStackEnricherConfiguration()
.SkipNamespace("System")
.SkipNamespace("Microsoft")
.SkipNamespace("Serilog")
.SkipType("MyApp.Infrastructure.LoggingWrapper");Choose which frame in the call stack to capture:
var config = new CallStackEnricherConfiguration()
.WithFrameOffset(1); // Skip 1 frame up the call stackConfigure how exceptions during enrichment are handled:
var config = new CallStackEnricherConfiguration()
.WithExceptionHandling(
suppress: true, // Don't throw exceptions
onException: ex => Console.WriteLine($"Enricher error: {ex.Message}"));For most common scenarios, use the convenient preset methods:
var logger = new LoggerConfiguration()
.Enrich.WithCallStackForProduction(maxFrames: 5)
.WriteTo.Console()
.CreateLogger();
// Optimized for performance:
// - Exception-like format with 5 frames max
// - Method and type names only (no file/line info)
// - Skips System/Microsoft namespaces
// - Suppresses enricher exceptionsvar logger = new LoggerConfiguration()
.Enrich.WithCallStackForDevelopment(maxFrames: 15)
.WriteTo.Console()
.CreateLogger();
// Comprehensive debugging info:
// - Exception-like format with 15 frames max
// - Includes file names and line numbers
// - Shows method parameters
// - Throws exceptions for troubleshootingFor production environments where you want minimal overhead:
var config = new CallStackEnricherConfiguration()
.WithIncludes(
methodName: true,
typeName: true,
fileName: false, // Skip file names to reduce overhead
lineNumber: false, // Skip line numbers
columnNumber: false,
assemblyName: false)
.WithFullNames(fullTypeName: false); // Use short type namesFor development environments where you want maximum detail:
var config = new CallStackEnricherConfiguration()
.WithIncludes(
methodName: true,
typeName: true,
fileName: true,
lineNumber: true,
columnNumber: true,
assemblyName: true)
.WithFullNames(
fullTypeName: true,
fullFileName: true,
fullParameterTypes: true)
.WithMethodParameters(includeParameters: true)
.WithExceptionHandling(suppress: false); // Throw exceptions for debuggingSkip common framework types and focus on application code:
var config = new CallStackEnricherConfiguration()
.SkipNamespace("System")
.SkipNamespace("Microsoft")
.SkipNamespace("Serilog")
.SkipNamespace("Newtonsoft")
.SkipType("MyApp.Infrastructure.LoggingService")
.WithFrameOffset(0);var logger = new LoggerConfiguration()
.Enrich.WithCallStack()
.WriteTo.Console(outputTemplate:
"[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} | Call Stack: {CallStack}{NewLine}{Exception}")
.CreateLogger();var logger = new LoggerConfiguration()
.Enrich.WithCallStack(config => config.WithCallStackFormat(useExceptionLikeFormat: false))
.WriteTo.Console(outputTemplate:
"[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} " +
"({TypeName}.{MethodName} in {FileName}:{LineNumber}){NewLine}{Exception}")
.CreateLogger();var logger = new LoggerConfiguration()
.Enrich.WithCallStack()
.WriteTo.File(new JsonFormatter(), "log.json")
.CreateLogger();var logger = new LoggerConfiguration()
.Enrich.WithCallStack()
.WriteTo.Seq("http://localhost:5341")
.CreateLogger();- Debug vs Release: Call stack information is more accurate in Debug builds
- File/Line Info: Including file names and line numbers requires debug symbols
- Method Parameters: Including parameter information adds overhead
- Frame Skipping: Use skip configurations to avoid walking unnecessary frames
- Exception Handling: Enable exception suppression in production
1. Limit Frame Depth
.Enrich.WithCallStack(config => config
.WithCallStackFormat(maxFrames: 10)) // Reduce from default unlimited2. Skip Framework Namespaces
.Enrich.WithCallStack(config => config
.SkipNamespace("System")
.SkipNamespace("Microsoft.AspNetCore")
.SkipNamespace("Microsoft.Extensions"))3. Selective Property Inclusion
// For production - minimal overhead
.Enrich.WithCallStack(config => config
.WithIncludes(methodName: true, typeName: true, fileName: false, lineNumber: false))
// For development - full details
.Enrich.WithCallStack(config => config
.WithIncludes(methodName: true, typeName: true, fileName: true, lineNumber: true))4. Configuration Validation
var config = new CallStackEnricherConfiguration()
.WithCallStackFormat(maxFrames: 150);
// Check for performance warnings
if (!config.Validate(warning => Console.WriteLine($"Warning: {warning}")))
{
// Handle validation errors
}For file names and line numbers to work properly, ensure your application is built with debug symbols:
<PropertyGroup>
<DebugType>portable</DebugType>
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>With the new exception-like format, log events include a single CallStack property:
{
"@t": "2025-07-29T00:30:45.123Z",
"@l": "Information",
"@m": "Processing user request",
"CallStack": "UserService.ProcessRequest(String userId, UserRequest request):45 --> UserController.CreateUser:23 --> Program.Main:12"
}When using legacy format (useExceptionLikeFormat: false), individual properties are included:
{
"@t": "2025-07-29T00:30:45.123Z",
"@l": "Information",
"@m": "Processing user request",
"MethodName": "ProcessRequest(String userId, UserRequest request)",
"TypeName": "MyApp.Services.UserService",
"FileName": "UserService.cs",
"LineNumber": 45,
"ColumnNumber": 12,
"AssemblyName": "MyApp.Services"
}- Use exception-like format for readability: The default format provides intuitive call stack traces
- Limit frame depth in production: Use
maxFramesto control overhead (default: 5 frames) - Skip framework namespaces: Focus on your application code by skipping system namespaces
- Consider performance impact: Call stack walking has overhead, tune
maxFramesaccordingly - Enable exception suppression in production: Prevent logging failures from breaking your application
- Use structured logging sinks: JSON-based sinks work best with the call stack properties
- Choose appropriate format: Exception-like for debugging, legacy for detailed property access
- .NET Standard 2.0+: Compatible with .NET Framework 4.6.1+, .NET Core 2.0+, .NET 5+
- Serilog 3.0+: Requires Serilog version 3.0 or higher
This project is licensed under the MIT License - see the LICENSE file for details.
We welcome contributions to improve Serilog.Enrichers.CallStack! Here's how you can help:
- Fork the repository and create your branch from
main - Make your changes with appropriate test coverage
- Ensure all tests pass and the code follows existing patterns
- Update documentation if you're changing functionality
- Submit a pull request with a clear description of changes
- Follow existing code style and conventions
- Add unit tests for new functionality
- Update the README for user-facing changes
- Keep commits focused and atomic
- Write clear, descriptive commit messages
If you find a bug or have a feature request, please open an issue with:
- A clear title and description
- Steps to reproduce (for bugs)
- Expected vs actual behavior
- Your environment details (OS, .NET version, etc.)
For questions or discussions about the enricher, please open a GitHub discussion or issue.