log4js
log4js is a Node.js logging framework built around named categories, severity levels, appenders, and layouts. It can write colored console output, rolling files, date-rolled files, TCP streams, and Express access logs, with category inheritance and contextual fields. It resembles the vocabulary of Java's Log4j but is a separate implementation, and its README warns that expecting identical behavior causes confusion. This is a server-side logging system with asynchronous appender shutdown duties, not a browser package or a direct port of Log4j 2.
log4js remains capable for category-heavy services and applications that truly need local rolling files. For a new containerized service writing JSON to stdout, install Pino instead and let the platform rotate and ship logs.
Use it if
- You need several appenders and different log levels for hierarchical application categories
- You want size-based or date-based file rotation inside a traditional long-running Node service
- You have an existing log4js configuration or plugin appender ecosystem that would be costly to replace
- You want built-in Express request logging with status-aware levels and filtering
- Your production logs go to stdout as structured JSON for a container platform: Pino is faster, JSON-first, and needs far less appender and layout configuration
- You want the Java Log4j 2 configuration model or exact semantics: despite the name, the project explicitly says assuming parity will bring confusion
- You run several independent processes that write the same rotating file: core cluster is specially handled, but unrelated processes need TCP aggregation or an external log collector to avoid rotation and write conflicts
- You want logs immediately after npm install: the default category level is OFF, so an unconfigured logger silently produces nothing
Setup reality
npm install log4js includes types and five runtime dependencies. You must configure at least one appender and a default category before expecting output, then decide category inheritance, layouts, file retention, and backpressure behavior. File and network appenders are asynchronous, so graceful shutdown must wait for log4js.shutdown. Multi-process file logging needs extra architecture and an external collector or TCP aggregation.
Patterns
Enable console loggingconfigure-console
const log4js = require('log4js')
log4js.configure({
appenders: { out: { type: 'stdout' } },
categories: { default: { appenders: ['out'], level: 'info' } },
})
const logger = log4js.getLogger()
logger.info('service started')Without configure or a manual logger level, the default category is OFF and emits nothing.
Set different category levelsuse-categories
log4js.configure({
appenders: { out: { type: 'stdout' } },
categories: {
default: { appenders: ['out'], level: 'info' },
database: { appenders: ['out'], level: 'warn' },
},
})
const dbLog = log4js.getLogger('database')
dbLog.debug('hidden query details')
dbLog.error('connection failed')Category levels are minimum thresholds; database debug is discarded while error is emitted.
Use dotted category inheritanceinherit-category
const queryLog = log4js.getLogger('app.database.query')
queryLog.info('query complete', { durationMs: 18 })Dotted categories inherit the closest configured parent category unless that category sets inherit: false.
Rotate a file by sizerotate-file-size
log4js.configure({
appenders: {
app: {
type: 'file',
filename: 'logs/app.log',
maxLogSize: '10M',
backups: 3,
compress: true,
},
},
categories: { default: { appenders: ['app'], level: 'info' } },
})The process needs permission to create the directory and files; log4js does not replace deployment-level disk monitoring.
Roll a log file dailyrotate-file-date
log4js.configure({
appenders: {
audit: {
type: 'dateFile',
filename: 'logs/audit.log',
pattern: 'yyyy-MM-dd',
numBackups: 14,
compress: true,
},
},
categories: { default: { appenders: ['audit'], level: 'info' } },
})Date rolling happens on the next log write after the date changes, not from a separate midnight timer.
Define a pattern layoutformat-pattern
log4js.configure({
appenders: {
out: {
type: 'stdout',
layout: { type: 'pattern', pattern: '%d %p %c %X{requestId} %m%n' },
},
},
categories: { default: { appenders: ['out'], level: 'info' } },
})%X reads logger context. Missing keys render empty, so add and clear request context deliberately.
Attach and clear contextual dataadd-request-context
const logger = log4js.getLogger('api')
logger.addContext('requestId', request.id)
try {
logger.info('handling request')
await handle(request)
} finally {
logger.removeContext('requestId')
}A logger is shared by category. Leaving context attached can put one request's identifier onto later logs.
Add Express access logslog-express-requests
const accessLog = log4js.getLogger('http')
app.use(log4js.connectLogger(accessLog, {
level: 'auto',
format: ':remote-addr :method :url :status :response-time ms',
nolog: req => req.path === '/health',
}))Place the middleware early enough to observe requests; level auto maps redirects to WARN and 4xx or 5xx responses to ERROR.
Skip expensive debug work when disabledavoid-expensive-log-data
if (logger.isDebugEnabled()) {
logger.debug('cache snapshot', buildLargeDiagnosticObject())
}Arguments are evaluated before logger.debug runs, so the guard is useful when constructing the data is costly.
Wait for appenders during shutdownflush-on-shutdown
process.once('SIGTERM', () => {
server.close(() => {
log4js.shutdown(error => {
if (error) console.error(error)
process.exit(error ? 1 : 0)
})
})
})File and network appenders buffer asynchronously; exiting before the shutdown callback can lose final log messages.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| pino | npm | You want high-throughput structured JSON logs for containers and centralized log pipelines |
| winston | npm | You want a popular transport-based logger with a large third-party transport ecosystem |
| bunyan | npm | You maintain an older JSON-logging codebase already organized around Bunyan streams |