Backend basics

See what your server is doing, then follow a request from logs to traces.

1. Save your connection

For a backend without a website, open Projects & services, create a project, choose an environment and open Connection setup. For an existing website, open its Backend → Connection setup.

Choose a service name such as checkout-api and check the suggested startup file against your build output. Setup saves these non-secret choices, so you can return later. A backend address is only needed for website linking. Jobs and private services can send data without a public URL.

2. Install in your backend package

SDK install

npm install @pipetrace/sdk-node@0.1.1

Use your project’s package manager and commit its lockfile. Node 22 or later is required; the tested runtime is Node 22.12.0. This is a Node server SDK, not a browser, Cloudflare Worker or edge-runtime package. Static websites only need the browser script.

3. Set server variables

backend variables

PIPETRACE_ENDPOINT=https://api.pipetrace.andginja.com PIPETRACE_SERVICE_NAME=checkout-api PIPETRACE_API_KEY=<your backend key>

Create a backend key in setup and put its value in your host’s secret settings. In Coolify, these are runtime variables on your application. The key selects the project and environment. You can reuse a valid key you already saved; its full value is shown only once. Never put it in a VITE_ or NEXT_PUBLIC_ variable.

PIPETRACE_ENDPOINT is the Pipetrace API base URL, not your own backend URL or a path ending in /v1/traces. Optional variables are PIPETRACE_SERVICE_NAMESPACE, PIPETRACE_ENVIRONMENT (a descriptive label), and PIPETRACE_ALLOWED_ORIGINS for browser linking.

4. Start telemetry before your application

CommonJS, including compiled Fastify or Express

CommonJS startup

node --require @pipetrace/sdk-node/preload ./dist/index.js

Replace the file with your actual compiled server entrypoint. Update the Dockerfile CMD or start script and rebuild. Load the SDK once per process, before HTTP, logger or database imports. If your app already calls startPipetrace(), keep that initialization instead of adding a second preload.

ES modules, including Nitro’s Node output

Save this file as pipetrace-bootstrap.mjs next to your application. Change the import to your actual built server file. Other frameworks and adapters need their import order tested.

ESM bootstrap

import { startPipetrace } from '@pipetrace/sdk-node'; export const telemetry = startPipetrace({ endpoint: process.env.PIPETRACE_ENDPOINT, apiKey: process.env.PIPETRACE_API_KEY, serviceName: process.env.PIPETRACE_SERVICE_NAME, serviceNamespace: process.env.PIPETRACE_SERVICE_NAMESPACE, environment: process.env.PIPETRACE_ENVIRONMENT, allowedOrigins: process.env.PIPETRACE_ALLOWED_ORIGINS?.split(',').filter(Boolean), }); await import('./.output/server/index.mjs'); // Connect telemetry.shutdown() to your application's shutdown lifecycle.

ESM startup

node --experimental-loader @pipetrace/sdk-node/hook ./pipetrace-bootstrap.mjs

The loader is required for the tested automatic ESM instrumentation. Node may print an experimental-loader warning. Plain static ESM imports before initialization can leave requests untracked.

What you get by default

SignalWhat to expect
Requests and tracesAutomatic Node HTTP client/server spans, request duration and status. A trace is the work recorded for one request or task.
Application logsSupported logger integration, tested with Pino 10.1. Logs inside a request include its trace ID. console.log and Docker stdout are not collected by this SDK.
MetricsMetrics emitted by supported instrumentation and your own counters or histograms. Exported every 30 seconds and during shutdown. This does not install host CPU, RAM or container monitoring.
Database operationsSupported drivers such as pg and MongoDB depend on their instrumentation’s version support. postgres.js and node:sqlite require manual spans. Drizzle coverage depends on its underlying driver.
Background tasksAdd spans around jobs. They can have logs and metrics without a visitor or website.

Start with one real endpoint and one log statement. Check the Backend overview, Traces and Logs after deploying. Saving the guide or creating a key alone does not install the SDK.

Add useful logs

Keep Pino initialized after telemetry. Log a short message and useful fields. Do not include passwords, request bodies, access tokens or personal data.

