> ## Documentation Index
> Fetch the complete documentation index at: https://docs.squasher.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Pino transport + Node tracing

> Send Pino logs to Squasher and use the same OTel-compatible tracing API as the edge SDK.

`@squasher-ai/pino` ships two things in one package:

1. **`squasherTransport()`** — a Pino transport that forwards log events to Squasher. Drop-in for existing Pino setups.
2. **`span()` / `log` / `recordMetric()`** — the same OpenTelemetry-compatible tracing primitives as [`@squasher-ai/edge`](/sdks/edge), but for Node services.

Same API across edge and Node. Pure OTLP wire format under the hood, so no vendor lock-in.

## Install

```bash theme={null}
bun add @squasher-ai/pino pino
```

Node ≥ 18 required (native `fetch` + `AsyncLocalStorage`).

## 60-second example

```typescript src/server.ts theme={null}
import pino from "pino";
import { squasherTransport, initSquasher, span, log, recordMetric } from "@squasher-ai/pino";

// 1. Pino transport — forwards logger calls to Squasher
export const logger = pino({
  transport: {
    target: "@squasher-ai/pino",
    options: {
      apiKey: process.env.SQUASHER_API_KEY,
      projectId: process.env.SQUASHER_PROJECT_ID,
      environment: process.env.NODE_ENV,
    },
  },
});

// 2. Initialize the tracing client once at startup
initSquasher({
  apiKey: process.env.SQUASHER_API_KEY!,
  projectId: process.env.SQUASHER_PROJECT_ID!,
});

// 3. Use spans / logs / metrics anywhere in the app
async function handleRequest(req) {
  return span("api.request", { kind: "server", attrs: { route: req.route } }, async () => {
    const user = await span("auth.lookup", () => getUser(req.token));
    const data = await span("db.query", { kind: "client" }, () => db.query(user.id));
    log.info("served request", { userId: user.id, rows: data.length });
    recordMetric("api.request.count", 1, { attrs: { route: req.route } });
    return data;
  });
}
```

Spans auto-nest via `AsyncLocalStorage`. Logs and metrics emitted inside a span auto-correlate.

## The Pino transport

Forwards Pino log events to Squasher's ingest endpoint with batching, retries, and level mapping.

```typescript src/logger.ts theme={null}
import pino from "pino";

const logger = pino(
  { level: "info" },
  pino.transport({
    target: "@squasher-ai/pino",
    options: {
      apiKey: process.env.SQUASHER_API_KEY,
      projectId: process.env.SQUASHER_PROJECT_ID,
      environment: "production",
      release: "v1.2.3",
      sampling: { successRate: 0.1, errorRate: 1, slowRate: 1 },
    },
  }),
);

logger.info({ userId: 42 }, "user signed up");
logger.error({ err: new Error("boom") }, "payment failed");
```

When you pass an `err` object, Squasher extracts the error message, type, and stack trace automatically.

### Transport options

| Option            | Default                      | Description                              |
| ----------------- | ---------------------------- | ---------------------------------------- |
| `apiKey`          | *required*                   | Project API key (`sq_pk_...`)            |
| `projectId`       | *required*                   | Squasher project ID                      |
| `endpoint`        | `https://ingest.squasher.ai` | Override the ingest base URL             |
| `environment`     | —                            | Environment tag                          |
| `release`         | —                            | Release identifier                       |
| `sampling`        | keep all                     | Deterministic rates by outcome           |
| `batchSize`       | `25`                         | Flush when this many events buffer up    |
| `flushIntervalMs` | `5000`                       | Max ms between automatic flushes         |
| `maxRetries`      | `3`                          | Max retry attempts on transient failures |

The transport automatically splits multi-event request bodies at 1 MB. `batchSize` and
`flushIntervalMs` still determine when buffered events are flushed.

## Tracing primitives

