UNPKG

inceptum

Version:

hipages take on the foundational library for enterprise-grade apps written in NodeJS

293 lines 11.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.LogManager = exports.LogManagerInternal = exports.Logger = void 0; // tslint:disable:jsdoc-format const timers_1 = require("timers"); const config = require("config"); const bunyan = require("bunyan"); const PrettyStream = require("bunyan-prettystream"); const path = require("path"); const os = require("os"); const fs = require("fs"); const stream = require("stream"); const stringify = require("json-stringify-safe"); const NewrelicUtil_1 = require("../newrelic/NewrelicUtil"); const ErrorUtil_1 = require("../util/ErrorUtil"); class Logger { } exports.Logger = Logger; class LevelStringifyTransform extends stream.Transform { static levelSerialiser(level) { switch (level) { case bunyan.TRACE: return 'TRACE'; case bunyan.DEBUG: return 'DEBUG'; case bunyan.INFO: return 'INFO'; case bunyan.WARN: return 'WARN'; case bunyan.ERROR: return 'ERROR'; case bunyan.FATAL: return 'FATAL'; default: return 'N/A'; } } constructor() { super({ writableObjectMode: true, readableObjectMode: true }); } // tslint:disable-next-line:prefer-function-over-method _flush(cb) { cb(); } // tslint:disable-next-line:prefer-function-over-method _transform(data, encoding, callback) { data.levelStr = data.level ? LevelStringifyTransform.levelSerialiser(data.level) : 'NA'; callback(null, data); } } class CloseableLevelStringifyTransform extends LevelStringifyTransform { } class StringifyTransform extends stream.Transform { constructor() { super({ writableObjectMode: true, readableObjectMode: false }); } // tslint:disable-next-line:prefer-function-over-method _flush(cb) { cb(); } // tslint:disable-next-line:prefer-function-over-method _transform(data, encoding, callback) { callback(null, `${stringify(data)}\n`); } } class ConsoleRawStream extends stream.Writable { constructor() { super({ objectMode: true }); } _write(rec, encoding, cb) { if (rec.level < bunyan.WARN) { return process.stdout.write(`${stringify(rec)}\n`, encoding, cb); } return process.stderr.write(`${stringify(rec)}\n`, encoding, cb); } } class LogManagerInternal { constructor() { this.streamCache = new Map(); this.appName = config.get('app.name', 'na'); } static beSmartOnThePath(thePath) { // Let's see if we can find an ancestor called 'src' const srcLoc = thePath.indexOf(`${path.sep}src${path.sep}`); if (srcLoc >= 0) { return thePath.substr(srcLoc + 5); } const builtLoc = thePath.indexOf(`${path.sep}dist${path.sep}`); if (builtLoc >= 0) { return thePath.substr(builtLoc + 6); } if (thePath.startsWith(process.cwd())) { const fromCwd = thePath.substr(process.cwd().length); return fromCwd.startsWith(path.sep) ? fromCwd.substr(1) : fromCwd; } return thePath; } static getEffectiveLevel(loggerName, streamName, configuredLevel) { let overrideEnv = `LOG_${loggerName}_${streamName}`.replace('/[^a-zA-z0-9_]+/', '_').replace('/[_]{2,}', '_').toUpperCase(); if (process.env[overrideEnv]) { return process.env[overrideEnv]; } overrideEnv = `LOG_${loggerName}`.replace('/[^a-zA-z0-9_]+/', '_').replace('/[_]{2,}', '_').toUpperCase(); if (process.env[overrideEnv]) { return process.env[overrideEnv]; } return configuredLevel; } static mkdirpSync(thePath, mode) { if (fs.existsSync(thePath)) { return true; } else if (this.mkdirpSync(path.dirname(thePath), mode)) { fs.mkdirSync(path.basename(thePath), mode); return true; } return false; } static resolvePath(thePath) { const basePath = process.env.LOG_DIR || (config.has('Logger.dir') && config.get('Logger.dir')) || os.tmpdir(); const finalPath = path.resolve(basePath, thePath); if (!fs.existsSync(path.dirname(finalPath))) { LogManagerInternal.mkdirpSync(path.dirname(finalPath), 0o766); } // eslint-disable-next-line no-console // tslint:disable-next-line:no-console console.log(`Logging to file: ${finalPath}`); return finalPath; } getLogger(filePath) { // console.log(`Got here with filePath: ${filePath}`); const thePath = removeExtension(filePath || _getCallerFile()); if (thePath.substr(0, 1) !== '/') { // It's not a full file path. return this.getLoggerInternal(thePath); } return this.getLoggerInternal(LogManagerInternal.beSmartOnThePath(thePath)); } scheduleShutdown() { (0, timers_1.setTimeout)(() => this.closeStreams(), 1000); } closeStreams() { this.streamCache.forEach((streamToClose) => { if (streamToClose instanceof CloseableLevelStringifyTransform) { streamToClose.end(); } }); } setAppName(appName) { this.appName = appName; } getAppName() { return this.appName; } getLoggerInternal(loggerPath) { if (!config.has('logging.loggers')) { throw new Error("Couldn't find loggers configuration!!! Your logging config is wrong!"); } const loggers = config.get('logging.loggers'); const candidates = loggers.filter((logger) => loggerPath.startsWith(logger.name)); if (!candidates || candidates.length === 0) { candidates.push(loggers.filter((logger) => logger.name === 'ROOT')[0]); } else { candidates.sort((a, b) => { if (a.length > b.length) { return -1; } if (a.length < b.length) { return 1; } return 0; }); } const loggerConfig = candidates[0]; const loggerName = loggerConfig.name; const streamNames = loggerConfig.streams; const streams = Object.keys(streamNames) .filter((streamName) => { const level = LogManagerInternal.getEffectiveLevel(loggerName, streamName, streamNames[streamName]); return level && level.toUpperCase() !== 'OFF'; }) .map((streamName) => this.getStreamConfig(streamName, LogManagerInternal.getEffectiveLevel(loggerName, streamName, streamNames[streamName]))); return bunyan.createLogger({ name: loggerPath, streams, serializers: bunyan.stdSerializers, appName: this.appName ? this.appName.toLowerCase() : 'appNameNotAvailable' }); } getStreamConfig(streamName, level) { const configKey = `logging.streams.${streamName}`; if (!config.has(configKey)) { throw new Error(`Couldn't find stream with name ${streamName}`); } const streamConfig = config.get(configKey); switch (streamConfig.type) { case 'console': return { stream: this.getUnderlyingStream(streamName), level, name: streamConfig.name, }; case 'json': return { type: 'raw', stream: this.getUnderlyingStream(streamName), closeOnExit: false, level, }; case 'file': return { type: 'raw', stream: this.getUnderlyingStream(streamName), closeOnExit: true, level, }; default: throw new Error('Unknown log stream type'); } } getUnderlyingStream(streamName) { if (!this.streamCache.has(streamName)) { const configKey = `logging.streams.${streamName}`; if (!config.has(configKey)) { throw new Error(`Couldn't find stream with name ${streamName}`); } const streamConfig = config.get(configKey); switch (streamConfig.type) { case 'console': { const prettyStream = new PrettyStream(); prettyStream.pipe(process.stdout); this.streamCache.set(streamName, prettyStream); } break; case 'json': { const levelStringifyTransform = new LevelStringifyTransform(); levelStringifyTransform.pipe(new ConsoleRawStream()); this.streamCache.set(streamName, levelStringifyTransform); } break; case 'file': { } break; default: throw new Error('Unknown log stream type'); } } return this.streamCache.get(streamName); } } exports.LogManagerInternal = LogManagerInternal; exports.LogManager = new LogManagerInternal(); const baseLogger = exports.LogManager.getLogger('ROOT'); process.on('unhandledRejection', (reason, promise) => { if (NewrelicUtil_1.NewrelicUtil.isNewrelicAvailable()) { NewrelicUtil_1.NewrelicUtil.getNewrelicIfAvailable().noticeError(reason, { source: 'unhandledRejection' }); } // eslint-disable-next-line no-underscore-dangle baseLogger.fatal(reason, `Unhandled promise: ${reason}`); }); process.on('uncaughtException', (err) => { if (NewrelicUtil_1.NewrelicUtil.isNewrelicAvailable()) { NewrelicUtil_1.NewrelicUtil.getNewrelicIfAvailable().noticeError(err, { source: 'unhandledException' }); } baseLogger.fatal(err, `Uncaught exception: ${err} | ${err.stack}`); }); process.on('warning', (err) => { baseLogger.warn(err, `Uncaught warning: ${err} | ${err.stack}`); }); function _getCallerFile() { let callerFile; const err = new ErrorUtil_1.ExtendedError('Getting Stack'); const structuredStackTrace = err.getStructuredStackTrace(); if (structuredStackTrace) { // console.log(structuredStackTrace.map((cs) => cs.getFileName())); const currentFile = structuredStackTrace.shift().getFileName(); callerFile = currentFile; while (structuredStackTrace.length && currentFile === callerFile) { callerFile = structuredStackTrace.shift().getFileName(); } if (!callerFile) { return '/na'; } return callerFile; } return '/na'; } function removeExtension(thePath) { if (['.js', '.ts'].indexOf(thePath.substr(-3)) >= 0) { return thePath.substring(0, thePath.length - 3); } return thePath; } //# sourceMappingURL=LogManager.js.map