Logger
Logging is a foundational requirement for debugging, monitoring, and operating production-grade backend systems.
The ServerCN Logger component provides two interchangeable logging strategies so you can choose what best fits your application:
- Winston — flexible, feature-rich, file-based logging
- Pino — ultra-fast, JSON-first logging optimized for performance
Both approaches follow the same philosophy:
- Centralized configuration
- Environment-aware behavior
- Structured log output
- Production-safe defaults
Installation Guide
Install the component using the ServerCN CLI:
npx servercn add logger-pinonpx servercn add logger-winstonChoosing a Logger
| Logger | Best For | Characteristics |
|---------|--------------------------|------------------------------------------------------------|
| Winston | Traditional backend apps | Multiple transports, log rotation, readable console output |
| Pino | High-performance APIs | Extremely fast, JSON logs, low overhead |Prerequisites
Ensure the following environment variables are defined before running the application:
PORT = "3000";
NODE_ENV = "development";
LOG_LEVEL = "info";src/configs/env.ts
Ensure the following configuration are defined:
interface Config {
PORT: number;
NODE_ENV: string;
LOG_LEVEL: string;
}
const config: Config = {
PORT: Number(process.env.PORT) || 3000,
NODE_ENV: process.env.NODE_ENV || "development",
LOG_LEVEL: process.env.LOG_LEVEL || "info"
};
export default config;Basic Implementation
1. Winston Logger ✔️
Winston is ideal if you want:
- File-based logs
- Daily rotation
- Human-readable console output
- Multiple log destinations
Winston Configuration
src/utils/logger.ts
Minimal Configuration
import winston from "winston";
export const logger = winston.createLogger({
level: process.env.LOG_LEVEL || "info",
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
transports: [new winston.transports.Console()]
});Advanced Configuration
import env from "../configs/env";
import winston from "winston";
import DailyRotateFile from "winston-daily-rotate-file";
const { combine, timestamp, printf, colorize, errors } = winston.format;
const logFormat = printf(({ level, message, timestamp, stack }) => {
return `${timestamp} [${level}] : ${stack || message}`;
});
const transports: winston.transport[] = [];
/**
* Console logging (development / local)
*/
if (env.NODE_ENV !== "production") {
transports.push(
new winston.transports.Console({
format: combine(
colorize(),
timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
errors({ stack: true }),
logFormat
)
})
);
}
/**
* File logging (staging / production)
*/
if (env.NODE_ENV !== "development") {
transports.push(
new DailyRotateFile({
dirname: "logs/app",
filename: "app-%DATE%.log",
datePattern: "YYYY-MM-DD",
zippedArchive: true,
maxSize: "20m",
maxFiles: "14d",
level: "info"
})
);
transports.push(
new DailyRotateFile({
dirname: "logs/error",
filename: "errors-%DATE%.log",
datePattern: "YYYY-MM-DD",
zippedArchive: true,
maxSize: "20m",
maxFiles: "30d",
level: "error"
})
);
}
export const logger = winston.createLogger({
level: env.LOG_LEVEL,
format: combine(
timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
errors({ stack: true }),
logFormat
),
transports,
exitOnError: false
});Winston Usage Example
import { logger } from "../utils/logger";
logger.info("Server started successfully");
logger.warn("Disk space running low");
logger.error("Failed to connect to database");Winston Usage Output
2026-01-03 18:43:23 [info] : Server started successfully
2026-01-03 18:43:23 [warn] : Disk space running low
2026-01-03 18:43:23 [error] : Failed to connect to databaseChild Logger
const authLogger = logger.child({ module: "auth" });
authLogger.info("Login started");
authLogger.error({ userId }, "Invalid credentials");Winston Installation
npx servercn add logger-winston2. Pino Logger ✅
Pino is designed for speed and structured logging. It is the recommended choice for:
- High-throughput APIs
- Containerized deployments
- Centralized log aggregation (ELK, Loki, Datadog)
Pino Configuration
src/utils/logger.ts
Minimal Configuration
import pino from "pino";
import env from "../configs/env";
export const logger = pino({
level: env.LOG_LEVEL,
transport:
env.NODE_ENV !== "production"
? {
target: "pino-pretty",
options: {
colorize: true,
translateTime: "yyyy-mm-dd HH:MM:ss",
ignore: "pid,hostname"
}
}
: undefined
});Advanced Configuration
import pino from "pino";
import env from "../configs/env";
const isProduction = env.NODE_ENV !== "production";
export const logger = pino({
level: env.LOG_LEVEL || "info",
base: {
pid: process.pid
},
timestamp: pino.stdTimeFunctions.isoTime,
formatters: {
level(label) {
return { level: label };
}
},
redact: {
paths: [
"req.headers.authorization",
"req.headers.cookie",
"password",
"token",
"refreshToken"
],
censor: "[REDACTED]"
},
...(isProduction
? {}
: {
transport: {
target: "pino-pretty",
options: {
colorize: true,
translateTime: "SYS:standard",
ignore: "pid,hostname"
}
}
})
});Pino Usage Example
import { logger } from "../utils/logger";
logger.info("Server started");
logger.warn({ userId: "123" }, "Suspicious activity detected");
logger.error(
{ err: "Database connection failed" },
"Database connection failed"
);Pino Usage Output
// NODE_ENV: production
{"level":30,"time":1767447427459,"pid":95816,"hostname":"HOST_NAME","msg":"Server started"}
{"level":40,"time":1767447427460,"pid":95816,"hostname":"HOST_NAME","userId":"123","msg":"Suspicious activity detected"}
{"level":50,"time":1767447427461,"pid":95816,"hostname":"HOST_NAME","err":"Database connection failed","msg":"Database connection failed"}
[2026-01-04 08:17:19] INFO: Server started
[2026-01-04 08:17:19] WARN: Suspicious activity detected
userId: "123"
[2026-01-04 08:17:19] ERROR: Database connection failed
err: "Database connection failed"
Child Logger
const authLogger = logger.child({ module: "auth" });
authLogger.info("Login started");
authLogger.error({ userId }, "Invalid credentials");Pino Installation
npx servercn add logger-pino