/

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

Previous
Global Properties