Replace NestJS's default console logger with structured JSON output and ship logs to LogFlow for search, alerts, and anomaly detection.
NestJS ships with a built-in Logger class that writes plain text to the console. It's fine for local development but useless in production — you can't filter by level, search by metadata, or set up alerts on unstructured text.
This tutorial replaces the default logger with Pino (the fastest Node.js JSON logger), integrates it with NestJS's dependency injection system, and ships every log to LogFlow.
npm install @getlogflow/js pino nestjs-pino pino-http
pino — fast JSON loggernestjs-pino — NestJS module that replaces the built-in loggerpino-http — automatic HTTP request logging@getlogflow/js — LogFlow SDK for shipping logsIn your app.module.ts, import and configure LoggerModule:
import { Module } from '@nestjs/common';
import { LoggerModule } from 'nestjs-pino';
@Module({
imports: [
LoggerModule.forRoot({
pinoHttp: {
level: process.env.LOG_LEVEL || 'info',
transport: process.env.NODE_ENV === 'development'
? { target: 'pino-pretty', options: { colorize: true } }
: undefined,
serializers: {
req: (req) => ({
method: req.method,
url: req.url,
params: req.params,
}),
res: (res) => ({
statusCode: res.statusCode,
}),
},
},
}),
],
})
export class AppModule {}
In development you get pretty-printed output. In production, raw JSON — perfect for log forwarders.
import { NestFactory } from '@nestjs/core';
import { Logger } from 'nestjs-pino';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule, { bufferLogs: true });
app.useLogger(app.get(Logger));
await app.listen(3000);
}
bootstrap();
bufferLogs: true ensures NestJS's startup logs also go through Pino instead of the default console logger.
Inject PinoLogger anywhere in your application:
import { Injectable } from '@nestjs/common';
import { PinoLogger, InjectPinoLogger } from 'nestjs-pino';
@Injectable()
export class OrderService {
constructor(
@InjectPinoLogger(OrderService.name)
private readonly logger: PinoLogger,
) {}
async createOrder(userId: string, items: CartItem[]) {
const total = items.reduce((sum, i) => sum + i.price * i.quantity, 0);
this.logger.info({ userId, itemCount: items.length, total }, 'Order created');
try {
const order = await this.orderRepo.save({ userId, items, total });
this.logger.info({ orderId: order.id, userId }, 'Order saved to database');
return order;
} catch (err) {
this.logger.error({ userId, err }, 'Failed to save order');
throw err;
}
}
}
Every log line automatically includes the class name as context, plus any request-scoped data (request ID, method, URL) from the HTTP layer.
Create a logflow.service.ts that initializes the SDK and exposes a transport:
import { Injectable, OnModuleInit } from '@nestjs/common';
import LogFlow from '@getlogflow/js';
@Injectable()
export class LogFlowService implements OnModuleInit {
private client: LogFlow;
onModuleInit() {
this.client = new LogFlow({
apiKey: process.env.LOGFLOW_API_KEY,
service: 'nestjs-app',
});
}
log(level: string, message: string, meta?: Record<string, any>) {
this.client[level]?.(message, meta);
}
}
For zero-code-change shipping, pipe Pino output to LogFlow using a custom transport:
// logflow-transport.ts
import { Transform } from 'stream';
import { fetch } from 'undici';
const LOGFLOW_URL = 'https://api.getlogflow.com/v1/logs';
const API_KEY = process.env.LOGFLOW_API_KEY;
const buffer: any[] = [];
let timer: NodeJS.Timeout | null = null;
async function flush() {
if (buffer.length === 0) return;
const batch = buffer.splice(0, buffer.length);
const payload = batch.map((entry) => ({
level: pinoLevelToString(entry.level),
message: entry.msg || '',
service: 'nestjs-app',
timestamp: new Date(entry.time).toISOString(),
attributes: extractAttributes(entry),
}));
try {
await fetch(LOGFLOW_URL, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
});
} catch {
// don't crash the app over logging failures
}
}
function pinoLevelToString(level: number): string {
if (level >= 60) return 'fatal';
if (level >= 50) return 'error';
if (level >= 40) return 'warn';
if (level >= 30) return 'info';
return 'debug';
}
function extractAttributes(entry: any): Record<string, string> {
const skip = new Set(['level', 'time', 'pid', 'hostname', 'msg', 'v']);
const attrs: Record<string, string> = {};
for (const [k, v] of Object.entries(entry)) {
if (!skip.has(k)) attrs[k] = String(v);
}
return attrs;
}
export function createLogFlowTransport() {
return new Transform({
objectMode: true,
transform(chunk, _enc, cb) {
try {
const entry = JSON.parse(chunk.toString());
buffer.push(entry);
if (!timer) timer = setTimeout(() => { flush(); timer = null; }, 1000);
} catch {}
cb();
},
});
}
This batches logs and flushes every second, keeping network overhead low.
For tracing requests across services, add a correlation ID middleware:
import { Injectable, NestMiddleware } from '@nestjs/common';
import { randomUUID } from 'crypto';
@Injectable()
export class CorrelationMiddleware implements NestMiddleware {
use(req: any, res: any, next: () => void) {
const traceId = req.headers['x-trace-id'] || randomUUID();
req.headers['x-trace-id'] = traceId;
res.setHeader('x-trace-id', traceId);
next();
}
}
Then include traceId in your Pino config:
pinoHttp: {
customProps: (req) => ({
traceId: req.headers['x-trace-id'],
}),
}
Every log from that request now carries the same traceId. In LogFlow, click any log's trace ID to see the full request timeline across services.
With structured logs flowing in, set up alerts for common failure patterns:
| Alert | Condition | Channel |
|---|---|---|
| High error rate | Error rate > 5% for nestjs-app |
Slack |
| Payment failures | Keyword payment_failed |
Telegram |
| Service silence | No logs for 10 minutes |
Go to Alerts → Create in the LogFlow dashboard. Each alert takes 30 seconds to configure.
Your NestJS app now has:
Total setup time: about 15 minutes. No infrastructure to manage.
Free plan available. No credit card required. Up and running in 2 minutes.
Get started freeAdd Structured Logging to FastAPI in 10 Minutes
Set up structured JSON logging in FastAPI and ship logs to LogFlow with automatic request context and error tracking.
Pino 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.