@rsdk/logging 5.12.0 → 5.13.0-next.0

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.
@@ -6,7 +6,7 @@ import type { LoggerOptions, LoggingContext, OnMessage } from './types';
6
6
  */
7
7
  export declare class LoggerFactory {
8
8
  private static readonly events;
9
- private static readonly opts;
9
+ private static opts;
10
10
  private static pinoTimestampFormatMap;
11
11
  /**
12
12
  * Under the hood every logger instance will use this
@@ -19,7 +19,10 @@ export declare class LoggerFactory {
19
19
  */
20
20
  private static _globalPino;
21
21
  private static _serializers;
22
- private static get pinoConfig();
22
+ /** Ссылка на активный worker-транспорт для закрытия его воркеров. */
23
+ private static _activeTransport;
24
+ /** Конфиг, по которому собран _activeTransport (для переиспользования воркера). */
25
+ private static _activeTransportConfig;
23
26
  /**
24
27
  * Переконфигурация pino нужна потому, что изначально логгер инициализируется
25
28
  * с дефолтными настройками, т. к. конфигурация ещё не прочитана.
@@ -44,6 +47,11 @@ export declare class LoggerFactory {
44
47
  * при помощи cli - можно немного упростить архитектуру
45
48
  */
46
49
  static applyInstrumentations<T extends Record<string, any>>(logHookFunction?: (record: T) => void): void;
50
+ /**
51
+ * Сливает буфер worker-транспорта и завершает его воркеры. Без вызова на
52
+ * остановке приложения забуференные логи теряются, а воркеры висят.
53
+ */
54
+ static shutdown(timeoutMs?: number): Promise<void>;
47
55
  static onMessage(handler: OnMessage): void;
48
56
  static create(context: LoggingContext): ILogger;
49
57
  static getSerializers(): LogSerializerBase[];
@@ -66,4 +74,31 @@ export declare class LoggerFactory {
66
74
  */
67
75
  static hasSerializer(name: string): boolean;
68
76
  private static applyOptions;
77
+ /**
78
+ * `stream`/`transport` исключаем из опций: способ доставки задаётся вторым
79
+ * аргументом `pino()`, а pino запрещает одновременно `transport` в опциях и
80
+ * stream-аргумент.
81
+ */
82
+ private static buildPinoOptions;
83
+ /**
84
+ * Создаёт инстанс pino. Воркер транспорта переиспользуется, если объект
85
+ * конфига транспорта тот же по ссылке (сравнение identity, не по значению).
86
+ * Это покрывает связку reconfigure→applyInstrumentations (между ними
87
+ * this.opts.transport не пересобирается) и не даёт пересоздавать и тут же
88
+ * закрывать только что поднятый, возможно медленный, OTEL-воркер. Новый
89
+ * объект конфига (например, при config.onUpdate) ожидаемо пересоздаёт воркер.
90
+ */
91
+ private static instantiatePino;
92
+ /**
93
+ * Поднимает worker-транспорт, закрыв предыдущий. Обработчик 'error' не даёт
94
+ * асинхронной ошибке воркера уронить процесс, но делает её видимой в stderr
95
+ * (сам логгер использовать нельзя — упал его транспорт). Ошибку резолва
96
+ * таргета pino.transport(...) бросает синхронно — она долетает до старта.
97
+ */
98
+ private static createTransport;
99
+ /**
100
+ * Завершает воркеры предыдущего транспорта (best-effort, не блокируя). stdout
101
+ * не затрагивается: для него хэндл не сохраняется.
102
+ */
103
+ private static disposeActiveTransport;
69
104
  }
@@ -6,6 +6,7 @@ const logger_metadata_registry_1 = require("./metadata/logger-metadata.registry"
6
6
  const defaults_1 = require("./defaults");
7
7
  const implementations_1 = require("./implementations");
8
8
  const types_1 = require("./types");
9
+ const DEFAULT_SHUTDOWN_TIMEOUT_MS = 1000;
9
10
  /**
10
11
  * ATTENTION: require('pino') тут не просто так, в общем они нужны для корректной работы хуков из `@opentelemetry/instrumentation-pino`
11
12
  */
@@ -47,13 +48,10 @@ class LoggerFactory {
47
48
  timestamp: this.pinoTimestampFormatMap[this.opts.timestampFormat],
48
49
  });
49
50
  static _serializers = null;
50
- static get pinoConfig() {
51
- const { timestampFormat, ...other } = this.opts;
52
- return {
53
- ...other,
54
- timestamp: this.pinoTimestampFormatMap[timestampFormat],
55
- };
56
- }
51
+ /** Ссылка на активный worker-транспорт для закрытия его воркеров. */
52
+ static _activeTransport = null;
53
+ /** Конфиг, по которому собран _activeTransport (для переиспользования воркера). */
54
+ static _activeTransportConfig = null;
57
55
  /**
58
56
  * Переконфигурация pino нужна потому, что изначально логгер инициализируется
59
57
  * с дефолтными настройками, т. к. конфигурация ещё не прочитана.
@@ -63,8 +61,7 @@ class LoggerFactory {
63
61
  */
64
62
  static reconfigure(opts) {
65
63
  this.applyOptions(opts);
66
- const { stream, ...other } = this.pinoConfig;
67
- this._globalPino = requirePino()(other, stream);
64
+ this._globalPino = this.instantiatePino();
68
65
  }
69
66
  /**
70
67
  * Этот метод должен быть вызван после того, как подключен instrumentation-pino.
@@ -82,11 +79,7 @@ class LoggerFactory {
82
79
  * при помощи cli - можно немного упростить архитектуру
83
80
  */
84
81
  static applyInstrumentations(logHookFunction) {
85
- const { stream, ...other } = this.opts;
86
- // eslint-disable-next-line @typescript-eslint/no-require-imports
87
- this._globalPino = requirePino()({
88
- ...other,
89
- timestamp: this.pinoTimestampFormatMap[this.opts.timestampFormat],
82
+ this._globalPino = this.instantiatePino({
90
83
  /**
91
84
  * Функция которая позволяет добавить дополнительные данные в логи Pino
92
85
  */
@@ -106,7 +99,36 @@ class LoggerFactory {
106
99
  },
107
100
  }
108
101
  : {},
109
- }, stream);
102
+ });
103
+ }
104
+ /**
105
+ * Сливает буфер worker-транспорта и завершает его воркеры. Без вызова на
106
+ * остановке приложения забуференные логи теряются, а воркеры висят.
107
+ */
108
+ static async shutdown(timeoutMs = DEFAULT_SHUTDOWN_TIMEOUT_MS) {
109
+ const stream = this._activeTransport;
110
+ if (!stream)
111
+ return;
112
+ this._activeTransport = null;
113
+ this._activeTransportConfig = null;
114
+ await new Promise((resolve) => {
115
+ let settled = false;
116
+ const finish = () => {
117
+ if (settled)
118
+ return;
119
+ settled = true;
120
+ resolve();
121
+ };
122
+ try {
123
+ stream.on('close', finish);
124
+ stream.flush(() => stream.end());
125
+ }
126
+ catch {
127
+ finish();
128
+ }
129
+ const timer = setTimeout(finish, timeoutMs);
130
+ timer.unref?.();
131
+ });
110
132
  }
