Appearance
GrydCrud + GrydAudit Integration
This guide explains how to enable automatic audit logging for entities managed by GrydCrud.
Overview
GrydCrud and GrydAudit integrate seamlessly through:
- Entity Interface: Mark entities with
IAuditableEntity - EF Core Interceptor:
AuditSaveChangesInterceptorautomatically captures changes - 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:
- Detects entities implementing
IAuditableEntity - Captures the change type (Created, Updated, Deleted)
- Records property changes with old/new values
- Stores audit logs with 5W1H context
Audit Log Contents
Each audit log includes:
| Field | Description | Source |
|---|---|---|
| UserId | Who made the change | IAuditContext |
| EntityType | Entity class name | Reflection |
| EntityId | Entity primary key | EF Core |
| Action | Created/Updated/Deleted | EF Core ChangeTracker |
| Timestamp | When it happened | UTC now |
| TenantId | Multi-tenant context | IAuditContext |
| PropertyChanges | Old → New values | EF Core ChangeTracker |
Distributed Tracing
With tracing enabled via AddCrudTracing<>():
[Crud.Create.Product] ──┬── entity.type: Product
├── action: Create
├── operation.success: true
└── duration: 45msAdvanced 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
- Always exclude sensitive data — Passwords, tokens, PII. See LGPD & Data Protection
- Use categories — Makes reporting and compliance easier
- Enable tracing in production — Valuable for debugging
- Set retention policies — Don't store audit logs forever
- Index audit tables — EntityType, EntityId, Timestamp for performance
Troubleshooting
Audit logs not being created
- Verify entity implements
IAuditableEntity - Check
AuditEnabledreturnstrue - Ensure interceptor is registered in DbContext
- Verify
IAuditContextis populated
Missing property changes
- Check property is not in
AuditExcludedProperties - Verify property is a public get/set property
- Complex types may need special handling
Performance issues
- Use async processing for high-volume entities
- Consider separate database for audit logs
- Implement audit log archiving