Skip to content

Commit

Permalink
✨ Port logging from v3 API
Browse files Browse the repository at this point in the history
  • Loading branch information
Pl217 committed Aug 18, 2021
1 parent 23593c9 commit d305432
Show file tree
Hide file tree
Showing 5 changed files with 551 additions and 2 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"lint": "yarn lint-prettier && yarn lint-eslint"
},
"dependencies": {
"bunyan": "^1.0.1",
"fp-ts": "^2.6.6",
"io-ts": "2.2.9",
"knex": "0.21.1",
Expand All @@ -20,6 +21,7 @@
"pg": "^8.5.1"
},
"devDependencies": {
"@types/bunyan": "^1.0.1",
"@types/hapi__hapi": "20.0.8",
"@types/lodash": "^4.14.170",
"@types/node": "^14.0.5",
Expand Down
228 changes: 228 additions & 0 deletions src/logging/log.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/**
* Use of `any` in this module is generally deliberate to help with generics
*/
import bunyan = require('bunyan');
import { format } from 'util';
import { Config } from '../Context';

export interface LogObject {
time: Date;
level: number;
msg: string;
stackTrace?: string;
}

export type LoggingListener = (log: LogObject) => void;

const listeners = new Set<LoggingListener>();

export const addLoggingListener = (l: LoggingListener): void => {
listeners.add(l);
};

export const removeLoggingListener = (l: LoggingListener): void => {
listeners.delete(l);
};

export const loggingListenerStream: bunyan.Stream = {
type: 'raw',
level: 0,
stream: {
write: (obj: LogObject) => {
listeners.forEach((l) => l(obj));
},
} as any,
};

const printLevel = (obj: LogObject, useColor: boolean): string | undefined => {
if (obj.level >= bunyan.FATAL) {
return (useColor ? '\x1b[0;31;1m' : '') + '[FATAL]';
}
if (obj.level >= bunyan.ERROR) {
return (useColor ? '\x1b[0;31;1m' : '') + '[ERROR]';
}
if (obj.level >= bunyan.WARN) {
return (useColor ? '\x1b[0;33;1m' : '') + '[WARN]';
}
if (obj.level >= bunyan.INFO) {
return (useColor ? '\x1b[0;36;1m' : '') + '[INFO]';
}
if (obj.level >= bunyan.DEBUG) {
return '[DEBUG]';
}
if (obj.level >= bunyan.TRACE) {
return '[TRACE]';
}
};

export const printLog = (obj: LogObject, useColor: boolean): void => {
process.stdout.write(
// Make Dim
(useColor ? '\x1b[2m' : '') +
// Time
'[' +
obj.time.getHours() +
':' +
obj.time.getMinutes() +
':' +
obj.time.getSeconds() +
':' +
obj.time.getMilliseconds() +
'] ' +
printLevel(obj, useColor) +
' ' +
// Reset colors
(useColor ? '\x1b[0m' : '') +
obj.msg +
'\n'
);
if (obj.stackTrace) {
process.stdout.write(
obj.stackTrace
.split('\n')
.map((s) => ` ${s}`)
.join('\n')
);
}
};

export const getLog = (config: Config): bunyan => {
interface LoggingConfig {
writeToFile: bunyan.LogLevel | false;
writeToStdout:
| {
level: bunyan.LogLevel;
color: boolean;
}
| false;
}

const LOGGING_MODES = ['live', 'devServer', 'jenkinsScript', 'test'] as const;

type LoggingMode = typeof LOGGING_MODES[number];

const isLoggingMode = (mode: string): mode is LoggingMode =>
LOGGING_MODES.indexOf(mode as LoggingMode) > -1;

const LOGGING_CONFIG_LIVE: LoggingConfig = {
writeToFile: 'warn',
writeToStdout: false,
};

/**
* Standard logging configurations
*/
const LOGGING_CONFIGS: { [key in LoggingMode]: LoggingConfig } = {
live: LOGGING_CONFIG_LIVE,
devServer: {
writeToFile: false,
writeToStdout: {
level: 'debug',
color: true,
},
},
jenkinsScript: {
// Use same file config level as live server
writeToFile: LOGGING_CONFIG_LIVE.writeToFile,
writeToStdout: {
level: 'debug',
color: false,
},
},
test: {
// Console can be independently activated for each test via lib/logging
writeToFile: false,
writeToStdout: false,
},
};

const getLoggingConfig = () => {
if (config.logging.mode) {
if (isLoggingMode(config.logging.mode)) {
return LOGGING_CONFIGS[config.logging.mode];
}

console.error('Unrecognized logging mode:', config.logging.mode);
}
// TODO:
// once all jenkins scripts have been updated to use the LOG_MODE env var,
// this should be updated to use devServer instead of jenkinsScript,
// and when all local scripts have been updated, this can be removed.
if (config.name === 'dockerdev') {
return LOGGING_CONFIGS.devServer;
}

return LOGGING_CONFIGS.live;
};

const logConfig: LoggingConfig = getLoggingConfig();

// Overwrite color settings if specified
if (config.logging.color !== undefined && logConfig.writeToStdout) {
logConfig.writeToStdout.color = config.logging.color;
}

if (process.env.JEST_WORKER_ID === undefined) {
console.log('Logging mode set to:', logConfig);
}

const streams: bunyan.Stream[] = [loggingListenerStream];

if (logConfig.writeToFile) {
streams.push({
level: logConfig.writeToFile,
path: '/var/log/hpc_service.log',
});
}

if (logConfig.writeToStdout) {
const shouldUseColor = logConfig.writeToStdout.color;
streams.push({
type: 'raw',
level: logConfig.writeToStdout.level,
stream: {
write: (obj: LogObject) => printLog(obj, shouldUseColor),
} as any,
});
}

const log = bunyan.createLogger({
name: config.name || '',
serializers: {
req: bunyan.stdSerializers.req,
},
streams: streams,
});

// Overwrite default console log behaviour to output to bunyan using json
console.log = (...args: any[]) => {
log.info({ data: 'console.log' }, format(args[0], ...args.slice(1)));
};
console.info = (...args: any[]) => {
log.info({ data: 'console.info' }, format(args[0], ...args.slice(1)));
};
console.warn = (...args: any[]) => {
log.warn({ data: 'console.warn' }, format(args[0], ...args.slice(1)));
};
console.error = (...args: any[]) => {
log.error({ data: 'console.error' }, format(args[0], ...args.slice(1)));
};
console.debug = (...args: any[]) => {
log.debug({ data: 'console.debug' }, format(args[0], ...args.slice(1)));
};

// Handle uncaught rejections by logging an error
process.on('unhandledRejection', (reason, promise) => {
log.error(
{
data: 'unhandledRejection',
promise: format(promise),
reason: format(reason),
},
`Unhandled Rejection: ${reason}`
);
});

return log;
};
Loading

0 comments on commit d305432

Please sign in to comment.