Skip to content

A Serilog enricher that adds call stack information to log events in an exception-like format. Displays call stacks as: Method:Line --> Method:Line --> Method:Line for intuitive debugging and tracing.

License

Notifications You must be signed in to change notification settings

hokagedami/serilog-stacktrace-enricher

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Serilog.Enrichers.CallStack

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

Features

  • Exception-like Format: Display call stack in familiar format: Method:Line --> Method:Line --> Method:Line
  • Single Property: Consolidates call stack into one CallStack property 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

Installation

dotnet add package Serilog.Enrichers.CallStack

Quick Start

Basic Usage

using 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"}

Exception-like Format (Default)

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();

Legacy Format

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();

Configuration Options

Call Stack Format

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

Include/Exclude Information

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 names

Property Names

Customize the property names used in log events:

var config = new CallStackEnricherConfiguration()
    .WithPropertyNames(
        methodName: "Method",
        typeName: "Class",
        fileName: "File",
        lineNumber: "Line",
        columnNumber: "Column",
        assemblyName: "Assembly");

Full vs. Short Names

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 parameters

Method Parameters

Include 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 Frames

Skip specific namespaces or types when walking the call stack:

var config = new CallStackEnricherConfiguration()
    .SkipNamespace("System")
    .SkipNamespace("Microsoft")
    .SkipNamespace("Serilog")
    .SkipType("MyApp.Infrastructure.LoggingWrapper");

Frame Offset

Choose which frame in the call stack to capture:

var config = new CallStackEnricherConfiguration()
    .WithFrameOffset(1); // Skip 1 frame up the call stack

Exception Handling

Configure 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}"));

Quick Start Examples

For most common scenarios, use the convenient preset methods:

Production Setup (Minimal Overhead)

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 exceptions

Development Setup (Full Details)

var 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 troubleshooting

Advanced Configuration Examples

Minimal Configuration

For 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 names

Development Configuration

For 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 debugging

Filtering Configuration

Skip 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);

Integration with Different Sinks

Console Output with Exception-like Format

var logger = new LoggerConfiguration()
    .Enrich.WithCallStack()
    .WriteTo.Console(outputTemplate: 
        "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} | Call Stack: {CallStack}{NewLine}{Exception}")
    .CreateLogger();

Console Output with Legacy Format

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();

JSON Output (for structured logging)

var logger = new LoggerConfiguration()
    .Enrich.WithCallStack()
    .WriteTo.File(new JsonFormatter(), "log.json")
    .CreateLogger();

Seq Integration

var logger = new LoggerConfiguration()
    .Enrich.WithCallStack()
    .WriteTo.Seq("http://localhost:5341")
    .CreateLogger();

Performance Considerations

Key Performance Factors

  • 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

Optimization Strategies

1. Limit Frame Depth

.Enrich.WithCallStack(config => config
    .WithCallStackFormat(maxFrames: 10)) // Reduce from default unlimited

2. 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
}

Debug Symbols

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>

Example Output

Exception-like Format (Default)

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"
}

Legacy Format

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"
}

Best Practices

  1. Use exception-like format for readability: The default format provides intuitive call stack traces
  2. Limit frame depth in production: Use maxFrames to control overhead (default: 5 frames)
  3. Skip framework namespaces: Focus on your application code by skipping system namespaces
  4. Consider performance impact: Call stack walking has overhead, tune maxFrames accordingly
  5. Enable exception suppression in production: Prevent logging failures from breaking your application
  6. Use structured logging sinks: JSON-based sinks work best with the call stack properties
  7. Choose appropriate format: Exception-like for debugging, legacy for detailed property access

Compatibility

  • .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

License

This project is licensed under the MIT License - see the LICENSE file for details.

Contributing

We welcome contributions to improve Serilog.Enrichers.CallStack! Here's how you can help:

How to Contribute

  1. Fork the repository and create your branch from main
  2. Make your changes with appropriate test coverage
  3. Ensure all tests pass and the code follows existing patterns
  4. Update documentation if you're changing functionality
  5. Submit a pull request with a clear description of changes

Development Guidelines

  • 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

Reporting Issues

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.)

Questions or Discussions

For questions or discussions about the enricher, please open a GitHub discussion or issue.

About

A Serilog enricher that adds call stack information to log events in an exception-like format. Displays call stacks as: Method:Line --> Method:Line --> Method:Line for intuitive debugging and tracing.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 8

Languages