Logging & observability
Ferron supports multiple observability outputs, so you can start with local log files and later move to centralized telemetry without changing your application stack.
This page focuses on common deployment patterns. For directive-level details, see Configuration: observability & logging and Observability backends reference.
Basic production logs to files
Use this when running Ferron directly on a VM or bare metal and collecting logs from disk.
globals {
log_date_format "%d/%b/%Y:%H:%M:%S %z"
log_format "{client_ip} - {auth_user} [{timestamp}] \"{method} {path_and_query} {version}\" {status_code} {content_length} \"{header:Referer}\" \"{header:User-Agent}\""
}
example.com {
log "/var/log/ferron/example.com.access.log"
error_log "/var/log/ferron/example.com.error.log"
}JSON-format access logs
Use this when you need structured logs for easier parsing by log aggregation tools (for example, ELK Stack, Splunk, or cloud-native log processors).
log_json directive is available in Ferron 2.7.0 or newer.
globals {
log_date_format "%d/%b/%Y:%H:%M:%S %z"
log_json
}
example.com {
log "/var/log/ferron/example.com.access.log"
error_log "/var/log/ferron/example.com.error.log"
}This produces JSON objects with the following default fields: timestamp, client_ip, auth_user, method, path_and_query, version, status_code, content_length, referer, and user_agent. When log_json is set, log_format is ignored, while log_date_format still controls the timestamp field format.
You can also add custom properties using placeholders:
globals {
log_date_format "%d/%b/%Y:%H:%M:%S %z"
log_json request_id="{header:X-Request-Id}" request_target="{method} {path_and_query}"
}
example.com {
log "/var/log/ferron/example.com.access.log"
error_log "/var/log/ferron/example.com.error.log"
}Container-friendly logging to stdout/stderr
Use this when running Ferron in containers (Docker, Kubernetes), where platform log collectors read process streams.
stdlog directives are available in Ferron 2.5.0 and newer.
example.com {
// Access logs to stdout
log_stdout
// Error logs to stderr
error_log_stderr
}If your platform expects everything on one stream, you can use log_stderr and error_log_stderr, or log_stdout and error_log_stdout.
Centralized observability with OTLP
Use this when shipping logs, metrics, and traces to an OpenTelemetry collector.
OTLP directives are available in Ferron 2.2.0 and newer.
globals {
otlp_service_name "ferron-prod"
otlp_logs "http://otel-collector.internal:4317/v1/logs" protocol="grpc"
otlp_metrics "http://otel-collector.internal:4317/v1/metrics" protocol="grpc"
otlp_traces "http://otel-collector.internal:4317/v1/traces" protocol="grpc"
}If you use HTTP OTLP endpoints, set protocol="http/protobuf" (or "http/json") and optionally an auth header:
globals {
otlp_logs "https://otel.example.net/v1/logs" protocol="http/protobuf" authorization="Bearer YOUR_TOKEN"
otlp_metrics "https://otel.example.net/v1/metrics" protocol="http/protobuf" authorization="Bearer YOUR_TOKEN"
otlp_traces "https://otel.example.net/v1/traces" protocol="http/protobuf" authorization="Bearer YOUR_TOKEN"
}Hybrid setup: local fallback + OTLP
A practical migration strategy is to keep file logs for local troubleshooting while also exporting telemetry centrally.
globals {
otlp_service_name "ferron-prod"
otlp_logs "http://otel-collector.internal:4317/v1/logs" protocol="grpc"
otlp_metrics "http://otel-collector.internal:4317/v1/metrics" protocol="grpc"
otlp_traces "http://otel-collector.internal:4317/v1/traces" protocol="grpc"
}
example.com {
log "/var/log/ferron/example.com.access.log"
error_log "/var/log/ferron/example.com.error.log"
}Log rotation for file-based logging
Use this when you need to manage disk space usage for log files by rotating and retaining them based on size and count.
Log rotation directives are available in Ferron 2.6.0 and newer.
globals {
log_date_format "%d/%b/%Y:%H:%M:%S %z"
log_format "{client_ip} - {auth_user} [{timestamp}] \"{method} {path_and_query} {version}\" {status_code} {content_length} \"{header:Referer}\" \"{header:User-Agent}\""
// Rotate access logs when they reach 100MB, keep 5 rotated files
log_rotate_size 104857600 // 100 * 1024 * 1024
log_rotate_keep 5
// Rotate error logs when they reach 50MB, keep 3 rotated files
error_log_rotate_size 52428800 // 50 * 1024 * 1024
error_log_rotate_keep 3
}
example.com {
log "/var/log/ferron/example.com.access.log"
error_log "/var/log/ferron/example.com.error.log"
}Hybrid setup with log rotation and OTLP
You can combine log rotation with OTLP export for a robust observability setup:
globals {
otlp_service_name "ferron-prod"
otlp_logs "http://otel-collector.internal:4317/v1/logs" protocol="grpc"
otlp_metrics "http://otel-collector.internal:4317/v1/metrics" protocol="grpc"
otlp_traces "http://otel-collector.internal:4317/v1/traces" protocol="grpc"
// Rotate access logs when they reach 100MB, keep 5 rotated files
log_rotate_size 104857600
log_rotate_keep 5
// Rotate error logs when they reach 50MB, keep 3 rotated files
error_log_rotate_size 52428800
error_log_rotate_keep 3
}
example.com {
log "/var/log/ferron/example.com.access.log"
error_log "/var/log/ferron/example.com.error.log"
}Notes and troubleshooting
- Start simple: file logs or std streams first, OTLP second.
- Keep
otlp_no_verification #falseunless you are in a controlled test environment. - If logs are missing, verify backend support in your Ferron build and check endpoint/protocol pairing.
- If Ferron is behind a reverse proxy, use either
trust_x_forwarded_for(when using a proxy that sendsX-Forwarded-Forheader) orprotocol_proxy(when using a proxy that send PROXY protocol header) to ensure correct client IP logging. - Use placeholders reference when customizing
log_format.