Zero-loss audit pipeline
How I designed an audit trail that keeps every event, even when the message broker goes down, across four .NET services and the browser.
- Role
- Designer & sole developer
- Context
- Production system at work
- Scope
- 4 services + React app
This is proprietary software, so this page covers the architecture and decisions rather than code or screenshots.
01The problem
The platform I work on has a hard requirement: every meaningful change must end up in an audit trail. A missing event isn't cosmetic. The record is incomplete, and in a regulated setting that matters.
The obvious approach, publishing to the message broker right after saving, has a gap. If the broker is down, or the process dies between the database commit and the publish, the event is gone and nobody notices.
Some auditable actions also happen only in the browser, where the network is even less dependable.
02My role
I designed and built the whole subsystem: the outbox in each service, the background flusher, the dedicated audit service and the browser-side queue.
I also run it in production, so I live with its failure modes.
03Architecture
Server
- Any servicewrites its data change
- Local outboxsame DB transaction
- Background flusherretries until delivered
- RabbitMQmessage broker
- Audit servicedeveloper · activity · data logs
Browser
- React appuser actions queued client-side
- Central dispatcherbatches and flushes
- API → outboxjoins the same pipeline
04Key decisions
Transactional outbox
Each service writes its audit event to a local outbox table in the same transaction as the data change. Either both are saved or neither is, so there is no window where data changes without a matching event.
Background flusher
A background worker picks up unsent outbox rows and ships them to the audit service, retrying until they are delivered. A broker outage delays events instead of losing them, and requests never wait on the broker.
Audit the browser too
Some actions only happen in the UI. Frontend events queue client-side and flush through a central dispatcher into the same outbox pipeline, so they get the same guarantee as server-side ones.
A dedicated audit service
Audit lives in its own service. Other services only write to their outbox, while storage, querying and log views stay in one place.
One pipeline, three audiences
Output is split into developer logs, activity logs and data-change logs. Engineers, operators and reviewers each get the view they need from one source of truth.
05Outcome
- The audit trail survives broker outages and restarts without losing events.
- Every service and the frontend share one pipeline instead of ad-hoc logging.
- It has run in production across 20+ client deployments.
06Stack
- .NET Core
- ASP.NET Core
- SQL Server
- EF Core
- RabbitMQ
- Background services
- React