Audit logs
Windmill provides audit logs for every operation and action that has side-effects. These logs capture the user responsible for the operation and include metadata specific to the type of operation.
As a user, you can only see your own audit logs unless you are an admin.
Audit logs can be filtered by Date, Username, Action (Create, Update, Delete, Execute), and specific Operation. The Runs menu is another feature that allows you to visualise all past and future runs.
Streaming a slow page in batches
The selector at the bottom of the table sets how many logs a page holds: 25, 100 or 1000. On instances with a lot of audit logs, a full page can take a while to come back. If a load takes more than 4 seconds, Windmill offers to stream it in batches of 25 instead, so rows appear as each batch arrives rather than only once the whole page is ready.
While a page is streaming, the table footer shows the progress, a batch size you can change mid-load (down to one log at a time, which Windmill also offers if the batches themselves are slow), and a Stop button that ends the load and keeps the logs already fetched on screen. The Runs page streams slow pages the same way.
Retention policy
For self-hosted Windmill instances, you can set how long audit log entries are kept in the database via the Audit log retention (days) instance setting, accessible in the top right corner of the workspace settings. Instance settings are accessible only to superadmins. It defaults to 365 days on Enterprise Edition and 14 days on the Community Edition. Audit logs are maintained in Postgres and output to the FileSystem as logs.
If you also export audit logs to object storage, the database is no longer the only durable copy, so you can set the database retention much lower (for example a few days, enough to keep the in-app audit log view useful) while keeping a complete long-term history in your bucket. Windmill never deletes the exported audit logs from object storage.
On Windmill's cloud service, audit log retention varies by plan:
- Community plan: Audit logs are redacted.
- Team plan (cloud): Retained for 7 days.
- Enterprise plan (cloud): Kept for 60 days, with logs stored in Postgres and the cloud for up to a year, extendable upon request.
Exporting audit logs to object storage
On Enterprise Edition, audit logs can be continuously exported to your instance object storage bucket (S3, Azure Blob or Google Cloud Storage). Enable it with the Store audit logs in object storage instance setting once instance object storage is configured.
Audit logs are written as newline-delimited JSON (NDJSON) to a dedicated logs/audit/ folder, partitioned by day:
logs/audit/dt=YYYY-MM-DD/audit_<min_id>_<max_id>.ndjson
Key properties:
- The export is incremental and runs in the background off the audit log hot path, so it adds no overhead to the requests that generate audit logs. In a highly available deployment a single server performs the export at a time.
- No history is backfilled: only audit logs created after the setting is enabled are exported. No audit log committed after enabling is ever skipped.
- Windmill never deletes the exported files from object storage, including when database retention expires or when the Delete logs from s3 periodically cleanup runs (that cleanup only applies to job and service logs, not the
logs/audit/folder).
This is the recommended setup when you forward audit logs to a SIEM for long-term security analysis: point your SIEM (or its collector) at the day-partitioned NDJSON files in the bucket. Because the bucket then holds the durable, complete history, you can safely lower the database Audit log retention (days) to only what you need for the in-app audit log view.
Exporting audit events through OpenTelemetry
On Enterprise Edition, every audit log is also emitted as an OpenTelemetry log record when it is written, so a collector can forward audit events to a SIEM in near real time. Nothing is replayed after a collector outage, so keep the object storage export for a complete history.
To enable it, turn on the Logs toggle of the OpenTelemetry instance setting (or set OTEL_LOGS_ENABLED=true on the servers and workers) and point Windmill at your collector with the endpoint of that setting or OTEL_EXPORTER_OTLP_ENDPOINT.
Audit events are the log records whose instrumentation scope name is windmill:audit.
With the OpenTelemetry Collector, a filter processor keeps only those:
processors:
filter/audit:
logs:
log_record:
- instrumentation_scope.name != "windmill:audit"
Each record carries these attributes, all strings:
| Attribute | Content |
|---|---|
kind | Always audit |
operation | The operation, such as scripts.create |
action_kind | Create, Update, Delete or Execute |
resource | The path, job id or setting name the operation applied to, when there is one |
parameters | The operation's metadata as a JSON-encoded object, or null |
workspace_id | The workspace, or global for instance-level operations |
username | The user who performed it |
email | That user's email |
span | The prefix of the token that authenticated the request, when there is one |
On a Pro license, operation, resource and parameters are redacted as in the audit logs page, and the Community Edition emits no audit events.
Audit events are emitted at INFO, so if you lower the log level, keep them with a directive for their target such as RUST_LOG=warn,windmill:audit=info.
Changes to instance settings are recorded as global_settings.update and global_settings.delete with the setting name as the resource, and the new value only when it is a boolean or a number.
Worker group changes are recorded as worker_config.update and worker_config.delete, and license key renewal, custom instance database setup or drop and secret backend migrations as settings.* operations.
Settings applied by windmill sync-config or the Kubernetes operator, or written by Windmill itself, are not recorded.
SCIM and SSO operations
Changes Windmill makes on behalf of your identity provider are recorded under the "Instance" scope and attributed to a system user rather than to the person who triggered them, so an audit reader can tell which channel moved a user. They only exist on Enterprise Edition, where SAML and SCIM are available.
| Username | Operations | Recorded when |
|---|---|---|
scim | users.scim_create, users.scim_update, users.scim_delete | SCIM provisioning creates a user, updates one (including deactivating it), or deletes one |
scim | instance_groups.scim_create, instance_groups.scim_update, instance_groups.scim_delete | SCIM provisioning creates an instance group, renames it or changes its membership, or deletes it |
sso | instance_groups.jit_adduser, instance_groups.jit_removeuser | Login-time group sync adds the user to, or removes them from, a SCIM-provisioned instance group at SSO login |
The scim and sso usernames belong to the system accounts [email protected] and [email protected], so no real user is credited with these entries.
The two login-time operations name the instance group as the resource and carry the affected user's email as a parameter.
They are only written when the "SSO groups claim" instance setting is set.
Viewing jobs metadata from the audit logs
You can retrieve the job ID from the job and then access all the job metadata, including the job arguments by fetching the information from the job itself which is stored for as long as the configured retention period.
Backing up audit logs
To back up the audit logs, simply backup the Postgres database or store the logs in a cold archive. In the cloud environment, we employ both methods for backup. On Enterprise self-hosted instances, exporting audit logs to object storage provides a durable, long-term archive that is independent of the database retention period.