Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Contextual Logger - Applying CLS context to logs
diff --git a/logger.js b/logger.js
index 3aa3418..d19e435 100644
--- a/logger.js
+++ b/logger.js
@@ -1,6 +1,41 @@
const pino = require('pino');
const { createNamespace } = require('cls-hooked');
+const logMethods = ['trace', 'debug', 'info', 'warn', 'error', 'fatal'];
+
+const logMethodHandler = {
+ apply(target, thisArg, argumentList) {
+ // eslint-disable-next-line camelcase
+ const { id, _ns_name, ...clsContext } =
+ ((thisArg || {}).cls || {}).active || {};
+
+ const [context, ...rest] = argumentList;
+
+ let finalArgList = argumentList;
+ if (typeof context === 'string') {
+ // Log was called only with message, no local context
+ const message = context;
+ finalArgList = [clsContext, message, ...rest];
+ } else {
+ // Log was called local context, so we merge it into clsContext
+ const fullContext = Object.assign({}, clsContext, context);
+ finalArgList = [fullContext, ...rest];
+ }
+
+ return target.apply(thisArg, finalArgList);
+ },
+};
+
+const loggerObjectHandler = {
+ get(target, prop) {
+ if (!logMethods.includes(prop)) {
+ return target[prop];
+ }
+
+ return new Proxy(target[prop], logMethodHandler);
+ },
+};
+
let counter = 0;
function createLogger(opts, destination) {
@@ -9,7 +44,7 @@ function createLogger(opts, destination) {
counter += 1;
- return Object.assign(baseLogger, { cls });
+ return new Proxy(Object.assign(baseLogger, { cls }), loggerObjectHandler);
}
module.exports = createLogger;
diff --git a/logger.test.js b/logger.test.js
index 45c16ca..155415a 100644
--- a/logger.test.js
+++ b/logger.test.js
@@ -42,3 +42,61 @@ test(`2 different loggers don't share the same namespace`, t => {
t.notDeepEqual(logger.cls, anotherLogger.cls);
});
+
+test.cb(`Should properly log message with cls context`, t => {
+ const stream = parseJSONStream();
+ const gen = streamToGenerator(stream);
+ const logger = createLogger({}, stream);
+
+ const msg = 'foo';
+ const clsValues = {
+ dummy: 'value',
+ another: 'another value',
+ };
+
+ logger.cls.run(() => {
+ logger.cls.set('dummy', clsValues.dummy);
+ logger.cls.set('another', clsValues.another);
+ process.nextTick(async () => {
+ logger.info(msg);
+
+ const entry = await gen.next().value;
+
+ t.is(entry.dummy, clsValues.dummy);
+ t.is(entry.another, clsValues.another);
+
+ t.end();
+ });
+ });
+});
+
+test.cb(
+ `Should properly log message with both cls and local context,
+ And local context should have precedence over cls context`,
+ t => {
+ const stream = parseJSONStream();
+ const gen = streamToGenerator(stream);
+ const logger = createLogger({}, stream);
+
+ const msg = 'foo';
+ const clsValues = {
+ dummy: 'value',
+ precedence: 'will be overwitten',
+ };
+
+ logger.cls.run(() => {
+ logger.cls.set('dummy', clsValues.dummy);
+ logger.cls.set('another', clsValues.another);
+ process.nextTick(async () => {
+ const localValues = { precedence: 'local' };
+ logger.info(localValues, msg);
+
+ const entry = await gen.next().value;
+
+ t.is(entry.precedence, localValues.precedence);
+
+ t.end();
+ });
+ });
+ }
+);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.