115 lines · 4.6 KB · Text
RawDownload
---title: Tracingdescription: "Export traces from a self-hosted server to an OpenTelemetry collector, so a slow request becomes a waterfall instead of a stopwatch."--- When a request through your server is slow, the useful question is which part wasslow: building the executor stack, initializing plugins, a storage query, or theupstream integration answering. The server already produces spans for all ofthat — pointing it at a collector is what makes them readable. Export is off by default and stays off until you set an endpoint. With none setthere is no exporter and no buffer, and nothing leaves the process. ## Turn it on Set the collector's base URL. The server appends `/v1/traces`. ```bashdocker run -d \  -p 4788:4788 \  -v executor-data:/data \  -e OTEL_EXPORTER_OTLP_ENDPOINT=http://collector.local:4318 \  ghcr.io/usefulsoftwareco/executor-selfhost:latest``` Traces are sent as OTLP over HTTP with a JSON payload, which every collectoraccepts — the OpenTelemetry Collector, Grafana Alloy, Jaeger, or a hosted backend.Port `4318` is the OTLP/HTTP convention, but the URL is whatever you point it at. | Variable                             | Default | Purpose                                                                     || ------------------------------------ | ------- | --------------------------------------------------------------------------- || `OTEL_EXPORTER_OTLP_ENDPOINT`        | unset   | Collector base URL. Turns on trace export; `/v1/traces` is appended.        || `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | unset   | Full traces URL, used as-is. Overrides the base for this signal.            || `OTEL_EXPORTER_OTLP_HEADERS`         | unset   | `key=value,key2=value2` headers, for a collector that needs authentication. || `EXECUTOR_OTEL_EXPORT_LOGS`          | `false` | Also export logs. A separate opt-in — logs carry more than timings.         || `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT`   | unset   | Full logs URL. Required if only the traces endpoint is set.                 | Traces report under the service name `executor-selfhost`. On boot the server logsthe endpoint it will export to, so a missing line means export is off. For a hosted backend, put the credential in the headers: ```bash-e OTEL_EXPORTER_OTLP_ENDPOINT=https://collector.example.com \-e OTEL_EXPORTER_OTLP_HEADERS='authorization=Bearer <token>'``` ## Using motel locally [motel](https://github.com/kitlangton/motel) is a local OTLP store and viewer: onecommand, a SQLite file, no Docker and no account. It is the quickest way to look ata trace on your own machine. It needs [Bun](https://bun.sh/), and listens on port `27686`: ```bashbunx @kitlangton/motel``` That starts the ingest server and opens the terminal UI. For ingest without theUI, run `bunx @kitlangton/motel server`. If Executor runs directly on the same machine, point it at motel: ```bashOTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:27686``` From a container, motel is on the host, not in the container's network — and itbinds loopback by default, so it also has to listen on an address the containercan reach: ```bash# start motel so it accepts connections from outside the hostMOTEL_OTEL_HOST=0.0.0.0 bunx @kitlangton/motel server docker run -d \  -p 4788:4788 \  -v executor-data:/data \  --add-host host.docker.internal:host-gateway \  -e OTEL_EXPORTER_OTLP_ENDPOINT=http://host.docker.internal:27686 \  ghcr.io/usefulsoftwareco/executor-selfhost:latest``` <Warning>  `MOTEL_OTEL_HOST=0.0.0.0` exposes the trace store to your whole network. Traces carry request URLs  and timings, and logs carry more. Use it on a trusted network, or publish the port only to the  container.</Warning> ## Reading a trace A single API request looks like this — each line a span, indented under its parent,with the time it took: ```http.server GET                        31ms  executor.stack.http.resolve          28ms    executor.stack.build               28ms      executor.stack.scoped_executor   27ms        executor.plugins.init          16ms        executor.stack.create_executor  2ms        executor.subject.touch          7ms          fumadb.subject.findFirst      2ms          fumadb.subject.create         5ms  fumadb.integration.findMany           1ms``` The indentation is where the answer lives. Time held by `http.server` but not byany child is time spent outside the instrumented code — the network in front ofthe server, or a tunnel. Time inside a single child is the phase to look at next. Requests arriving with a `traceparent` header continue that trace rather thanstarting a new one, so a request through a proxy or tunnel keeps one trace id fromend to end.