Logging
Control Zero Self-Managed produces two categories of logs: application logs (operational events from each service) and audit logs (governance decisions). This guide covers configuration, file locations, rotation, and debug mode.
Log Levels
Five log levels are available, from least to most verbose:
| Level | Description | Use Case |
|---|---|---|
error | Errors that require attention. Service failures, unhandled exceptions. | Production (minimal output) |
warn | Warnings that do not stop operation but indicate potential issues. | Production (default) |
info | Standard operational events. Service startup, configuration loaded, policy bundle refreshed. | Production (recommended) |
debug | Detailed operational events. Request/response metadata, policy evaluation details, timing. | Troubleshooting |
trace | Maximum verbosity. Full request/response bodies, internal state dumps. | Deep debugging only |
Default level: info for all services.
Setting Log Levels
All services at once
czctl set-log-level <level>
For example, to set all services to debug:
czctl set-log-level debug
Per-service
czctl set-log-level <level> --service <service-name>
Via runtime configuration
Log levels can also be changed at runtime through the cz_config table. Updates take effect within 60 seconds without a service restart:
curl -s https://localhost:8080/api/v1/config \
-X PATCH \
-H "Authorization: Bearer <admin-token>" \
-H "Content-Type: application/json" \
-d '{"key": "gateway.log_level", "value": "debug"}'
Available runtime config keys:
| Config Key | Equivalent Environment Variable |
|---|---|
gateway.log_level | CZ_GATEWAY_LOG_LEVEL |
api.log_level | CZ_API_LOG_LEVEL |
engine.log_level | CZ_ENGINE_LOG_LEVEL |
proxy.log_level | CZ_PROXY_LOG_LEVEL |
scout.log_level | CZ_SCOUT_LOG_LEVEL |
Via environment variables
Set the log level in config/controlzero.env or directly in docker-compose.yml:
| Service | Environment Variable |
|---|---|
| Gateway | CZ_GATEWAY_LOG_LEVEL |
| API | CZ_API_LOG_LEVEL |
| Compiled Engine | CZ_ENGINE_LOG_LEVEL |
| SSL Proxy | CZ_PROXY_LOG_LEVEL |
| Scout | CZ_SCOUT_LOG_LEVEL |
After changing environment variables, restart the affected service:
docker compose restart <service-name>
Log File Locations
Container logs (via Docker)
docker compose logs <service-name>
docker compose logs <service-name> --tail 100
docker compose logs <service-name> --follow
Persistent log files
If file-based logging is enabled (default for enterprise deployments), logs are written to:
| Service | File Path |
|---|---|
| Gateway | /var/log/controlzero/gateway.log |
| API | /var/log/controlzero/api.log |
| Compiled Engine | /var/log/controlzero/engine.log |
| SSL Proxy | /var/log/controlzero/proxy.log |
| Scout | /var/log/controlzero/scout.log |
| Audit DB | /var/log/controlzero/audit-db.log |
These paths are inside the containers and mapped to the host via Docker volumes. The host path defaults to /opt/controlzero/logs/.
Log Rotation
Default rotation policy
| Setting | Default Value |
|---|---|
| Maximum file size | 100 MB |
| Maximum file count | 10 (per service) |
| Compression | gzip after rotation |
Configuring rotation
Log rotation is managed through the Docker logging driver. Configure it in docker-compose.yml under each service:
services:
gateway:
logging:
driver: json-file
options:
max-size: '100m'
max-file: '10'
Apply changes:
docker compose restart
External log rotation
If you prefer to manage rotation with an external tool (logrotate or similar), set the Docker logging driver to local or none and configure your external rotation tool to manage files in /opt/controlzero/logs/.
SIEM Compatibility
All Control Zero services write Docker JSON logs to the standard Docker log path:
/var/lib/docker/containers/<container-id>/<container-id>-json.log
These logs are compatible with any SIEM or log collector that supports the Docker JSON log format, including:
- Splunk -- Use the Splunk Docker logging driver or forward from
/var/lib/docker/containers/ - Elasticsearch / OpenSearch -- Use Filebeat with the Docker input
- Datadog -- Use the Datadog Agent with Docker log collection enabled
- Fluentd / Fluent Bit -- Use the Fluentd Docker logging driver or tail the JSON log files
- Syslog -- Use the Docker syslog logging driver for direct forwarding
To forward logs via the Docker daemon, configure the logging driver globally in /etc/docker/daemon.json or per-service in docker-compose.yml.
Audit Logs vs Application Logs
Application logs
Operational events from each service. Used for troubleshooting and monitoring service health. Contain information like startup messages, configuration changes, error stack traces, and request timing.
Audit logs
Governance decision records stored in the immutable audit trail. Every policy evaluation produces an audit record with:
- Timestamp
- Agent identity
- Action evaluated
- Resource
- Decision (allow or deny)
- Policy ID that matched
- Token usage (if applicable)
- Request metadata
Audit logs are queried through the dashboard or the management API. They are stored separately from application logs and have their own retention policy.
Audit log retention
Configure retention in config/audit.yml:
audit:
retention_days: 365
partition_by: month
Debug Mode
Enable debug mode when troubleshooting specific issues. Debug mode increases log verbosity for all services simultaneously.
Enable debug mode
czctl set-log-level debug
Enable trace mode
Use trace mode only when debug output is insufficient. Trace mode logs full request and response bodies, which can generate large volumes of output and may include sensitive data.
czctl set-log-level trace
Disable debug mode
Restore the default log level after troubleshooting:
czctl set-log-level info
Structured JSON Log Format
All services output logs in structured JSON format for compatibility with log aggregation systems (Splunk, Elasticsearch, Datadog, and similar):
{
"timestamp": "2026-04-03T10:15:30.123Z",
"level": "info",
"service": "gateway",
"message": "Policy evaluation completed",
"fields": {
"agent_id": "agent-prod-01",
"action": "database:query",
"decision": "allow",
"latency_ms": 1.2,
"policy_id": "pol_abc123"
}
}
Parsing with common tools
# Filter errors from gateway logs
cat /opt/controlzero/logs/gateway.log | jq 'select(.level == "error")'
# Count decisions by result
cat /opt/controlzero/logs/gateway.log | jq 'select(.message == "Policy evaluation completed") | .fields.decision' | sort | uniq -c