Open source · self-hosted · AGPL-3.0

Every layer of your stack, folded onto one timeline.

Syncline replays a user session as video and the backend distributed trace and the SQL queries that ran — on one synchronized scrubber.

I.The problem

Three tools. One clock.

Session replay

Shows you the browser. The click, the hesitation, the field they gave up on — and then it stops at the network boundary.

Distributed tracing

Shows you the backend. Spans, services, the query that took 1.8 seconds — with no idea which person was sitting there waiting on it.

The seam

Neither shows you this. So “checkout felt slow” is still archaeology: eyeball a video, guess a timestamp, go hunting in another tool with a different clock.

II.How the stitch works

Four steps. None of them yours.

  1. 01
    The browser mints the trace id. The SDK patches fetch and XHR and attaches a W3C traceparent to requests bound for origins you list — never to anyone else’s.
  2. 02
    It writes that id into the recording. An rrweb custom event lands at the exact frame the request fired, so the replay carries its own index.
  3. 03
    Your backend does nothing special. Standard OpenTelemetry reads the header and continues the trace. Database spans inherit it.
  4. 04
    The viewer resolves the seam. Player time, to trace id, to span tree — drawn beneath the video on the same clock.

III.The whole integration

No backend SDK. Ever.

If your services already emit OpenTelemetry, the backend change is two environment variables.

Once, at the top level
import { startRecording } from 'syncline-browser';

startRecording({
  key: 'pk_live_...',
  endpoint: 'https://syncline.example.com',
  traceOrigins: ['https://api.acme.com'],
});

IV.What makes it different

Six decisions. Each one load-bearing.

a.

The link is an id, not a timestamp

The browser mints a W3C traceparent and writes it into the replay stream at the frame the request fired. Clock skew can misdraw a lane by milliseconds; it can never attribute a request to the wrong trace.

b.

Sampling runs backwards

The browser decides. A recorded session forces sampled=1, so you never open the replay of a slow request whose spans were thrown away — which is exactly the request you wanted.

c.

No backend SDK, ever

Syncline is a plain OTLP sink. Point any OpenTelemetry exporter at it and the trace continues. It can sit beside your existing tracing vendor through a collector fan-out instead of replacing it.

d.

The recording explains itself

Trace ids live inside the rrweb stream, not in a side table. Export a session to a file, hand it to someone else, and it still resolves to its spans.

e.

Masked by default

Inputs are masked unless you opt out. Query values are stripped from recorded URLs and fragments dropped entirely. Headers and request bodies are never captured at all.

f.

Your infrastructure

Postgres, Redis, and any S3-compatible store. Session recordings are the most sensitive telemetry you will ever collect, and none of it leaves your network.

V.In the viewer

Drag to 00:42. See all of it at once.

The user clicked Checkout, that fired POST /api/checkout, which fanned out to four spans, one of which was a query that took 1.8 seconds. One scrubber, one answer.

Three strata, one clock

Network, backend and database lanes are drawn beneath the replay and read the player’s own time every frame. The video is the master clock, so nothing can drift out from under it.

Click a request to zoom

Selecting a bar focuses the timeline on that request with room either side, and one control returns you to the whole recording.

The full span, not a summary

Every attribute the span carried — db.statement, the HTTP status, the service that served it, the trace id — is there to read, not rolled up into an average.

An honest error bar

When the round trip to the browser was slow enough for the two clocks to disagree, the viewer draws the uncertainty band rather than a confidently wrong line.

Running in about five minutes.

Docker for Postgres, Redis and MinIO. Three Node processes. One seeded project key.