UNPKG

sidelines-logz

Version:

logging sdk for logz.io

328 lines (305 loc) 11.8 kB
const logzio = require('logzio-nodejs'); const COLORS = require('./colors'); const chalk = require('chalk'); const prettier = require('prettier'); const { FIELDS_TO_INDEX } = require('./fields'); const { YELLOW } = require('./colors'); let _logger; let _serviceName; let _environment; let _isMonitor; let _isLoggingToConsole; let _indexFields = FIELDS_TO_INDEX; const reservedWords = ['message', 'stack', 'errorName', 'errorMessage', 'logLevel', 'serviceName', 'critical', 'environment', 'data', 'type', '@timestamp']; const prepareLogMessage = ({ message, error, data, critical, serviceName, environment }, level) => { const errors = []; const obj = {}; obj.critical = !!critical; obj.logLevel = level.level; if (_isMonitor && (!serviceName || !environment)) { _serviceName = serviceName || 'monitor'; _environment = environment || 'production'; errors.push('isMonitor=true serviceName and environment must be defined and of type string'); return [null, errors]; } obj.serviceName = _isMonitor ? serviceName : _serviceName; obj.environment = _isMonitor ? environment : _environment; _serviceName = obj.serviceName; _environment = obj.environment; if (message == null) { errors.push('Message must be defined and of type string'); } else if (message != null && typeof message != 'string') { errors.push(`Message must be of type string and not ${typeof message}`); } else { obj.message = message; } if (error != null && typeof error.name == 'string') { if (error.stack) obj.stack = error.stack.toString(); obj.errorName = error.name; obj.errorMessage = error.message; } else if (error != null && typeof error == 'string') { obj.errMessage = error; } else if (error != null && Object.getPrototypeOf(error) === Object.prototype) { const errorObj = { ...error }; Object.assign(obj, errorObj); } else if (error != null) { errors.push('you are not a good developer!!!!!!!'); } if (data != null && Object.getPrototypeOf(data) === Object.prototype) { let dataObj = { ...data }; reservedWords.forEach((word) => { deleteReservedFieldFromObject(word, dataObj, errors); }); const [toParseObj, toStringObj] = sortDataKeysToIndex({ data: dataObj }); dataObj = { ...toParseObj, unIndexedData: prettier.format(logzio.jsonToString(toStringObj), { parser: 'json' }) }; Object.assign(obj, dataObj); } return [obj, errors]; }; const sortDataKeysToIndex = ({ data, keyName = null }) => { const { toParseObj, toStringObj } = Object.keys(data).reduce( ({ toParseObj, toStringObj }, key) => { const fullKeyName = keyName ? `${keyName}.${key}` : key; if (data[key] && typeof data[key] === 'object' && !Array.isArray(data[key]) && _indexFields.includes(key)) { const [toPareObjNested, toStringObjNested] = sortDataKeysToIndex({ data: data[key], keyName: fullKeyName }); if (Object.keys(toPareObjNested).length) { toParseObj[key] = { ...toPareObjNested }; } if (Object.keys(toStringObjNested).length) { toStringObj[key] = { ...toStringObjNested }; } } else if (_indexFields.includes(fullKeyName)) { toParseObj[key] = data[key]; } else { toStringObj[key] = data[key]; } return { toParseObj, toStringObj }; }, { toParseObj: {}, toStringObj: {} } ); return [toParseObj, toStringObj]; }; const deleteReservedFieldFromObject = (fieldName, obj, errors) => { if (obj.hasOwnProperty(fieldName)) { delete obj[fieldName]; errors.push(`data property can't contain ${fieldName} field, it is a reserved field`); } }; const init = ({ isMonitor = false, accessToken, serviceName, environment, extraFields, isLoggingToConsole = false, indexFields = [] }) => { _isMonitor = isMonitor; if (environment == 'test') { _environment = environment; _currentLogger = () => {}; //we don't want to output any logging during testing return; } if (!accessToken) { throw new Error('Must provide access token for the logger'); } if (!serviceName && !isMonitor) { throw new Error('Must provide serviceName for the logger'); } if (!environment && !isMonitor) { throw new Error('Must provide environment for the logger'); } if (environment == 'development') { _environment = environment; _serviceName = serviceName; console.info('Logger running in development environment... will only log to console'); _currentLogger = logToConsole; return; } _currentLogger = logToLogzIO; const logzIOConfig = { token: accessToken, host: 'listener.logz.io', protocol: 'https', port: '8071', callback: (error) => { if (error) { console.error(error); } }, }; if (extraFields != null && Object.getPrototypeOf(extraFields) === Object.prototype) { const extraFieldsObj = { ...extraFields }; const extraFieldsErrors = []; reservedWords.forEach((word) => { deleteReservedFieldFromObject(word, extraFieldsObj, extraFieldsErrors); }); if (Object.keys(extraFields).length > 0) { reservedWords.push(...Object.keys(extraFields)); } if (Object.keys(extraFieldsObj).length > 0) { logzIOConfig.extraFields = extraFieldsObj; } } _logger = logzio.createLogger(logzIOConfig); _serviceName = serviceName; _environment = environment; _isLoggingToConsole = isLoggingToConsole; if (indexFields.length > 10) { const errorsLogMessage = { message: `Logger init warn-indexFields Array contains more then 10 keyNames`, environment: _environment, serviceName: _serviceName, logLevel: 'warn', }; warn(errorsLogMessage, { level: 'warn', color: COLORS.YELLOW }); } _indexFields.push(...indexFields.slice(0, 10)); }; const logToLogzIO = (options, level, topFunction) => { if (!topFunction) { topFunction = log; } const shouldLogToConsole = options.logToConsole !== 'undefined' ? options.logToConsole : _isLoggingToConsole; const [logData, logErrors] = prepareLogMessage(options, level); if (logData) { _logger.log(logData); if (shouldLogToConsole) { toConsole(logData, level); } } if (logErrors.length > 0) { const logError = new Error('log message was send with bad parameters'); logError.name = 'LogError'; Error.captureStackTrace(logError, topFunction); level = { level: 'warn', color: YELLOW }; const errorsLogMessage = { message: 'Log message was send with errors', environment: _environment, serviceName: _serviceName, logLevel: 'warn', errorName: logError.name, errorMessage: logError.message, stack: logError.stack.toString(), logErrors: logErrors.map((x) => x + '\n').join(''), critical: false, }; _logger.log(errorsLogMessage); if (shouldLogToConsole) { toConsole(errorsLogMessage, level); } } }; const logToConsole = (options, level, topFunction) => { if (!topFunction) { topFunction = log; } const [logData, logErrors] = prepareLogMessage(options, level); if (logData) { toConsole(logData, level); } if (logErrors.length > 0) { const logError = new Error('log message was send with bad parameters'); logError.name = 'LogError'; Error.captureStackTrace(logError, topFunction); const errorsLogMessage = { message: 'Log message was send with errors', environment: _environment, serviceName: _serviceName, logLevel: 'warn', errorName: logError.name, errorMessage: logError.message, stack: logError.stack.toString(), logErrors: logErrors.map((x) => x + '\n').join(''), critical: false, }; toConsole(errorsLogMessage, level); } }; const toConsole = (options, { level, color }) => { if (['info', 'warn', 'error'].includes(level)) { const chalkFn = chalk.hex(color); const critical = options.critical ? 'critical][' : ''; const template = `[${critical}${options.logLevel}][${options.serviceName}]: `; const optionsCopy = { ...options }; let logMessage = template + options.message; reservedWords.forEach((word) => { delete optionsCopy[word]; }); let additionalDataAdded = false; if (Object.keys(optionsCopy).length > 0) { additionalDataAdded = true; logMessage += '\n\r' + template + prettier.format(logzio.jsonToString(optionsCopy), { parser: 'json' }) + '\r'; } if (options.stack) { if (!additionalDataAdded) { logMessage += '\n'; } logMessage += template + '\n' + options.stack; } console[level](chalkFn(logMessage)); } }; const logBeforeInit = (options, level, topFunction) => { const err = new Error('Must init the logger before trying to log'); err.name = 'LogBeforeInitError'; Error.captureStackTrace(err, topFunction); throw err; }; let _currentLogger = logBeforeInit; const error = (options) => { let obj = {}; if (options instanceof Error) { obj.message = options.message; obj.error = options; } else if (typeof options == 'string') { obj.message = options; } else if (options && options.message == null && options.error && options.error.message) { obj.message = options.error.message; Object.assign(obj, options); } else { obj = { ...options }; } _currentLogger(obj, { level: 'error', color: COLORS.RED }, error); }; const warn = (options) => { if (typeof options == 'string') { options = { message: options }; } _currentLogger(options, { level: 'warn', color: COLORS.YELLOW }, warn); }; const info = (options, color) => { if (typeof options == 'string') { options = { message: options }; } _currentLogger(options, { level: 'info', color: typeof color == 'string' ? color : COLORS.GREEN }, info); }; const handleAppErrors = () => { process.on('unhandledRejection', async (reason, promise) => await handleUnHandleRejection(reason)); process.on('SIGINT', async () => await handleInterrupt()); process.on('uncaughtException', async (reason, promise) => await handleUnHandleRejection(reason)); }; const handleUnHandleRejection = async (reason) => { if (!_logger) process.exit(1); error({ error: reason, message: 'unhandledRejection' }); await sendBeforeClose(); process.exit(1); }; const handleInterrupt = async () => { if (!_logger) process.exit(0); info('SIGINT'); await sendBeforeClose(); process.exit(0); }; const sendBeforeClose = async () => new Promise((resolve, _) => { if (!_logger) resolve(); _logger.sendAndClose((err, _) => { if (err) { console.error(err); } resolve(); }); }); handleAppErrors(); const Logger = { info, warn, error, init, COLOR: COLORS, sendBeforeClose, }; module.exports = Logger;