@rsdk/logging 5.10.0 → 5.10.1-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/decorators/log-serializer.decorator.d.ts +2 -0
- package/dist/decorators/log-serializer.decorator.js +18 -0
- package/dist/decorators/log-serializer.decorator.js.map +1 -0
- package/dist/implementations/pino-logger.class.d.ts +3 -2
- package/dist/implementations/pino-logger.class.js +32 -8
- package/dist/implementations/pino-logger.class.js.map +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.js +12 -20
- package/dist/index.js.map +1 -1
- package/dist/logger.factory.d.ts +60 -3
- package/dist/logger.factory.js +205 -28
- package/dist/logger.factory.js.map +1 -1
- package/dist/logger.interface.d.ts +5 -0
- package/dist/metadata/constants.d.ts +1 -0
- package/dist/metadata/constants.js +5 -0
- package/dist/metadata/constants.js.map +1 -0
- package/dist/metadata/logger-metadata.registry.d.ts +7 -0
- package/dist/metadata/logger-metadata.registry.js +14 -0
- package/dist/metadata/logger-metadata.registry.js.map +1 -0
- package/dist/types.d.ts +25 -2
- package/dist/types.js +8 -1
- package/dist/types.js.map +1 -1
- package/package.json +4 -3
- package/src/decorators/log-serializer.decorator.ts +21 -0
- package/src/implementations/pino-logger.class.ts +43 -14
- package/src/index.ts +14 -5
- package/src/logger.factory.ts +286 -52
- package/src/logger.interface.ts +7 -0
- package/src/metadata/constants.ts +1 -0
- package/src/metadata/logger-metadata.registry.ts +21 -0
- package/src/types.ts +27 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger-metadata.registry.js","sourceRoot":"","sources":["../../src/metadata/logger-metadata.registry.ts"],"names":[],"mappings":";;;AAIA,MAAa,sBAAsB;IACzB,MAAM,CAAU,WAAW,GAAG,IAAI,GAAG,EAG1C,CAAC;IAEJ,MAAM,CAAC,kBAAkB,CACvB,MAAmB,EACnB,QAAkC;QAElC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACzC,CAAC;IAED,MAAM,CAAC,cAAc;QACnB,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC,CAAC;IAC/C,CAAC;;AAfH,wDAgBC"}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Constructor } from '@rsdk/common';
|
|
2
2
|
import type pino from 'pino';
|
|
3
|
+
import type { TransportMultiOptions } from 'pino';
|
|
3
4
|
export declare enum LogLevel {
|
|
4
5
|
fatal = "fatal",
|
|
5
6
|
error = "error",
|
|
@@ -8,11 +9,33 @@ export declare enum LogLevel {
|
|
|
8
9
|
debug = "debug",
|
|
9
10
|
trace = "trace"
|
|
10
11
|
}
|
|
12
|
+
export declare enum LogTimestampFormat {
|
|
13
|
+
epochTime = "epochTime",
|
|
14
|
+
unixTime = "unixTime",
|
|
15
|
+
nullTime = "nullTime",
|
|
16
|
+
isoTime = "isoTime"
|
|
17
|
+
}
|
|
18
|
+
export type TimeFn = () => string;
|
|
11
19
|
export type Params = Record<string, unknown>;
|
|
12
|
-
export interface
|
|
20
|
+
export interface LoggerOptionsBase {
|
|
13
21
|
level: LogLevel;
|
|
14
22
|
redact: string[];
|
|
15
|
-
|
|
23
|
+
timestampFormat: LogTimestampFormat;
|
|
16
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* `stream` и `transport` — взаимоисключающие способы доставки (pino не
|
|
27
|
+
* принимает оба сразу). Это закодировано в типе: задание одного запрещает
|
|
28
|
+
* другой.
|
|
29
|
+
*/
|
|
30
|
+
export type LoggerOptions = LoggerOptionsBase & ({
|
|
31
|
+
stream?: pino.DestinationStream;
|
|
32
|
+
transport?: never;
|
|
33
|
+
} | {
|
|
34
|
+
stream?: never;
|
|
35
|
+
transport?: TransportMultiOptions;
|
|
36
|
+
});
|
|
17
37
|
export type LoggingContext = string | Constructor;
|
|
18
38
|
export type OnMessage = (level: LogLevel, data: Record<string, unknown>) => void;
|
|
39
|
+
export interface LoggerSerializerMetadata {
|
|
40
|
+
target: Constructor;
|
|
41
|
+
}
|
package/dist/types.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.LogLevel = void 0;
|
|
3
|
+
exports.LogTimestampFormat = exports.LogLevel = void 0;
|
|
4
4
|
var LogLevel;
|
|
5
5
|
(function (LogLevel) {
|
|
6
6
|
LogLevel["fatal"] = "fatal";
|
|
@@ -10,4 +10,11 @@ var LogLevel;
|
|
|
10
10
|
LogLevel["debug"] = "debug";
|
|
11
11
|
LogLevel["trace"] = "trace";
|
|
12
12
|
})(LogLevel || (exports.LogLevel = LogLevel = {}));
|
|
13
|
+
var LogTimestampFormat;
|
|
14
|
+
(function (LogTimestampFormat) {
|
|
15
|
+
LogTimestampFormat["epochTime"] = "epochTime";
|
|
16
|
+
LogTimestampFormat["unixTime"] = "unixTime";
|
|
17
|
+
LogTimestampFormat["nullTime"] = "nullTime";
|
|
18
|
+
LogTimestampFormat["isoTime"] = "isoTime";
|
|
19
|
+
})(LogTimestampFormat || (exports.LogTimestampFormat = LogTimestampFormat = {}));
|
|
13
20
|
//# sourceMappingURL=types.js.map
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";;;AAIA,IAAY,QAOX;AAPD,WAAY,QAAQ;IAClB,2BAAe,CAAA;IACf,2BAAe,CAAA;IACf,yBAAa,CAAA;IACb,yBAAa,CAAA;IACb,2BAAe,CAAA;IACf,2BAAe,CAAA;AACjB,CAAC,EAPW,QAAQ,wBAAR,QAAQ,QAOnB;AAED,IAAY,kBAKX;AALD,WAAY,kBAAkB;IAC5B,6CAAuB,CAAA;IACvB,2CAAqB,CAAA;IACrB,2CAAqB,CAAA;IACrB,yCAAmB,CAAA;AACrB,CAAC,EALW,kBAAkB,kCAAlB,kBAAkB,QAK7B"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rsdk/logging",
|
|
3
|
-
"version": "5.10.
|
|
3
|
+
"version": "5.10.1-next.1",
|
|
4
4
|
"description": "Base framework independent logging functionality",
|
|
5
5
|
"license": "Apache License 2.0",
|
|
6
6
|
"publishConfig": {
|
|
@@ -11,11 +11,12 @@
|
|
|
11
11
|
},
|
|
12
12
|
"main": "dist/index.js",
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"pino": "^8.0.0 || ^9.0.0"
|
|
14
|
+
"pino": "^8.0.0 || ^9.0.0",
|
|
15
|
+
"reflect-metadata": "^0.1.12 || ^0.2.0"
|
|
15
16
|
},
|
|
16
17
|
"peerDependencies": {
|
|
17
18
|
"@rsdk/common": "*",
|
|
18
19
|
"rxjs": "^7.1.0"
|
|
19
20
|
},
|
|
20
|
-
"gitHead": "
|
|
21
|
+
"gitHead": "793a6c54dc689007b86a8a817d0ed198ee83ec5d"
|
|
21
22
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Constructor } from '@rsdk/common';
|
|
2
|
+
|
|
3
|
+
import 'reflect-metadata';
|
|
4
|
+
|
|
5
|
+
import { LOGGER_METADATA_KEY } from '../metadata/constants';
|
|
6
|
+
import { LoggerMetadataRegistry } from '../metadata/logger-metadata.registry';
|
|
7
|
+
import type { LoggerSerializerMetadata } from '../types';
|
|
8
|
+
|
|
9
|
+
const logSerializerDecorator: ClassDecorator = function (
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
11
|
+
target: Function,
|
|
12
|
+
): void {
|
|
13
|
+
const metadata: LoggerSerializerMetadata = {
|
|
14
|
+
target: target as Constructor,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
Reflect.defineMetadata(LOGGER_METADATA_KEY, metadata, target);
|
|
18
|
+
LoggerMetadataRegistry.registerSerializer(target as Constructor, metadata);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const LogSerializer = (): ClassDecorator => logSerializerDecorator;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type { ErrorLike
|
|
2
|
-
import { isErrorLike
|
|
1
|
+
import type { ErrorLike } from '@rsdk/common';
|
|
2
|
+
import { isErrorLike } from '@rsdk/common';
|
|
3
3
|
import type { Logger as Pino } from 'pino';
|
|
4
4
|
|
|
5
5
|
import { stringifyContext } from '../helpers';
|
|
6
|
-
import type { ILogger } from '../logger.interface';
|
|
6
|
+
import type { ILogger, LogSerializerBase } from '../logger.interface';
|
|
7
7
|
import type { LoggingContext, OnMessage, Params } from '../types';
|
|
8
8
|
import { LogLevel } from '../types';
|
|
9
9
|
|
|
@@ -23,11 +23,18 @@ export class PinoLogger implements ILogger {
|
|
|
23
23
|
*/
|
|
24
24
|
private readonly _pino: GetLogger;
|
|
25
25
|
private readonly emit: OnMessage;
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
private readonly getSerializers: () => LogSerializerBase[];
|
|
27
|
+
|
|
28
|
+
constructor(
|
|
29
|
+
pino: GetLogger,
|
|
30
|
+
emit: OnMessage,
|
|
31
|
+
context: LoggingContext,
|
|
32
|
+
getSerializers: () => LogSerializerBase[],
|
|
33
|
+
) {
|
|
28
34
|
this._pino = pino;
|
|
29
35
|
this.emit = emit;
|
|
30
36
|
this.context = stringifyContext(context);
|
|
37
|
+
this.getSerializers = getSerializers;
|
|
31
38
|
}
|
|
32
39
|
|
|
33
40
|
get pino(): Pino {
|
|
@@ -106,20 +113,42 @@ export class PinoLogger implements ILogger {
|
|
|
106
113
|
}
|
|
107
114
|
|
|
108
115
|
const paramsOrError = typeof arg1 === 'string' ? arg2 : arg1;
|
|
109
|
-
|
|
110
116
|
if (paramsOrError) {
|
|
111
|
-
const options: NormalizeOptions = {
|
|
112
|
-
sortKeys: sortErrorKeys,
|
|
113
|
-
};
|
|
114
|
-
|
|
115
117
|
if (level === 'fatal' || level === 'error') {
|
|
116
|
-
args
|
|
118
|
+
args.error = paramsOrError;
|
|
117
119
|
} else {
|
|
118
|
-
Object.assign(args,
|
|
120
|
+
Object.assign(args, paramsOrError);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (message) {
|
|
125
|
+
args.message = message;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const serializers = this.getSerializers();
|
|
129
|
+
|
|
130
|
+
for (const [key, value] of Object.entries(args)) {
|
|
131
|
+
// Пропускаем ненужные поля
|
|
132
|
+
if (key === 'context' || key === 'message') continue;
|
|
133
|
+
if (value == null || typeof value !== 'object') continue;
|
|
134
|
+
|
|
135
|
+
for (const serializer of serializers) {
|
|
136
|
+
try {
|
|
137
|
+
// eslint-disable-next-line unicorn/prefer-regexp-test
|
|
138
|
+
if (serializer.match(value)) {
|
|
139
|
+
const serialized = serializer.serialize(value);
|
|
140
|
+
if (serialized && typeof serialized === 'object') {
|
|
141
|
+
args[key] = serialized;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Можно прервать цикл — нашли нужный сериалайзер
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
} catch {}
|
|
119
148
|
}
|
|
120
149
|
}
|
|
121
150
|
|
|
122
|
-
this.emit(level,
|
|
123
|
-
this.pino[level](args
|
|
151
|
+
this.emit(level, args);
|
|
152
|
+
this.pino[level](args);
|
|
124
153
|
}
|
|
125
154
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
export { DEFAULT_LEVEL } from './defaults';
|
|
2
2
|
|
|
3
|
-
export
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
export { PinoLogger } from './implementations/pino-logger.class';
|
|
4
|
+
export {
|
|
5
|
+
LogLevel,
|
|
6
|
+
Params,
|
|
7
|
+
LoggerOptions,
|
|
8
|
+
LoggingContext,
|
|
9
|
+
OnMessage,
|
|
10
|
+
LoggerSerializerMetadata,
|
|
11
|
+
LogTimestampFormat,
|
|
12
|
+
} from './types';
|
|
13
|
+
export { ILogger, ILogSerializer, LogSerializerBase } from './logger.interface';
|
|
14
|
+
export { stringifyContext } from './helpers';
|
|
15
|
+
export { LoggerFactory } from './logger.factory';
|
|
16
|
+
export { LogSerializer } from './decorators/log-serializer.decorator';
|
package/src/logger.factory.ts
CHANGED
|
@@ -1,21 +1,44 @@
|
|
|
1
|
-
import EventEmitter from 'node:events';
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
2
|
import type _pino from 'pino';
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
DestinationStream,
|
|
5
|
+
Logger as Pino,
|
|
6
|
+
LoggerOptions as LoggerOptsPino,
|
|
7
|
+
TransportMultiOptions,
|
|
8
|
+
} from 'pino';
|
|
4
9
|
|
|
10
|
+
import { LoggerMetadataRegistry } from './metadata/logger-metadata.registry';
|
|
5
11
|
import { DEFAULT_LEVEL } from './defaults';
|
|
6
12
|
import { PinoLogger } from './implementations';
|
|
7
|
-
import type { ILogger } from './logger.interface';
|
|
13
|
+
import type { ILogger, LogSerializerBase } from './logger.interface';
|
|
8
14
|
import type {
|
|
9
15
|
LoggerOptions,
|
|
16
|
+
LoggerOptionsBase,
|
|
17
|
+
LoggerSerializerMetadata,
|
|
10
18
|
LoggingContext,
|
|
11
19
|
LogLevel,
|
|
12
20
|
OnMessage,
|
|
21
|
+
TimeFn,
|
|
13
22
|
} from './types';
|
|
23
|
+
import { LogTimestampFormat } from './types';
|
|
24
|
+
|
|
25
|
+
/** Хэндл worker-транспорта pino (thread-stream) для teardown воркеров. */
|
|
26
|
+
interface TransportStream extends DestinationStream {
|
|
27
|
+
end(): void;
|
|
28
|
+
flush(cb?: (err?: Error) => void): void;
|
|
29
|
+
flushSync(): void;
|
|
30
|
+
unref(): void;
|
|
31
|
+
on(event: string, listener: (...args: unknown[]) => void): void;
|
|
32
|
+
removeAllListeners(event?: string): void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const DEFAULT_SHUTDOWN_TIMEOUT_MS = 1000;
|
|
14
36
|
|
|
15
37
|
/**
|
|
16
38
|
* ATTENTION: require('pino') тут не просто так, в общем они нужны для корректной работы хуков из `@opentelemetry/instrumentation-pino`
|
|
17
39
|
*/
|
|
18
40
|
const requirePino = (): typeof _pino => {
|
|
41
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
19
42
|
return require('pino');
|
|
20
43
|
};
|
|
21
44
|
|
|
@@ -25,7 +48,21 @@ const requirePino = (): typeof _pino => {
|
|
|
25
48
|
*/
|
|
26
49
|
export class LoggerFactory {
|
|
27
50
|
// eslint-disable-next-line unicorn/prefer-event-target
|
|
28
|
-
private static events = new EventEmitter();
|
|
51
|
+
private static readonly events = new EventEmitter();
|
|
52
|
+
|
|
53
|
+
private static opts: LoggerOptions = {
|
|
54
|
+
level: DEFAULT_LEVEL,
|
|
55
|
+
redact: [],
|
|
56
|
+
stream: requirePino().destination(),
|
|
57
|
+
timestampFormat: LogTimestampFormat.isoTime,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
private static pinoTimestampFormatMap: Record<LogTimestampFormat, TimeFn> = {
|
|
61
|
+
[LogTimestampFormat.epochTime]: requirePino().stdTimeFunctions.epochTime,
|
|
62
|
+
[LogTimestampFormat.unixTime]: requirePino().stdTimeFunctions.unixTime,
|
|
63
|
+
[LogTimestampFormat.nullTime]: requirePino().stdTimeFunctions.nullTime,
|
|
64
|
+
[LogTimestampFormat.isoTime]: requirePino().stdTimeFunctions.isoTime,
|
|
65
|
+
};
|
|
29
66
|
|
|
30
67
|
/**
|
|
31
68
|
* Under the hood every logger instance will use this
|
|
@@ -36,16 +73,19 @@ export class LoggerFactory {
|
|
|
36
73
|
* Downside is that the idea is not composable with child
|
|
37
74
|
* loggers (which are no used anywhere yet)
|
|
38
75
|
*/
|
|
39
|
-
// eslint-disable-next-line @typescript-eslint/no-
|
|
76
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
40
77
|
private static _globalPino: Pino = requirePino()({
|
|
41
78
|
level: DEFAULT_LEVEL,
|
|
79
|
+
timestamp: this.pinoTimestampFormatMap[this.opts.timestampFormat],
|
|
42
80
|
});
|
|
43
81
|
|
|
44
|
-
private static
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
82
|
+
private static _serializers: Map<string, LogSerializerBase> | null = null;
|
|
83
|
+
|
|
84
|
+
/** Ссылка на активный worker-транспорт для закрытия его воркеров. */
|
|
85
|
+
private static _activeTransport: TransportStream | null = null;
|
|
86
|
+
|
|
87
|
+
/** Конфиг, по которому собран _activeTransport (для переиспользования воркера). */
|
|
88
|
+
private static _activeTransportConfig: TransportMultiOptions | null = null;
|
|
49
89
|
|
|
50
90
|
/**
|
|
51
91
|
* Переконфигурация pino нужна потому, что изначально логгер инициализируется
|
|
@@ -57,9 +97,7 @@ export class LoggerFactory {
|
|
|
57
97
|
static reconfigure(opts: Partial<LoggerOptions>): void {
|
|
58
98
|
this.applyOptions(opts);
|
|
59
99
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
this._globalPino = requirePino()(other, stream);
|
|
100
|
+
this._globalPino = this.instantiatePino();
|
|
63
101
|
}
|
|
64
102
|
|
|
65
103
|
/**
|
|
@@ -80,43 +118,64 @@ export class LoggerFactory {
|
|
|
80
118
|
static applyInstrumentations<T extends Record<string, any>>(
|
|
81
119
|
logHookFunction?: (record: T) => void,
|
|
82
120
|
): void {
|
|
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
|
-
|
|
121
|
+
this._globalPino = this.instantiatePino({
|
|
122
|
+
/**
|
|
123
|
+
* Функция которая позволяет добавить дополнительные данные в логи Pino
|
|
124
|
+
*/
|
|
125
|
+
hooks: logHookFunction
|
|
126
|
+
? {
|
|
127
|
+
/**
|
|
128
|
+
* Реализация взята с официальной документации
|
|
129
|
+
* https://getpino.io/#/docs/api?id=logmethod
|
|
130
|
+
*/
|
|
131
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
132
|
+
logMethod: function (inputArgs: any[], method: Function): unknown {
|
|
133
|
+
const arg1 = inputArgs[0];
|
|
134
|
+
if (arg1 && typeof arg1 === 'object') {
|
|
135
|
+
logHookFunction(arg1 as T);
|
|
136
|
+
}
|
|
137
|
+
// eslint-disable-next-line unicorn/prefer-reflect-apply
|
|
138
|
+
return method.apply(this, inputArgs);
|
|
139
|
+
},
|
|
140
|
+
}
|
|
141
|
+
: {},
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Сливает буфер worker-транспорта и завершает его воркеры. Без вызова на
|
|
147
|
+
* остановке приложения забуференные логи теряются, а воркеры висят.
|
|
148
|
+
*/
|
|
149
|
+
static async shutdown(
|
|
150
|
+
timeoutMs: number = DEFAULT_SHUTDOWN_TIMEOUT_MS,
|
|
151
|
+
): Promise<void> {
|
|
152
|
+
const stream = this._activeTransport;
|
|
153
|
+
|
|
154
|
+
if (!stream) return;
|
|
155
|
+
|
|
156
|
+
this._activeTransport = null;
|
|
157
|
+
this._activeTransportConfig = null;
|
|
158
|
+
|
|
159
|
+
await new Promise<void>((resolve) => {
|
|
160
|
+
let settled = false;
|
|
161
|
+
|
|
162
|
+
const finish = (): void => {
|
|
163
|
+
if (settled) return;
|
|
164
|
+
settled = true;
|
|
165
|
+
resolve();
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
stream.on('close', finish);
|
|
170
|
+
stream.flush(() => stream.end());
|
|
171
|
+
} catch {
|
|
172
|
+
finish();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const timer = setTimeout(finish, timeoutMs);
|
|
176
|
+
|
|
177
|
+
timer.unref?.();
|
|
178
|
+
});
|
|
120
179
|
}
|
|
121
180
|
|
|
122
181
|
static onMessage(handler: OnMessage): void {
|
|
@@ -128,12 +187,187 @@ export class LoggerFactory {
|
|
|
128
187
|
() => this._globalPino,
|
|
129
188
|
(level: LogLevel, data: unknown) => this.events.emit('msg', level, data),
|
|
130
189
|
context,
|
|
190
|
+
() => this.getSerializers(),
|
|
131
191
|
);
|
|
132
192
|
}
|
|
133
193
|
|
|
194
|
+
static getSerializers(): LogSerializerBase[] {
|
|
195
|
+
if (this._serializers) return Array.from(this._serializers.values());
|
|
196
|
+
|
|
197
|
+
const metas: LoggerSerializerMetadata[] =
|
|
198
|
+
LoggerMetadataRegistry.getSerializers();
|
|
199
|
+
|
|
200
|
+
this._serializers = new Map<string, LogSerializerBase>();
|
|
201
|
+
|
|
202
|
+
for (const meta of metas) {
|
|
203
|
+
try {
|
|
204
|
+
const instance = Reflect.construct(meta.target, []);
|
|
205
|
+
const serializerName = meta.target.name || meta.target.constructor.name;
|
|
206
|
+
|
|
207
|
+
this._serializers.set(serializerName, instance);
|
|
208
|
+
} catch {}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return Array.from(this._serializers.values());
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Manually set a serializer instance. Useful when using without DI.
|
|
216
|
+
* @param {string} name Unique name for the serializer
|
|
217
|
+
* @param {LogSerializerBase} serializer Serializer instance
|
|
218
|
+
*/
|
|
219
|
+
static setSerializer(name: string, serializer: LogSerializerBase): void {
|
|
220
|
+
this._serializers ??= new Map<string, LogSerializerBase>();
|
|
221
|
+
this._serializers.set(name, serializer);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Remove a serializer by name.
|
|
226
|
+
* @param {string} name Name of the serializer to remove
|
|
227
|
+
* @returns {boolean} true if the serializer was removed, false if it didn't exist
|
|
228
|
+
*/
|
|
229
|
+
static deleteSerializer(name: string): boolean {
|
|
230
|
+
if (!this._serializers) return false;
|
|
231
|
+
|
|
232
|
+
return this._serializers.delete(name);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Check if a serializer exists by name.
|
|
237
|
+
* @param {string} name Name of the serializer to check
|
|
238
|
+
* @returns {boolean} true if the serializer exists, false otherwise
|
|
239
|
+
*/
|
|
240
|
+
static hasSerializer(name: string): boolean {
|
|
241
|
+
if (!this._serializers) return false;
|
|
242
|
+
|
|
243
|
+
return this._serializers.has(name);
|
|
244
|
+
}
|
|
245
|
+
|
|
134
246
|
private static applyOptions(opts: Partial<LoggerOptions>): void {
|
|
135
|
-
const { redact, level, stream
|
|
247
|
+
const { redact, level, stream, transport, timestampFormat } = opts || {};
|
|
248
|
+
|
|
249
|
+
const base: LoggerOptionsBase = {
|
|
250
|
+
redact: redact ?? this.opts.redact ?? [],
|
|
251
|
+
level: level ?? DEFAULT_LEVEL,
|
|
252
|
+
timestampFormat: timestampFormat ?? LogTimestampFormat.isoTime,
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Выбираем ровно один способ доставки. Если ни stream, ни transport не
|
|
257
|
+
* переданы — сохраняем текущий, чтобы частичный reconfigure({ level }) не
|
|
258
|
+
* сбрасывал ранее заданный transport.
|
|
259
|
+
*/
|
|
260
|
+
if (transport) {
|
|
261
|
+
this.opts = { ...base, transport };
|
|
262
|
+
} else if (stream) {
|
|
263
|
+
this.opts = { ...base, stream };
|
|
264
|
+
} else if (this.opts.transport) {
|
|
265
|
+
this.opts = { ...base, transport: this.opts.transport };
|
|
266
|
+
} else {
|
|
267
|
+
this.opts = {
|
|
268
|
+
...base,
|
|
269
|
+
stream: this.opts.stream ?? requirePino().destination(),
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* `stream`/`transport` исключаем из опций: способ доставки задаётся вторым
|
|
276
|
+
* аргументом `pino()`, а pino запрещает одновременно `transport` в опциях и
|
|
277
|
+
* stream-аргумент.
|
|
278
|
+
*/
|
|
279
|
+
private static buildPinoOptions(
|
|
280
|
+
extra: Partial<LoggerOptsPino> = {},
|
|
281
|
+
): LoggerOptsPino {
|
|
282
|
+
const {
|
|
283
|
+
timestampFormat,
|
|
284
|
+
stream: _stream,
|
|
285
|
+
transport: _transport,
|
|
286
|
+
...other
|
|
287
|
+
} = this.opts;
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
...other,
|
|
291
|
+
timestamp: this.pinoTimestampFormatMap[timestampFormat],
|
|
292
|
+
...extra,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Создаёт инстанс pino. Воркер транспорта переиспользуется, если объект
|
|
298
|
+
* конфига транспорта тот же по ссылке (сравнение identity, не по значению).
|
|
299
|
+
* Это покрывает связку reconfigure→applyInstrumentations (между ними
|
|
300
|
+
* this.opts.transport не пересобирается) и не даёт пересоздавать и тут же
|
|
301
|
+
* закрывать только что поднятый, возможно медленный, OTEL-воркер. Новый
|
|
302
|
+
* объект конфига (например, при config.onUpdate) ожидаемо пересоздаёт воркер.
|
|
303
|
+
*/
|
|
304
|
+
private static instantiatePino(extra: Partial<LoggerOptsPino> = {}): Pino {
|
|
305
|
+
const pino = requirePino();
|
|
306
|
+
const options = this.buildPinoOptions(extra);
|
|
307
|
+
|
|
308
|
+
if (!this.opts.transport) {
|
|
309
|
+
this.disposeActiveTransport();
|
|
310
|
+
|
|
311
|
+
return pino(options, this.opts.stream);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const reuse =
|
|
315
|
+
this._activeTransport &&
|
|
316
|
+
this.opts.transport === this._activeTransportConfig;
|
|
317
|
+
|
|
318
|
+
const stream = reuse
|
|
319
|
+
? this._activeTransport!
|
|
320
|
+
: this.createTransport(this.opts.transport);
|
|
321
|
+
|
|
322
|
+
return pino(options, stream);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Поднимает worker-транспорт, закрыв предыдущий. Обработчик 'error' не даёт
|
|
327
|
+
* асинхронной ошибке воркера уронить процесс, но делает её видимой в stderr
|
|
328
|
+
* (сам логгер использовать нельзя — упал его транспорт). Ошибку резолва
|
|
329
|
+
* таргета pino.transport(...) бросает синхронно — она долетает до старта.
|
|
330
|
+
*/
|
|
331
|
+
private static createTransport(
|
|
332
|
+
transport: TransportMultiOptions,
|
|
333
|
+
): TransportStream {
|
|
334
|
+
this.disposeActiveTransport();
|
|
335
|
+
|
|
336
|
+
const stream = requirePino().transport(transport) as TransportStream;
|
|
337
|
+
|
|
338
|
+
stream.on('error', (...args: unknown[]) => {
|
|
339
|
+
const error = args[0];
|
|
340
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
341
|
+
|
|
342
|
+
process.stderr.write(`[@rsdk/logging] transport error: ${message}\n`);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
this._activeTransport = stream;
|
|
346
|
+
this._activeTransportConfig = transport;
|
|
347
|
+
|
|
348
|
+
return stream;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Завершает воркеры предыдущего транспорта (best-effort, не блокируя). stdout
|
|
353
|
+
* не затрагивается: для него хэндл не сохраняется.
|
|
354
|
+
*/
|
|
355
|
+
private static disposeActiveTransport(): void {
|
|
356
|
+
const previous = this._activeTransport;
|
|
357
|
+
|
|
358
|
+
if (!previous) return;
|
|
359
|
+
|
|
360
|
+
this._activeTransport = null;
|
|
361
|
+
this._activeTransportConfig = null;
|
|
362
|
+
|
|
363
|
+
// на teardown ошибки ожидаемы (например, "end() took too long" у медленных
|
|
364
|
+
// транспортов) — глушим их молча и не держим event loop
|
|
365
|
+
previous.unref?.();
|
|
366
|
+
previous.removeAllListeners?.('error');
|
|
367
|
+
previous.on('error', () => {});
|
|
136
368
|
|
|
137
|
-
|
|
369
|
+
try {
|
|
370
|
+
previous.end();
|
|
371
|
+
} catch {}
|
|
138
372
|
}
|
|
139
373
|
}
|
package/src/logger.interface.ts
CHANGED
|
@@ -37,3 +37,10 @@ export interface ILogger {
|
|
|
37
37
|
log(level: LogLevel, message: string, params?: Params): void;
|
|
38
38
|
log(level: LogLevel, message: string, error?: ErrorLike): void;
|
|
39
39
|
}
|
|
40
|
+
|
|
41
|
+
export interface ILogSerializer<TEntry, TSerialized> {
|
|
42
|
+
match(entry: unknown): entry is TEntry;
|
|
43
|
+
serialize(entry: TEntry): TSerialized;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type LogSerializerBase = ILogSerializer<unknown, unknown>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const LOGGER_METADATA_KEY = Symbol('LOGGER_METADATA_KEY');
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Constructor } from '@rsdk/common';
|
|
2
|
+
|
|
3
|
+
import type { LoggerSerializerMetadata } from '../types';
|
|
4
|
+
|
|
5
|
+
export class LoggerMetadataRegistry {
|
|
6
|
+
private static readonly serializers = new Map<
|
|
7
|
+
Constructor,
|
|
8
|
+
LoggerSerializerMetadata
|
|
9
|
+
>();
|
|
10
|
+
|
|
11
|
+
static registerSerializer(
|
|
12
|
+
target: Constructor,
|
|
13
|
+
metadata: LoggerSerializerMetadata,
|
|
14
|
+
): void {
|
|
15
|
+
this.serializers.set(target, metadata);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static getSerializers(): LoggerSerializerMetadata[] {
|
|
19
|
+
return Array.from(this.serializers.values());
|
|
20
|
+
}
|
|
21
|
+
}
|