https://github.com/hokagedami/serilog-stacktrace-enricher
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.
https://github.com/hokagedami/serilog-stacktrace-enricher
callstack csharp debugging diagnostics dotnet enricher exception-format logging serilog stack-trace
Last synced: 26 days ago
JSON representation
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.
- Host: GitHub
- URL: https://github.com/hokagedami/serilog-stacktrace-enricher
- Owner: hokagedami
- License: mit
- Created: 2025-07-28T22:14:36.000Z (7 months ago)
- Default Branch: main
- Last Pushed: 2025-08-27T12:58:28.000Z (6 months ago)
- Last Synced: 2025-12-05T05:14:59.726Z (2 months ago)
- Topics: callstack, csharp, debugging, diagnostics, dotnet, enricher, exception-format, logging, serilog, stack-trace
- Language: C#
- Size: 726 KB
- Stars: 275
- Watchers: 0
- Forks: 0
- Open Issues: 13
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# 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
```bash
dotnet add package Serilog.Enrichers.CallStack
```
## Quick Start
### Basic Usage
```csharp
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.$:8"}
```
### Exception-like Format (Default)
```csharp
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
```csharp
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:
```csharp
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
```csharp
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:
```csharp
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:
```csharp
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:
```csharp
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:
```csharp
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:
```csharp
var config = new CallStackEnricherConfiguration()
.WithFrameOffset(1); // Skip 1 frame up the call stack
```
### Exception Handling
Configure how exceptions during enrichment are handled:
```csharp
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)
```csharp
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)
```csharp
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:
```csharp
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:
```csharp
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:
```csharp
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
```csharp
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
```csharp
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)
```csharp
var logger = new LoggerConfiguration()
.Enrich.WithCallStack()
.WriteTo.File(new JsonFormatter(), "log.json")
.CreateLogger();
```
### Seq Integration
```csharp
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**
```csharp
.Enrich.WithCallStack(config => config
.WithCallStackFormat(maxFrames: 10)) // Reduce from default unlimited
```
**2. Skip Framework Namespaces**
```csharp
.Enrich.WithCallStack(config => config
.SkipNamespace("System")
.SkipNamespace("Microsoft.AspNetCore")
.SkipNamespace("Microsoft.Extensions"))
```
**3. Selective Property Inclusion**
```csharp
// 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**
```csharp
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:
```xml
portable
true
```
## Example Output
### Exception-like Format (Default)
With the new exception-like format, log events include a single CallStack property:
```json
{
"@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:
```json
{
"@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](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.