Backend tracing

There is no Syncline backend SDK, and there should not be one. Syncline is a plain OTLP sink: if your services already emit OpenTelemetry, the integration is two environment variables.

Point your exporter at Syncline

OTEL_EXPORTER_OTLP_ENDPOINT=https://syncline.example.com/v1/ingest
OTEL_EXPORTER_OTLP_HEADERS=x-syncline-key=sk_live_...

The secret key, not the public one. Public keys are browser credentials and are refused here.

Both spellings work. An exporter appends /v1/traces to OTEL_EXPORTER_OTLP_ENDPOINT but uses OTEL_EXPORTER_OTLP_TRACES_ENDPOINT verbatim, so Syncline answers at both paths. Getting that wrong otherwise produces a 404 swallowed inside a batch exporter, which surfaces only as traces never arriving.

Allow the header

Access-Control-Allow-Headers: traceparent
Your own API has to accept this from the browser. It is the most common reason an integration appears broken.

Nothing else changes

Auto-instrumentation reads the incoming traceparent, makes the server span a child of the browser’s span, and propagates the trace id to everything downstream — including database spans, which is what fills the database lane.

// Node, for example. Nothing Syncline-specific.
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';

new NodeSDK({
  instrumentations: [getNodeAutoInstrumentations()],
}).start();

Keeping your existing vendor

Being an OTLP sink means Syncline does not have to replace anything. Fan out from a collector and send the same spans to both.

# otel-collector-config.yaml
exporters:
  otlphttp/syncline:
    endpoint: https://syncline.example.com/v1/ingest
    headers:
      x-syncline-key: sk_live_...
  otlphttp/vendor:
    endpoint: https://your-vendor.example.com

service:
  pipelines:
    traces:
      exporters: [otlphttp/syncline, otlphttp/vendor]

Formats

  • OTLP/HTTP with JSON is supported. Protobuf is not yet.
  • Trace and span ids may be hex or base64. OTLP/JSON specifies hex, unlike proto3, and not every producer complies — so both are accepted.
  • service.name comes from the resource. A span without one is stored as unknown rather than dropped.
  • A malformed span is dropped and counted, never failing its batch. One bad span must not cost the other 499.

Checking it works

curl -s localhost:4000/v1/health
# {"status":"ok","checks":{"database":...,"redis":...}}

Then open a recording. If the network lane has a bar but the backend lane is empty, the spans are arriving under a different trace id — or the traceparent never reached your server, which is the CORS header again.