@rawnodes/logger 2.5.0 → 2.6.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.
package/README.md CHANGED
@@ -414,6 +414,68 @@ logger.for('payments').error('Payment failed');
414
414
  logger.for('auth').error('Login failed');
415
415
  ```
416
416
 
417
+ ## Graceful Shutdown
418
+
419
+ External transports (Discord, Telegram, CloudWatch) buffer messages before sending. To ensure no logs are lost on process exit, use graceful shutdown.
420
+
421
+ ### Automatic (Recommended)
422
+
423
+ Enable `autoShutdown` in config to automatically handle SIGTERM/SIGINT:
424
+
425
+ ```typescript
426
+ const logger = Logger.create({
427
+ level: 'info',
428
+ console: { format: 'plain' },
429
+ cloudwatch: { /* ... */ },
430
+ autoShutdown: true, // Auto-register shutdown handlers
431
+ });
432
+
433
+ // Or with options:
434
+ const logger = Logger.create({
435
+ level: 'info',
436
+ console: { format: 'plain' },
437
+ cloudwatch: { /* ... */ },
438
+ autoShutdown: {
439
+ timeout: 10000, // Max wait time (default: 5000ms)
440
+ signals: ['SIGTERM'], // Signals to handle (default: ['SIGTERM', 'SIGINT'])
441
+ },
442
+ });
443
+ ```
444
+
445
+ ### Manual
446
+
447
+ For more control, use `registerShutdown` or call `shutdown()` directly:
448
+
449
+ ```typescript
450
+ import { Logger, registerShutdown } from '@rawnodes/logger';
451
+
452
+ const logger = Logger.create(config);
453
+
454
+ // Option 1: Register handlers manually
455
+ registerShutdown(logger, {
456
+ timeout: 10000,
457
+ onShutdown: async () => {
458
+ console.log('Flushing logs...');
459
+ },
460
+ });
461
+
462
+ // Option 2: Call shutdown directly (e.g., in your own signal handler)
463
+ process.on('SIGTERM', async () => {
464
+ await logger.shutdown(); // Flush all buffered messages
465
+ process.exit(0);
466
+ });
467
+ ```
468
+
469
+ ### In Tests
470
+
471
+ For tests, call `shutdown()` in `afterAll` to flush pending logs:
472
+
473
+ ```typescript
474
+ afterAll(async () => {
475
+ await logger.shutdown();
476
+ });
477
+ ```
478
+
417
479
  ## Singleton Pattern
418
480
 
419
481
  For app-wide logging:
package/dist/index.d.mts CHANGED
@@ -100,6 +100,12 @@ interface CallerInfo {
100
100
  column?: number;
101
101
  function?: string;
102
102
  }
103
+ interface AutoShutdownConfig {
104
+ /** Timeout in ms before forcing exit (default: 5000) */
105
+ timeout?: number;
106
+ /** Custom signals to handle (default: ['SIGTERM', 'SIGINT']) */
107
+ signals?: NodeJS.Signals[];
108
+ }
103
109
  interface LoggerConfig {
104
110
  level: LevelConfig;
105
111
  console: ConsoleConfig;
@@ -111,6 +117,8 @@ interface LoggerConfig {
111
117
  caller?: boolean | CallerConfig;
112
118
  /** Hostname to include in all log entries. Defaults to os.hostname() if not specified */
113
119
  hostname?: string;
120
+ /** Auto-register graceful shutdown handlers. Pass true for defaults or config object */
121
+ autoShutdown?: boolean | AutoShutdownConfig;
114
122
  }
115
123
  type LoggerContext = Record<string, unknown>;
116
124
  type LevelOverrideMatch<TContext extends LoggerContext> = Partial<TContext> & {
@@ -141,6 +149,11 @@ declare class Logger<TContext extends LoggerContext = LoggerContext> {
141
149
  removeLevelOverride(match: LevelOverrideMatch<TContext>): boolean;
142
150
  clearLevelOverrides(): void;
143
151
  getLevelOverrides(): LevelOverride<TContext>[];
152
+ /**
153
+ * Gracefully shutdown the logger, flushing all pending messages.
154
+ * Should be called before process exit to ensure no logs are lost.
155
+ */
156
+ shutdown(): Promise<void>;
144
157
  profile(id: string, meta?: object): void;
145
158
  error(errorOrMessage: Error | string, messageOrMeta?: string | Meta, meta?: Meta): void;
146
159
  warn(message: string, meta?: Meta): void;
@@ -165,10 +178,6 @@ interface SingletonLogger<TContext extends LoggerContext> {
165
178
  }
166
179
  declare function createSingletonLogger<TContext extends LoggerContext = LoggerContext>(): SingletonLogger<TContext>;
167
180
 
168
- declare function matchesContext(storeContext: LoggerContext | undefined, loggerContext: string | undefined, match: Record<string, unknown> & {
169
- context?: string;
170
- }): boolean;
171
-
172
181
  interface BufferedMessage {
173
182
  level: LogLevel;
174
183
  message: string;
@@ -216,6 +225,10 @@ declare abstract class BaseHttpTransport extends EventEmitter {
216
225
  protected abstract sendBatch(messages: BufferedMessage[]): Promise<void>;
217
226
  }
218
227
 
228
+ declare function matchesContext(storeContext: LoggerContext | undefined, loggerContext: string | undefined, match: Record<string, unknown> & {
229
+ context?: string;
230
+ }): boolean;
231
+
219
232
  declare class DiscordTransport extends BaseHttpTransport {
220
233
  private config;
221
234
  constructor(config: DiscordConfig);
@@ -295,7 +308,35 @@ declare function createMasker(options?: MaskSecretsOptions): (obj: unknown) => u
295
308
  declare function getCallerInfo(config: CallerConfig, additionalOffset?: number): CallerInfo | undefined;
296
309
  declare function formatCallerInfo(info: CallerInfo): string;
297
310
 
298
- declare function flattenObject(obj: Record<string, unknown>, prefix?: string): Record<string, unknown>;
311
+ interface GracefulShutdownOptions {
312
+ /** Timeout in ms before forcing exit (default: 5000) */
313
+ timeout?: number;
314
+ /** Exit code on successful shutdown (default: 0) */
315
+ exitCode?: number;
316
+ /** Custom signals to handle (default: ['SIGTERM', 'SIGINT']) */
317
+ signals?: NodeJS.Signals[];
318
+ /** Called before shutdown starts */
319
+ onShutdown?: () => void | Promise<void>;
320
+ }
321
+ /**
322
+ * Register graceful shutdown handlers for the logger.
323
+ * Ensures all buffered logs are flushed before process exit.
324
+ *
325
+ * @example
326
+ * ```typescript
327
+ * const logger = Logger.create(config);
328
+ * registerShutdown(logger);
329
+ *
330
+ * // Or with options:
331
+ * registerShutdown(logger, {
332
+ * timeout: 10000,
333
+ * onShutdown: () => console.log('Shutting down...'),
334
+ * });
335
+ * ```
336
+ */
337
+ declare function registerShutdown<TContext extends LoggerContext>(logger: Logger<TContext>, options?: GracefulShutdownOptions): () => Promise<void>;
338
+
339
+ declare function flattenObject(obj: Record<string, unknown>, prefix?: string, ancestors?: WeakSet<object>, depth?: number): Record<string, unknown>;
299
340
  declare function formatLogfmtValue(value: unknown): string;
300
341
  declare function formatLogfmt(data: Record<string, unknown>): string;
301
342
 
@@ -315,6 +356,7 @@ declare const CloudWatchConfigSchema: z.ZodType<CloudWatchConfig>;
315
356
  declare const LevelConfigObjectSchema: z.ZodType<LevelConfigObject>;
316
357
  declare const LevelConfigSchema: z.ZodType<LevelConfig>;
317
358
  declare const CallerConfigSchema: z.ZodType<CallerConfig>;
359
+ declare const AutoShutdownConfigSchema: z.ZodType<AutoShutdownConfig>;
318
360
  declare const LoggerConfigSchema: z.ZodObject<{
319
361
  level: z.ZodType<LevelConfig, unknown, z.core.$ZodTypeInternals<LevelConfig, unknown>>;
320
362
  console: z.ZodType<ConsoleConfig, unknown, z.core.$ZodTypeInternals<ConsoleConfig, unknown>>;
@@ -324,6 +366,7 @@ declare const LoggerConfigSchema: z.ZodObject<{
324
366
  cloudwatch: z.ZodOptional<z.ZodUnion<readonly [z.ZodType<CloudWatchConfig, unknown, z.core.$ZodTypeInternals<CloudWatchConfig, unknown>>, z.ZodArray<z.ZodType<CloudWatchConfig, unknown, z.core.$ZodTypeInternals<CloudWatchConfig, unknown>>>]>>;
325
367
  caller: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodType<CallerConfig, unknown, z.core.$ZodTypeInternals<CallerConfig, unknown>>]>>;
326
368
  hostname: z.ZodOptional<z.ZodString>;
369
+ autoShutdown: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodType<AutoShutdownConfig, unknown, z.core.$ZodTypeInternals<AutoShutdownConfig, unknown>>]>>;
327
370
  }, z.core.$strip>;
328
371
  declare function validateConfig(config: unknown): z.infer<typeof LoggerConfigSchema>;
329
372
  declare function safeValidateConfig(config: unknown): z.ZodSafeParseResult<{
@@ -335,6 +378,7 @@ declare function safeValidateConfig(config: unknown): z.ZodSafeParseResult<{
335
378
  cloudwatch?: CloudWatchConfig | CloudWatchConfig[] | undefined;
336
379
  caller?: boolean | CallerConfig | undefined;
337
380
  hostname?: string | undefined;
381
+ autoShutdown?: boolean | AutoShutdownConfig | undefined;
338
382
  }>;
339
383
 
340
- export { BaseHttpTransport, type BaseHttpTransportOptions, type BufferOptions, type BufferedMessage, type CallerConfig, CallerConfigSchema, type CallerInfo, type CloudWatchConfig, CloudWatchConfigSchema, CloudWatchTransport, type ConsoleConfig, ConsoleConfigSchema, type DiscordConfig, DiscordConfigSchema, DiscordTransport, type FileConfig, FileConfigSchema, type HttpTransportBaseConfig, HttpTransportBaseConfigSchema, LOG_LEVELS, type LevelConfig, type LevelConfigObject, LevelConfigObjectSchema, LevelConfigSchema, type LevelOverride, type LevelOverrideMatch, type LevelRule, LevelRuleSchema, type LogFormat, LogFormatSchema, type LogLevel, LogLevelSchema, type LogStreamName, LogStreamNameSchema, type LogStreamPattern, type LogStreamPatternConfig, LogStreamPatternConfigSchema, LogStreamPatternSchema, type LogStreamTemplateConfig, LogStreamTemplateConfigSchema, Logger, type LoggerConfig, LoggerConfigSchema, type LoggerContext, LoggerStore, type MaskSecretsOptions, MessageBuffer, type Meta, type RequestIdOptions, type SingletonLogger, type TelegramConfig, TelegramConfigSchema, TelegramTransport, type TimingResult, assertLogLevel, createMasker, createSingletonLogger, extractRequestId, flattenObject, formatCallerInfo, formatLogfmt, formatLogfmtValue, generateRequestId, getCallerInfo, getOrGenerateRequestId, isValidLogLevel, maskSecrets, matchesContext, measureAsync, measureSync, safeValidateConfig, validateConfig };
384
+ export { type AutoShutdownConfig, AutoShutdownConfigSchema, BaseHttpTransport, type BaseHttpTransportOptions, type BufferOptions, type BufferedMessage, type CallerConfig, CallerConfigSchema, type CallerInfo, type CloudWatchConfig, CloudWatchConfigSchema, CloudWatchTransport, type ConsoleConfig, ConsoleConfigSchema, type DiscordConfig, DiscordConfigSchema, DiscordTransport, type FileConfig, FileConfigSchema, type GracefulShutdownOptions, type HttpTransportBaseConfig, HttpTransportBaseConfigSchema, LOG_LEVELS, type LevelConfig, type LevelConfigObject, LevelConfigObjectSchema, LevelConfigSchema, type LevelOverride, type LevelOverrideMatch, type LevelRule, LevelRuleSchema, type LogFormat, LogFormatSchema, type LogLevel, LogLevelSchema, type LogStreamName, LogStreamNameSchema, type LogStreamPattern, type LogStreamPatternConfig, LogStreamPatternConfigSchema, LogStreamPatternSchema, type LogStreamTemplateConfig, LogStreamTemplateConfigSchema, Logger, type LoggerConfig, LoggerConfigSchema, type LoggerContext, LoggerStore, type MaskSecretsOptions, MessageBuffer, type Meta, type RequestIdOptions, type SingletonLogger, type TelegramConfig, TelegramConfigSchema, TelegramTransport, type TimingResult, assertLogLevel, createMasker, createSingletonLogger, extractRequestId, flattenObject, formatCallerInfo, formatLogfmt, formatLogfmtValue, generateRequestId, getCallerInfo, getOrGenerateRequestId, isValidLogLevel, maskSecrets, matchesContext, measureAsync, measureSync, registerShutdown, safeValidateConfig, validateConfig };
package/dist/index.d.ts CHANGED
@@ -100,6 +100,12 @@ interface CallerInfo {
100
100
  column?: number;
101
101
  function?: string;
102
102
  }
103
+ interface AutoShutdownConfig {
104
+ /** Timeout in ms before forcing exit (default: 5000) */
105
+ timeout?: number;
106
+ /** Custom signals to handle (default: ['SIGTERM', 'SIGINT']) */
107
+ signals?: NodeJS.Signals[];
108
+ }
103
109
  interface LoggerConfig {
104
110
  level: LevelConfig;
105
111
  console: ConsoleConfig;
@@ -111,6 +117,8 @@ interface LoggerConfig {
111
117
  caller?: boolean | CallerConfig;
112
118
  /** Hostname to include in all log entries. Defaults to os.hostname() if not specified */
113
119
  hostname?: string;
120
+ /** Auto-register graceful shutdown handlers. Pass true for defaults or config object */
121
+ autoShutdown?: boolean | AutoShutdownConfig;
114
122
  }
115
123
  type LoggerContext = Record<string, unknown>;
116
124
  type LevelOverrideMatch<TContext extends LoggerContext> = Partial<TContext> & {
@@ -141,6 +149,11 @@ declare class Logger<TContext extends LoggerContext = LoggerContext> {
141
149
  removeLevelOverride(match: LevelOverrideMatch<TContext>): boolean;
142
150
  clearLevelOverrides(): void;
143
151
  getLevelOverrides(): LevelOverride<TContext>[];
152
+ /**
153
+ * Gracefully shutdown the logger, flushing all pending messages.
154
+ * Should be called before process exit to ensure no logs are lost.
155
+ */
156
+ shutdown(): Promise<void>;
144
157
  profile(id: string, meta?: object): void;
145
158
  error(errorOrMessage: Error | string, messageOrMeta?: string | Meta, meta?: Meta): void;
146
159
  warn(message: string, meta?: Meta): void;
@@ -165,10 +178,6 @@ interface SingletonLogger<TContext extends LoggerContext> {
165
178
  }
166
179
  declare function createSingletonLogger<TContext extends LoggerContext = LoggerContext>(): SingletonLogger<TContext>;
167
180
 
168
- declare function matchesContext(storeContext: LoggerContext | undefined, loggerContext: string | undefined, match: Record<string, unknown> & {
169
- context?: string;
170
- }): boolean;
171
-
172
181
  interface BufferedMessage {
173
182
  level: LogLevel;
174
183
  message: string;
@@ -216,6 +225,10 @@ declare abstract class BaseHttpTransport extends EventEmitter {
216
225
  protected abstract sendBatch(messages: BufferedMessage[]): Promise<void>;
217
226
  }
218
227
 
228
+ declare function matchesContext(storeContext: LoggerContext | undefined, loggerContext: string | undefined, match: Record<string, unknown> & {
229
+ context?: string;
230
+ }): boolean;
231
+
219
232
  declare class DiscordTransport extends BaseHttpTransport {
220
233
  private config;
221
234
  constructor(config: DiscordConfig);
@@ -295,7 +308,35 @@ declare function createMasker(options?: MaskSecretsOptions): (obj: unknown) => u
295
308
  declare function getCallerInfo(config: CallerConfig, additionalOffset?: number): CallerInfo | undefined;
296
309
  declare function formatCallerInfo(info: CallerInfo): string;
297
310
 
298
- declare function flattenObject(obj: Record<string, unknown>, prefix?: string): Record<string, unknown>;
311
+ interface GracefulShutdownOptions {
312
+ /** Timeout in ms before forcing exit (default: 5000) */
313
+ timeout?: number;
314
+ /** Exit code on successful shutdown (default: 0) */
315
+ exitCode?: number;
316
+ /** Custom signals to handle (default: ['SIGTERM', 'SIGINT']) */
317
+ signals?: NodeJS.Signals[];
318
+ /** Called before shutdown starts */
319
+ onShutdown?: () => void | Promise<void>;
320
+ }
321
+ /**
322
+ * Register graceful shutdown handlers for the logger.
323
+ * Ensures all buffered logs are flushed before process exit.
324
+ *
325
+ * @example
326
+ * ```typescript
327
+ * const logger = Logger.create(config);
328
+ * registerShutdown(logger);
329
+ *
330
+ * // Or with options:
331
+ * registerShutdown(logger, {
332
+ * timeout: 10000,
333
+ * onShutdown: () => console.log('Shutting down...'),
334
+ * });
335
+ * ```
336
+ */
337
+ declare function registerShutdown<TContext extends LoggerContext>(logger: Logger<TContext>, options?: GracefulShutdownOptions): () => Promise<void>;
338
+
339
+ declare function flattenObject(obj: Record<string, unknown>, prefix?: string, ancestors?: WeakSet<object>, depth?: number): Record<string, unknown>;
299
340
  declare function formatLogfmtValue(value: unknown): string;
300
341
  declare function formatLogfmt(data: Record<string, unknown>): string;
301
342
 
@@ -315,6 +356,7 @@ declare const CloudWatchConfigSchema: z.ZodType<CloudWatchConfig>;
315
356
  declare const LevelConfigObjectSchema: z.ZodType<LevelConfigObject>;
316
357
  declare const LevelConfigSchema: z.ZodType<LevelConfig>;
317
358
  declare const CallerConfigSchema: z.ZodType<CallerConfig>;
359
+ declare const AutoShutdownConfigSchema: z.ZodType<AutoShutdownConfig>;
318
360
  declare const LoggerConfigSchema: z.ZodObject<{
319
361
  level: z.ZodType<LevelConfig, unknown, z.core.$ZodTypeInternals<LevelConfig, unknown>>;
320
362
  console: z.ZodType<ConsoleConfig, unknown, z.core.$ZodTypeInternals<ConsoleConfig, unknown>>;
@@ -324,6 +366,7 @@ declare const LoggerConfigSchema: z.ZodObject<{
324
366
  cloudwatch: z.ZodOptional<z.ZodUnion<readonly [z.ZodType<CloudWatchConfig, unknown, z.core.$ZodTypeInternals<CloudWatchConfig, unknown>>, z.ZodArray<z.ZodType<CloudWatchConfig, unknown, z.core.$ZodTypeInternals<CloudWatchConfig, unknown>>>]>>;
325
367
  caller: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodType<CallerConfig, unknown, z.core.$ZodTypeInternals<CallerConfig, unknown>>]>>;
326
368
  hostname: z.ZodOptional<z.ZodString>;
369
+ autoShutdown: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodType<AutoShutdownConfig, unknown, z.core.$ZodTypeInternals<AutoShutdownConfig, unknown>>]>>;
327
370
  }, z.core.$strip>;
328
371
  declare function validateConfig(config: unknown): z.infer<typeof LoggerConfigSchema>;
329
372
  declare function safeValidateConfig(config: unknown): z.ZodSafeParseResult<{
@@ -335,6 +378,7 @@ declare function safeValidateConfig(config: unknown): z.ZodSafeParseResult<{
335
378
  cloudwatch?: CloudWatchConfig | CloudWatchConfig[] | undefined;
336
379
  caller?: boolean | CallerConfig | undefined;
337
380
  hostname?: string | undefined;
381
+ autoShutdown?: boolean | AutoShutdownConfig | undefined;
338
382
  }>;
339
383
 
340
- export { BaseHttpTransport, type BaseHttpTransportOptions, type BufferOptions, type BufferedMessage, type CallerConfig, CallerConfigSchema, type CallerInfo, type CloudWatchConfig, CloudWatchConfigSchema, CloudWatchTransport, type ConsoleConfig, ConsoleConfigSchema, type DiscordConfig, DiscordConfigSchema, DiscordTransport, type FileConfig, FileConfigSchema, type HttpTransportBaseConfig, HttpTransportBaseConfigSchema, LOG_LEVELS, type LevelConfig, type LevelConfigObject, LevelConfigObjectSchema, LevelConfigSchema, type LevelOverride, type LevelOverrideMatch, type LevelRule, LevelRuleSchema, type LogFormat, LogFormatSchema, type LogLevel, LogLevelSchema, type LogStreamName, LogStreamNameSchema, type LogStreamPattern, type LogStreamPatternConfig, LogStreamPatternConfigSchema, LogStreamPatternSchema, type LogStreamTemplateConfig, LogStreamTemplateConfigSchema, Logger, type LoggerConfig, LoggerConfigSchema, type LoggerContext, LoggerStore, type MaskSecretsOptions, MessageBuffer, type Meta, type RequestIdOptions, type SingletonLogger, type TelegramConfig, TelegramConfigSchema, TelegramTransport, type TimingResult, assertLogLevel, createMasker, createSingletonLogger, extractRequestId, flattenObject, formatCallerInfo, formatLogfmt, formatLogfmtValue, generateRequestId, getCallerInfo, getOrGenerateRequestId, isValidLogLevel, maskSecrets, matchesContext, measureAsync, measureSync, safeValidateConfig, validateConfig };
384
+ export { type AutoShutdownConfig, AutoShutdownConfigSchema, BaseHttpTransport, type BaseHttpTransportOptions, type BufferOptions, type BufferedMessage, type CallerConfig, CallerConfigSchema, type CallerInfo, type CloudWatchConfig, CloudWatchConfigSchema, CloudWatchTransport, type ConsoleConfig, ConsoleConfigSchema, type DiscordConfig, DiscordConfigSchema, DiscordTransport, type FileConfig, FileConfigSchema, type GracefulShutdownOptions, type HttpTransportBaseConfig, HttpTransportBaseConfigSchema, LOG_LEVELS, type LevelConfig, type LevelConfigObject, LevelConfigObjectSchema, LevelConfigSchema, type LevelOverride, type LevelOverrideMatch, type LevelRule, LevelRuleSchema, type LogFormat, LogFormatSchema, type LogLevel, LogLevelSchema, type LogStreamName, LogStreamNameSchema, type LogStreamPattern, type LogStreamPatternConfig, LogStreamPatternConfigSchema, LogStreamPatternSchema, type LogStreamTemplateConfig, LogStreamTemplateConfigSchema, Logger, type LoggerConfig, LoggerConfigSchema, type LoggerContext, LoggerStore, type MaskSecretsOptions, MessageBuffer, type Meta, type RequestIdOptions, type SingletonLogger, type TelegramConfig, TelegramConfigSchema, TelegramTransport, type TimingResult, assertLogLevel, createMasker, createSingletonLogger, extractRequestId, flattenObject, formatCallerInfo, formatLogfmt, formatLogfmtValue, generateRequestId, getCallerInfo, getOrGenerateRequestId, isValidLogLevel, maskSecrets, matchesContext, measureAsync, measureSync, registerShutdown, safeValidateConfig, validateConfig };
package/dist/index.js CHANGED
@@ -689,6 +689,7 @@ function createFormattedFilterStream(format, level, rules, store, destination) {
689
689
  }
690
690
  function createStreams(config, store) {
691
691
  const streams = [];
692
+ const transports = [];
692
693
  const consoleStream = createFormattedFilterStream(
693
694
  config.console.format,
694
695
  config.console.level,
@@ -736,6 +737,7 @@ function createStreams(config, store) {
736
737
  }
737
738
  for (const discordConfig of toArray(config.discord)) {
738
739
  const transport = new DiscordTransport(discordConfig);
740
+ transports.push(transport);
739
741
  const discordStream = createHttpTransportStream(transport, discordConfig.level, discordConfig.rules, store);
740
742
  streams.push({
741
743
  level: "trace",
@@ -744,6 +746,7 @@ function createStreams(config, store) {
744
746
  }
745
747
  for (const telegramConfig of toArray(config.telegram)) {
746
748
  const transport = new TelegramTransport(telegramConfig);
749
+ transports.push(transport);
747
750
  const telegramStream = createHttpTransportStream(transport, telegramConfig.level, telegramConfig.rules, store);
748
751
  streams.push({
749
752
  level: "trace",
@@ -752,13 +755,17 @@ function createStreams(config, store) {
752
755
  }
753
756
  for (const cloudwatchConfig of toArray(config.cloudwatch)) {
754
757
  const transport = new CloudWatchTransport(cloudwatchConfig, config.hostname);
758
+ transports.push(transport);
755
759
  const cwStream = createHttpTransportStream(transport, cloudwatchConfig.level, cloudwatchConfig.rules, store);
756
760
  streams.push({
757
761
  level: "trace",
758
762
  stream: cwStream
759
763
  });
760
764
  }
761
- return pino__default.default.multistream(streams);
765
+ return {
766
+ destination: pino__default.default.multistream(streams),
767
+ transports
768
+ };
762
769
  }
763
770
  function toArray(value) {
764
771
  if (!value) return [];
@@ -843,14 +850,14 @@ function createState(config, store) {
843
850
  levelOverrides.set(key, rule);
844
851
  }
845
852
  const { contextIndex, complexRules } = buildIndexes(levelOverrides);
846
- const streams = createStreams(config, loggerStore);
853
+ const { destination, transports } = createStreams(config, loggerStore);
847
854
  const options = {
848
855
  level: "trace",
849
856
  // Accept all, we filter in shouldLog()
850
857
  customLevels: CUSTOM_LEVELS,
851
858
  base: { hostname: config.hostname ?? os.hostname() }
852
859
  };
853
- const pinoLogger = pino__default.default(options, streams);
860
+ const pinoLogger = pino__default.default(options, destination);
854
861
  let callerConfig;
855
862
  if (config.caller === true) {
856
863
  callerConfig = {};
@@ -864,7 +871,8 @@ function createState(config, store) {
864
871
  levelOverrides,
865
872
  contextIndex,
866
873
  complexRules,
867
- callerConfig
874
+ callerConfig,
875
+ transports
868
876
  };
869
877
  }
870
878
  function rebuildIndexes(state) {
@@ -1010,6 +1018,10 @@ var CallerConfigSchema = zod.z.object({
1010
1018
  depth: zod.z.number().int().nonnegative().optional(),
1011
1019
  fullPath: zod.z.boolean().optional()
1012
1020
  });
1021
+ var AutoShutdownConfigSchema = zod.z.object({
1022
+ timeout: zod.z.number().int().positive().optional(),
1023
+ signals: zod.z.array(zod.z.string()).optional()
1024
+ });
1013
1025
  var LoggerConfigSchema = zod.z.object({
1014
1026
  level: LevelConfigSchema,
1015
1027
  console: ConsoleConfigSchema,
@@ -1018,7 +1030,8 @@ var LoggerConfigSchema = zod.z.object({
1018
1030
  telegram: zod.z.union([TelegramConfigSchema, zod.z.array(TelegramConfigSchema)]).optional(),
1019
1031
  cloudwatch: zod.z.union([CloudWatchConfigSchema, zod.z.array(CloudWatchConfigSchema)]).optional(),
1020
1032
  caller: zod.z.union([zod.z.boolean(), CallerConfigSchema]).optional(),
1021
- hostname: zod.z.string().optional()
1033
+ hostname: zod.z.string().optional(),
1034
+ autoShutdown: zod.z.union([zod.z.boolean(), AutoShutdownConfigSchema]).optional()
1022
1035
  });
1023
1036
  function validateConfig(config) {
1024
1037
  return LoggerConfigSchema.parse(config);
@@ -1087,6 +1100,53 @@ function formatCallerInfo(info) {
1087
1100
  return location;
1088
1101
  }
1089
1102
 
1103
+ // src/utils/shutdown.ts
1104
+ var DEFAULT_OPTIONS2 = {
1105
+ timeout: 5e3,
1106
+ exitCode: 0,
1107
+ signals: ["SIGTERM", "SIGINT"]
1108
+ };
1109
+ var registered = false;
1110
+ function registerShutdown(logger, options = {}) {
1111
+ const opts = { ...DEFAULT_OPTIONS2, ...options };
1112
+ const shutdown = async (signal) => {
1113
+ if (signal) {
1114
+ console.error(`[Logger] Received ${signal}, shutting down gracefully...`);
1115
+ }
1116
+ try {
1117
+ if (opts.onShutdown) {
1118
+ await opts.onShutdown();
1119
+ }
1120
+ const timeoutPromise = new Promise((_, reject) => {
1121
+ setTimeout(() => {
1122
+ reject(new Error(`Shutdown timed out after ${opts.timeout}ms`));
1123
+ }, opts.timeout);
1124
+ });
1125
+ await Promise.race([
1126
+ logger.shutdown(),
1127
+ timeoutPromise
1128
+ ]);
1129
+ if (signal) {
1130
+ console.error("[Logger] Graceful shutdown completed");
1131
+ process.exit(opts.exitCode);
1132
+ }
1133
+ } catch (error) {
1134
+ console.error("[Logger] Shutdown error:", error instanceof Error ? error.message : error);
1135
+ if (signal) {
1136
+ process.exit(1);
1137
+ }
1138
+ }
1139
+ };
1140
+ if (!registered) {
1141
+ for (const signal of opts.signals) {
1142
+ process.on(signal, () => void shutdown(signal));
1143
+ }
1144
+ process.on("beforeExit", () => void shutdown());
1145
+ registered = true;
1146
+ }
1147
+ return shutdown;
1148
+ }
1149
+
1090
1150
  // src/logger.ts
1091
1151
  var Logger = class _Logger {
1092
1152
  constructor(state, context) {
@@ -1097,7 +1157,12 @@ var Logger = class _Logger {
1097
1157
  static create(config, store) {
1098
1158
  const validatedConfig = validateConfig(config);
1099
1159
  const state = createState(validatedConfig, store);
1100
- return new _Logger(state, "APP");
1160
+ const logger = new _Logger(state, "APP");
1161
+ if (validatedConfig.autoShutdown) {
1162
+ const shutdownConfig = typeof validatedConfig.autoShutdown === "object" ? validatedConfig.autoShutdown : {};
1163
+ registerShutdown(logger, shutdownConfig);
1164
+ }
1165
+ return logger;
1101
1166
  }
1102
1167
  for(context) {
1103
1168
  return new _Logger(this.state, context);
@@ -1139,6 +1204,15 @@ var Logger = class _Logger {
1139
1204
  getLevelOverrides() {
1140
1205
  return Array.from(this.state.levelOverrides.values());
1141
1206
  }
1207
+ // Shutdown
1208
+ /**
1209
+ * Gracefully shutdown the logger, flushing all pending messages.
1210
+ * Should be called before process exit to ensure no logs are lost.
1211
+ */
1212
+ async shutdown() {
1213
+ const closePromises = this.state.transports.map((transport) => transport.close());
1214
+ await Promise.all(closePromises);
1215
+ }
1142
1216
  // Profiling
1143
1217
  profile(id, meta) {
1144
1218
  const existing = this.profileTimers.get(id);
@@ -1405,16 +1479,28 @@ function createMasker(options = {}) {
1405
1479
  }
1406
1480
 
1407
1481
  // src/formatters.ts
1408
- function flattenObject(obj, prefix = "") {
1482
+ var MAX_FLATTEN_DEPTH = 10;
1483
+ var CIRCULAR_REF = "[Circular]";
1484
+ function flattenObject(obj, prefix = "", ancestors = /* @__PURE__ */ new WeakSet(), depth = 0) {
1409
1485
  const result = {};
1486
+ if (depth > MAX_FLATTEN_DEPTH) {
1487
+ result[prefix || "value"] = "[Max depth exceeded]";
1488
+ return result;
1489
+ }
1490
+ ancestors.add(obj);
1410
1491
  for (const [key, value] of Object.entries(obj)) {
1411
1492
  const newKey = prefix ? `${prefix}.${key}` : key;
1412
1493
  if (value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Error)) {
1413
- Object.assign(result, flattenObject(value, newKey));
1494
+ if (ancestors.has(value)) {
1495
+ result[newKey] = CIRCULAR_REF;
1496
+ } else {
1497
+ Object.assign(result, flattenObject(value, newKey, ancestors, depth + 1));
1498
+ }
1414
1499
  } else {
1415
1500
  result[newKey] = value;
1416
1501
  }
1417
1502
  }
1503
+ ancestors.delete(obj);
1418
1504
  return result;
1419
1505
  }
1420
1506
  function formatLogfmtValue(value) {
@@ -1443,6 +1529,7 @@ function formatLogfmt(data) {
1443
1529
  return Object.entries(flattened).filter(([, value]) => value !== void 0 && value !== null).map(([key, value]) => `${key}=${formatLogfmtValue(value)}`).join(" ");
1444
1530
  }
1445
1531
 
1532
+ exports.AutoShutdownConfigSchema = AutoShutdownConfigSchema;
1446
1533
  exports.BaseHttpTransport = BaseHttpTransport;
1447
1534
  exports.CallerConfigSchema = CallerConfigSchema;
1448
1535
  exports.CloudWatchConfigSchema = CloudWatchConfigSchema;
@@ -1484,6 +1571,7 @@ exports.maskSecrets = maskSecrets;
1484
1571
  exports.matchesContext = matchesContext;
1485
1572
  exports.measureAsync = measureAsync;
1486
1573
  exports.measureSync = measureSync;
1574
+ exports.registerShutdown = registerShutdown;
1487
1575
  exports.safeValidateConfig = safeValidateConfig;
1488
1576
  exports.validateConfig = validateConfig;
1489
1577
  //# sourceMappingURL=index.js.map