inceptum
Version:
hipages take on the foundational library for enterprise-grade apps written in NodeJS
299 lines • 12 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
// 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 RotatingFileStream = require("bunyan-rotating-file-stream");
const RedisTransport = require("bunyan-redis");
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 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 getRedisStream(redisConfig) {
return new RedisTransport({
container: redisConfig.key || 'phplog',
host: process.env.REDIS_HOST || redisConfig.host || '127.0.0.1',
port: process.env.REDIS_PORT || redisConfig.port || 6379,
db: 0,
});
}
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() {
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 'file':
return {
type: 'raw',
stream: this.getUnderlyingStream(streamName),
closeOnExit: true,
level,
};
case 'redis':
return {
type: 'raw',
level,
stream: this.getUnderlyingStream(streamName),
};
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 'file':
{
const levelStringifyTransform = new LevelStringifyTransform();
levelStringifyTransform.pipe(new StringifyTransform()).pipe(new RotatingFileStream({
path: LogManagerInternal.resolvePath(streamConfig.path),
period: streamConfig.period || '1d',
count: streamConfig.count || 14,
}));
this.streamCache.set(streamName, levelStringifyTransform);
}
break;
case 'redis':
{
const levelStringifyTransform = new CloseableLevelStringifyTransform();
const redisStream = LogManagerInternal.getRedisStream(streamConfig);
redisStream['end'] = () => null;
levelStringifyTransform.pipe(redisStream);
levelStringifyTransform.on('end', () => {
redisStream._client.quit();
});
this.streamCache.set(streamName, levelStringifyTransform);
}
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} ${promise && promise._trace && promise._trace.stack ? promise._trace.stack : ''}`);
});
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}`);
});
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