Skip to content

Instantly share code, notes, and snippets.

@alfasin
Last active December 19, 2023 01:27
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save alfasin/ddd5b559658751af35f765a7a690f777 to your computer and use it in GitHub Desktop.
Save alfasin/ddd5b559658751af35f765a7a690f777 to your computer and use it in GitHub Desktop.
A Nodejs implementation of a console transport logger based on Winston 3.x which also prints the filename and line-number
const winston = require('winston');
const { format } = winston;
const { combine, colorize, timestamp, printf } = format;
/**
* /**
* Use CallSite to extract filename and number, for more info read: https://v8.dev/docs/stack-trace-api#customizing-stack-traces
* @param numberOfLinesToFetch - optional, when we want more than one line back from the stacktrace
* @returns {string|null} filename and line number separated by a colon, if numberOfLinesToFetch > 1 we'll return a string
* that represents multiple CallSites (representing the latest calls in the stacktrace)
*
*/
const getFileNameAndLineNumber = function getFileNameAndLineNumber (numberOfLinesToFetch = 1) {
const oldStackTrace = Error.prepareStackTrace;
const boilerplateLines = line => line &&
line.getFileName() &&
// in the following line you may want to "play" with adding a '/' as a prefix/postfix to your module name
(line.getFileName().indexOf('<The Name of This Module>') &&
(line.getFileName().indexOf('/node_modules/') < 0));
try {
// eslint-disable-next-line handle-callback-err
Error.prepareStackTrace = (err, structuredStackTrace) => structuredStackTrace;
Error.captureStackTrace(this);
// we need to "peel" the first CallSites (frames) in order to get to the caller we're looking for
// in our case we're removing frames that come from logger module or from winston
const callSites = this.stack.filter(boilerplateLines);
if (callSites.length === 0) {
// bail gracefully: even though we shouldn't get here, we don't want to crash for a log print!
return null;
}
const results = [];
for (let i = 0; i < numberOfLinesToFetch; i++) {
const callSite = callSites[i];
let fileName = callSite.getFileName();
// BASE_DIR_NAME is the path to the project root folder
const BASE_DIR_NAME = require('path').resolve('.');
fileName = fileName.includes(BASE_DIR_NAME) ? fileName.substring(BASE_DIR_NAME.length + 1) : fileName;
results.push(fileName + ':' + callSite.getLineNumber());
}
return results.join('\n');
} finally {
Error.prepareStackTrace = oldStackTrace;
}
};
function humanReadableFormatter ({ level, message, ...metadata }) {
const filename = getFileNameAndLineNumber();
return `[${level}] [${filename}] ${message} ${JSON.stringify(metadata)}`;
}
const logger = winston.createLogger({
transports: [
new winston.transports.Console({
level: 'info',
handleExceptions: true,
humanReadableUnhandledException: true,
json: false,
colorize: { all: true },
stderrLevels: ['error', 'alert', 'critical', 'bizAlert'],
format: combine(
colorize(),
timestamp(),
humanReadableFormatter,
),
})
]
});
logger.info('test print', { a: 1, b: 2 });
@andhovesyan
Copy link

What's BASE_DIR_NAME?

@alfasin
Copy link
Author

alfasin commented Oct 3, 2021

What's BASE_DIR_NAME?

it's the root of the project

@sudhiryadav
Copy link

please change BASE_DIR_NAME to require('path').resolve('.')

@ljrahn
Copy link

ljrahn commented Nov 2, 2022

Copy and pasted exact code. Got Error:

/usr/src/app/node_modules/logform/combine.js:37
throw new Error([
 ^

Error: No transform function found on format. Did you create a format instance?
const myFormat = format(formatFn);
const instance = myFormat();
at isValidFormat (/usr/src/app/node_modules/logform/combine.js:37:11)
at Array.every (<anonymous>)
at cascade (/usr/src/app/node_modules/logform/combine.js:13:16)
at module.exports (/usr/src/app/node_modules/logform/combine.js:55:33)
at Object.<anonymous> (/usr/src/app/src/utils/logger.js:58:15)
at Module._compile (node:internal/modules/cjs/loader:1155:14)
at Object.Module._extensions..js (node:internal/modules/cjs/loader:1209:10)
at Module.load (node:internal/modules/cjs/loader:1033:32)
at Function.Module._load (node:internal/modules/cjs/loader:868:12)
at Module.require (node:internal/modules/cjs/loader:1057:19)

@alfasin
Copy link
Author

alfasin commented Nov 6, 2022

@lucasrahn09 this is coming from winston, maybe something changed but according to their documents it looks like this code is still using the correct way to set a format to a transport. See: https://github.com/winstonjs/winston#common-transport-options

@alfasin
Copy link
Author

alfasin commented Nov 6, 2022

@sudhiryadav good idea thanks!
done!

@ayurchen
Copy link

ayurchen commented Mar 6, 2023

@alfasin Had the same issue as @ljrahn , the following fixed it for me:

-                humanReadableFormatter,
+                winston.format.printf(humanReadableFormatter),

but apparently this breaks the stack, maximum that I'm getting (after disabling filtering) is

[�[32minfo�[39m] [logger2.cjs:26
logger2.cjs:50
node_modules/logform/printf.js:11
node_modules/logform/combine.js:20
node_modules/winston-transport/index.js:91
node_modules/winston-transport/node_modules/readable-stream/lib/_stream_writable.js:389
node_modules/winston-transport/node_modules/readable-stream/lib/_stream_writable.js:380
node_modules/winston-transport/node_modules/readable-stream/lib/_stream_writable.js:301
node_modules/readable-stream/lib/_stream_readable.js:619
node:events:513] test print {"a":1,"b":2,"timestamp":"2023-03-06T17:17:45.404Z"}

so the closest line is the callSite is line 50, far from 72

BTW, downgrading to winston 3.0.0 didn't help with the error, so it is probably something with the build system configuration...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment