Skip to main content

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:

LevelDescriptionUse Case
errorErrors that require attention. Service failures, unhandled exceptions.Production (minimal output)
warnWarnings that do not stop operation but indicate potential issues.Production (default)
infoStandard operational events. Service startup, configuration loaded, policy bundle refreshed.Production (recommended)
debugDetailed operational events. Request/response metadata, policy evaluation details, timing.Troubleshooting
traceMaximum 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 KeyEquivalent Environment Variable
gateway.log_levelCZ_GATEWAY_LOG_LEVEL
api.log_levelCZ_API_LOG_LEVEL
engine.log_levelCZ_ENGINE_LOG_LEVEL
proxy.log_levelCZ_PROXY_LOG_LEVEL
scout.log_levelCZ_SCOUT_LOG_LEVEL

Via environment variables

Set the log level in config/controlzero.env or directly in docker-compose.yml:

ServiceEnvironment Variable
GatewayCZ_GATEWAY_LOG_LEVEL
APICZ_API_LOG_LEVEL
Compiled EngineCZ_ENGINE_LOG_LEVEL
SSL ProxyCZ_PROXY_LOG_LEVEL
ScoutCZ_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:

ServiceFile 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

SettingDefault Value
Maximum file size100 MB
Maximum file count10 (per service)
Compressiongzip 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