Skip to content

GrydCrud + GrydAudit Integration

This guide explains how to enable automatic audit logging for entities managed by GrydCrud.

Overview

GrydCrud and GrydAudit integrate seamlessly through:

  1. Entity Interface: Mark entities with IAuditableEntity
  2. EF Core Interceptor: AuditSaveChangesInterceptor automatically captures changes
  3. Tracing Decorators: Optional distributed tracing for CRUD operations

Quick Setup

1. Mark Entity as Auditable

csharp
using Gryd.Domain.Abstractions;
using Gryd.Observability.Audit;

public class Product : IEntity, IAuditableEntity
{
    public Guid Id { get; set; }
    public string Name { get; set; } = string.Empty;
    public decimal Price { get; set; }
    public string? Description { get; set; }
    
    // Exclude sensitive properties from audit
    public IEnumerable<string> AuditExcludedProperties => ["InternalCode"];
    
    // Categorize for audit queries
    public string? AuditCategory => "Inventory";
}

2. Configure Services

csharp
// Program.cs
var builder = WebApplication.CreateBuilder(args);

// 1. Add GrydCrud with operations
builder.Services.AddGrydCrudCore();
builder.Services.AddCrudOperations<Product, ProductCreateDto, ProductUpdateDto, ProductDto>();

// 2. Add distributed tracing (optional)
builder.Services.AddGrydObservability(config => config.ServiceName = "MyApi");
builder.Services.AddCrudTracing<Product, ProductCreateDto, ProductUpdateDto, ProductDto>();

// 3. Add GrydAudit with interceptor
builder.Services.AddGrydAudit(options => options.UseSqlServer(connectionString));
builder.Services.AddAuditInterceptor();

3. Register Interceptor in DbContext

csharp
// In your DbContext configuration
services.AddDbContext<AppDbContext>((sp, options) =>
{
    options.UseSqlServer(connectionString);
    
    // Add the audit interceptor
    var auditInterceptor = sp.GetService<AuditSaveChangesInterceptor>();
    if (auditInterceptor is not null)
    {
        options.AddInterceptors(auditInterceptor);
    }
});

How It Works

Automatic Change Tracking

When GrydCrud operations call SaveChangesAsync(), the AuditSaveChangesInterceptor:

  1. Detects entities implementing IAuditableEntity
  2. Captures the change type (Created, Updated, Deleted)
  3. Records property changes with old/new values
  4. Stores audit logs with 5W1H context

Audit Log Contents

Each audit log includes:

FieldDescriptionSource
UserIdWho made the changeIAuditContext
EntityTypeEntity class nameReflection
EntityIdEntity primary keyEF Core
ActionCreated/Updated/DeletedEF Core ChangeTracker
TimestampWhen it happenedUTC now
TenantIdMulti-tenant contextIAuditContext
PropertyChangesOld → New valuesEF Core ChangeTracker

Distributed Tracing

With tracing enabled via AddCrudTracing<>():

[Crud.Create.Product] ──┬── entity.type: Product
                        ├── action: Create
                        ├── operation.success: true
                        └── duration: 45ms

Advanced Configuration

Conditional Audit

Disable audit for specific entities conditionally:

csharp
public class DraftDocument : IEntity, IAuditableEntity
{
    public Guid Id { get; set; }
    public string Content { get; set; }
    public bool IsDraft { get; set; }
    
    // Only audit when published
    public bool AuditEnabled => !IsDraft;
}

Exclude Sensitive Properties

csharp
public class User : IEntity, IAuditableEntity
{
    public Guid Id { get; set; }
    public string Email { get; set; }
    public string PasswordHash { get; set; }
    public string SecurityStamp { get; set; }
    
    // Never audit password-related fields
    public IEnumerable<string> AuditExcludedProperties => 
        ["PasswordHash", "SecurityStamp", "RefreshToken"];
}

Custom Audit Categories

csharp
public class PaymentTransaction : IEntity, IAuditableEntity
{
    public Guid Id { get; set; }
    public decimal Amount { get; set; }
    
    // Financial category for compliance reports
    public string? AuditCategory => "Financial";
}

Querying Audit Logs

By Entity

csharp
// Get all changes to a specific product
var history = await mediator.Send(new GetAuditLogsByEntityQuery("Product", productId));

By User

csharp
// Get all changes made by a user
var userActivity = await mediator.Send(new GetAuditLogsByUserQuery(userId));

With Filters

csharp
// Complex search
var results = await mediator.Send(new SearchAuditLogsQuery(new AuditLogFilter
{
    EntityType = "Product",
    Action = EntityChangeType.Updated,
    FromDate = DateTime.UtcNow.AddDays(-7),
    TenantId = tenantId
}));

Best Practices

  1. Always exclude sensitive data — Passwords, tokens, PII. See LGPD & Data Protection
  2. Use categories — Makes reporting and compliance easier
  3. Enable tracing in production — Valuable for debugging
  4. Set retention policies — Don't store audit logs forever
  5. Index audit tables — EntityType, EntityId, Timestamp for performance

Troubleshooting

Audit logs not being created

  1. Verify entity implements IAuditableEntity
  2. Check AuditEnabled returns true
  3. Ensure interceptor is registered in DbContext
  4. Verify IAuditContext is populated

Missing property changes

  1. Check property is not in AuditExcludedProperties
  2. Verify property is a public get/set property
  3. Complex types may need special handling

Performance issues

  1. Use async processing for high-volume entities
  2. Consider separate database for audit logs
  3. Implement audit log archiving

Released under the MIT License.