Metadata & Context
Metadata and context allow you to add structured information to individual logs, making them more informative and useful for debugging and analysis.
Adding Metadata and Context
Use .WithMetadata()
and .WithContext()
to add key-value information to logs:
// Database query context
Log.WithContext("QueryType", "SELECT")
.WithContext("TableName", "Orders")
.WithMetadata("ExecutionTimeMs", 1250)
.Warn("Slow database query detected");
// Transaction context with complex object
var transactionData = new
{
TransactionId = "txn_abc123",
Amount = 99.99m,
Currency = "USD",
PaymentMethod = "CreditCard"
};
Log.WithContext("Transaction", transactionData)
.WithMetadata("ProcessingAttempt", 3)
.Error("Payment processing failed");
// API request context
Log.WithContext("RequestId", Guid.NewGuid())
.WithContext("Endpoint", "/api/orders")
.WithMetadata("ResponseTimeMs", 2500)
.Warn("API response time exceeded threshold");
Common Patterns
Database Operations
// SQL query logging
var stopwatch = Stopwatch.StartNew();
var orders = await _database.QueryAsync("SELECT * FROM Orders WHERE Status = @status", new { status = "Pending" });
stopwatch.Stop();
Log.WithContext("SqlQuery", "SELECT * FROM Orders WHERE Status = @status")
.WithContext("TableName", "Orders")
.WithMetadata("RowCount", orders.Count())
.WithMetadata("ExecutionTimeMs", stopwatch.ElapsedMilliseconds)
.Info("Database query executed");
Error Context
// Error information with context
try
{
await ProcessOrderAsync(order);
}
catch (Exception ex)
{
Log.WithContext("OrderId", order.Id)
.WithContext("OrderStatus", order.Status)
.WithMetadata("ErrorType", ex.GetType().Name)
.WithMetadata("RetryAttempt", retryCount)
.Error("Order processing failed");
throw;
}
Performance Tracking
// API call performance
var stopwatch = Stopwatch.StartNew();
var response = await _httpClient.GetAsync("/api/external-service");
stopwatch.Stop();
if (stopwatch.ElapsedMilliseconds > 5000)
{
Log.WithContext("ExternalAPI", "/api/external-service")
.WithContext("HttpMethod", "GET")
.WithMetadata("ResponseTimeMs", stopwatch.ElapsedMilliseconds)
.WithMetadata("StatusCode", (int)response.StatusCode)
.Warn("Slow external API response");
}
Complex Values
Objects and Arrays
// Complex objects as values
var queryStats = new
{
QueryType = "SELECT",
AffectedRows = 150,
IndexesUsed = new[] { "idx_order_status", "idx_customer_id" },
ExecutionPlan = "Index Scan"
};
Log.WithContext("SqlQuery", "SELECT * FROM Orders o JOIN Customers c ON o.CustomerId = c.Id")
.WithContext("QueryStatistics", queryStats)
.WithMetadata("ExecutionTimeMs", 850)
.Info("Complex query executed");
// Configuration and settings
var cacheConfig = new
{
Provider = "Redis",
TTLSeconds = 3600,
Partitions = 8
};
Log.WithContext("CacheOperation", "SET")
.WithContext("CacheKey", "user_preferences_12345")
.WithContext("Configuration", cacheConfig)
.WithMetadata("CacheHit", false)
.Info("Cache entry created");
ASP.NET Core Integration
Controller Context
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
[HttpPost]
public async Task<IActionResult> CreateOrder(CreateOrderRequest request)
{
var stopwatch = Stopwatch.StartNew();
Log.WithContext("RequestId", HttpContext.TraceIdentifier)
.WithContext("Endpoint", "/api/orders")
.WithContext("HttpMethod", "POST")
.WithMetadata("RequestSize", Request.ContentLength ?? 0)
.Info("Order creation started");
try
{
var order = await _orderService.CreateOrderAsync(request);
stopwatch.Stop();
Log.WithContext("RequestId", HttpContext.TraceIdentifier)
.WithContext("OrderId", order.Id)
.WithMetadata("ProcessingTimeMs", stopwatch.ElapsedMilliseconds)
.WithMetadata("ItemCount", request.Items?.Count ?? 0)
.Info("Order created successfully");
return Ok(order);
}
catch (Exception ex)
{
stopwatch.Stop();
Log.WithContext("RequestId", HttpContext.TraceIdentifier)
.WithContext("ErrorType", ex.GetType().Name)
.WithMetadata("ProcessingTimeMs", stopwatch.ElapsedMilliseconds)
.Error("Order creation failed");
return StatusCode(500);
}
}
}
Middleware Context
public class RequestContextMiddleware
{
private readonly RequestDelegate _next;
public RequestContextMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
Log.WithContext("RequestId", context.TraceIdentifier)
.WithContext("HttpMethod", context.Request.Method)
.WithContext("RequestPath", context.Request.Path.ToString())
.WithMetadata("ContentLength", context.Request.ContentLength ?? 0)
.WithMetadata("HasQueryString", context.Request.QueryString.HasValue)
.Info("HTTP request started");
var stopwatch = Stopwatch.StartNew();
try
{
await _next(context);
stopwatch.Stop();
Log.WithContext("RequestId", context.TraceIdentifier)
.WithContext("ResponseContentType", context.Response.ContentType ?? "unknown")
.WithMetadata("StatusCode", context.Response.StatusCode)
.WithMetadata("ResponseTimeMs", stopwatch.ElapsedMilliseconds)
.Info("HTTP request completed");
}
catch (Exception ex)
{
stopwatch.Stop();
Log.WithContext("RequestId", context.TraceIdentifier)
.WithContext("ExceptionType", ex.GetType().Name)
.WithMetadata("ResponseTimeMs", stopwatch.ElapsedMilliseconds)
.WithMetadata("StatusCode", context.Response.StatusCode)
.Error("HTTP request failed");
throw;
}
}
}
Combining with Tags
You can combine metadata, context and tags for better organization:
// Database operation with tags
Log.WithContext("SqlQuery", "UPDATE Products SET Stock = @stock WHERE Id = @id")
.WithMetadata("AffectedRows", 1)
.WithMetadata("ExecutionTimeMs", 45)
.WithTags("Database", "Products")
.Info("Product stock updated");
// Including sensitive data example
Log.WithMetadata("password", "123456")
.WithContext("operation", "user_login")
.WithTags("Security", "Authentication")
.Warn("This log contains sensitive data");
// Example from the user
Log.WithContext("lorena", null)
.WithTags("seco", "mohado")
.Warn("Lorena te va a secar !!!");
Best Practices
Best Practices
- Use descriptive keys for context values
- Keep context relevant and focused for debugging
- Avoid logging sensitive data like passwords or tokens
- Combine with tags for better log organization
- Use fluent syntax for multiple context values
Next Steps
- Tags - Organize and categorize logs
- Global Properties - Add consistent context to all logs
- User Identification - Associate logs with users