# Audit log

Record API traffic across REST, SOAP, IMAP and pub/sub, with database storage.

The audit log records structured events describing the traffic that flows through your integrations. Every event includes a correlation ID (CID), a timestamp, the name of the object it belongs to, the outcome, the size of the data and the data itself.

Traffic analytics drill-downs and the per-caller usage reports of [API versioning](https://zato.io/docs/dev/rest/versioning.html) both read these events, so an object with its audit log off contributes to neither.

![The audit log](https://zatosource-production.b-cdn.net/docs/gfx/audit-log/overview.webp?v=1789221712)

Dashboard menu: Monitoring > Audit log

The audit log is source-agnostic - the same log, storage and Dashboard views serve everything below. The source is what an event is filed under, and it is also the first filter on every screen.

### Message traffic {#message-traffic}

| Source | What is recorded |
| --- | --- |
| REST channels | Each request received and each response sent |
| SOAP channels | Each request received and each response sent |
| REST outgoing | Each request sent and each response received |
| SOAP outgoing | Each request sent and each response received |
| Pub/sub | Each message published, delivered, or whose delivery failed or expired |
| IMAP | Each message received, marked as seen or deleted, with its attachments |
| SMTP | Each message sent, with its attachments, and each credential failure |
| File transfer | Each file transferred - metadata always, the file's bytes only when the connection opts in |
| SQL | Each statement or stored procedure call run through an outgoing SQL connection, with its duration - how much of it is kept depends on the [connection's audit level](https://zato.io/docs/admin/audit-log/index.html#outgoing-sql-connections) |
| LLM | Each model invocation, with its duration |
| MCP | Each request an AI agent makes - initialize, tools/list, tools/call - with payload sizes only, never payloads themselves |
| MLLP channels | Each HL7 message received and the ACK sent back |
| MLLP outgoing | Each HL7 message sent and the ACK received |
| FHIR outgoing | Each FHIR request sent and the response received |
| Odoo | Each remote call, with its duration |
| Microsoft cloud | Each call a Microsoft 365, Teams, Power Automate or Fabric connection makes - with Microsoft 365, that is every Graph call, mail, OneDrive and SharePoint included |

### Configuration and access {#configuration-and-access}

| Source | What is recorded |
| --- | --- |
| Log access | Each configuration object created, edited or deleted, with a masked before-and-after summary, and each time somebody opened a message body - who, which message, which screen |

This is the source to look at when the question is who changed something or who read a payload. Opening a message in the audit log is itself an audited act.

### Scheduler {#scheduler}

| Source | What is recorded |
| --- | --- |
| Scheduler | Each job run - the job, the service, the outcome, how long it took, how late it started and the log lines it produced |

### Probes {#probes}

These are events Zato produces about itself rather than about traffic somebody sent:

| Source | What is recorded |
| --- | --- |
| REST checks | Each [health check](https://zato.io/docs/admin/health-checks.html) ping of an outgoing REST connection |
| SOAP checks | Each health check ping of an outgoing SOAP connection |
| Certificate | Each certificate expiry check, with the days remaining |
| Microsoft health | Each poll of a Microsoft service's own reported health state |
| Test transfer | Each test file transfer - upload, download, compare, delete |

Health check events are kept apart from the connection's own traffic and counted separately from it.

Only user-defined objects are recorded. Channels and connections that are internal to the platform, such as the built-in publish/subscribe REST channels, are skipped entirely.

> **Auditing AI agents?** The [MCP audit log](https://zato.io/docs/ai/mcp/audit-log.html) page describes the MCP-specific schema - event types, the tool and caller columns, and why payloads are never recorded for agent traffic.

## Querying from Python {#querying-from-python}

Your own services can query everything the Dashboard shows - `self.audit` searches events by free text, source, outcome or time window, returns the newest event time of each channel or connection and reads stored payloads, with no SQL and no database configuration:

```python
# Zato
from zato.common.audit_log.api import AuditOutcome

events = self.audit.search(outcome=AuditOutcome.Error, time_from='2026-08-29')
```

The whole API, with the filters and the structure of the results, is on [the audit log Python API](https://zato.io/docs/admin/audit-log/python.html).

## Example: a REST channel {#example-a-rest-channel}

In Dashboard, go to `Connections → Channels → REST` and click the `Audit log` link in the channel's row.

One invocation of a channel produces two events:

- `request-received` - the incoming request, including its payload
- `response-sent` - the outgoing response, including its body

Each event holds the CID shared by the request and its response, the name of the service that handled the request, the outcome (`ok` or `error`), the size of the data and a preview of the data itself. Clicking the CID opens the complete message.

The same applies to outgoing REST connections, under `Connections → Outgoing → REST`, whose events are `request-sent` and `response-received`. Pinging a connection - from the Dashboard or through a [health check](https://zato.io/docs/admin/health-checks.html) - writes the same request and response pair, but under the connection's health source, so the checks stay apart from the traffic the connection delivers.

## Example: an IMAP connection {#example-an-imap-connection}

In Dashboard, go to `Connections → Email → IMAP` and click the `Audit log` link in the connection's row.

Working with a mailbox produces these events:

- `message-received` - a message was downloaded from the server
- `message-marked-seen` - a message was marked as seen
- `message-deleted` - a message was deleted

Each event records the CID, the folder the message is stored in, the message ID, the outcome, the size and a preview of the message.

## Turning the audit log on and off {#turning-the-audit-log-on-and-off}

Each item covered by the audit log has its own `Audit log` checkbox, right next to the `Active` one in its create and edit forms. The checkbox is on by default - every new item records events unless you decide otherwise.

The checkbox exists for:

- REST and SOAP channels
- Outgoing REST and SOAP connections
- IMAP and SMTP connections
- HL7 MLLP channels and outgoing connections
- Outgoing FHIR connections
- Publish/subscribe topics
- [MCP gateways](https://zato.io/docs/ai/mcp/audit-log.html)

Unchecking it stops new events for that item only - traffic keeps flowing as before and other items keep recording theirs. Checking it again resumes the events. Events recorded earlier remain in the database either way, subject to the usual retention.

Two kinds of items opt in with something richer than a checkbox:

- **Outgoing SQL connections** have an audit level instead of a checkbox - see [below](https://zato.io/docs/admin/audit-log/index.html#outgoing-sql-connections).
- **File transfer connections** always record each transfer's metadata, and their `should_store_content` field controls whether the file's own bytes are kept with the event too - it is off by default.

### Outgoing SQL connections {#outgoing-sql-connections}

An outgoing SQL connection has an `Audit log` dropdown instead of a checkbox. What each level stores per statement or stored procedure call:

| Dashboard | enmasse | Stored |
| --- | --- | --- |
| Off | `off` | Connection, database, outcome, duration. The default. |
| Statements | `statement` | The above, plus the SQL text |
| Statements and parameters | `statement-params` | The above, plus the bound parameter values |
| Statements, parameters and rows | `full` | The above, plus the rows returned and their count |

A stored procedure call is stored as `EXEC name` (MS SQL) or `BEGIN name(); END;` (Oracle), its arguments as the parameters, its result sets as the rows. With `use_yield=True` the rows are not stored. A failed statement is stored with the error's text.

`statement-params` and `full` put business data into the audit database - keep [retention](https://zato.io/docs/admin/audit-log/index.html#retention) in mind.

In [enmasse](https://zato.io/docs/admin/enmasse.html), the level is the `audit_log` attribute of an `sql` connection, exported only when it is not `off`:

```yaml
sql:
  - name: Airport MS SQL
    type: mssql
    host: 10.0.0.15
    port: 1433
    db_name: airport
    username: zato
    audit_log: statement
```

The [enmasse reference](https://zato.io/docs/admin/enmasse-reference.html) lists the attribute with the rest of the connection's fields.

In [enmasse](https://zato.io/docs/admin/enmasse.html), the toggle is the `is_audit_log_active` attribute. For channels, connections and topics it is exported only when it is off - an item without the attribute in YAML has its audit log on. [MCP gateways](https://zato.io/docs/ai/mcp/audit-log.html) are the exception - their attribute is exported when it is on and a gateway without it in YAML has its audit log off:

```yaml
channel_rest:
  - name: orders.api
    service: orders.handler
    url_path: /orders
    is_audit_log_active: false
```

### The whole environment {#the-whole-environment}

The audit log can be switched off for an entire environment:

```bash
export Zato_Audit_Log_Enabled=False
```

No audit events are recorded for any object. It takes effect without a restart.

## Storage and configuration {#storage-and-configuration}

By default, all events go to an SQLite database file called `audit.db`, shared by all servers of an environment, and there is nothing to configure.

For production environments with multiple hosts or higher volumes, the audit log can store its events in an external database instead - MySQL, PostgreSQL or Oracle DB. Which database is used comes from environment variables that all the servers and the Dashboard read:

| Variable | Default | Description |
| --- | --- | --- |
| `Zato_Audit_Log_DB_Type` | `sqlite` | The database type - one of `sqlite`, `mysql`, `postgresql` or `oracle` |
| `Zato_Audit_Log_DB_Host` | (none) | The host the database runs on |
| `Zato_Audit_Log_DB_Port` | `3306`, `5432` or `1521` | The port the database listens on, defaulting per database type |
| `Zato_Audit_Log_DB_Username` | (none) | The username to connect with |
| `Zato_Audit_Log_DB_Password` | (none) | The password to connect with |
| `Zato_Audit_Log_DB_Name` | `audit.db` | The database name - the service name with Oracle DB, or the full file path with SQLite |
| `Zato_Audit_Log_DB_SSL` | `off` | Whether to encrypt the connections with SSL/TLS - `on` or `off` |
| `Zato_Audit_Log_DB_SSL_CA_File` | (system store) | Path to the CA certificate the server certificate is verified against |
| `Zato_Audit_Log_DB_SSL_Cert_File` | (none) | Path to the client certificate, for mutual TLS |
| `Zato_Audit_Log_DB_SSL_Key_File` | (none) | Path to the client private key, for mutual TLS |
| `Zato_Audit_Log_DB_SSL_Verify` | `on` | Whether to verify the server certificate and hostname when SSL is on |

The schema is created automatically the first time a server connects - there is no migration step. What you do need to create upfront is the database itself and its user, which is described separately for each database:

- [MySQL setup](https://zato.io/docs/admin/audit-log/mysql.html)
- [PostgreSQL setup](https://zato.io/docs/admin/audit-log/postgresql.html)
- [Oracle DB setup](https://zato.io/docs/admin/audit-log/oracle.html)

For instance, this points the audit log at a PostgreSQL server:

```bash
export Zato_Audit_Log_DB_Type=postgresql
export Zato_Audit_Log_DB_Host=db.example.com
export Zato_Audit_Log_DB_Port=5432
export Zato_Audit_Log_DB_Username=zato_audit_log
export Zato_Audit_Log_DB_Password=my.secret.password
export Zato_Audit_Log_DB_Name=zato_audit_log
```

### How events are written {#how-events-are-written}

By default every event is written to the database as it happens. Events can be batched instead:

| Variable | Default | Description |
| --- | --- | --- |
| `Zato_Audit_Log_Flush_Max_Size` | `1` | How many events are buffered before they are written. `1` means no buffering - every event is written as it happens |
| `Zato_Audit_Log_Flush_Max_Wait_Ms` | `500` | How long a buffered event may wait before it is written regardless of how few there are |

Raising the flush size means fewer writes, at the cost of losing the buffered events if the container stops abruptly.

Attachments - the files delivered by IMAP and SMTP messages and by file transfers - have a size cap of their own. An attachment bigger than the cap is still recorded as an event, but its bytes are not stored:

```bash
# 50 MB by default
export Zato_Audit_Log_Max_Attachment_Size=104857600
```

## Exporting with OpenTelemetry {#exporting-with-opentelemetry}

Besides storing events, the servers can send a copy of each one to an OpenTelemetry Collector, or to Splunk, Datadog, Elastic or Grafana Cloud without a collector in between, as an OTLP log record whose attributes are the event's columns, attributes and, for sources such as MCP and configuration changes, its `data` document. One environment variable turns it on:

```bash
export Zato_Audit_Log_Export_Endpoint=http://otel-collector:4318
```

The [OpenTelemetry export](https://zato.io/docs/admin/audit-log/opentelemetry.html) chapter has the remaining variables, what each record contains, and what happens while the collector is unreachable. The export does not change what is stored here - events stay in the database under the retention below, with or without a copy having left.

## Retention {#retention}

Events are kept for 30 days by default. Older events are deleted automatically and periodically by the servers themselves, on every database type.

The window is configurable with an environment variable that all the servers read:

```bash
export Zato_Audit_Log_Retention_Days=90
```

### Per-source retention {#per-source-retention}

Not all events are worth keeping for the same length of time. Any source can be given a window of its own by appending its name to the variable, upper-cased and with hyphens replaced by underscores:

```bash
export Zato_Audit_Log_Retention_Days_REST_CHANNEL=14
export Zato_Audit_Log_Retention_Days_SQL_OUTGOING=7
```

Two sets of sources already have a default of their own:

| Source | Variable suffix | Default |
| --- | --- | --- |
| REST checks | `REST_OUTGOING_HEALTH` | 7 days |
| SOAP checks | `SOAP_OUTGOING_HEALTH` | 7 days |

A source with a default of its own keeps it unless you set that source's own variable.

### Keeping payloads for less time than events {#keeping-payloads-for-less-time-than-events}

Message payloads are usually the bulk of the database, and they are the part that is most sensitive. They can be expired ahead of the events that contain them, leaving the record of what happened while removing what was in it:

| Variable | Default | Description |
| --- | --- | --- |
| `Zato_Audit_Log_Content_Retention_Days` | `0` | How many days payloads are kept. `0` means payloads are kept for as long as their events |
| `Zato_Audit_Log_Content_Retention_Days_<SOURCE>` | `0` | The same, for one source, with the suffix built exactly as above |

So this keeps a quarter of history while holding payloads for a week:

```bash
export Zato_Audit_Log_Retention_Days=90
export Zato_Audit_Log_Content_Retention_Days=7
```

Payloads of failed events are exempt - an event whose outcome is an error keeps its payload, because that is the one you will want when somebody asks what went wrong.

### Archiving before deletion {#archiving-before-deletion}

If a directory is named, everything about to be deleted is written out first as JSON Lines:

```bash
export Zato_Audit_Log_Archive_Dir=/opt/zato/audit-archive
```

Each run produces a file named after its start time, for instance `audit-archive-20260812-030000.jsonl`. Unset, which is the default, nothing is archived and expired rows are simply deleted. Point it at a mounted volume rather than the container filesystem.

Retention windows, payload windows and archive directories can also be kept in configuration as named [retention policies](https://zato.io/docs/admin/enmasse-reference.html#audit-retention-policies), imported and exported with the rest of an environment through [enmasse](https://zato.io/docs/admin/enmasse.html).

### Retention and long-term trends {#retention-and-long-term-trends}

Retention does not bound long-term trends - traffic analytics extracts hourly aggregates from the audit log before its rows expire and keeps them indefinitely, so quarter-long trend charts work regardless of how long the raw events are retained.

## SSL/TLS {#ssltls}

Connections to MySQL, PostgreSQL and Oracle DB can be encrypted with SSL/TLS. Turn it on with `Zato_Audit_Log_DB_SSL` and point `Zato_Audit_Log_DB_SSL_CA_File` at the CA certificate that issued your database server's certificate:

```bash
export Zato_Audit_Log_DB_Type=mysql
export Zato_Audit_Log_DB_Host=db.example.com
export Zato_Audit_Log_DB_Username=zato_audit_log
export Zato_Audit_Log_DB_Password=my.secret.password
export Zato_Audit_Log_DB_Name=zato_audit_log

export Zato_Audit_Log_DB_SSL=on
export Zato_Audit_Log_DB_SSL_CA_File=/path/to/ca.crt
```

The server certificate and its hostname are verified by default. If your database uses a certificate whose hostname does not match, e.g. in a development environment, verification can be turned off with `Zato_Audit_Log_DB_SSL_Verify=off` - the connection remains encrypted but the certificate is not checked.

If the database requires client certificates (mutual TLS), add the certificate and its key:

```bash
export Zato_Audit_Log_DB_SSL_Cert_File=/path/to/client.crt
export Zato_Audit_Log_DB_SSL_Key_File=/path/to/client.key
```

How to enable TLS on the database server itself, including how to require it for all connections, is covered in the per-database setup pages:

- [MySQL setup](https://zato.io/docs/admin/audit-log/mysql.html)
- [PostgreSQL setup](https://zato.io/docs/admin/audit-log/postgresql.html)
- [Oracle DB setup](https://zato.io/docs/admin/audit-log/oracle.html)

## Learn more {#learn-more}

- [Production checklist](https://zato.io/docs/admin/production-checklist.html) - What to confirm before an environment takes real traffic
- [Health checks](https://zato.io/docs/admin/health-checks.html) - Have Zato ping your outgoing connections instead of waiting for traffic to fail
- [Logging](https://zato.io/docs/admin/logging.html) - What is written where, and how to ship it to Splunk, Loki, Elastic or Datadog
- [GitOps with enmasse](https://zato.io/docs/admin/enmasse.html) - Keep the whole environment in git as YAML and import it anywhere