Pino example

// Application code, loaded after the SDK preload. const logger = require('pino')(); // Inside an instrumented request handler: logger.info({ operation: 'checkout', itemCount: 3 }, 'Checkout started'); logger.warn({ dependency: 'inventory' }, 'Inventory response was slow');

For direct OpenTelemetry logging, install @opentelemetry/api-logs@0.222.0 as an application dependency. Use this after SDK startup:

direct log example

const { logs, SeverityNumber } = require('@opentelemetry/api-logs'); logs.getLogger('checkout').emit({ severityNumber: SeverityNumber.INFO, severityText: 'INFO', body: 'Checkout started', attributes: { operation: 'checkout' }, });

Logs made inside an active span can link to that trace. A background log may have no trace. In Pipetrace, filter by level, search the message, expand a row for details, or open its linked trace.

Record database calls and background work

For manual spans, install @opentelemetry/api@1.9.1 as a direct application dependency. Wrap the real operation, end the span even on failure, and keep names stable.

database span example

const { trace, SpanKind, SpanStatusCode } = require('@opentelemetry/api'); const tracer = trace.getTracer('checkout'); async function withDatabaseSpan(operation) { return tracer.startActiveSpan('orders.select', { kind: SpanKind.CLIENT, attributes: { 'db.system.name': 'postgresql', 'db.operation.name': 'SELECT' }, }, async span => { try { return await operation(); } catch (error) { span.setStatus({ code: SpanStatusCode.ERROR }); // Add a sanitized exception only if it contains no sensitive data. throw error; } finally { span.end(); } }); } // Await your real postgres.js / Drizzle query inside the callback: // const orders = await withDatabaseSpan(() => sql`SELECT count(*) FROM orders`);

For SQLite, use db.system.name: 'sqlite' and wrap the actual statement execution. Node 22.12 needs --experimental-sqlite. Do not add manual spans around a driver operation already instrumented automatically. For jobs, use SpanKind.INTERNAL and a stable task name instead.

Add a counter

metric example

const { metrics } = require('@opentelemetry/api'); const meter = metrics.getMeter('checkout'); const completed = meter.createCounter('checkout.completed', { unit: '1' }); // After a successful operation: completed.add(1, { payment_method: 'card' });

Use a small set of label values. User IDs, order IDs and arbitrary URLs create too many series. Allow at least 30 seconds for the scheduled export, or flush on shutdown. Metrics, request counts and log counts describe different signals; they need not be equal.

Business events: confirm what completed

This optional report counts operations your server explicitly confirms: signups, payments, imports or background jobs. It does not infer conversions from an HTTP response. Existing APIs call these events “outcomes.”

After committing the business operation, send the event from a durable job or outbox. Reuse the same event ID, timestamp and body when retrying. Keep this separate from the success of the customer’s operation.

Business event request

// Run from a durable server-side job after the operation commits. // Save this body with the job; retries must reuse it unchanged. const event = { eventId: "signup:internal-operation-id", kind: "signup", name: "account_created", result: "success", eventTimeNs: String(BigInt(Date.now()) * 1000000n), }; const response = await fetch(process.env.PIPETRACE_ENDPOINT + "/api/v1/telemetry/outcomes", { method: "POST", headers: { "Content-Type": "application/json", Authorization: "Bearer " + process.env.PIPETRACE_API_KEY, }, body: JSON.stringify(event), signal: AbortSignal.timeout(5000), }); if (!response.ok) throw new Error("Business event delivery failed: " + response.status);

The key must allow the outcomes signal. Report failures with result: "failure". An optional traceId links the event to an active request. Avoid personal data in event names and IDs. Event receipts are kept for 90 days; idempotency is bounded by receipt retention. Event labels in Settings only rename your definitions; they do not instrument your application.

Keys and automatic retention

A backend key authorizes ingestion into one project and environment. Keep using a valid key; create another when separating deployments or rotating credentials. Deploy the replacement and verify data before revoking the old key. Dashboard access is controlled separately by project membership.

Expired data is excluded from reports automatically. New environments keep traces and logs for 14 days and metrics for 90 days. Settings supports up to 90 days of traces/logs and 365 days of metrics. Unlimited retention is not currently supported. Increasing retention applies to newly ingested data and does not restore expired records. Shortening retention immediately limits query access while historical cleanup runs separately.

Retention and query windows are different: requests and logs can be queried in windows up to 14 days, metrics and business events up to 90 days. Move the date window to investigate older retained data.

Flush when your application stops

Connect shutdown to your framework’s lifecycle: stop new requests, finish work, close database clients, then await telemetry shutdown. For CommonJS preload:

Fastify shutdown example

// After creating your Fastify app: let stopping = false; async function stop() { if (stopping) return; stopping = true; try { await app.close(); // Configure database cleanup in your app's close hooks. } finally { await require('@pipetrace/sdk-node/preload').pipetrace.shutdown(); } } for (const signal of ['SIGTERM', 'SIGINT']) { process.once(signal, () => { void stop().catch(() => { process.exitCode = 1; }); }); }

For ESM, call the telemetry handle’s shutdown() from the bootstrap or a separate shared telemetry module. Do not import the bootstrap back from the application if it creates a circular startup dependency. Allow your host enough time to finish draining. Natural process exit can flush the preload, but process.exit() and SIGKILL do not wait. Export queues are bounded and can drop data during sustained outages; the SDK is not a durable log agent.

  1. Open backend setup from the website. Enter the exact backend address the browser calls, including scheme and port.
  2. Copy the updated browser script. It keeps the same tracking ID and adds data-trace-origins. Replace the old tag.
  3. Set PIPETRACE_ALLOWED_ORIGINS on your backend to the exact website origins, separated by commas.
  4. Keep your app’s CORS policy configured. Allow traceparent if requests send it, and expose response traceparent. Preserve those headers in framework middleware.
  5. Deploy both changes. Use a website feature that calls the backend, then open Verify.

The SDK can return trace context to allowed websites; it does not set your application’s CORS allow-origin policy. Default Fetch requests link using response context. Outgoing context injection is limited to explicitly allowed requests using manual/error redirect mode. Browser consent and content blockers can prevent linking even when backend collection works.

Other languages or an existing OpenTelemetry SDK

Keep your existing SDK and configure OTLP over HTTP. Pipetrace accepts HTTP/JSON and HTTP/protobuf traces, logs and metrics. Use the official exporter for your language; gRPC is not supported by this endpoint.

native OTLP variables

OTEL_EXPORTER_OTLP_ENDPOINT=https://api.pipetrace.andginja.com OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer <your backend key> OTEL_SERVICE_NAME=checkout-api

Set these as hosting variables, replacing the key placeholder privately. If configuring signal URLs separately, use /v1/traces, /v1/logs and /v1/metrics. Give each process a unique service.instance.id. Follow your language exporter’s rules for headers and URL suffixes. Browser linking with a native SDK needs equivalent response middleware; it is not automatic merely because exports succeed.

Official language guides · JavaScript instrumentation · Pipetrace API endpoints

When data does not appear

SymptomCheck
Unable to checkRetry. If the Pipetrace server reports backend unavailable, its operator needs to restore collection/query services. Reinstalling your SDK will not fix that.
No requests yetConfirm runtime variables, project/environment key, startup order and service name. Call a real endpoint after deploying. Verification looks for this service’s latest trace in the last 24 hours.
Requests, but no logsUse a supported logger or the direct logging API after SDK initialization. Check its level threshold. Console output alone is not an exported log.
Logs, but no linked traceEmit the log inside an active request/task span. Background logs and logs created before a span starts may legitimately be unlinked.
No database spansCheck the actual driver and version. Add a manual CLIENT span for postgres.js or SQLite; ORM usage alone does not prove instrumentation.
No website linkCheck the updated tracking tag, both origin lists, response traceparent, CORS exposure, consent and the request’s actual destination.
Data stops during deploysCheck graceful shutdown and host termination timeout. Ensure the SDK is a production dependency and the startup command is in the final image.