boltZharp Docs
speedApplications

Tracing Setup

A trace follows one request through every service it touches, so you can see where the time went and what caused what — not just that something was slow.

Which route you take depends on what your application already has. If it is not instrumented, install the SDK. If it already reports OpenTelemetry, point it at the Zharp collector and change nothing else.

code

Option 1: The Zharp SDK

@zharp/apm traces browser and Node.js apps and records browser sessions. For logs, add @zharp/logs 1.1.0 and start it first: ZharpAPM.init() then reuses its settings, and every log written inside a trace carries the trace id. Every option is on the JavaScript APM page.

1

Install

npm install @zharp/apm
2

Start it on the server

Every incoming request becomes a span, and every outgoing call becomes one beneath it. Express, Fastify, Nest and Koa all build on Node's HTTP server, so none of them need anything framework-specific. Preloading patches http before any library loads, which is the surest way to catch every call.

# From the environment, before your code loads
ZHARP_API_KEY=your-ingest-only-key ZHARP_SERVICE=checkout-api ZHARP_ENV=production \
  node --import @zharp/apm/init app.js

# Prints:
# [Zharp] APM active — service "checkout-api", environment "production"

Or start it in code:

import { ZharpAPM } from '@zharp/apm';

ZharpAPM.init();   // at the very top of your entry file; reads ZHARP_* from the environment
3

Start it in the browser

Every fetch and XMLHttpRequest becomes a span, and carries a trace header so the backend request it triggers joins the same trace. The key ships inside your bundle, so use one with the Ingest only scope: it can send data but has no access to the REST API. See the JavaScript APM page for every option.

import { ZharpAPM } from '@zharp/apm';

ZharpAPM.init({
  apiKey: 'YOUR_INGEST_ONLY_KEY', // anyone can read it in your bundle
  service: 'checkout-web',
  // Only your own API gets the trace header. See the note below.
  propagateTo: [/api\.yourdomain\.com/],
});

By default the browser sends the trace header same-origin only. Adding a header to a cross-origin request turns it into a CORS preflight, and a third party that does not allow traceparent will reject the request outright. Use propagateTo to name the hosts you control.

4

Time your own code

HTTP traffic is traced for you. Wrap anything else worth measuring — the span ends when the function settles, and a thrown error is recorded with its stack before being re-thrown unchanged.

const order = await ZharpAPM.startActiveSpan('createOrder', async (span) => {
  span.setAttribute('cart.items', items.length);
  return db.orders.create(input);
});
hub

Option 2: OpenTelemetry Collector

If your application already exports OpenTelemetry — from any language — the collector receives it and forwards it to Zharp. The Zharp Agent turns this on when it installs, so with the agent on the server there is nothing to set up — point the app at 127.0.0.1:4317 (gRPC) or :4318 (HTTP). For a collector you already run, add:

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 127.0.0.1:4317
      http:
        endpoint: 127.0.0.1:4318

exporters:
  zharp:
    api_key: "${env:ZHARP_API_KEY}"

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [zharp]

The OTLP ports bind to 127.0.0.1. A trace carries request URLs, query text and error messages, so exposing the receiver to the network should be a deliberate decision rather than a default.

Session replay

Records the page and plays it back, so you can watch what happened instead of reconstructing it from logs. It is part of @zharp/apm, browser only and off until you turn it on. The recorder is an order of magnitude larger than the rest of the SDK, so it is loaded in a chunk of its own only when recording starts — an app that never turns replay on, or a session that is sampled out, never downloads it.

import { ZharpAPM } from '@zharp/apm';

ZharpAPM.init({
  apiKey: 'YOUR_INGEST_ONLY_KEY',
  service: 'checkout-web',
  replay: {
    sampleRate: 0.1,                   // record one session in ten
    blockSelector: '.payment-iframe',  // never recorded — space kept, contents never captured
  },
});

// Or later, after consent: await ZharpAPM.startReplay({ sampleRate: 0.1 });

Every input is masked by default. Turning that off ships whatever people type to a monitoring service. For anything genuinely sensitive prefer blockSelector over masking: masked text is still recorded as a shape of the right length, while a blocked element is never serialised at all.

Recordings, traces and logs from the same page load share a session id, so a replay links to the requests it made and a failing trace links back to the recording of the session that caused it. Sessions where an error was recorded are flagged — which is how you find the ones worth watching.

Sampling

sampleRate is decided once at the start of a trace and inherited by every span in it, so a recorded trace is always whole. A half-recorded trace is worse than none — its gaps look like latency that never happened.

Alerting on what you collect

Once traces are arriving, create an Application monitor to watch response time, error rate or throughput for a service, and raise an incident when it crosses a threshold. Logs written inside a trace carry its ID, so an error in the log viewer links back to the request that produced it.

Watch an application

Once traces are flowing, set a threshold and get told when your service crosses it.

Create Application Monitorarrow_forward