Guides
Add Audit Logs
Record application audit events and review them in the SqlOS dashboard.
AddSqlOS<TContext>()app.MapSqlOS()When a user changes an important business resource, SqlOS should store a structured event that an operator can later answer:
Use stable dot-delimited action names. Prefer past-tense or outcome-specific names:
| Good | Avoid |
|---|---|
retail.chain.created | create |
retail.inventory_item.updated | item changed |
document.shared | share document button clicked |
application.access.denied | error |
Keep action names stable because operators filter and export by action.
The actor is the principal that performed the action.
Common actor types:
userclientservice_accountagentdashboardsystemTargets are the affected resources. A single operation can have multiple targets:
Targets:
[
new SqlOSAuditTarget("location", location.Id, location.Name),
new SqlOSAuditTarget("inventory_item", item.Id, item.Name)
]Inject ISqlOSAuditLogService into the endpoint, controller, or domain service that performs the mutation.
app.MapPut("/api/locations/{locationId}/inventory/{itemId}", async (
string locationId,
string itemId,
UpdateInventoryRequest request,
ExampleAppDbContext db,
ISqlOSAuditLogService auditLogs,
HttpContext httpContext,
CancellationToken ct) =>
{
var item = await db.InventoryItems.FindAsync([itemId], ct);
if (item is null)
{
return Results.NotFound();
}
var previousQuantity = item.QuantityOnHand;
item.QuantityOnHand = request.QuantityOnHand;
await db.SaveChangesAsync(ct);
await auditLogs.RecordAsync(new SqlOSAuditLogRecordRequest(
Action: "retail.inventory_item.updated",
OrganizationId: request.OrganizationId,
ApplicationKey: "northwind-retail",
Source: "application",
Actor: new SqlOSAuditActor("user", request.UserId, request.UserDisplayName),
Targets:
[
new SqlOSAuditTarget("location", locationId),
new SqlOSAuditTarget("inventory_item", item.Id, item.Name)
],
Context: SqlOSAuditContext.FromHttpContext(httpContext),
Metadata: new Dictionary<string, object?>
{
["result"] = "success",
["sku"] = item.Sku,
["previousQuantity"] = previousQuantity,
["newQuantity"] = item.QuantityOnHand,
["delta"] = item.QuantityOnHand - previousQuantity
},
IdempotencyKey: $"retail:inventory:{item.Id}:updated:{httpContext.TraceIdentifier}"),
ct);
return Results.Ok(item);
});Use ApplicationKey for the product surface that produced the event. If the key matches a registered SqlOS client id, SqlOS also links the event to that client row.
Set IdempotencyKey when a request may be retried. SqlOS hashes the key before storage. A duplicate key returns the original event instead of inserting another row.
Good idempotency keys usually include:
Never include passwords, access tokens, refresh tokens, API keys, client secrets, raw authorization headers, cookies, private keys, raw stack traces, request bodies, or response bodies.
Metadata should explain the decision or outcome without exposing sensitive data:
Metadata: new Dictionary<string, object?>
{
["result"] = "denied",
["reason"] = "missing_permission",
["permission"] = "inventory.edit"
}Open:
/sqlos/admin/audit/logsUseful filters:
| Filter | Example |
|---|---|
| Application | northwind-retail |
| Source | application |
| Action | retail.inventory_item.updated |
| Actor type | user |
| Target type | inventory_item |
| Result | success or denied |
Select a row to inspect actor, targets, context, and metadata.
Use the dashboard export button. The CSV endpoint is protected by the same admin authorization as /sqlos/admin; it is not a public reporting API.
Exports use the same filters as the dashboard list. SqlOS caps dashboard exports at 5,000 rows and a 366-day date range. If no dates are supplied, export defaults to the last 30 days.
After a mutation, the Audit Logs dashboard shows:
Source = applicationIn the Retail example, create or edit a chain, store, or inventory item and filter Audit Logs by northwind-retail.