TurboMediator
Persistence

Audit

Automatic audit logging for mediator operations

The audit behavior automatically records detailed audit entries for operations, including who did what, when, and what data was involved.

Using the Attribute

[Auditable]
public record CreateUserCommand(string Name, string Email) : ICommand<User>;

[Auditable(
    IncludeRequest = true,
    IncludeResponse = false,
    ActionName = "DeleteUser",
    ExcludeProperties = new[] { "Password", "Token" }
)]
public record DeleteUserCommand(Guid UserId) : ICommand;

Attribute Properties

PropertyDefaultDescription
IncludeRequesttrueStore request payload in audit
IncludeResponsefalseStore response payload in audit
ActionNameMessage type nameCustom action name
ExcludePropertiesEmptyProperties to exclude from serialization

AuditEntry

public class AuditEntry
{
    public Guid Id { get; set; }
    public string? UserId { get; set; }
    public string Action { get; set; }
    public string EntityType { get; set; }
    public string? EntityId { get; set; }
    public string? RequestPayload { get; set; }   // JSON
    public string? ResponsePayload { get; set; }   // JSON
    public bool Success { get; set; }
    public string? ErrorMessage { get; set; }
    public DateTime Timestamp { get; set; }
    public long DurationMs { get; set; }
    public string? IpAddress { get; set; }
    public string? UserAgent { get; set; }
    public string? CorrelationId { get; set; }
    public string? Metadata { get; set; }          // JSON
}

IAuditStore

public interface IAuditStore
{
    ValueTask SaveAsync(AuditEntry entry, CancellationToken ct);
    IAsyncEnumerable<AuditEntry> GetByEntityAsync(
        string entityType, string entityId, CancellationToken ct);
    IAsyncEnumerable<AuditEntry> GetByUserAsync(
        string userId, CancellationToken ct);
    IAsyncEnumerable<AuditEntry> GetByTimeRangeAsync(
        DateTime from, DateTime to, CancellationToken ct);
}

Configuration

builder.Services.AddTurboMediator(m =>
{
    m.WithAudit<IMessage, object>(options =>
    {
        options.IncludeResponse = false;
        options.AuditFailures = true;
        options.ThrowOnAuditFailure = false; // Don't fail the operation if audit fails
    });

    // Or audit everything
    m.WithAuditForAll();
});

AuditOptions

OptionDefaultDescription
IncludeResponsefalseInclude response in audit entries
AuditFailurestrueAudit failed operations too
ThrowOnAuditFailurefalseThrow if audit storage fails

Querying Audit Log

public class AuditController : ControllerBase
{
    private readonly IAuditStore _auditStore;

    public AuditController(IAuditStore auditStore) => _auditStore = auditStore;

    [HttpGet("audit/user/{userId}")]
    public async Task<IActionResult> GetUserAudit(string userId, CancellationToken ct)
    {
        var entries = new List<AuditEntry>();
        await foreach (var entry in _auditStore.GetByUserAsync(userId, ct))
            entries.Add(entry);
        return Ok(entries);
    }

    [HttpGet("audit/entity/{entityType}/{entityId}")]
    public async Task<IActionResult> GetEntityAudit(string entityType, string entityId, CancellationToken ct)
    {
        var entries = new List<AuditEntry>();
        await foreach (var entry in _auditStore.GetByEntityAsync(entityType, entityId, ct))
            entries.Add(entry);
        return Ok(entries);
    }
}

The audit behavior records both successful and failed operations (when AuditFailures is true), including the error message for failures. This provides a complete audit trail for compliance.

On this page