Skip to content
MLReps
Back to setup

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 telemetry

By hand, for every language

Four steps, and two of them are one-offs.

  1. 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 in
  2. Point 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_COMMIT
    Where you deployWhere the variables go
    VercelProject → Settings → Environment Variables (or `vercel env add`), then redeploy.With `@vercel/otel` the exporter reads them at startup; no separate agent.
    KubernetesThe 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 / FargateThe 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 LambdaThe 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.
    RenderDashboard → 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`.
    DockerThe 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`.
    NetlifySite 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.
    TerraformThe `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]
  3. 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":[]}'
  4. 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 repository

    What 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.