All files LoggerWrapper.js

100% Statements 19/19
100% Branches 9/9
100% Functions 3/3
100% Lines 19/19

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81                              11x                                     9x     1x         8x 1x       8x 8x     8x 8x 8x 8x   8x 8x 8x   8x 2x     8x                     1x       1x  
'use strict';
 
/**
 * This is a wrapper for the logger provided by service runner.
 * It provides more user-friendly logging APIs and better error
 * signaling for when it is used incorrectly.
 *
 * This is the logger that other scripts in this project will interact with.
 * Usage:
 * const logger = new LoggerWrapper( <somelogger> );
 * logger.log('warn', 'hello this is a message');
 * logger.warn('hello this is also a message');
 */
class LoggerWrapper {
	constructor( logger ) {
		this._logger = logger;
	}
 
	/**
	 * Logs a message on a given severity level.
	 * Acceptable levels: 'trace', 'debug', 'info', 'warn', 'error', and 'fatal'.
	 *
	 * @param {string} level Severity level using one of the level options.
	 * Can also be: 'trace' or 'trace/request'.
	 *
	 * @param {Object} data Contains message and any relevant info for the log.
	 * @param {string|undefined} data.message Log message string
	 * @param {string|undefined} data.requestId The 'x-request-id' HTTP header, for traceability
	 * @param {string|Object|undefined} data.info request details or JSON object
	 */
	log( level, data = { message: 'No message set!', requestId: 'No requestId set!', info: 'No info set!' } ) {
		// TODO (T369560):
		// confirm in Logstash Prod that message param is key of an object, not string;
		// and that is what actually gets emitted as 'message' && 'msg'; i.e. function-evaluator
		if ( !level || !data ) {
			// The service runner implementation will just silently no-op
			// in this situation. We want to alert the caller here.
			throw new Error(
				`Incorrect usage of the logger. Both arguments need to be
				present. E.g. logger.log(level, data).` );
		}
		// temporarily adding this in case there's an undetected old version of logging somewhere
		if ( typeof data === 'string' ) {
			data = { message: data };
		}
 
		// We want to output the request ID under this special name, but it's awkward.
		data[ 'x-request-id' ] = data.requestId;
		delete data.requestId;
 
		// add timestamp and stacktrace per log
		const timeStamp = new Date().toISOString();
		data.time = timeStamp;
		const stackTrace = new Error().stack;
		const detailedStack = stackTrace.split( '\n' ).slice( 2 ).join( '\n' );
		// adding compacted details to message, in case data object gets swallowed up in Logstash
		const simpleTrace = detailedStack.split( '\n' )[ 0 ].trim();
		data.trace = simpleTrace;
		data.message = data.message + `, time: ${ timeStamp }, reqId: ${ data[ 'x-request-id' ] }, trace: ${ simpleTrace }`;
		// this is so we can easily debug/detect in docker logs
		if ( process.env.WIKIFUNCTIONS_DEBUG_LOCAL ) {
			console.log( 'Logging LEVEL:', level, ', MESSAGE:', data.message, ', DATA:', data, ', DETAILS:', detailedStack );
		}
 
		this._logger.log( level, data );
	}
 
	/**
	 * Creates a child logger for a sub-component of your application.
	 * This directly wraps its core logger obj's implementation.
	 *
	 * @param {*} args arguments for the child wrapper.
	 * @return {LoggerWrapper} A new logger for the sub-component.
	 */
	child( args ) {
		return new LoggerWrapper( this._logger.child( args ) );
	}
}
 
module.exports = { LoggerWrapper };