Logging
bext outputs structured JSON logs by default, with per-request tracing fields, configurable levels, and built-in rotation. Every log line includes enough context to correlate requests across the stack without external instrumentation.
Configuration
# bext.config.toml
[logging]
level = "info" # trace | debug | info | warn | error
format = "json" # json | pretty (pretty is human-readable, for dev)
output = "stdout" # stdout | file
file_path = "/var/log/bext/bext.log"
rotate = true # enable built-in log rotation
rotate_max_size_mb = 100 # rotate when file exceeds this size
rotate_max_files = 10 # keep this many rotated files
rotate_compress = true # gzip rotated files
# Filter by module for targeted debugging
[logging.filters]
bext_server = "debug" # server core at debug level
bext_core = "info" # core library at info
bext_plugin = "warn" # suppress noisy plugin logs
hyper = "warn" # quiet the HTTP transport layer
You can also set the log level via environment variable or CLI flag:
# Environment variable (overrides config file)
BEXT_LOG_LEVEL=debug bext-server run
# CLI flag (overrides both)
bext-server --log-level trace run
Structured Log Fields
Every HTTP request produces a structured log entry with these fields:
{
"timestamp": "2026-04-03T14:22:01.847Z",
"level": "INFO",
"module": "bext_server::handler",
"request_id": "01J5K8M2N3P4Q5R6S7T8",
"method": "GET",
"path": "/products/widget-pro",
"status": 200,
"duration_ms": 12.4,
"bytes": 28430,
"encoding": "br",
"cache": "HIT",
"tenant_id": "acme-corp",
"remote_addr": "203.0.113.42",
"user_agent": "Mozilla/5.0 ...",
"referer": "https://acme.com/"
}
Key fields:
| Field | Description |
|---|---|
request_id |
Unique ULID for each request. Passed downstream via the X-Request-Id header. |
duration_ms |
Total time from request received to response body complete. |
cache |
HIT, MISS, STALE (SWR), or BYPASS. |
tenant_id |
Multi-tenant identifier resolved from the hostname. |
encoding |
Response compression: br, gzip, or identity. |
Log Levels
| Level | Use |
|---|---|
trace |
Wire-level detail: header bytes, V8 pool acquire/release, cache key computation. Very verbose. |
debug |
Request routing decisions, plugin hook invocations, cache operations, SSR render timing. |
info |
Request log lines, server startup/shutdown, TLS certificate issuance, deploy events. |
warn |
Deprecated config options, slow renders (>500ms), retried operations, approaching rate limits. |
error |
SSR failures, plugin panics, TLS errors, database connection failures. |
For production, info is recommended. Drop to debug on a single instance when investigating issues.
Forwarding to External Systems
Loki (Grafana)
bext's JSON output works with Promtail out of the box:
# promtail-config.yaml
scrape_configs:
- job_name: bext
static_configs:
- targets: [localhost]
labels:
job: bext
__path__: /var/log/bext/bext.log
pipeline_stages:
- json:
expressions:
level: level
request_id: request_id
status: status
duration: duration_ms
ELK Stack (Elasticsearch + Logstash + Kibana)
Pipe stdout to Filebeat, or point Filebeat at the log file:
# filebeat.yml
filebeat.inputs:
- type: log
paths:
- /var/log/bext/bext.log
json.keys_under_root: true
json.add_error_key: true
output.elasticsearch:
hosts: ["https://es.internal:9200"]
index: "bext-logs-%{+yyyy.MM.dd}"
Datadog
Use the Datadog Agent's log collection with JSON auto-parsing:
# /etc/datadog-agent/conf.d/bext.d/conf.yaml
logs:
- type: file
path: /var/log/bext/bext.log
service: bext
source: bext
sourcecategory: web
Docker and Stdout
When running in Docker, keep output = "stdout" and let the container runtime handle log collection. Docker captures stdout as JSON by default with json-file or journald drivers:
# View logs
docker logs -f bext-server
# Follow with jq for readable output
docker logs -f bext-server 2>&1 | jq .
Filtering Noisy Modules
During development, you may want to silence the HTTP transport or TLS handshake logs:
BEXT_LOG_LEVEL="info,hyper=warn,rustls=warn,h2=warn" bext-server run
The filter syntax follows the RUST_LOG format: level sets the default, and module=level overrides for specific modules.