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.1Use 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.jsReplace 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.mjsThe 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
| Signal | What to expect |
|---|---|
| Requests and traces | Automatic Node HTTP client/server spans, request duration and status. A trace is the work recorded for one request or task. |
| Application logs | Supported 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. |
| Metrics | Metrics 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 operations | Supported 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 tasks | Add 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.
Link a website to its backend
- Open backend setup from the website. Enter the exact backend address the browser calls, including scheme and port.
- Copy the updated browser script. It keeps the same tracking ID and adds
data-trace-origins. Replace the old tag. - Set
PIPETRACE_ALLOWED_ORIGINSon your backend to the exact website origins, separated by commas. - Keep your app’s CORS policy configured. Allow
traceparentif requests send it, and expose responsetraceparent. Preserve those headers in framework middleware. - 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-apiSet 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
| Symptom | Check |
|---|---|
| Unable to check | Retry. If the Pipetrace server reports backend unavailable, its operator needs to restore collection/query services. Reinstalling your SDK will not fix that. |
| No requests yet | Confirm 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 logs | Use 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 trace | Emit the log inside an active request/task span. Background logs and logs created before a span starts may legitimately be unlinked. |
| No database spans | Check the actual driver and version. Add a manual CLIENT span for postgres.js or SQLite; ORM usage alone does not prove instrumentation. |
| No website link | Check the updated tracking tag, both origin lists, response traceparent, CORS exposure, consent and the request’s actual destination. |
| Data stops during deploys | Check graceful shutdown and host termination timeout. Ensure the SDK is a production dependency and the startup command is in the final image. |