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.