sidelines-logz
Version:
logging sdk for logz.io
328 lines (305 loc) • 11.8 kB
JavaScript
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;