Proxy access logs (nginx, Traefik)

The SDK sees what your app did. The reverse proxy in front of it sees what your users got, and the two differ exactly when it matters:

All of this shows on the Edge page, per app.

Why it is separate from request logs

An app running the SDK is seen twice: once by the proxy and once by itself. If proxy lines were stored as request logs, every count, error rate and quota reading would double. Edge lines are kept apart. They are metered as log lines, not requests, and nothing that counts requests reads them.

Setup

1. An Edge key per app

In the dashboard, API keys โ†’ Generate โ†’ Proxy access logs. An Edge key can send access-log lines and nothing else, so a key on a proxy host cannot be used to write errors or read data.

One proxy usually fronts several apps. Give each app its own key; the collector's routes file decides which line goes to which app (step 3).

2. A log format with timing

Traefik (including Dokploy) already writes everything needed when JSON access logs are on:

# traefik.yml
accessLog:
  filePath: /var/log/traefik/access.log
  format: json

Dokploy writes /etc/dokploy/traefik/dynamic/access.log this way by default.

By default Traefik drops request headers from the log. Keeping three of them adds the user agent and links each line to the app's trace:

accessLog:
  filePath: /var/log/traefik/access.log
  format: json
  fields:
    headers:
      names:
        User-Agent: keep
        Traceparent: keep
        X-Request-Id: keep

nginx's default combined format has no timing and no upstream status, so add a JSON format next to it:

log_format sentrinel escape=json
  '{"time":"$time_iso8601","host":"$host","server":"$server_name","method":"$request_method",'
  '"path":"$uri","status":$status,"request_time":$request_time,'
  '"upstream_time":"$upstream_response_time","upstream_header_time":"$upstream_header_time",'
  '"upstream_status":"$upstream_status",'
  '"upstream":"$upstream_addr","bytes_in":$request_length,"bytes_out":$bytes_sent,'
  '"client":"$remote_addr","ua":"$http_user_agent","request_id":"$request_id",'
  '"protocol":"$server_protocol","tls":"$ssl_protocol"}';

access_log /var/log/nginx/sentrinel.log sentrinel;
proxy_set_header X-Request-Id $request_id;

$uri has no query string, so tokens in URLs never reach the file.

$upstream_header_time is what tells a 502 the app sent from one nginx made up. When nginx cannot connect to the app, or gives up waiting, it still writes 502 or 504 into $upstream_status, but no headers ever arrived, so the header time is -. Those lines are the ones counted as not reached.

The last line is optional and worth having. nginx's $request_id is 32 hex characters, and the Sentrinel SDK accepts an incoming X-Request-Id in that shape as the trace id. With it, each proxy line links to the trace of the request the app handled.

3. The collector

curl -fsSL https://sentrinel.dev/install-edge.sh | \
  sudo EDGE_LOG=/var/log/nginx/sentrinel.log SENTRINEL_KEY=snt_edge_โ€ฆ bash

That installs sentrinel-edge as a systemd service running as an unprivileged user, which is added to the adm group so it can read nginx's logs. With a single key every line goes to that app. For several apps, edit the routes file /etc/sentrinel/edge-default.routes:

# <host or router:glob>   <key>
api.example.com           snt_edge_โ€ฆ
*.example.com             snt_edge_โ€ฆ
router:billing-*          snt_edge_โ€ฆ

The first match wins. router: matches Traefik's router name (without the @provider suffix) or nginx's server_name. Lines that match nothing are dropped and counted, never sent to the wrong app. Set EDGE_DEFAULT_KEY to catch them instead.

Check the setup before relying on it:

sudo sentrinel-edge --check

It reads the last 200 lines and prints the detected format and a parsed sample. It then lists every host it saw with the key it matched, and verifies each key against the API.

What is sent, and what is not

Default Setting
Query strings stripped, on the collector and again on the server always
Client IP truncated: IPv4 to /24 (41.59.12.0), IPv6 to /48 EDGE_CLIENT_IP=full or none
Static assets (.js, .css, images, fonts) skipped EDGE_KEEP_STATIC=true
Request and response bodies, headers never read โ€”
User agent sent, cut to 512 characters โ€”

The collector starts at the end of the file, so installing it does not replay history. It keeps its position in /var/lib/sentrinel, and it survives log rotation, whether the file is renamed or truncated in place.

Requirements

Edge logs are stored in ClickHouse, like database monitoring. A self-hosted Sentrinel on the Postgres-only telemetry store answers ingest with 501 and shows the setup card instead of data.