forked from iamolegga/nestjs-pino
-
Notifications
You must be signed in to change notification settings - Fork 0
/
PinoLogger.ts
158 lines (139 loc) · 4.52 KB
/
PinoLogger.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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
/* eslint-disable @typescript-eslint/ban-types */
import { Injectable, Inject, Scope } from '@nestjs/common';
import pino from 'pino';
import { Params, isPassedLogger, PARAMS_PROVIDER_TOKEN } from './params';
import { storage } from './storage';
type PinoMethods = Pick<
pino.Logger,
'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal'
>;
/**
* This is copy of pino.LogFn but with possibilty to make method override.
* Current usage works:
*
* trace(msg: string, ...args: any[]): void;
* trace(obj: object, msg?: string, ...args: any[]): void;
* trace(...args: Parameters<LoggerFn>) {
* this.call('trace', ...args);
* }
*
* But if change local LoggerFn to pino.LogFn – this will say that overrides
* are incompatible
*/
type LoggerFn =
| ((msg: string, ...args: any[]) => void)
| ((obj: object, msg?: string, ...args: any[]) => void);
let outOfContext: pino.Logger | undefined;
export function __resetOutOfContextForTests() {
outOfContext = undefined;
// @ts-ignore reset root for tests only
PinoLogger.root = undefined;
}
@Injectable({ scope: Scope.TRANSIENT })
export class PinoLogger implements PinoMethods {
/**
* root is the most root logger that can be used to change params at runtime.
* Accessible only when `useExisting` is not set to `true` in `Params`.
* Readonly, but you can change it's properties.
*/
static readonly root: pino.Logger;
private context = '';
private readonly contextName: string;
constructor(
@Inject(PARAMS_PROVIDER_TOKEN) { pinoHttp, renameContext }: Params,
) {
if (!outOfContext) {
if (Array.isArray(pinoHttp)) {
outOfContext = pino(...pinoHttp);
} else if (isPassedLogger(pinoHttp)) {
outOfContext = pinoHttp.logger;
} else if (
typeof pinoHttp === 'object' &&
'stream' in pinoHttp &&
typeof pinoHttp.stream !== 'undefined'
) {
outOfContext = pino(pinoHttp, pinoHttp.stream);
} else {
outOfContext = pino(pinoHttp);
}
}
this.contextName = renameContext || 'context';
}
trace(msg: string, ...args: any[]): void;
trace(obj: unknown, msg?: string, ...args: any[]): void;
trace(...args: Parameters<LoggerFn>) {
this.call('trace', ...args);
}
debug(msg: string, ...args: any[]): void;
debug(obj: unknown, msg?: string, ...args: any[]): void;
debug(...args: Parameters<LoggerFn>) {
this.call('debug', ...args);
}
info(msg: string, ...args: any[]): void;
info(obj: unknown, msg?: string, ...args: any[]): void;
info(...args: Parameters<LoggerFn>) {
this.call('info', ...args);
}
warn(msg: string, ...args: any[]): void;
warn(obj: unknown, msg?: string, ...args: any[]): void;
warn(...args: Parameters<LoggerFn>) {
this.call('warn', ...args);
}
error(msg: string, ...args: any[]): void;
error(obj: unknown, msg?: string, ...args: any[]): void;
error(...args: Parameters<LoggerFn>) {
this.call('error', ...args);
}
fatal(msg: string, ...args: any[]): void;
fatal(obj: unknown, msg?: string, ...args: any[]): void;
fatal(...args: Parameters<LoggerFn>) {
this.call('fatal', ...args);
}
setContext(value: string) {
this.context = value;
}
private call(method: pino.Level, ...args: Parameters<LoggerFn>) {
if (this.context) {
if (isFirstArgObject(args)) {
const firstArg = args[0];
if (firstArg instanceof Error) {
args = [
Object.assign(
{ [this.contextName]: this.context },
{ err: firstArg },
),
...args.slice(1),
];
} else {
args = [
Object.assign({ [this.contextName]: this.context }, firstArg),
...args.slice(1),
];
}
} else {
args = [{ [this.contextName]: this.context }, ...args];
}
}
// @ts-ignore args are union of tuple types
this.logger[method](...args);
}
public get logger(): pino.Logger {
// outOfContext is always set in runtime before starts using
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return storage.getStore()?.logger || outOfContext!;
}
public assign(fields: pino.Bindings) {
const store = storage.getStore();
if (!store) {
throw new Error(
`${PinoLogger.name}: unable to assign extra fields out of request scope`,
);
}
store.logger = store.logger.child(fields);
}
}
function isFirstArgObject(
args: Parameters<LoggerFn>,
): args is [obj: object, msg?: string, ...args: any[]] {
return typeof args[0] === 'object';
}