/
logging.ts
85 lines (75 loc) · 2.47 KB
/
logging.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import { createLogger as _createLogger, Logger, transports } from 'winston'
import { MESSAGE } from 'triple-beam'
import { TransformableInfo, format } from 'logform'
import { inspect } from 'util'
/**
* Return a sanitized log level.
*
* @param value The raw log level.
*/
function toLogLevel(value: string): 'debug' | 'info' | 'warn' | 'error' {
return ['debug', 'info', 'warn', 'error'].includes(value) ? (value as any) : 'info'
}
/**
* The maximum level log message to output.
*/
const LOG_LEVEL: 'debug' | 'info' | 'warn' | 'error' = toLogLevel((process.env.LOG_LEVEL || 'info').toLowerCase())
/**
* Create a structured logger.
*/
export function createLogger(service: string): Logger {
const formatTransformer = (info: TransformableInfo): TransformableInfo => {
const attributes: { [k: string]: any } = {}
for (const [key, value] of Object.entries(info)) {
if (key !== 'level' && key !== 'message') {
attributes[key] = value
}
}
info[MESSAGE] = `${info.level} ${info.message} ${inspect(attributes)}`
return info
}
const uppercaseTransformer = (info: TransformableInfo): TransformableInfo => {
info.level = info.level.toUpperCase()
return info
}
const colors = {
debug: 'dim',
info: 'cyan',
warn: 'yellow',
error: 'red',
}
return _createLogger({
level: LOG_LEVEL,
// Need to upper case level before colorization or we destroy ANSI codes
format: format.combine({ transform: uppercaseTransformer }, format.colorize({ level: true, colors }), {
transform: formatTransformer,
}),
defaultMeta: { service },
transports: [new transports.Console({})],
})
}
/**
* Creates a silent logger.
*/
export function createSilentLogger(): Logger {
return _createLogger({ silent: true })
}
/**
* Log the beginning, end, and exception of an operation.
*
* @param name The log message to output.
* @param logger The logger instance.
* @param f The operation to perform.
*/
export async function logCall<T>(name: string, logger: Logger, f: () => Promise<T> | T): Promise<T> {
const timer = logger.startTimer()
logger.debug(name)
try {
const value = await f()
timer.done({ message: `finished ${name}`, level: 'debug' })
return value
} catch (error) {
timer.done({ message: `failed ${name}`, level: 'error', error })
throw error
}
}