If you already use Pino for logging, the @squasher/pino transport sends your logs directly to Squasher with zero code changes to your existing log calls. Errors are automatically enriched with stack traces, and all log levels flow into your Squasher dashboard alongside SDK-captured errors.
Installation
npm install @squasher/pino pino
Setup
Add the Squasher transport to your Pino logger:
import pino from "pino";
const logger = pino(
{ level: "info" },
pino.transport({
target: "@squasher/pino",
options: {
apiKey: process.env.SQUASHER_API_KEY,
projectId: process.env.SQUASHER_PROJECT_ID,
},
}),
);
export default logger;
Add the environment variables to your .env:
SQUASHER_API_KEY=sq_pk_your_key_here
SQUASHER_PROJECT_ID=your_project_id
That’s it. Every log call now sends to Squasher automatically.
Usage
Use your Pino logger exactly as you normally would:
logger.info("Server started on port 3000");
logger.warn({ userId: "usr-42" }, "Rate limit approaching");
logger.error({ err: new Error("Connection refused") }, "Database unavailable");
logger.fatal({ err: new Error("Out of memory") }, "Process crashing");
When you pass an err object (Pino’s standard error serialization), Squasher automatically extracts the error type, message, and stack trace.
Multiple Transports
Send logs to both stdout and Squasher at the same time:
import pino from "pino";
const logger = pino({
level: "info",
transport: {
targets: [
{
target: "pino/file",
options: { destination: 1 }, // stdout
},
{
target: "@squasher/pino",
options: {
apiKey: process.env.SQUASHER_API_KEY,
projectId: process.env.SQUASHER_PROJECT_ID,
environment: process.env.NODE_ENV,
release: process.env.GIT_COMMIT_SHA,
},
},
],
},
});
export default logger;
This is the recommended setup for production — you get local log output for debugging and Squasher for monitoring in a single logger.
Framework Examples
import express from "express";
import logger from "./logger";
const app = express();
app.get("/api/users", async (req, res) => {
try {
const users = await db.getUsers();
logger.info({ count: users.length }, "Fetched users");
res.json(users);
} catch (error) {
logger.error({ err: error, url: req.originalUrl }, "Request failed");
res.status(500).json({ error: "Internal server error" });
}
});
app.listen(3000, () => {
logger.info({ port: 3000 }, "Server started");
});
import Fastify from "fastify";
import logger from "./logger";
// Fastify natively supports Pino — pass your logger instance
const app = Fastify({ logger });
app.get("/api/users", async (request, reply) => {
request.log.info("Fetching users");
const users = await db.getUsers();
return users;
});
app.listen({ port: 3000 });
import { serve } from "@hono/node-server";
import { Hono } from "hono";
import logger from "./logger";
const app = new Hono();
app.get("/api/users", async (c) => {
try {
const users = await db.getUsers();
logger.info({ count: users.length }, "Fetched users");
return c.json(users);
} catch (error) {
logger.error({ err: error }, "Request failed");
return c.json({ error: "Internal server error" }, 500);
}
});
serve({ fetch: app.fetch, port: 3000 });
logger.info({ port: 3000 }, "Server started");
Error Enrichment
When a Pino log includes an err object, Squasher extracts structured error data:
// This log call:
logger.error({ err: new Error("ECONNREFUSED") }, "Database connection failed");
// Sends an event with:
// - message: "ECONNREFUSED"
// - type: "Error"
// - stack: full stack trace
// - frames: parsed stack frames with file, line, and column
// - level: "error"
// - extra.pino_msg: "Database connection failed"
Standard Pino fields like pid and hostname are stored as tags. Any additional properties you bind are stored as extra data, fully searchable in the dashboard.
Log Level Mapping
Pino log levels map to Squasher levels automatically:
| Pino Level | Squasher Level |
|---|
trace (10) | debug |
debug (20) | debug |
info (30) | info |
warn (40) | warning |
error (50) | error |
fatal (60) | fatal |
Configuration
| Option | Type | Default | Description |
|---|
apiKey | string | Required | Your project API key (sq_pk_...) |
projectId | string | Required | Your project ID |
endpoint | string | https://ingest.squasher.ai | Ingestion endpoint URL |
environment | string | undefined | Environment tag (production, staging, etc.) |
release | string | undefined | Release/version tag |
debug | boolean | false | Log transport activity to console |
batchSize | number | 25 | Flush when this many events are buffered |
flushIntervalMs | number | 5000 | Max milliseconds between automatic flushes |
maxRetries | number | 3 | Retry attempts on transient failures |
Combining with the Node.js SDK
The Pino transport can be used alongside @squasher/node if you want both structured logging and explicit error capture with breadcrumbs and user context:
import { init, captureError, getClient } from "@squasher/node";
import logger from "./logger"; // Pino with @squasher/pino transport
// SDK for explicit error capture with rich context
init({
apiKey: process.env.SQUASHER_API_KEY!,
projectId: process.env.SQUASHER_PROJECT_ID!,
});
// Set user context (only available via the SDK)
getClient().setUser({ id: "usr-42", email: "user@example.com" });
// Use pino for logging — all logs flow to Squasher automatically
logger.info("Request started");
// Use the SDK for errors that need breadcrumbs or user context
try {
await processOrder(order);
} catch (error) {
await captureError(error as Error, { orderId: order.id });
}
If you only need log-based monitoring without breadcrumbs or user context, the Pino transport alone is sufficient. You don’t need @squasher/node.
Requirements
- Node.js 18 or higher
- Pino 8 or higher