111
133
  static onMessage(handler) {
112
134
  this.events.on('msg', handler);
@@ -159,13 +181,105 @@ class LoggerFactory {
159
181
  return this._serializers.has(name);
160
182
  }
161
183
  static applyOptions(opts) {
162
- const { redact, level, stream, timestampFormat } = opts || {};
163
- Object.assign(this.opts, {
164
- redact,
184
+ const { redact, level, stream, transport, timestampFormat } = opts || {};
185
+ const base = {
186
+ redact: redact ?? this.opts.redact ?? [],
165
187
  level: level ?? defaults_1.DEFAULT_LEVEL,
166
- stream: stream ?? requirePino().destination(),
167
188
  timestampFormat: timestampFormat ?? types_1.LogTimestampFormat.isoTime,
189
+ };
190
+ /**
191
+ * Выбираем ровно один способ доставки. Если ни stream, ни transport не
192
+ * переданы — сохраняем текущий, чтобы частичный reconfigure({ level }) не
193
+ * сбрасывал ранее заданный transport.
194
+ */
195
+ if (transport) {
196
+ this.opts = { ...base, transport };
197
+ }
198
+ else if (stream) {
199
+ this.opts = { ...base, stream };
200
+ }
201
+ else if (this.opts.transport) {
202
+ this.opts = { ...base, transport: this.opts.transport };
203
+ }
204
+ else {
205
+ this.opts = {
206
+ ...base,
207
+ stream: this.opts.stream ?? requirePino().destination(),
208
+ };
209
+ }
210
+ }
211
+ /**
212
+ * `stream`/`transport` исключаем из опций: способ доставки задаётся вторым
213
+ * аргументом `pino()`, а pino запрещает одновременно `transport` в опциях и
214
+ * stream-аргумент.
215
+ */
216
+ static buildPinoOptions(extra = {}) {
217
+ const { timestampFormat, stream: _stream, transport: _transport, ...other } = this.opts;
218
+ return {
219
+ ...other,
220
+ timestamp: this.pinoTimestampFormatMap[timestampFormat],
221
+ ...extra,
222
+ };
223
+ }
224
+ /**
225
+ * Создаёт инстанс pino. Воркер транспорта переиспользуется, если объект
226
+ * конфига транспорта тот же по ссылке (сравнение identity, не по значению).
227
+ * Это покрывает связку reconfigure→applyInstrumentations (между ними
228
+ * this.opts.transport не пересобирается) и не даёт пересоздавать и тут же
229
+ * закрывать только что поднятый, возможно медленный, OTEL-воркер. Новый
230
+ * объект конфига (например, при config.onUpdate) ожидаемо пересоздаёт воркер.
231
+ */
232
+ static instantiatePino(extra = {}) {
233
+ const pino = requirePino();
234
+ const options = this.buildPinoOptions(extra);
235
+ if (!this.opts.transport) {
236
+ this.disposeActiveTransport();
237
+ return pino(options, this.opts.stream);
238
+ }
239
+ const reuse = this._activeTransport &&
240
+ this.opts.transport === this._activeTransportConfig;
241
+ const stream = reuse
242
+ ? this._activeTransport
243
+ : this.createTransport(this.opts.transport);
244
+ return pino(options, stream);
245
+ }
246
+ /**
247
+ * Поднимает worker-транспорт, закрыв предыдущий. Обработчик 'error' не даёт
248
+ * асинхронной ошибке воркера уронить процесс, но делает её видимой в stderr
249
+ * (сам логгер использовать нельзя — упал его транспорт). Ошибку резолва
250
+ * таргета pino.transport(...) бросает синхронно — она долетает до старта.
251
+ */
252
+ static createTransport(transport) {
253
+ this.disposeActiveTransport();
254
+ const stream = requirePino().transport(transport);
255
+ stream.on('error', (...args) => {
256
+ const error = args[0];
257
+ const message = error instanceof Error ? error.message : String(error);
258
+ process.stderr.write(`[@rsdk/logging] transport error: ${message}\n`);
168
259
  });
260
+ this._activeTransport = stream;
261
+ this._activeTransportConfig = transport;
262
+ return stream;
263
+ }
264
+ /**
265
+ * Завершает воркеры предыдущего транспорта (best-effort, не блокируя). stdout
266
+ * не затрагивается: для него хэндл не сохраняется.
267
+ */
268
+ static disposeActiveTransport() {
269
+ const previous = this._activeTransport;
270
+ if (!previous)
271
+ return;
272
+ this._activeTransport = null;
273
+ this._activeTransportConfig = null;
274
+ // на teardown ошибки ожидаемы (например, "end() took too long" у медленных
275
+ // транспортов) — глушим их молча и не держим event loop
276
+ previous.unref?.();
277
+ previous.removeAllListeners?.('error');
278
+ previous.on('error', () => { });
279
+ try {
280
+ previous.end();
281
+ }
282
+ catch { }
169
283
  }
170
284
  }
171
285
  exports.LoggerFactory = LoggerFactory;
@@ -1 +1 @@
1
- {"version":3,"file":"logger.factory.js","sourceRoot":"","sources":["../src/logger.factory.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"logger.factory.js","sourceRoot":"","sources":["../src/logger.factory.ts"],"names":[],"mappings":"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"}
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",
@@ -16,12 +17,23 @@ export declare enum LogTimestampFormat {
16
17
  }
17
18
  export type TimeFn = () => string;
18
19
  export type Params = Record<string, unknown>;
19
- export interface LoggerOptions {
20
+ export interface LoggerOptionsBase {
20
21
  level: LogLevel;
21
22
  redact: string[];
22
- stream: pino.DestinationStream;
23
23
  timestampFormat: LogTimestampFormat;
24
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
+ });
25
37
  export type LoggingContext = string | Constructor;
26
38
  export type OnMessage = (level: LogLevel, data: Record<string, unknown>) => void;
27
39
  export interface LoggerSerializerMetadata {
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";;;AAGA,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"}
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.12.0",
3
+ "version": "5.13.0-next.0",
4
4
  "description": "Base framework independent logging functionality",
5
5
  "license": "Apache License 2.0",
6
6
  "publishConfig": {
@@ -18,5 +18,5 @@
18
18
  "reflect-metadata": "^0.1.12 || ^0.2.0",
19
19
  "rxjs": "^7.1.0"
20
20
  },
21
- "gitHead": "e37dbd5dfabbc6150e3d22d545b03ee5e41bf6a4"
21
+ "gitHead": "6efbb4a745c7e9cf44c5c71b6365ffb290820894"
22
22
  }
@@ -4,6 +4,7 @@ import type {
4
4
  DestinationStream,
5
5
  Logger as Pino,
6
6
  LoggerOptions as LoggerOptsPino,
7
+ TransportMultiOptions,
7
8
  } from 'pino';
8
9
 
9
10
  import { LoggerMetadataRegistry } from './metadata/logger-metadata.registry';
@@ -12,6 +13,7 @@ import { PinoLogger } from './implementations';
12
13
  import type { ILogger, LogSerializerBase } from './logger.interface';
13
14
  import type {
14
15
  LoggerOptions,
16
+ LoggerOptionsBase,
15
17
  LoggerSerializerMetadata,
16
18
  LoggingContext,
17
19
  LogLevel,
@@ -20,6 +22,18 @@ import type {
20
22
  } from './types';
21
23
  import { LogTimestampFormat } from './types';
22
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;
36
+
23
37
  /**
24
38
  * ATTENTION: require('pino') тут не просто так, в общем они нужны для корректной работы хуков из `@opentelemetry/instrumentation-pino`
25
39
  */
@@ -36,7 +50,7 @@ export class LoggerFactory {
36
50
  // eslint-disable-next-line unicorn/prefer-event-target
37
51
  private static readonly events = new EventEmitter();
38
52
 
39
- private static readonly opts: LoggerOptions = {
53
+ private static opts: LoggerOptions = {
40
54
  level: DEFAULT_LEVEL,
41
55
  redact: [],
42
56
  stream: requirePino().destination(),
@@ -67,16 +81,11 @@ export class LoggerFactory {
67
81
 
68
82
  private static _serializers: Map<string, LogSerializerBase> | null = null;
69
83
 
70
- private static get pinoConfig(): LoggerOptsPino & {
71
- stream: DestinationStream;
72
- } {
73
- const { timestampFormat, ...other } = this.opts;
84
+ /** Ссылка на активный worker-транспорт для закрытия его воркеров. */
85
+ private static _activeTransport: TransportStream | null = null;
74
86
 
75
- return {
76
- ...other,
77
- timestamp: this.pinoTimestampFormatMap[timestampFormat],
78
- };
79
- }
87
+ /** Конфиг, по которому собран _activeTransport (для переиспользования воркера). */
88
+ private static _activeTransportConfig: TransportMultiOptions | null = null;
80
89
 
81
90
  /**
82
91
  * Переконфигурация pino нужна потому, что изначально логгер инициализируется
@@ -88,9 +97,7 @@ export class LoggerFactory {
88
97
  static reconfigure(opts: Partial<LoggerOptions>): void {
89
98
  this.applyOptions(opts);
90
99
 
91
- const { stream, ...other } = this.pinoConfig;
92
-
93
- this._globalPino = requirePino()(other, stream);
100
+ this._globalPino = this.instantiatePino();
94
101
  }
95
102
 
96
103
  /**
@@ -111,39 +118,63 @@ export class LoggerFactory {
111
118
  static applyInstrumentations<T extends Record<string, any>>(
112
119
  logHookFunction?: (record: T) => void,
113
120
  ): void {
114
- const { stream, ...other } = this.opts;
115
-
116
- // eslint-disable-next-line @typescript-eslint/no-require-imports
117
- this._globalPino = requirePino()(
118
- {
119
- ...other,
120
- timestamp: this.pinoTimestampFormatMap[this.opts.timestampFormat],
121
-
122
- /**
123
- * Функция которая позволяет добавить дополнительные данные в логи Pino
124
- */
125
- hooks: logHookFunction
126
- ? {
127
- /**
128
- * Реализация взята с официальной документации
129
- * https://getpino.io/#/docs/api?id=logmethod
130
- */
131
- logMethod: function (
132
- inputArgs: any[],
133
- method: Function,
134
- ): unknown {
135
- const arg1 = inputArgs[0];
136
- if (arg1 && typeof arg1 === 'object') {
137
- logHookFunction(arg1 as T);
138
- }
139
- // eslint-disable-next-line unicorn/prefer-reflect-apply
140
- return method.apply(this, inputArgs);
141
- },
142
- }
143
- : {},
144
- },
145
- stream,
146
- );
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
+ logMethod: function (inputArgs: any[], method: Function): unknown {
132
+ const arg1 = inputArgs[0];
133
+ if (arg1 && typeof arg1 === 'object') {
134
+ logHookFunction(arg1 as T);
135
+ }
136
+ // eslint-disable-next-line unicorn/prefer-reflect-apply
137
+ return method.apply(this, inputArgs);
138
+ },
139
+ }
140
+ : {},
141
+ });
142
+ }
143
+
144
+ /**
145
+ * Сливает буфер worker-транспорта и завершает его воркеры. Без вызова на
146
+ * остановке приложения забуференные логи теряются, а воркеры висят.
147
+ */
148
+ static async shutdown(
149
+ timeoutMs: number = DEFAULT_SHUTDOWN_TIMEOUT_MS,
150
+ ): Promise<void> {
151
+ const stream = this._activeTransport;
152
+
153
+ if (!stream) return;
154
+
155
+ this._activeTransport = null;
156
+ this._activeTransportConfig = null;
157
+
158
+ await new Promise<void>((resolve) => {
159
+ let settled = false;
160
+
161
+ const finish = (): void => {
162
+ if (settled) return;
163
+ settled = true;
164
+ resolve();
165
+ };
166
+
167
+ try {
168
+ stream.on('close', finish);
169
+ stream.flush(() => stream.end());
170
+ } catch {
171
+ finish();
172
+ }
173
+
174
+ const timer = setTimeout(finish, timeoutMs);
175
+
176
+ timer.unref?.();
177
+ });
147
178
  }
148
179
 
149
180
  static onMessage(handler: OnMessage): void {
@@ -212,13 +243,130 @@ export class LoggerFactory {
212
243
  }
213
244
 
214
245
  private static applyOptions(opts: Partial<LoggerOptions>): void {
215
- const { redact, level, stream, timestampFormat } = opts || {};
246
+ const { redact, level, stream, transport, timestampFormat } = opts || {};
216
247
 
217
- Object.assign(this.opts, {
218
- redact,
248
+ const base: LoggerOptionsBase = {
249
+ redact: redact ?? this.opts.redact ?? [],
219
250
  level: level ?? DEFAULT_LEVEL,
220
- stream: stream ?? requirePino().destination(),
221
251
  timestampFormat: timestampFormat ?? LogTimestampFormat.isoTime,
252
+ };
253
+
254
+ /**
255
+ * Выбираем ровно один способ доставки. Если ни stream, ни transport не
256
+ * переданы — сохраняем текущий, чтобы частичный reconfigure({ level }) не
257
+ * сбрасывал ранее заданный transport.
258
+ */
259
+ if (transport) {
260
+ this.opts = { ...base, transport };
261
+ } else if (stream) {
262
+ this.opts = { ...base, stream };
263
+ } else if (this.opts.transport) {
264
+ this.opts = { ...base, transport: this.opts.transport };
265
+ } else {
266
+ this.opts = {
267
+ ...base,
268
+ stream: this.opts.stream ?? requirePino().destination(),
269
+ };
270
+ }
271
+ }
272
+
273
+ /**
274
+ * `stream`/`transport` исключаем из опций: способ доставки задаётся вторым
275
+ * аргументом `pino()`, а pino запрещает одновременно `transport` в опциях и
276
+ * stream-аргумент.
277
+ */
278
+ private static buildPinoOptions(
279
+ extra: Partial<LoggerOptsPino> = {},
280
+ ): LoggerOptsPino {
281
+ const {
282
+ timestampFormat,
283
+ stream: _stream,
284
+ transport: _transport,
285
+ ...other
286
+ } = this.opts;
287
+
288
+ return {
289
+ ...other,
290
+ timestamp: this.pinoTimestampFormatMap[timestampFormat],
291
+ ...extra,
292
+ };
293
+ }
294
+
295
+ /**
296
+ * Создаёт инстанс pino. Воркер транспорта переиспользуется, если объект
297
+ * конфига транспорта тот же по ссылке (сравнение identity, не по значению).
298
+ * Это покрывает связку reconfigure→applyInstrumentations (между ними
299
+ * this.opts.transport не пересобирается) и не даёт пересоздавать и тут же
300
+ * закрывать только что поднятый, возможно медленный, OTEL-воркер. Новый
301
+ * объект конфига (например, при config.onUpdate) ожидаемо пересоздаёт воркер.
302
+ */
303
+ private static instantiatePino(extra: Partial<LoggerOptsPino> = {}): Pino {
304
+ const pino = requirePino();
305
+ const options = this.buildPinoOptions(extra);
306
+
307
+ if (!this.opts.transport) {
308
+ this.disposeActiveTransport();
309
+
310
+ return pino(options, this.opts.stream);
311
+ }
312
+
313
+ const reuse =
314
+ this._activeTransport &&
315
+ this.opts.transport === this._activeTransportConfig;
316
+
317
+ const stream = reuse
318
+ ? this._activeTransport!
319
+ : this.createTransport(this.opts.transport);
320
+
321
+ return pino(options, stream);
322
+ }
323
+
324
+ /**
325
+ * Поднимает worker-транспорт, закрыв предыдущий. Обработчик 'error' не даёт
326
+ * асинхронной ошибке воркера уронить процесс, но делает её видимой в stderr
327
+ * (сам логгер использовать нельзя — упал его транспорт). Ошибку резолва
328
+ * таргета pino.transport(...) бросает синхронно — она долетает до старта.
329
+ */
330
+ private static createTransport(
331
+ transport: TransportMultiOptions,
332
+ ): TransportStream {
333
+ this.disposeActiveTransport();
334
+
335
+ const stream = requirePino().transport(transport) as TransportStream;
336
+
337
+ stream.on('error', (...args: unknown[]) => {
338
+ const error = args[0];
339
+ const message = error instanceof Error ? error.message : String(error);
340
+
341
+ process.stderr.write(`[@rsdk/logging] transport error: ${message}\n`);
222
342
  });
343
+
344
+ this._activeTransport = stream;
345
+ this._activeTransportConfig = transport;
346
+
347
+ return stream;
348
+ }
349
+
350
+ /**
351
+ * Завершает воркеры предыдущего транспорта (best-effort, не блокируя). stdout
352
+ * не затрагивается: для него хэндл не сохраняется.
353
+ */
354
+ private static disposeActiveTransport(): void {
355
+ const previous = this._activeTransport;
356
+
357
+ if (!previous) return;
358
+
359
+ this._activeTransport = null;
360
+ this._activeTransportConfig = null;
361
+
362
+ // на teardown ошибки ожидаемы (например, "end() took too long" у медленных
363
+ // транспортов) — глушим их молча и не держим event loop
364
+ previous.unref?.();
365
+ previous.removeAllListeners?.('error');
366
+ previous.on('error', () => {});
367
+
368
+ try {
369
+ previous.end();
370
+ } catch {}
223
371
  }
224
372
  }
package/src/types.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
 
4
5
  export enum LogLevel {
5
6
  fatal = 'fatal',
@@ -16,17 +17,28 @@ export enum LogTimestampFormat {
16
17
  nullTime = 'nullTime',
17
18
  isoTime = 'isoTime',
18
19
  }
20
+
19
21
  export type TimeFn = () => string;
20
22
 
21
23
  export type Params = Record<string, unknown>;
22
24
 
23
- export interface LoggerOptions {
25
+ export interface LoggerOptionsBase {
24
26
  level: LogLevel;
25
27
  redact: string[];
26
- stream: pino.DestinationStream;
27
28
  timestampFormat: LogTimestampFormat;
28
29
  }
29
30
 
31
+ /**
32
+ * `stream` и `transport` — взаимоисключающие способы доставки (pino не
33
+ * принимает оба сразу). Это закодировано в типе: задание одного запрещает
34
+ * другой.
35
+ */
36
+ export type LoggerOptions = LoggerOptionsBase &
37
+ (
38
+ | { stream?: pino.DestinationStream; transport?: never }
39
+ | { stream?: never; transport?: TransportMultiOptions }
40
+ );
41
+
30
42
  export type LoggingContext = string | Constructor;
31
43
 
32
44
  export type OnMessage = (