A wrapper for Winston Logging Node.js library that formats the output on STDOUT as Logstash JSON format.
Main features:
- Dual Package Support: Native support for both ES Modules (ESM) and CommonJS (CJS).
- Node 24 Ready: Optimized for native TypeScript execution (Type Stripping).
- Distributed Tracing: Automated request tracking using cls-rtracer.
npm install @zenvia/loggerThe following environment variables can be used for increase the log information:
- APP_NAME: value to filled the "application" field in the output JSON. If empty, the name attribute on
package.jsonwill be used instead. - NODE_ENV: value to filled the "environment" field in the output JSON.
- HOST or HOSTNAME: value to filled the "host" field in the output JSON.
- LOGGING_LEVEL: set the level of messages that the logger should log. Default to
DEBUG. - LOGGING_FORMATTER_DISABLED (version 1.1.0 and above): When
true, the output logging will not be formatted to JSON. Useful during development time. Default tofalse. - LOGGING_FRAMEWORK_MIDDLEWARE (version 1.5.0 and above): Value that defines which middleware will be used. It is possible to choose between the middlewares: EXPRESS, FASTIFY, HAPI and KOA. If empty, the middleware default is
EXPRESS. - LOGGING_TRACE_HEADER (version 1.5.0 and above): Value indicating the header name that must be obtained from the traceId value in the request. Default is
X-TraceId.
// ES6 or Typescript
import express from 'express';
import logger, { traceMiddleware } from '@zenvia/logger';
const app = express();
app.use(traceMiddleware);
logger.info('some message');// ES6 or Typescript
import fastify from 'fastify'
import logger, { traceMiddleware } from '@zenvia/logger';
fastify.register(traceMiddleware);
logger.info('some message');// ES6 or Typescript
import Koa from 'koa';
import logger, { traceMiddleware } from '@zenvia/logger';
const app = new Koa();
app.use(traceMiddleware);
logger.info('some message');// ES6 or Typescript
import Koa from 'koa';
import logger, { traceMiddleware } from '@zenvia/logger';
const init = async () => {
const server = Hapi.server({
port: 3000,
host: 'localhost'
});
await server.register({
plugin: traceMiddleware
});
}
logger.info('some message');Output:
{
"@timestamp": "2018-06-05T18:20:42.345Z",
"@version": 1,
"application": "application-name",
"message": "some message",
"level": "INFO",
"traceID": "123e4567-e32b-12d3-a432-626614174888"
}The log levels are as follows.
- fatal
- error
- warn
- info
- debug
For backward compatibility purposes, "verbose" and "silly" levels will behave the same as "debug" level.
logger.debug('Some text message', { keyA: 'value A', keyB: 'value B' });Output:
{
"keyA": "value A",
"keyB": "value B",
"@timestamp": "2018-06-05T22:04:42.039Z",
"@version": 1,
"application": "application-name",
"message": "Some text message",
"level": "DEBUG"
}logger.error('Ops!', new Error('Something goes wrong'));Output:
{
"message": "Ops!: Something goes wrong",
"@timestamp": "2018-06-05T22:14:09.683Z",
"@version": 1,
"application": "application-name",
"level": "ERROR",
"stack_trace": "Error: Something goes wrong\n at repl:1:34\n at Script.runInThisContext (vm.js:91:20)\n at REPLServer.defaultEval (repl.js:317:29)\n at bound (domain.js:396:14)\n at REPLServer.runBound [as eval] (domain.js:409:12)\n at REPLServer.onLine (repl.js:615:10)\n at REPLServer.emit (events.js:187:15)\n at REPLServer.EventEmitter.emit (domain.js:442:20)\n at REPLServer.Interface._onLine (readline.js:290:10)\n at REPLServer.Interface._line (readline.js:638:8)"
}Due to limitations of winston lib, when a text, an error and extra key/value fields are logged at once, the output message field will contain the text message, the error message and the full stack trace as shown.
logger.fatal('Ops!', { new Error('Something goes wrong'), { keyA: 'value A', keyB: 'value B' } });Output:
{
"keyA": "value A",
"keyB": "value B",
"@timestamp": "2018-06-05T22:09:22.750Z",
"@version": 1,
"application": "application-name",
"message": "Ops! Error: Something goes wrong\n at repl:1:34\n at Script.runInThisContext (vm.js:91:20)\n at REPLServer.defaultEval (repl.js:317:29)\n at bound (domain.js:396:14)\n at REPLServer.runBound [as eval] (domain.js:409:12)\n at REPLServer.onLine (repl.js:615:10)\n at REPLServer.emit (events.js:187:15)\n at REPLServer.EventEmitter.emit (domain.js:442:20)\n at REPLServer.Interface._onLine (readline.js:290:10)\n at REPLServer.Interface._line (readline.js:638:8)",
"level": "FATAL"
}From version 1.5.0 it is possible to track logs. To do traceability, the cls-rTrace package is used. To use it, just add the middleware in the framework you are using. In this way it is possible to propagate the traceId received in a request to the logs throughout your project. If no traceId is passed in the request, Zenvia Logger generates a random traceId for the request being processed.
Request example sending traceId:
curl 'http://localhost/your-application' \
--header 'X-TraceId: dbcdd40e-10cd-40a7-b912-1b0a17483d67' \Log
logger.info('message with traceID');Log Output
{
"@timestamp": "2018-06-05T18:20:42.345Z",
"@version": 1,
"application": "application-name",
"message": "message with traceID",
"level": "INFO",
"traceID": "dbcdd40e-10cd-40a7-b912-1b0a17483d67'"
}Request example without sending traceId:
curl 'http://localhost/your-application'Log
logger.info('message without traceID');Log Output
{
"@timestamp": "2018-06-05T18:20:42.345Z",
"@version": 1,
"application": "application-name",
"message": "message with traceID",
"level": "INFO",
"traceID": "912c029c-c38f-49e7-9968-e575c5108178'"
}You can use runWithContext and addContext to manage metadata context across asynchronous flows (like Kafka consumers or complex background jobs). This ensures all logs within a specific execution thread share the same context without passing a logger instance manually.
Useful for isolating logs from different event sources or partitions.
import logger from '@zenvia/logger';
async function handleEvent(event) {
await logger.runWithContext({
partition: event.partition,
topic: 'orders-topic'
}, async () => {
// All logs here will include partition and topic automatically
logger.info('Processing started');
await processOrder(event.data);
});
}You can inject new metadata into the current context at any point during the execution.
import logger from '@zenvia/logger';
async function processOrder(data) {
logger.info('Validating...');
const provider = await api.getProvider(data.id);
// Mutates the current context to include providerId in all subsequent logs
logger.addContext({ providerId: provider.id });
logger.info('Validation complete');
// Output JSON will contain: partition, topic AND providerId
}Contexts can be nested. The inner context will inherit metadata from the parent.
await logger.runWithContext({ level1: 'A' }, async () => {
await logger.runWithContext({ level2: 'B' }, async () => {
logger.info('Log with A and B');
});
});