Skip to main content

Centralized Logs

OPBX can collect the structured logs of its services into one searchable store (Grafana Loki), viewed in Grafana. The stack is off by default and is meant for platform operators and developers. Without it, the same logs are still written to the container output (docker compose logs).

What you get​

  • Platform logs: UI actions, configuration changes and provisioning.
  • Call flow logs: voice webhooks, CXML routing decisions, queue, dialer and AMD activity. Every call-related line carries the Cloudonix session token, so you can follow one call across all services.

Call-related entries are shown with the session token as a prefix:

[19461556-42] {"ts":"...","msg":"CDR created successfully","disposition":"ANSWER",...}

Prerequisites​

  • The stack is started with Docker Compose. The logs profile adds the Loki, Grafana, Alloy and docker-socket-proxy services.
  • Port 80 (the nginx proxy) is reachable from your browser, because Grafana is served at /logs/.
  • You can edit .env.

Enable the stack​

  1. Make sure .env has a Grafana password. It is required, even when the logs profile is off, and there is no default; docker/scripts/generate-secrets.sh sets one. Also set LOG_STACK_ENABLED=true.

    LOG_STACK_ENABLED=true
    GRAFANA_ADMIN_PASSWORD=<a long random value, for example from: openssl rand -hex 32>
  2. Start the profile:

    docker compose --profile logs up -d
  3. Open http://localhost/logs/ (or your public URL plus /logs/) and log in as admin with GRAFANA_ADMIN_PASSWORD.

Two dashboards are provisioned: OPBX Call Flow Logs (with the session-token audit-trail filter) and OPBX Platform Logs. Both have a free-text search box. Click a log row to see its parsed fields in the details panel.

Configuration​

VariableDefaultDescription
LOG_STACK_ENABLEDfalseDoes not start anything; the logs profile starts the stack. When true, the app container's start-up check also refuses an empty or placeholder GRAFANA_ADMIN_PASSWORD
GRAFANA_ADMIN_USERadminGrafana admin user name
GRAFANA_ADMIN_PASSWORDnoneGrafana admin password. Required: Compose refuses to start any service while it is unset
GRAFANA_ROOT_URLhttp://localhost/logs/Must match the URL your browser uses. A mismatch makes Grafana refuse dashboard queries with "origin not allowed"
GRAFANA_CSRF_TRUSTED_ORIGINSemptyExtra hostnames that reach the UI (for example an ngrok or second domain). Space-separated, no scheme
LOKI_RETENTION_HOURS168How long logs are kept (7 days)

Restart Grafana after changing these: docker compose --profile logs up -d --force-recreate grafana.

Example queries​

In Grafana, open Explore and choose the Loki data source:

# Full audit trail for one call
{log_type="call_flow"} | session_token="sess-abc123"

# All errors across the stack
{job="opbx"} |= "\"level\":\"error\""

# One organization's call traffic
{log_type="call_flow", org_id="12"}

# Queue offers
{service="acd-worker"} |~ "offering to agents"

Exporting to a file​

  • CSV: in the panel menu (⋮), choose Inspect → Data → Download CSV. The export contains exactly what your filters show.

  • Plain text, on the host:

    ./scripts/export-logs.sh --session sess-abc123 call-audit.txt

    Options: --type call_flow|platform (default call_flow), --session <token>, --text <string>, --all (include lines without a session token) and --since <duration> (default 24h). Run ./scripts/export-logs.sh --help for the full list.

How it works​

  • Every service writes one JSON object per line to stdout. The shared fields are ts, level, msg, service and log_type. org_id, session_token and call_id are added when known.
  • Alloy reads the logs of containers that carry the label logging.opbx/collect=true, and sends them to Loki. It does not mount the Docker socket itself: a docker-socket-proxy service holds a read-only view of the Docker API and Alloy talks to that.
  • Credentials (API keys, passwords, bearer tokens and signed-URL signatures) are masked before logs leave Laravel. session_token is deliberately not masked because it is the audit-trail key.
  • Grafana is served through the nginx proxy at /logs/. No extra port is published.
  • With the profile off, nothing is collected. The container output is unchanged.
Platform operators only

The log viewer shows every tenant's call traffic. Protect the Grafana admin password, and do not share the dashboards outside the operations team.