MillerByte.Logging.Api

Service API

Last updated: 1/28/2026

The IApiLoggingService interface provides methods for manual logging, session management, and GDPR compliance operations.

CQRS Pattern (v1.2.0+): This service handles write operations(logging, session management, data deletion). For read operations (querying logged data), use IApiLoggingQueryService which provides advanced filtering, pagination, and multi-database support.

Injecting the Service

public class MyController : ControllerBase
{
    private readonly IApiLoggingService _loggingService;
    
    public MyController(IApiLoggingService loggingService)
    {
        _loggingService = loggingService;
    }
}

Methods

StartSessionAsync

Manually start a new user session. Returns the session ID.

Task<string> StartSessionAsync(
    string userId, 
    SessionMetadata metadata, 
    CancellationToken ct = default);

// Usage
var sessionId = await _loggingService.StartSessionAsync(
    userId: "user-123",
    metadata: new SessionMetadata
    {
        TenantId = "tenant-abc",
        IpAddress = HttpContext.Connection.RemoteIpAddress?.ToString(),
        UserAgent = Request.Headers["User-Agent"].ToString()
    });

EndSessionAsync

End an active session with a reason code.

Task EndSessionAsync(
    string sessionId, 
    SessionEndReason reason, 
    CancellationToken ct = default);

// Usage
await _loggingService.EndSessionAsync(
    sessionId: "session-id",
    reason: SessionEndReason.Logout);

// Available reasons:
// - SessionEndReason.Logout
// - SessionEndReason.Timeout
// - SessionEndReason.TokenExpired
// - SessionEndReason.ManualClose
// - SessionEndReason.SystemShutdown

GetOrCreateSessionAsync

Get the current session ID or create a new session based on the HTTP context. Used internally by the [ApiLogging] attribute.

Task<string?> GetOrCreateSessionAsync(
    HttpContext context, 
    CancellationToken ct = default);

// Usage
var sessionId = await _loggingService.GetOrCreateSessionAsync(HttpContext);

if (sessionId == null)
{
    // No user identity found and anonymous sessions disabled
    return Unauthorized();
}

LogActionAsync

Manually log an API action. The action is enqueued to a background channel for batch processing.

Task LogActionAsync(
    ApiAction actionLog, 
    CancellationToken ct = default);

// Usage
var action = new ApiAction
{
    Id = ObjectId.GenerateNewId().ToString(),
    SessionId = sessionId,
    TimeStamp = DateTime.UtcNow,
    EndpointInfo = new EndpointInfo
    {
        Controller = "OrdersController",
        Action = "CreateOrder",
        Route = "/api/orders"
    },
    PayloadInfo = new PayloadInfo
    {
        Method = "POST",
        Body = orderData
    },
    ResponseInfo = new ResponseInfo
    {
        StatusCode = 201,
        Duration = TimeSpan.FromMilliseconds(45)
    }
};

await _loggingService.LogActionAsync(action);

AddLogMessageAsync

Add a log message to an in-flight action (before the request completes). The action ID is stored in HttpContext.Items["ApiLoggingActionId"].

Task AddLogMessageAsync(
    string actionId, 
    string message, 
    CancellationToken ct = default);

// Usage in a controller action
[HttpPost]
[ApiLogging]
public async Task<IActionResult> ProcessOrder(OrderDto order)
{
    var actionId = HttpContext.Items["ApiLoggingActionId"]?.ToString();
    
    await _loggingService.AddLogMessageAsync(actionId, "Starting order validation");
    
    var validationResult = await ValidateOrder(order);
    await _loggingService.AddLogMessageAsync(actionId, $"Validation complete: {validationResult.Status}");
    
    await _loggingService.AddLogMessageAsync(actionId, "Processing payment");
    var paymentResult = await ProcessPayment(order);
    
    await _loggingService.AddLogMessageAsync(actionId, $"Payment processed: {paymentResult.TransactionId}");
    
    return Ok(new { OrderId = order.Id, TransactionId = paymentResult.TransactionId });
}

DeleteUserDataAsync

Delete all logging data for a user (GDPR compliance).

Task DeleteUserDataAsync(
    string userId, 
    string tenantId, 
    CancellationToken ct = default);

// Usage
[HttpDelete("users/{userId}/data")]
public async Task<IActionResult> DeleteUserData(string userId)
{
    var tenantId = User.FindFirst("tenantId")?.Value ?? "default";
    
    await _loggingService.DeleteUserDataAsync(userId, tenantId);
    
    return NoContent();
}

ExportUserDataStreamAsync

Export all user data in a streaming fashion for GDPR data portability requests. Returns an IAsyncEnumerable of pages.

IAsyncEnumerable<UserDataExportPage> ExportUserDataStreamAsync(
    string userId,
    int pageSize = 1000,
    CancellationToken ct = default);

// Usage
[HttpGet("users/{userId}/export")]
public async IAsyncEnumerable<UserDataExportPage> ExportUserData(
    string userId,
    [EnumeratorCancellation] CancellationToken ct)
{
    await foreach (var page in _loggingService.ExportUserDataStreamAsync(userId, pageSize: 500, ct))
    {
        if (!page.Success)
        {
            _logger.LogError("Export error: {Error}", page.ErrorMessage);
            yield break;
        }
        
        yield return page;
    }
}

Data Models

ApiAction

public class ApiAction
{
    public string Id { get; set; }
    public string SessionId { get; set; }
    public string? UserId { get; set; }
    public string? TenantId { get; set; }
    public DateTime TimeStamp { get; set; }
    
    // Tracing
    public string? CorrelationId { get; set; }
    public string? IdempotencyKey { get; set; }
    public string? TraceId { get; set; }
    public string? SpanId { get; set; }
    public string? ParentSpanId { get; set; }
    
    // Request info
    public RequesterInfo? RequesterInfo { get; set; }
    public PayloadInfo? PayloadInfo { get; set; }
    public EndpointInfo? EndpointInfo { get; set; }
    
    // Response info
    public ResponseInfo? ResponseInfo { get; set; }
    public List<string> LogMessages { get; set; }
    
    // Metadata
    public string? MachineName { get; set; }
    public string? EnvironmentName { get; set; }
    public string? ApiVersion { get; set; }
    public Dictionary<string, object>? CustomData { get; set; }
}

SessionMetadata

public class SessionMetadata
{
    public string? TenantId { get; set; }
    public string? IpAddress { get; set; }
    public string? UserAgent { get; set; }
    public string? DeviceId { get; set; }
    public Dictionary<string, object>? CustomData { get; set; }
}

UserDataExportPage

public class UserDataExportPage
{
    public bool Success { get; set; }
    public string? ErrorMessage { get; set; }
    public int PageNumber { get; set; }
    public bool HasMore { get; set; }
    
    // Only populated on first page
    public List<LoginSession>? Sessions { get; set; }
    
    // Populated on every page
    public List<ApiAction> Actions { get; set; }
}

Best Practices

  • Use AddLogMessageAsync to add context to long-running operations
  • Store the action ID from HttpContext.Items["ApiLoggingActionId"]
  • For GDPR exports, use streaming to handle large datasets
  • Always specify the tenant ID for multi-tenant applications