Add request logging middleware, capture uncaught exceptions, and send structured logs with trace IDs to LogFlow from your Express.js application.
Structured logging in Express.js means every request, error, and event is captured as a JSON object — not a plain text line — so you can filter, search, and correlate logs in LogFlow without parsing strings.
npm install @getlogflow/js
Create src/lib/logger.js (or .ts for TypeScript):
import { LogFlow } from '@getlogflow/js'
export const logger = new LogFlow({
apiKey: process.env.LOGFLOW_API_KEY,
service: 'api', // rename to your service name
flushInterval: 3000,
maxRetries: 3,
})
// Flush on shutdown so no logs are lost
process.on('SIGTERM', async () => {
await logger.destroy()
process.exit(0)
})
Add your API key to .env:
LOGFLOW_API_KEY=lf_your_api_key_here
Add this middleware before your routes. It logs every request with method, path, status code, and response time:
import express from 'express'
import { logger } from './lib/logger.js'
const app = express()
// Request logging middleware
app.use((req, res, next) => {
const start = Date.now()
const { method, path, ip } = req
res.on('finish', () => {
const duration = Date.now() - start
const level = res.statusCode >= 500 ? 'error'
: res.statusCode >= 400 ? 'warn'
: 'info'
logger[level](`${method} ${path} ${res.statusCode}`, {
method,
path,
statusCode: res.statusCode,
durationMs: duration,
ip,
})
})
next()
})
Trace IDs let you group all logs from a single request together in LogFlow. Install a UUID library or use Node's built-in crypto:
import { randomUUID } from 'crypto'
app.use((req, res, next) => {
req.traceId = req.headers['x-trace-id'] || randomUUID()
res.setHeader('x-trace-id', req.traceId)
next()
})
Option A — pass traceId per call:
logger.info('User signed up', {
traceId: req.traceId,
userId: user.id,
email: user.email,
})
Option B — use a child logger (recommended):
Create a per-request child logger that carries traceId automatically — no need to repeat it on every call:
app.use((req, res, next) => {
req.traceId = req.headers['x-trace-id'] || randomUUID()
res.setHeader('x-trace-id', req.traceId)
// Child logger merges traceId into every log it produces
req.log = logger.child({ traceId: req.traceId })
next()
})
app.post('/orders', async (req, res) => {
req.log.info('Order received') // traceId included automatically
const order = await createOrder(req.body)
req.log.info('Order created', { orderId: order.id }) // traceId + orderId
res.json(order)
})
In LogFlow's Logs Explorer, click any log and hit View trace to see all logs from that request.
Add a global error handler after all routes. This catches any error passed to next(err) and logs it before returning a 500:
// Error handler — must have 4 parameters
app.use((err, req, res, next) => {
logger.error(err.message || 'Unhandled error', {
traceId: req.traceId,
stack: err.stack,
path: req.path,
method: req.method,
})
res.status(500).json({ error: 'Internal Server Error' })
})
// Catch uncaught exceptions
process.on('uncaughtException', (err) => {
logger.fatal('Uncaught exception', { stack: err.stack, message: err.message })
process.exit(1)
})
process.on('unhandledRejection', (reason) => {
logger.error('Unhandled promise rejection', { reason: String(reason) })
})
Beyond HTTP requests, log important business events with structured metadata:
app.post('/orders', async (req, res) => {
const order = await createOrder(req.body)
logger.info('Order created', {
traceId: req.traceId,
orderId: order.id,
userId: req.user.id,
amount: order.totalCents,
currency: order.currency,
})
res.json(order)
})
Start your server and make a request. Open LogFlow → Logs Explorer — you should see your request logs appear within a few seconds.
Filter by service: type service:api in the search bar to see only your Express logs.
Should I log request bodies? Be careful — request bodies often contain passwords and personal data. Log only the fields you need (like user ID, order ID) rather than the entire body.
What log level should I use?
Use info for normal operations, warn for recoverable issues (rate limits, retries), and error for failures that affect users. Reserve fatal for errors that crash the process.
How do I test that logs are reaching LogFlow? Check the Live Tail page in your dashboard — it shows logs in real time as they arrive.
Free plan available. No credit card required. Up and running in 2 minutes.
Get started freePino Logging Integration
Connect Pino (the fastest Node.js logger) to LogFlow in under 5 minutes.
Winston Logging Integration
Plug LogFlow into an existing Winston setup without changing your application code.
Add Logging to a Next.js App
Set up LogFlow in a Next.js project — capture server errors, API route logs, and frontend exceptions in under 15 minutes.