Skip to main content
LogBrew is an observability platform for developers and coding agents, with a generous free tier and no card needed. This guide sets up Winston logging and Pino logging in Node.js, adds an Express logger, and sends each log to LogBrew with its request trace. Each example ran on Node.js 22 with Winston 3.19, Pino 10.4, Express 5.3, @logbrew/sdk 0.1.40, @logbrew/node 0.1.24, and @logbrew/express 0.1.10. Each output block shows its real output.

Choose Winston or Pino

Both libraries write structured JSON logs. Keep the library that your app already uses. The LogBrew setup is different for each one.

Prepare a LogBrew project

1

Create a project key

Sign in with the CLI. Create a project and store its ingest key in an owner-only file.
The CLI does not print the key. In production, use the secret manager of your platform.
2

Install the packages

The packages require Node.js 18.19 or newer. Install Winston, Pino, or both.
3

Set the key and the deployment identity

createLogBrewNodeClient() reads the key from LOGBREW_SERVER_API_KEY. The service name is the name in package.json. Set LOGBREW_SERVICE_NAME to use a different name. The examples use ES modules, so set "type": "module" in package.json.

Node.js log levels

Each library has its own level names and numbers. LogBrew stores 4 levels and keeps the original level in metadata. In Winston, a lower number is more severe. In Pino, a higher number is more severe. Set info in production and debug only for a short diagnosis.

Winston logger

Save this file as winston-app.js. The first format drops records below the logger level before any transport gets them. The LogBrew transport receives every record that the logger writes, so it needs this filter.
Run node winston-app.js. The console gets 1 JSON line for each record at info or higher. The stack is shortened here.
LogBrew receives the same 3 records. The fields become context.* metadata. The transport sends the error name and message, but not the stack. Set includeErrorStack: true in createLogBrewWinstonTransport() to send the stack. Each message also becomes a breadcrumb on the next issue. Set breadcrumbs: false to stop this.

Pino logger

Save this file as pino-app.js. Call installLogBrewPinoInstrumentation() once, before the first log. It reads each finished Pino record. Pino still writes to its own destination.
Run node pino-app.js. The host name and the stack are shortened here.
LogBrew receives 3 records. Pino drops the debug record before LogBrew sees it. The token field is not in LogBrew. LogBrew drops credential, cookie, body, query, URL, and stack fields, and keeps your Pino redact paths as a second control. pid, hostname, and time are Pino defaults, so LogBrew does not copy them. Automatic capture requires Pino 9.11 or newer in the 9.x line, or Pino 10.1 or newer. For older Pino versions, use createLogBrewPinoDestination() from @logbrew/sdk as a Pino destination. To read Pino output in development, send it to pino-pretty.

Express logger

logbrewMiddleware() creates 1 request span for each request. logbrewErrorHandler() reports errors as issues. A Pino log inside a request gets the trace ID and span ID of that request. Save this file as express-app.js.
Start the app. Then send 2 requests from a second terminal.
LogBrew receives these events. The log and the span of each request have the same trace ID. The span name uses the route template, not the concrete path. The listening on 3000 log has no trace, because it is outside a request. Let the middleware create its own request clients. Do not pass the process-wide client to logbrewMiddleware() or logbrewErrorHandler(). The middleware shuts down its request client after each response.

Add the trace to Winston logs

For Winston, give the transport the active trace.
Add logbrewTransport to the transports list of the logger. A Winston log inside an Express request then has the trace ID and span ID of the request.

Read the logs back

The last command reads all logs of 1 request. Copy <trace_id> from a trace or an issue. To read from CI or a coding agent, use a project read key. See Verify your setup.

Shut down without lost logs

The client sends queued events 1 second after the first event or at 50 events. When the event loop is empty, the client sends the remaining events for at most 2 seconds. process.exit() and signals skip that step. Call await client.shutdown() before you exit, as the SIGTERM handler in the Express example does.

Recover from problems

To choose log fields, see Structured logging. For other frameworks, see SDK integration.