Skip to content

Commit b2df67b

Browse files
committed
✨ Port logging from v3 API
1 parent ee1e5ca commit b2df67b

File tree

5 files changed

+551
-2
lines changed

5 files changed

+551
-2
lines changed

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"lint": "yarn lint-prettier && yarn lint-eslint"
1313
},
1414
"dependencies": {
15+
"bunyan": "^1.0.1",
1516
"fp-ts": "^2.6.6",
1617
"io-ts": "2.2.9",
1718
"knex": "0.21.1",
@@ -20,6 +21,7 @@
2021
"pg": "^8.5.1"
2122
},
2223
"devDependencies": {
24+
"@types/bunyan": "^1.0.1",
2325
"@types/hapi__hapi": "20.0.8",
2426
"@types/lodash": "^4.14.170",
2527
"@types/node": "^14.0.5",

src/logging/log.ts

+228
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
/**
3+
* Use of `any` in this module is generally deliberate to help with generics
4+
*/
5+
import bunyan = require('bunyan');
6+
import { format } from 'util';
7+
import { Config } from '../Context';
8+
9+
export interface LogObject {
10+
time: Date;
11+
level: number;
12+
msg: string;
13+
stackTrace?: string;
14+
}
15+
16+
export type LoggingListener = (log: LogObject) => void;
17+
18+
const listeners = new Set<LoggingListener>();
19+
20+
export const addLoggingListener = (l: LoggingListener): void => {
21+
listeners.add(l);
22+
};
23+
24+
export const removeLoggingListener = (l: LoggingListener): void => {
25+
listeners.delete(l);
26+
};
27+
28+
export const loggingListenerStream: bunyan.Stream = {
29+
type: 'raw',
30+
level: 0,
31+
stream: {
32+
write: (obj: LogObject) => {
33+
listeners.forEach((l) => l(obj));
34+
},
35+
} as any,
36+
};
37+
38+
const printLevel = (obj: LogObject, useColor: boolean): string | undefined => {
39+
if (obj.level >= bunyan.FATAL) {
40+
return (useColor ? '\x1b[0;31;1m' : '') + '[FATAL]';
41+
}
42+
if (obj.level >= bunyan.ERROR) {
43+
return (useColor ? '\x1b[0;31;1m' : '') + '[ERROR]';
44+
}
45+
if (obj.level >= bunyan.WARN) {
46+
return (useColor ? '\x1b[0;33;1m' : '') + '[WARN]';
47+
}
48+
if (obj.level >= bunyan.INFO) {
49+
return (useColor ? '\x1b[0;36;1m' : '') + '[INFO]';
50+
}
51+
if (obj.level >= bunyan.DEBUG) {
52+
return '[DEBUG]';
53+
}
54+
if (obj.level >= bunyan.TRACE) {
55+
return '[TRACE]';
56+
}
57+
};
58+
59+
export const printLog = (obj: LogObject, useColor: boolean): void => {
60+
process.stdout.write(
61+
// Make Dim
62+
(useColor ? '\x1b[2m' : '') +
63+
// Time
64+
'[' +
65+
obj.time.getHours() +
66+
':' +
67+
obj.time.getMinutes() +
68+
':' +
69+
obj.time.getSeconds() +
70+
':' +
71+
obj.time.getMilliseconds() +
72+
'] ' +
73+
printLevel(obj, useColor) +
74+
' ' +
75+
// Reset colors
76+
(useColor ? '\x1b[0m' : '') +
77+
obj.msg +
78+
'\n'
79+
);
80+
if (obj.stackTrace) {
81+
process.stdout.write(
82+
obj.stackTrace
83+
.split('\n')
84+
.map((s) => ` ${s}`)
85+
.join('\n')
86+
);
87+
}
88+
};
89+
90+
export const getLog = (config: Config): bunyan => {
91+
interface LoggingConfig {
92+
writeToFile: bunyan.LogLevel | false;
93+
writeToStdout:
94+
| {
95+
level: bunyan.LogLevel;
96+
color: boolean;
97+
}
98+
| false;
99+
}
100+
101+
const LOGGING_MODES = ['live', 'devServer', 'jenkinsScript', 'test'] as const;
102+
103+
type LoggingMode = typeof LOGGING_MODES[number];
104+
105+
const isLoggingMode = (mode: string): mode is LoggingMode =>
106+
LOGGING_MODES.indexOf(mode as LoggingMode) > -1;
107+
108+
const LOGGING_CONFIG_LIVE: LoggingConfig = {
109+
writeToFile: 'warn',
110+
writeToStdout: false,
111+
};
112+
113+
/**
114+
* Standard logging configurations
115+
*/
116+
const LOGGING_CONFIGS: { [key in LoggingMode]: LoggingConfig } = {
117+
live: LOGGING_CONFIG_LIVE,
118+
devServer: {
119+
writeToFile: false,
120+
writeToStdout: {
121+
level: 'debug',
122+
color: true,
123+
},
124+
},
125+
jenkinsScript: {
126+
// Use same file config level as live server
127+
writeToFile: LOGGING_CONFIG_LIVE.writeToFile,
128+
writeToStdout: {
129+
level: 'debug',
130+
color: false,
131+
},
132+
},
133+
test: {
134+
// Console can be independently activated for each test via lib/logging
135+
writeToFile: false,
136+
writeToStdout: false,
137+
},
138+
};
139+
140+
const getLoggingConfig = () => {
141+
if (config.logging.mode) {
142+
if (isLoggingMode(config.logging.mode)) {
143+
return LOGGING_CONFIGS[config.logging.mode];
144+
}
145+
146+
console.error('Unrecognized logging mode:', config.logging.mode);
147+
}
148+
// TODO:
149+
// once all jenkins scripts have been updated to use the LOG_MODE env var,
150+
// this should be updated to use devServer instead of jenkinsScript,
151+
// and when all local scripts have been updated, this can be removed.
152+
if (config.name === 'dockerdev') {
153+
return LOGGING_CONFIGS.devServer;
154+
}
155+
156+
return LOGGING_CONFIGS.live;
157+
};
158+
159+
const logConfig: LoggingConfig = getLoggingConfig();
160+
161+
// Overwrite color settings if specified
162+
if (config.logging.color !== undefined && logConfig.writeToStdout) {
163+
logConfig.writeToStdout.color = config.logging.color;
164+
}
165+
166+
if (process.env.JEST_WORKER_ID === undefined) {
167+
console.log('Logging mode set to:', logConfig);
168+
}
169+
170+
const streams: bunyan.Stream[] = [loggingListenerStream];
171+
172+
if (logConfig.writeToFile) {
173+
streams.push({
174+
level: logConfig.writeToFile,
175+
path: '/var/log/hpc_service.log',
176+
});
177+
}
178+
179+
if (logConfig.writeToStdout) {
180+
const shouldUseColor = logConfig.writeToStdout.color;
181+
streams.push({
182+
type: 'raw',
183+
level: logConfig.writeToStdout.level,
184+
stream: {
185+
write: (obj: LogObject) => printLog(obj, shouldUseColor),
186+
} as any,
187+
});
188+
}
189+
190+
const log = bunyan.createLogger({
191+
name: config.name || '',
192+
serializers: {
193+
req: bunyan.stdSerializers.req,
194+
},
195+
streams: streams,
196+
});
197+
198+
// Overwrite default console log behaviour to output to bunyan using json
199+
console.log = (...args: any[]) => {
200+
log.info({ data: 'console.log' }, format(args[0], ...args.slice(1)));
201+
};
202+
console.info = (...args: any[]) => {
203+
log.info({ data: 'console.info' }, format(args[0], ...args.slice(1)));
204+
};
205+
console.warn = (...args: any[]) => {
206+
log.warn({ data: 'console.warn' }, format(args[0], ...args.slice(1)));
207+
};
208+
console.error = (...args: any[]) => {
209+
log.error({ data: 'console.error' }, format(args[0], ...args.slice(1)));
210+
};
211+
console.debug = (...args: any[]) => {
212+
log.debug({ data: 'console.debug' }, format(args[0], ...args.slice(1)));
213+
};
214+
215+
// Handle uncaught rejections by logging an error
216+
process.on('unhandledRejection', (reason, promise) => {
217+
log.error(
218+
{
219+
data: 'unhandledRejection',
220+
promise: format(promise),
221+
reason: format(reason),
222+
},
223+
`Unhandled Rejection: ${reason}`
224+
);
225+
});
226+
227+
return log;
228+
};

0 commit comments

Comments
 (0)