Skip to main content

Audit Logging

Implement audit trails for critical actions in under 5 minutes.


1. Install Package

dotnet add package PrimusSaaS.Logging

2. Setup in Program.cs

using PrimusSaaS.Logging.Extensions;

var builder = WebApplication.CreateBuilder(args);

// Add Primus Logging with audit support
builder.Logging.ClearProviders();
builder.Logging.AddPrimus(builder.Configuration.GetSection("PrimusLogging"));

var app = builder.Build();

// Add correlation ID middleware
app.UsePrimusLogging();

app.MapControllers();
app.Run();

3. Configure appsettings.json

{
"PrimusLogging": {
"ApplicationId": "my-api",
"Environment": "Production",
"MinLevel": 1,
"Targets": [
{ "Type": "console", "Pretty": true },
{
"Type": "file",
"Path": "logs/audit-.log",
"Async": true,
"MaxFileSize": 52428800,
"MaxRetainedFiles": 30,
"CompressRotatedFiles": true
}
],
"Pii": {
"MaskEmails": true,
"MaskSSN": true,
"MaskCreditCards": true
},
"Audit": {
"Enabled": true,
"IncludeRequestBody": false,
"IncludeResponseBody": false,
"SensitiveEndpoints": ["/auth/*", "/admin/*", "/api/users/*"]
}
}
}
Audit configuration options
OptionTypeDefaultDescription
EnabledbooltrueEnable audit logging
IncludeRequestBodyboolfalseLog request bodies (use with caution)
IncludeResponseBodyboolfalseLog response bodies
SensitiveEndpointsarray[]Endpoints to always audit
ExcludedEndpointsarray["/health", "/metrics"]Endpoints to never audit

4. Use Audit Logging

using PrimusSaaS.Logging.Extensions;

public class AccountService
{
private readonly ILogger<AccountService> _logger;

public AccountService(ILogger<AccountService> logger)
{
_logger = logger;
}

public async Task<bool> UpdateUserRoleAsync(string userId, string newRole, string changedBy)
{
// Log the audit event
await _logger.LogAuditAsync("UserRoleChanged", new
{
UserId = userId,
NewRole = newRole,
ChangedBy = changedBy,
Timestamp = DateTime.UtcNow
});

// Perform the actual update
return true;
}
}

Structured Audit Events

public class OrderController : ControllerBase
{
private readonly ILogger<OrderController> _logger;

public OrderController(ILogger<OrderController> logger)
{
_logger = logger;
}

[HttpPost]
[Authorize]
public async Task<IActionResult> CreateOrder([FromBody] CreateOrderRequest request)
{
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var orderId = Guid.NewGuid().ToString();

// Audit: Order Created
await _logger.LogAuditAsync("OrderCreated", new
{
OrderId = orderId,
UserId = userId,
Amount = request.Amount,
ItemCount = request.Items.Count,
IpAddress = HttpContext.Connection.RemoteIpAddress?.ToString()
});

return Created($"/orders/{orderId}", new { orderId });
}

[HttpDelete("{id}")]
[Authorize(Roles = "Admin")]
public async Task<IActionResult> DeleteOrder(string id)
{
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;

// Audit: Order Deleted (sensitive action)
await _logger.LogAuditAsync("OrderDeleted", new
{
OrderId = id,
DeletedBy = userId,
Reason = "Admin request",
IpAddress = HttpContext.Connection.RemoteIpAddress?.ToString()
});

return NoContent();
}
}

5. Test It

# Make a request that triggers audit logging
curl -X POST http://localhost:5001/api/orders \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"amount": 99.99, "items": ["item1", "item2"]}'

# Check audit log file
cat logs/audit-*.log | jq .

# Expected output:
# {
# "eventType": "OrderCreated",
# "timestamp": "2024-01-15T10:30:45Z",
# "data": {
# "orderId": "abc-123",
# "userId": "user-456",
# "amount": 99.99,
# "itemCount": 2,
# "ipAddress": "127.0.0.1"
# }
# }

Audit Event Types

Event TypeWhen to UseExample Properties
UserLoginAuthentication successUserId, IpAddress, Method
UserLogoutSession terminationUserId, SessionDuration
PasswordChangedCredential updateUserId, ChangedBy
RoleChangedPermission modificationUserId, OldRole, NewRole
DataAccessedSensitive data readUserId, ResourceType, ResourceId
DataModifiedSensitive data changeUserId, ResourceType, ChangeType
DataDeletedData removalUserId, ResourceType, ResourceId

Compliance Considerations

SOC 2 / HIPAA / GDPR

  • Always include: Who (userId), What (action), When (timestamp), Where (IP)
  • Enable MaskPii for personal data protection
  • Set appropriate retention (30-90 days minimum)
  • Use immutable storage (append-only files or database)

These patterns can support compliance programs, but they are not a certification. Validate requirements with your legal/compliance team.


Next Steps

Want to...See Guide
Configure log targetsConsole
Add OpenTelemetry tracesObservability
Set up role-based accessRBAC