MillerByte.Logging.Api

Attributes

Last updated: 1/28/2026

The [ApiLogging] attribute provides automatic request/response logging for controllers and actions with minimal configuration.

Basic Usage

Apply to a controller to log all actions:

using MillerByte.Logging.Api.Attributes;

[ApiController]
[Route("api/[controller]")]
[ApiLogging]  // Log all actions in this controller
public class UsersController : ControllerBase
{
    [HttpGet]
    public IActionResult GetUsers() => Ok(users);
    
    [HttpPost]
    public IActionResult CreateUser(CreateUserDto dto) => Ok(user);
}

Or apply to specific actions:

[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
    [HttpGet]
    public IActionResult GetOrders() => Ok(orders);  // Not logged
    
    [HttpPost]
    [ApiLogging]  // Only this action is logged
    public IActionResult CreateOrder(OrderDto dto) => Ok(order);
}

Properties

PropertyTypeDefaultDescription
LogRequestBodybooltrueWhether to capture the request body
LogResponseBodybooltrueWhether to capture the response body
CheckIdempotencyboolfalseCheck for duplicate Idempotency-Key headers
DatabaseNamestring?nullOptional database name override for multi-database routing (v1.2.0+)
CollectionNamestring?nullOptional collection name override for multi-database routing (v1.2.0+)

Configuring Body Logging

Disable body logging for sensitive endpoints:

[HttpPost("login")]
[ApiLogging(LogRequestBody = false)]  // Don't log passwords
public IActionResult Login(LoginDto dto) => Ok(token);

[HttpGet("profile")]
[ApiLogging(LogResponseBody = false)]  // Don't log sensitive profile data
public IActionResult GetProfile() => Ok(profile);

Idempotency Checking

Enable idempotency checking to prevent duplicate logs when clients retry requests with the same Idempotency-Key header:

[HttpPost]
[ApiLogging(CheckIdempotency = true)]
public IActionResult CreatePayment(PaymentDto dto)
{
    // Client sends: Idempotency-Key: abc123
    // If this key was already logged, the request proceeds
    // but is not logged again
    
    return Ok(payment);
}

Idempotency key requirements:

  • Maximum 256 characters
  • Alphanumeric characters, hyphens, and underscores only
  • Passed via Idempotency-Key header

What Gets Captured

The attribute automatically captures:

Request Information

  • HTTP method (GET, POST, etc.)
  • Route and path parameters
  • Query string parameters
  • Request body (if enabled and not too large)
  • Headers (sensitive headers filtered)
  • IP address and User-Agent
  • Authentication status and claims

Response Information

  • Status code
  • Response body (if enabled)
  • Duration (timing)
  • Exception details (if an error occurred)

Tracing Information

  • Trace ID and Span ID (OpenTelemetry)
  • Correlation ID
  • Parent Span ID

Sensitive Header Filtering

The following headers are automatically excluded from logs:

// These headers are never logged:
"Authorization"
"Cookie"
"X-API-Key"
"X-API-Secret"
"Proxy-Authorization"
"X-Auth-Token"
"Authentication"
"Set-Cookie"

Body Size Limits

Request and response bodies are subject to the MaxBodySizeBytes configuration option (default 1MB). Bodies larger than this limit are replaced with a placeholder:

// If body exceeds MaxBodySizeBytes:
// "[Body too large: 2097152 bytes]"

Multi-Database Routing (v1.2.0+)

Route specific endpoints to different databases or collections using theDatabaseName and CollectionName properties:

// Route critical operations to a separate audit database
[HttpPost("critical-operation")]
[ApiLogging(DatabaseName = "audit-logs", CollectionName = "critical-actions")]
public IActionResult CriticalOperation() => Ok();

// Route all actions in a controller to a tenant-specific database
[ApiController]
[Route("api/premium/[controller]")]
[ApiLogging(DatabaseName = "logs-tenant-premium")]
public class PremiumController : ControllerBase
{
    [HttpGet]
    public IActionResult GetPremiumData() => Ok();
}

// Override only the collection name
[HttpPost("payment")]
[ApiLogging(CollectionName = "payment-actions")]
public IActionResult ProcessPayment(PaymentDto dto) => Ok();

Multi-Database Use Cases

  • Multi-Tenancy: Separate databases per tenant for data isolation
  • Compliance: PCI/HIPAA data in separate, secured databases
  • Audit Logs: Critical operations logged to immutable audit database
  • Performance Tiers: Premium customers get dedicated logging infrastructure
  • Retention Policies: Different databases with different backup/retention schedules

The background service automatically routes actions to the correct database and collection. No additional configuration needed beyond specifying the attribute properties.

Adding Log Messages

Add custom log messages during request processing:

[HttpPost]
[ApiLogging]
public async Task<IActionResult> ProcessOrder(OrderDto order)
{
    var actionId = HttpContext.Items["ApiLoggingActionId"]?.ToString();
    
    // Add contextual log messages
    await _loggingService.AddLogMessageAsync(actionId, "Validating order");
    await _loggingService.AddLogMessageAsync(actionId, $"Processing {order.Items.Count} items");
    await _loggingService.AddLogMessageAsync(actionId, "Order completed successfully");
    
    return Ok(order);
}

Interaction with Global Options

Attribute properties work with global ApiLoggingOptions:

// Global configuration
builder.Services.AddApiLogging(options =>
{
    options.LogRequestBody = true;   // Global default
    options.LogResponseBody = true;  // Global default
    options.IncludeGetRequestLogs = false;  // Skip GET requests globally
});

// Per-action override
[HttpPost]
[ApiLogging(LogRequestBody = false)]  // Override for this action only
public IActionResult SecureAction() => Ok();

Skipping GET Requests

By default, GET requests are not logged (controlled by IncludeGetRequestLogs). The attribute will still fire but won't create a log entry:

// With options.IncludeGetRequestLogs = false (default):

[ApiLogging]
public class ProductsController : ControllerBase
{
    [HttpGet]
    public IActionResult GetProducts() => Ok(products);  // Not logged
    
    [HttpPost]
    public IActionResult CreateProduct(ProductDto dto) => Ok(product);  // Logged
}

Sampling

The attribute respects the SamplingRate configuration. If sampling is enabled, only a percentage of requests will be logged:

builder.Services.AddApiLogging(options =>
{
    options.SamplingRate = 0.1;      // Log 10% of requests
    options.AlwaysLogErrors = true;  // Always log errors regardless of sampling
});