Optional · Observe
Send what is actually running.
Point an OpenTelemetry exporter here and the architecture map gains a second half: which files traffic went through, how slow they were, and which calls your running system made that the scan did not find.
What leaves your process
OpenTelemetry’s own SDK does the instrumentation — there is no agent of ours inside your application. Of what it sends, we keep counts and latency buckets against a file or an edge, and nothing else. Not collected by MLReps: logs, exception messages, URLs, query text, container and custom metrics. They stay where you already collect them. A trace id is a handle onto one customer’s request; following one is a separate thing we have not built.
The line is drawn when a batch arrives, not inside your process: of what your SDK sent, MLReps reads a fixed list of fields — each one checked to be the kind of thing it claims to be, so a query statement in a field meant to hold SELECT is refused — and never reads the rest. An exporter of ours that would apply that list before a batch leaves your network is not built.
Until it is set, Runtime says so
Nothing above depends on this: the architecture, the issues and the pull-request checks all work without it. Where latency would be, Runtime reads not measured — and shows no numbers rather than zeros.
The command line never writes to your repository
No commit, no branch, no pull request — `npx mlreps telemetry` holds no credential of ours that could. What it does, running on your own machine, is offer to put those lines into your compose file or your Kubernetes manifest: it prints the diff, asks, and leaves the change in your working tree for you to review and commit like any other. Terraform it names and does not edit, because an ECS task definition carries its environment inside a JSON string and a wrong edit to your infrastructure is worse than none.
Or let MLReps do it
On a Node.js or Next.js repository, npx mlreps telemetry mints the key itself after one click here and writes the instrumentation file with that key already in it — that key may send telemetry and nothing else: it cannot read anything back, and it cannot publish a scan. Where your runtime needs one more line to load that file — a compose file, a Kubernetes manifest — it offers to make the edit too: it shows the diff, asks, and leaves it in your working tree. The steps below are the same thing by hand, and are what to follow for every other language.
command
npx mlreps telemetryBy hand, for every language
Four steps, and two of them are one-offs.
A project key
Telemetry belongs to an organisation, and a key is how a running system says which one it is. A key for telemetry is refused by the publish endpoint, so the one in your production environment cannot rewrite the scan your dashboards are drawn from.
Sign inPoint an exporter at it
Two of these are the ones everybody’s first attempt misses. The protocol, because several SDKs default to gRPC and this endpoint reads OTLP over HTTP; and the endpoint, which is a base — the SDK appends /v1/traces itself, so pasting the full path produces a 404 that looks like we are down.
environment
OTEL_EXPORTER_OTLP_ENDPOINT=https://www.mlreps.com/api/observe OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer <your project key> OTEL_SERVICE_NAME=your-service # CPU and memory ride on the metrics exporter (see below for your language) OTEL_METRICS_EXPORTER=otlp # and the deployed commit, so traffic is drawn on the scan it belongs to OTEL_RESOURCE_ATTRIBUTES=service.version=$GIT_COMMITWhere you deploy Where the variables go Vercel Project → Settings → Environment Variables (or `vercel env add`), then redeploy.With `@vercel/otel` the exporter reads them at startup; no separate agent. Kubernetes The container's `env:` in the Deployment, or point `OTEL_EXPORTER_OTLP_ENDPOINT` at an OpenTelemetry Collector Service.A Collector as a sidecar or DaemonSet keeps the endpoint and key out of every pod spec. AWS ECS / Fargate The task definition's `environment` (or `secrets` from Secrets Manager for the key).The ADOT sidecar collector is the common shape; the app then exports to it on localhost. AWS Lambda The function's environment variables, plus the OpenTelemetry Lambda layer (the extension that flushes spans before the freeze).Without the layer a short invocation exits before its batch is sent, and nothing arrives. Fly.io `fly secrets set OTEL_EXPORTER_OTLP_ENDPOINT=… OTEL_EXPORTER_OTLP_HEADERS=…` — secrets become environment variables on the next deploy. Render Dashboard → Environment, or `envVars` in `render.yaml`. Heroku `heroku config:set OTEL_EXPORTER_OTLP_ENDPOINT=… OTEL_EXPORTER_OTLP_HEADERS=…`. Google App Engine `env_variables:` in `app.yaml`. Docker The service's `environment:` in compose, or `--env` / `--env-file` on `docker run`.A variable exported in your shell is not inherited by the container — it has to be passed in. Serverless Framework `provider.environment` in `serverless.yml`. Netlify Site configuration → Environment variables, or `netlify env:set`, then redeploy.A build-time variable is not a runtime one for functions — set it for all deploy contexts. Terraform The `environment` / `env` block of the resource that runs the container (`aws_ecs_task_definition`, `google_cloud_run_service`, `kubernetes_deployment`), reading the key from a variable or a secret manager.A credential written into a committed .tf file is a credential in your git history — use a var or a secret reference. Bare VM / systemd `Environment=` lines in the service's unit file (or an `EnvironmentFile=`), then `systemctl daemon-reload` and restart.A variable set in an interactive shell does not reach a service systemd starts. CPU and memory are read from the metrics exporter, and only those — nothing else your metrics carry is kept. Each stack sends them from its own package:
- Node.js —
@opentelemetry/host-metrics: process.cpu.utilization, process.memory.usage - Python —
opentelemetry-instrumentation-system-metrics: process.cpu.utilization or process.runtime.cpython.* - Java —
the Java agent, by default: jvm.cpu.recent_utilization, jvm.memory.used - Go —
go.opentelemetry.io/contrib/instrumentation/runtime: go.memory.used — memory only - .NET —
OpenTelemetry.Instrumentation.Process: process.memory.usage — memory only
The machines themselves — CPU, memory, the fullest disk, disk and network traffic — come from an OpenTelemetry Collector on each host. A host is shown by a fingerprint of its name; the name is not stored unless you set a label.
collector config for host metrics
receivers: hostmetrics: collection_interval: 60s scrapers: cpu: metrics: system.cpu.utilization: { enabled: true } memory: metrics: system.memory.utilization: { enabled: true } filesystem: {} disk: {} network: {} processors: resourcedetection: detectors: [system] # sets host.name, which MLReps keeps only as a fingerprint # resource: # attributes: # - { key: mlreps.host.label, value: db-primary, action: upsert } # optional: how MLReps names this host exporters: otlphttp: endpoint: https://www.mlreps.com/api/observe headers: { Authorization: "Bearer <your project key>" } service: pipelines: metrics: receivers: [hostmetrics] processors: [resourcedetection] exporters: [otlphttp]- Node.js —
Or check the path by hand
An empty batch is accepted and tells you whether the key works. The response to every batch is the diagnostic: how many calls arrived, how many were placed on the graph, and a named count for each one that was not.
in a terminal
curl -X POST https://www.mlreps.com/api/observe/v1/traces \ -H "Content-Type: application/json" \ -H "Authorization: Bearer <your project key>" \ -d '{"resourceSpans":[]}'Then look at it
Traffic appears under the map on the architecture screen. If spans arrive and none of them can be placed, that screen says so rather than showing a quiet system — the two are different failures and only one of them is yours.
Start with one repositoryWhat the spans buy you: the database call a route repeats inside one request, counted rather than inferred.
Spans are placed by code.filepath and code.function, which most SDKs do not send by default. Without them a span carries a route at best, and everything lands on the handler rather than the code inside it. The endpoint is /api/observe/v1/traces.