Same API as [`@squasher-ai/edge`](/sdks/edge#tracing-logs-and-metrics). Quick version below — see the edge SDK page for the full reference.

### `initSquasher(config)` — one-time startup

Call once at process startup. Sets up the global tracing client and auto-registers a `beforeExit` handler that flushes pending events.

```typescript theme={null}
import { initSquasher } from "@squasher-ai/pino";

initSquasher({
  apiKey: process.env.SQUASHER_API_KEY!,
  projectId: process.env.SQUASHER_PROJECT_ID!,
  environment: process.env.NODE_ENV,
  release: process.env.GIT_SHA,
});
```

### `span(name, fn)` — one-line spans

```typescript theme={null}
import { span } from "@squasher-ai/pino";

const rows = await span("db.query", () => db.query("SELECT ..."));

await span("db.insert", { kind: "client", attrs: { "db.system": "database" } }, () =>
  db.insertRows(rows),
);
```

Nested spans auto-parent. Errors are auto-captured with `status: "error"` and re-thrown unchanged.

### `log.{debug, info, warn, error}(message, attrs?)`

```typescript theme={null}
import { log } from "@squasher-ai/pino";

log.info("served request", { rows: data.length });
log.error(new Error("payment failed"), { userId, amount });
```

These are the SDK's structured-log helpers, separate from your Pino logger. Use them when you want the log to attach to the active span. Plain Pino records also correlate when they include `trace_id` and `span_id`, `traceId` and `spanId`, or `trace.id` and `span.id`.

### `recordMetric(name, value, opts?)`

```typescript theme={null}
import { recordMetric } from "@squasher-ai/pino";

recordMetric("cache.hit.rate", 0.94, { unit: "1" });
recordMetric("api.requests", 1, { type: "counter", attrs: { route: "/users" } });
```

### Cross-process traces

Propagate via W3C `traceparent` to outbound HTTP:

```typescript theme={null}
import { getActiveSpan, span } from "@squasher-ai/pino";

await span("external.fetch", async () => {
  const active = getActiveSpan();
  const headers = new Headers({ "content-type": "application/json" });
  if (active) {
    headers.set("traceparent", `00-${active.traceId}-${active.spanId}-01`);
  }
  return fetch("https://api.example.com/data", { headers });
});
```

The receiving service (Node or Cloudflare Worker) reconstructs the parent and passes `parent: { traceId, spanId }` to its own `span()` call.

## Why both the transport AND tracing primitives?

* **Pino transport** is for logs. Drop-in for existing Pino setups, zero code changes.
* **`span()` / `log.*` / `recordMetric()`** are for traces, metrics, and structured logs that need to correlate to spans.

Most apps use both. Pino logs that don't need trace correlation flow through the transport. Spans + metrics + structured log calls that should attach to a trace go through the tracing helpers.

## Switch destinations

The SDK sends the Squasher ingest event format to the configured Squasher endpoint. Use the [OpenTelemetry integration](/integrations/opentelemetry) when you need a standard OTLP exporter or collector destination.

```typescript theme={null}
initSquasher({
  apiKey: process.env.SQUASHER_API_KEY!,
  projectId: process.env.SQUASHER_PROJECT_ID!,
  endpoint: "https://ingest.squasher.ai",
});
```

## API reference

| Export                                               | Purpose                                                  |
| ---------------------------------------------------- | -------------------------------------------------------- |
| `squasherTransport(options)`                         | Pino transport.                                          |
| `initSquasher(config)`                               | One-time startup init for tracing client.                |
| `span(name, fn)` / `span(name, opts, fn)`            | Wrap async fn in a span.                                 |
| `startSpan(name, opts?)`                             | Manual lifecycle. Returns `{ end, setAttr, setStatus }`. |
| `getActiveSpan()`                                    | Read `{ traceId, spanId }` of active span.               |
| `log.{debug,info,warn,error}(msg, attrs?)`           | Structured logs that auto-correlate to spans.            |
| `recordMetric(name, value, opts?)`                   | OTLP gauge / counter.                                    |
| `setTag(k, v)` / `setTags({...})` / `setUser({...})` | Per-scope context.                                       |
| `captureError(err, extra?)` / `captureMessage(...)`  | Manual capture.                                          |
| `createTraceId()` / `createSpanId()`                 | Mint OTLP-compliant IDs.                                 |
| `flush()`                                            | Force-flush pending events.                              |

## Related guides

* [Cloudflare Workers / Edge](/sdks/edge) — same tracing API for Workers.
* [Node.js SDK](/sdks/node) — error capture without Pino.
* [Winston transport](/sdks/winston) — same idea for Winston.
* [OpenTelemetry](/integrations/opentelemetry) — vendor-neutral OTLP setup.

## Agent handoff

```text theme={null}
Install @squasher-ai/pino and pino. Configure pino.transport({ target: "@squasher-ai/pino" }) with SQUASHER_API_KEY and SQUASHER_PROJECT_ID from env, initialize tracing with initSquasher if spans or metrics are needed, and verify with one logger.error event.
```
