Skip to content

Quickstart 2: Tiny Shop without the ouo11y SDK

You build the same Tiny Shop as in quickstart 1: an Express API and a React page with three products, a checkout, a broken API call and a button that throws. The difference is the browser. There is no @ouo11y/browser in it. You instrument it yourself with three upstream open-source libraries:

At the end, ouo11y shows the same session the SDK would have produced: crashed, with its activity list, its traces from the click to the API, and a replay. The code that made it is about 400 lines in one folder of your app, and you own every one of them.

This is for teams that want ouo11y’s sessions, health, traces and replays, but keep their browser instrumentation to standard libraries. It is also the clearest way to see what any RUM SDK does: you write it.

You build it one step at a time, with no scripts. Each page is one step, and each ends with what you should see.

  1. The plain app: an Express API and a React page, with no telemetry at all.
  2. The stack: the collector, Tempo, Loki, Mimir, rum-api and Grafana, one folder per service.
  3. Backend traces: the API traced with the OpenTelemetry Node SDK.
  4. The browser, by hand, in four parts: spans and the session, RUM events, replay and a custom event.
  5. The full picture: one crashed session in Grafana, with everything joined.
  6. What the SDK adds: what @ouo11y/browser does on top, and how hard each part is to add yourself.
  7. Clean up and troubleshooting.

Plan on about 45 minutes. You end with this folder:

  • Directorytiny-shop-otel/
    • Directorystack/ the ouo11y stack, one folder per service
      • compose.yaml
      • Directory10-collector/
        • …
      • Directory20-tempo/
        • …
      • Directory30-loki/
        • …
      • Directory40-mimir/
        • …
      • Directory50-rum-api/
        • …
      • Directory60-grafana/
        • …
    • Directorybackend/ Express API, OpenTelemetry traces
      • instrumentation.js
      • server.js
    • Directoryfrontend/ React app, OpenTelemetry + rrweb + web-vitals, no ouo11y SDK
      • Directorysrc/
        • Directorytelemetry/
          • config.js where the data goes
          • session.js the session id
          • otel.js spans, logs, propagation
          • events.js the RUM events
          • replay.js rrweb in chunks
          • index.js starts it all
        • App.jsx
        • main.jsx

This is the whole contract. The ouo11y SDK does exactly this. Here you do it yourself, and each row links to the step that builds it.

What How Step
Where OTLP/HTTP to the collector: :4318/v1/traces and :4318/v1/logs. Every request carries Authorization: Basic base64("<project id>:<project token>"). Step 6a
Who Resource attributes service.name, service.version, deployment.environment.name, and ouo.platform = "web". Step 6a
Which session session.id and user.id on every span and log record. As attributes, not on the resource: the session can change while the page lives. Also in the baggage header to your API. Step 6a
What happened OTLP log records whose event.name is session.start, session.end, exception, http.request.failed, navigation or web_vital, with the attributes of each. Any other name is a custom event. Step 6b
Replay rrweb events in chunks { session_id, seq, started_at, ended_at, events }, sent with POST to the collector’s replay port (:4319/v1/replay/chunks) with the same credential and the x-ouo-session-id and x-ouo-seq headers. Step 6c
Your browser code
otel.jsspans, each with session.id and user.id
events.jslog records: session.start, exception, navigation, web_vital, …
replay.jsrrweb events, cut into chunks
Collector
:4318/v1/tracesproject credential
:4318/v1/logsproject credential
:4319/v1/replay/chunkscredential + x-ouo-session-id, x-ouo-seq
Stored in
Tempotraces
Lokievents
rum-apireplay chunks
You see
ouo11y RUM in Grafanaone session: status, activity, traces, replay
Three files in your app make the three kinds of data. The collector is the only thing the browser talks to.

From these, ouo11y builds the session, its timeline, its traces and its replay, and gives it a status. A session whose events carry ouo.exception.fatal = true is crashed. One with a non-fatal exception or an http.request.failed is errored. Any other session is healthy. Sessions and health explains the rules.

The ouo. attributes in this table wait for an OpenTelemetry semantic convention. Everything else is an OpenTelemetry name already. See Semantic conventions.

  • Node 24 or newer, Docker Desktop, and VS Code with its code command. The first page checks all three.
  • Pull access to the ouo11y images. The collector, rum-api and Grafana images on ghcr.io/ouo11y are private for now.

Every file of the finished quickstart is on these pages. To skip ahead, download the finished folder:

⬇ Download the finished files (tiny-shop-otel.zip)

Unzip it (it makes a tiny-shop-otel/ folder), log in to ghcr.io (step 4b), then run each command from the folder named above it.

From stack/:

Terminal window
docker compose up -d

From backend/:

Terminal window
npm install
Terminal window
npm start

From frontend/, in a second terminal:

Terminal window
npm install
Terminal window
npm run dev

Open http://localhost:5173 and follow the full picture to see the session.

Ready to build it yourself? Start with the plain app.