All jobs

Add OpenTelemetry Tracing to an Express Service

Yes. An agent instruments your Express service with OpenTelemetry for HTTP, Prisma and outbound calls, then exports real traces to a collector to prove it.

Run this on RushIn a Project that has your repository.

What the agent does

The agent starts with how the service boots, because tracing has to load before Express does. In an Express service like gothinkster/node-express-realworld-example-app it reads package.json and finds express ~4.18.1, @prisma/client ^4.16.1, typescript ~5.2.2, Nx 17.2.6 driving start (nx serve), build and test, and npm's package-lock.json. src/main.ts creates the app, adds cors and body-parser, mounts the routers from src/app/routes/routes.ts under /api, and listens on process.env.PORT || 3000. There is no tracing and no logger beyond console.*.

It adds @opentelemetry/sdk-node, @opentelemetry/auto-instrumentations-node and the OTLP trace exporter, and writes a src/instrumentation.ts that starts the SDK before anything else is imported, then loads it first from src/main.ts (or with --require in the Nx serve target and the Dockerfile's CMD). Configuration comes only from the standard variables: OTEL_SERVICE_NAME, OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_TRACES_SAMPLER and OTEL_RESOURCE_ATTRIBUTES, with no endpoint hard-coded. HTTP server spans come from the http and Express instrumentations, so each request gets a span named for its route template, such as GET /api/articles/:slug, rather than the raw path.

Database spans need more than auto-instrumentation here. Prisma 4 emits its own spans only with the tracing preview feature enabled in src/prisma/schema.prisma (provider postgresql, models Article, Comment, Tag and User) and @prisma/instrumentation registered with the SDK; the agent adds both and regenerates the client. For outbound HTTP, the http and undici instrumentations cover any call the service makes. In this repository axios is only used by the e2e tests and src/ makes no outbound calls, so the agent says so instead of inventing one, and the instrumentation is ready for the first one someone adds.

To prove it, the agent runs an OpenTelemetry Collector in its sandbox with an OTLP receiver and a file exporter, starts Postgres, applies the Prisma schema and seed (src/prisma/seed.ts), and starts the service with OTEL_EXPORTER_OTLP_ENDPOINT pointing at the collector. It calls the main endpoints (register, log in, list and create articles, add a comment, list tags), then reads the exported file and checks that each request produced a server span with Prisma child spans under the same trace id. It also runs the unit tests in src/tests/services/ to confirm nothing changed behavior.

Start it with one API call

POST https://api.prix.dev/o/{org}/sessions with this body:

{
  "agent": "claude",
  "input": "Add OpenTelemetry tracing to this Express service: HTTP server spans named by route, database spans for every Prisma query as children of the request span, and outbound HTTP client spans. Load the SDK before Express starts, and configure it only through the standard OTEL_* environment variables. Run a local OpenTelemetry Collector with a file exporter in the sandbox, start Postgres and the service, exercise the main endpoints, and check that each request's trace contains its database spans. Run the existing tests. Open a pull request with the changes, a short excerpt of the exported traces, and the environment variables a deployment must set.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-service"
    }
  ]
}
  1. Get an API key. In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
  2. Send the request. POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
  3. Read what came back. Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

What comes back

What comes back is a pull request. In a service shaped like the RealWorld Express app it adds the OpenTelemetry packages to package.json and package-lock.json, a new src/instrumentation.ts, the import or --require that loads it first, the previewFeatures = ["tracing"] line in schema.prisma, and a collector config used for local runs, such as otel-collector.yaml. The description lists every OTEL_* variable with its purpose and states which spans exist today and which will appear once the service makes outbound calls.

The proof is in the description and the trajectory: an excerpt of the exported spans showing a request span, for example on GET /api/articles, with Prisma query spans beneath it under one trace id, and the commands that started the collector, the database and the service, with their output. The service's own tests run in the same sandbox, and their output is kept in the trajectory.

Before merging, a reviewer checks that the SDK starts before Express in every entry point (local nx serve, the built dist/api and the Dockerfile), that request bodies, the Authorization header and query parameters with tokens are not recorded as span attributes, that the sampler setting suits production traffic, and that the exporter endpoint will be set by deployment rather than defaulting to localhost.

Run it on every service without tracing

An organization with many Node services can keep a list of the ones with no tracing and send this request once per repository, each as its own run with its own pull request. The prompt stays the same; the agent reads each service's entry point, framework version and database client and adapts the instrumentation to it.

Give the API key a webhook URL and each finished run POSTs run.completed or run.failed to your endpoint with the session URL, where the pull request link is. A key runs at most max_concurrent sessions at once (4 by default) and answers 429 past that, so a rollout across dozens of services goes through a queue.

The use case this job follows, with its automation and trigger: Hand off from your terminal.

Why run it on Rush?

Instrumentation work is easy to get half right on a laptop: spans appear for HTTP but not for the database, or only in dev because the production entry point never loads the SDK. Proving it needs a collector, a database and real requests. On Rush the agent sets all three up in its own sandbox, drives the endpoints and reads the exported spans, so the pull request shows traces the change actually produced, not a description of what it should produce.

The run does not touch your machine or your observability backend; the collector lives and dies with the sandbox. Every command, the collector's config and the exported spans stay in the session's trajectory, so a reviewer can see exactly how the proof was made. And the same request can instrument several services at once, each in isolation.

Frequently asked questions

Will it send traces to my vendor?

Not during the run: the proof uses a collector inside the sandbox with a file exporter. In your deployment the service exports over OTLP to whatever OTEL_EXPORTER_OTLP_ENDPOINT names, which works with any OTLP backend: Grafana Tempo, Honeycomb, Datadog's OTLP intake, Jaeger or your own collector.

Does it work with Prisma, TypeORM or Knex?

Yes, with different packages. Prisma needs its tracing preview feature on older versions and @prisma/instrumentation; TypeORM and Knex are traced through the pg or mysql2 driver instrumentations. The agent reads which client your service uses and wires the matching one.

Will tracing slow the service down?

Span creation is cheap, and the exporter batches spans in the background. The pull request sets the sampler through OTEL_TRACES_SAMPLER so production can sample a fraction of traces; pick the ratio from your traffic. Ask in the prompt for a measured comparison and the agent runs a short load test with and without the SDK in the sandbox.

What about logs and metrics?

This request adds traces only. Asking for metrics adds the OTLP metric exporter and HTTP server metrics through the same SDK; correlating logs means adding a logger such as pino with trace ids injected, which this service does not have yet. Keep them as separate pull requests so each is easy to review.