Beginner

Winston Logging Integration

Replace or extend your existing Winston logger with LogFlow in minutes. Send structured logs, use child loggers for request context, and see everything in the LogFlow dashboard.

LogFlow TeamJune 8, 20268 minWinstonNode.js

If your Node.js application already uses Winston, you can add LogFlow as a second transport — your existing console/file logs keep working, and LogFlow receives every log automatically.

Prerequisites

  • Node.js 18+ application using Winston
  • A LogFlow account (free tier works)
  • Your API key from Settings → API Key

Step 1 — Install the SDK

npm install @getlogflow/js

Step 2 — Add the LogFlow transport

Open wherever you create your Winston logger and add LogFlowWinstonTransport:

import winston from 'winston'
import { LogFlowWinstonTransport } from '@getlogflow/js/transports/winston'

export const logger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports: [
    // Keep your existing transports
    new winston.transports.Console({
      format: winston.format.simple(),
    }),

    // Add LogFlow
    new LogFlowWinstonTransport({
      apiKey: process.env.LOGFLOW_API_KEY!,
      service: 'api',            // rename to your service
      environment: process.env.NODE_ENV,
      release: process.env.APP_VERSION, // optional
    }),
  ],
})

Add your API key to .env:

LOGFLOW_API_KEY=lf_your_api_key_here

That's it — every logger.info(...), logger.error(...), etc. now flows to LogFlow automatically. No other code changes needed.

Step 3 — Structured metadata

Winston supports passing metadata as the second argument. LogFlow preserves all of it:

logger.info('User signed up', { userId: 'u_123', plan: 'starter', source: 'google' })
logger.warn('Rate limit hit', { ip: req.ip, path: req.path, limit: 100 })
logger.error('Payment failed', { orderId: 'ord_456', code: 'CARD_DECLINED', amount: 4999 })

In LogFlow's Logs Explorer, click any log to expand its attributes.

Step 4 — Request context with child loggers

Instead of manually passing requestId to every log call, create a per-request child logger. Child loggers merge fixed attributes automatically:

app.use((req, res, next) => {
  // Create a child logger for this request
  req.log = logger.child({
    traceId: req.headers['x-trace-id'] || crypto.randomUUID(),
    requestId: req.id,
  })
  next()
})

app.post('/orders', async (req, res) => {
  req.log.info('Order request received')               // traceId + requestId included
  const order = await createOrder(req.body)
  req.log.info('Order created', { orderId: order.id }) // traceId + requestId + orderId
  res.json(order)
})

In LogFlow, click View trace on any log to see the full request timeline grouped by traceId.

Step 5 — Capture unhandled errors

process.on('uncaughtException', (err) => {
  logger.error('Uncaught exception', { stack: err.stack, message: err.message })
  process.exit(1)
})

process.on('unhandledRejection', (reason) => {
  logger.error('Unhandled rejection', { reason: String(reason) })
})

Verify

Start your server and trigger a few requests. Open LogFlow → Live Tail — logs should appear within seconds.

Filter by service: type service:api in the Logs Explorer search bar.

FAQ

Will this slow down my app? No. The SDK batches logs in the background — your logger.info(...) calls return immediately without waiting for the HTTP request.

What happens if LogFlow is unreachable? The SDK retries up to 3 times with exponential back-off (1s → 2s → 4s). If all retries fail, logs are dropped and your onError callback is invoked if set.

What about log levels? Winston levels are mapped to LogFlow levels: errorerror, warnwarn, infoinfo, verbose/debugdebug, sillytrace.

Start monitoring your logs today

Free plan available. No credit card required. Up and running in 2 minutes.

Get started free