@rawnodes/logger 2.4.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/dist/index.mjs CHANGED
@@ -6,9 +6,9 @@ import { mkdir } from 'fs/promises';
6
6
  import { createStream } from 'rotating-file-stream';
7
7
  import { EventEmitter } from 'events';
8
8
  import { CloudWatchLogsClient, PutLogEventsCommand, CreateLogGroupCommand, CreateLogStreamCommand, DescribeLogStreamsCommand } from '@aws-sdk/client-cloudwatch-logs';
9
+ import { randomUUID } from 'crypto';
9
10
  import { z } from 'zod';
10
11
  import { relative, basename } from 'path';
11
- import { randomUUID } from 'crypto';
12
12
 
13
13
  // src/state.ts
14
14
  var LoggerStore = class {
@@ -416,13 +416,50 @@ var TelegramTransport = class extends BaseHttpTransport {
416
416
  return text.replace(/[&<>"']/g, (c) => entities[c] || c);
417
417
  }
418
418
  };
419
+ var instanceUuid = randomUUID().slice(0, 8);
420
+ function formatDate() {
421
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
422
+ }
423
+ function formatDateTime() {
424
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace(/[T:]/g, "-");
425
+ }
426
+ function resolveLogStreamName(config, configHostname) {
427
+ const hostname$1 = configHostname || process.env.HOSTNAME || hostname();
428
+ if (!config) {
429
+ return hostname$1;
430
+ }
431
+ if (typeof config === "string") {
432
+ return config;
433
+ }
434
+ if ("pattern" in config) {
435
+ switch (config.pattern) {
436
+ case "hostname":
437
+ return hostname$1;
438
+ case "hostname-date":
439
+ return `${hostname$1}/${formatDate()}`;
440
+ case "hostname-uuid":
441
+ return `${hostname$1}-${instanceUuid}`;
442
+ case "date":
443
+ return formatDate();
444
+ case "uuid":
445
+ return randomUUID();
446
+ default:
447
+ return hostname$1;
448
+ }
449
+ }
450
+ if ("template" in config) {
451
+ return config.template.replace(/\{hostname\}/g, hostname$1).replace(/\{date\}/g, formatDate()).replace(/\{datetime\}/g, formatDateTime()).replace(/\{uuid\}/g, instanceUuid).replace(/\{pid\}/g, String(process.pid)).replace(/\{env\}/g, process.env.NODE_ENV || "development");
452
+ }
453
+ return hostname$1;
454
+ }
419
455
  var CloudWatchTransport = class extends BaseHttpTransport {
420
456
  config;
421
457
  client;
422
458
  sequenceToken;
423
459
  initialized = false;
424
460
  initPromise = null;
425
- constructor(config) {
461
+ resolvedLogStreamName;
462
+ constructor(config, configHostname) {
426
463
  super({
427
464
  batchSize: config.batchSize ?? 100,
428
465
  flushInterval: config.flushInterval ?? 1e3,
@@ -430,6 +467,7 @@ var CloudWatchTransport = class extends BaseHttpTransport {
430
467
  retryDelay: config.retryDelay
431
468
  });
432
469
  this.config = config;
470
+ this.resolvedLogStreamName = resolveLogStreamName(config.logStreamName, configHostname);
433
471
  this.client = new CloudWatchLogsClient({
434
472
  region: config.region,
435
473
  credentials: {
@@ -452,7 +490,7 @@ var CloudWatchTransport = class extends BaseHttpTransport {
452
490
  logEvents.sort((a, b) => (a.timestamp ?? 0) - (b.timestamp ?? 0));
453
491
  const command = new PutLogEventsCommand({
454
492
  logGroupName: this.config.logGroupName,
455
- logStreamName: this.config.logStreamName,
493
+ logStreamName: this.resolvedLogStreamName,
456
494
  logEvents,
457
495
  sequenceToken: this.sequenceToken
458
496
  });
@@ -464,7 +502,7 @@ var CloudWatchTransport = class extends BaseHttpTransport {
464
502
  await this.fetchSequenceToken();
465
503
  const retryCommand = new PutLogEventsCommand({
466
504
  logGroupName: this.config.logGroupName,
467
- logStreamName: this.config.logStreamName,
505
+ logStreamName: this.resolvedLogStreamName,
468
506
  logEvents,
469
507
  sequenceToken: this.sequenceToken
470
508
  });
@@ -510,7 +548,7 @@ var CloudWatchTransport = class extends BaseHttpTransport {
510
548
  await this.client.send(
511
549
  new CreateLogStreamCommand({
512
550
  logGroupName: this.config.logGroupName,
513
- logStreamName: this.config.logStreamName
551
+ logStreamName: this.resolvedLogStreamName
514
552
  })
515
553
  );
516
554
  } catch (error) {
@@ -523,11 +561,11 @@ var CloudWatchTransport = class extends BaseHttpTransport {
523
561
  const response = await this.client.send(
524
562
  new DescribeLogStreamsCommand({
525
563
  logGroupName: this.config.logGroupName,
526
- logStreamNamePrefix: this.config.logStreamName,
564
+ logStreamNamePrefix: this.resolvedLogStreamName,
527
565
  limit: 1
528
566
  })
529
567
  );
530
- const stream = response.logStreams?.find((s) => s.logStreamName === this.config.logStreamName);
568
+ const stream = response.logStreams?.find((s) => s.logStreamName === this.resolvedLogStreamName);
531
569
  this.sequenceToken = stream?.uploadSequenceToken;
532
570
  }
533
571
  isResourceAlreadyExistsError(error) {
@@ -645,6 +683,7 @@ function createFormattedFilterStream(format, level, rules, store, destination) {
645
683
  }
646
684
  function createStreams(config, store) {
647
685
  const streams = [];
686
+ const transports = [];
648
687
  const consoleStream = createFormattedFilterStream(
649
688
  config.console.format,
650
689
  config.console.level,
@@ -692,6 +731,7 @@ function createStreams(config, store) {
692
731
  }
693
732
  for (const discordConfig of toArray(config.discord)) {
694
733
  const transport = new DiscordTransport(discordConfig);
734
+ transports.push(transport);
695
735
  const discordStream = createHttpTransportStream(transport, discordConfig.level, discordConfig.rules, store);
696
736
  streams.push({
697
737
  level: "trace",
@@ -700,6 +740,7 @@ function createStreams(config, store) {
700
740
  }
701
741
  for (const telegramConfig of toArray(config.telegram)) {
702
742
  const transport = new TelegramTransport(telegramConfig);
743
+ transports.push(transport);
703
744
  const telegramStream = createHttpTransportStream(transport, telegramConfig.level, telegramConfig.rules, store);
704
745
  streams.push({
705
746
  level: "trace",
@@ -707,14 +748,18 @@ function createStreams(config, store) {
707
748
  });
708
749
  }
709
750
  for (const cloudwatchConfig of toArray(config.cloudwatch)) {
710
- const transport = new CloudWatchTransport(cloudwatchConfig);
751
+ const transport = new CloudWatchTransport(cloudwatchConfig, config.hostname);
752
+ transports.push(transport);
711
753
  const cwStream = createHttpTransportStream(transport, cloudwatchConfig.level, cloudwatchConfig.rules, store);
712
754
  streams.push({
713
755
  level: "trace",
714
756
  stream: cwStream
715
757
  });
716
758
  }
717
- return pino.multistream(streams);
759
+ return {
760
+ destination: pino.multistream(streams),
761
+ transports
762
+ };
718
763
  }
719
764
  function toArray(value) {
720
765
  if (!value) return [];
@@ -799,14 +844,14 @@ function createState(config, store) {
799
844
  levelOverrides.set(key, rule);
800
845
  }
801
846
  const { contextIndex, complexRules } = buildIndexes(levelOverrides);
802
- const streams = createStreams(config, loggerStore);
847
+ const { destination, transports } = createStreams(config, loggerStore);
803
848
  const options = {
804
849
  level: "trace",
805
850
  // Accept all, we filter in shouldLog()
806
851
  customLevels: CUSTOM_LEVELS,
807
852
  base: { hostname: config.hostname ?? hostname() }
808
853
  };
809
- const pinoLogger = pino(options, streams);
854
+ const pinoLogger = pino(options, destination);
810
855
  let callerConfig;
811
856
  if (config.caller === true) {
812
857
  callerConfig = {};
@@ -820,7 +865,8 @@ function createState(config, store) {
820
865
  levelOverrides,
821
866
  contextIndex,
822
867
  complexRules,
823
- callerConfig
868
+ callerConfig,
869
+ transports
824
870
  };
825
871
  }
826
872
  function rebuildIndexes(state) {
@@ -921,6 +967,24 @@ var TelegramConfigSchema = z.object({
921
967
  threadId: z.number().int().optional(),
922
968
  replyToMessageId: z.number().int().optional()
923
969
  });
970
+ var LogStreamPatternSchema = z.enum([
971
+ "hostname",
972
+ "hostname-date",
973
+ "hostname-uuid",
974
+ "date",
975
+ "uuid"
976
+ ]);
977
+ var LogStreamPatternConfigSchema = z.object({
978
+ pattern: LogStreamPatternSchema
979
+ });
980
+ var LogStreamTemplateConfigSchema = z.object({
981
+ template: z.string().min(1, "template is required")
982
+ });
983
+ var LogStreamNameSchema = z.union([
984
+ z.string().min(1, "logStreamName string must not be empty"),
985
+ LogStreamPatternConfigSchema,
986
+ LogStreamTemplateConfigSchema
987
+ ]);
924
988
  var CloudWatchConfigSchema = z.object({
925
989
  level: LogLevelSchema.optional(),
926
990
  rules: z.array(LevelRuleSchema).optional(),
@@ -929,7 +993,7 @@ var CloudWatchConfigSchema = z.object({
929
993
  maxRetries: z.number().int().nonnegative().optional(),
930
994
  retryDelay: z.number().int().positive().optional(),
931
995
  logGroupName: z.string().min(1, "logGroupName is required"),
932
- logStreamName: z.string().min(1, "logStreamName is required"),
996
+ logStreamName: LogStreamNameSchema.optional(),
933
997
  region: z.string().min(1, "region is required"),
934
998
  accessKeyId: z.string().min(1, "accessKeyId is required"),
935
999
  secretAccessKey: z.string().min(1, "secretAccessKey is required"),
@@ -948,6 +1012,10 @@ var CallerConfigSchema = z.object({
948
1012
  depth: z.number().int().nonnegative().optional(),
949
1013
  fullPath: z.boolean().optional()
950
1014
  });
1015
+ var AutoShutdownConfigSchema = z.object({
1016
+ timeout: z.number().int().positive().optional(),
1017
+ signals: z.array(z.string()).optional()
1018
+ });
951
1019
  var LoggerConfigSchema = z.object({
952
1020
  level: LevelConfigSchema,
953
1021
  console: ConsoleConfigSchema,
@@ -956,7 +1024,8 @@ var LoggerConfigSchema = z.object({
956
1024
  telegram: z.union([TelegramConfigSchema, z.array(TelegramConfigSchema)]).optional(),
957
1025
  cloudwatch: z.union([CloudWatchConfigSchema, z.array(CloudWatchConfigSchema)]).optional(),
958
1026
  caller: z.union([z.boolean(), CallerConfigSchema]).optional(),
959
- hostname: z.string().optional()
1027
+ hostname: z.string().optional(),
1028
+ autoShutdown: z.union([z.boolean(), AutoShutdownConfigSchema]).optional()
960
1029
  });
961
1030
  function validateConfig(config) {
962
1031
  return LoggerConfigSchema.parse(config);
@@ -1025,6 +1094,53 @@ function formatCallerInfo(info) {
1025
1094
  return location;
1026
1095
  }
1027
1096
 
1097
+ // src/utils/shutdown.ts
1098
+ var DEFAULT_OPTIONS2 = {
1099
+ timeout: 5e3,
1100
+ exitCode: 0,
1101
+ signals: ["SIGTERM", "SIGINT"]
1102
+ };
1103
+ var registered = false;
1104
+ function registerShutdown(logger, options = {}) {
1105
+ const opts = { ...DEFAULT_OPTIONS2, ...options };
1106
+ const shutdown = async (signal) => {
1107
+ if (signal) {
1108
+ console.error(`[Logger] Received ${signal}, shutting down gracefully...`);
1109
+ }
1110
+ try {
1111
+ if (opts.onShutdown) {
1112
+ await opts.onShutdown();
1113
+ }
1114
+ const timeoutPromise = new Promise((_, reject) => {
1115
+ setTimeout(() => {
1116
+ reject(new Error(`Shutdown timed out after ${opts.timeout}ms`));
1117
+ }, opts.timeout);
1118
+ });
1119
+ await Promise.race([
1120
+ logger.shutdown(),
1121
+ timeoutPromise
1122
+ ]);
1123
+ if (signal) {
1124
+ console.error("[Logger] Graceful shutdown completed");
1125
+ process.exit(opts.exitCode);
1126
+ }
1127
+ } catch (error) {
1128
+ console.error("[Logger] Shutdown error:", error instanceof Error ? error.message : error);
1129
+ if (signal) {
1130
+ process.exit(1);
1131
+ }
1132
+ }
1133
+ };
1134
+ if (!registered) {
1135
+ for (const signal of opts.signals) {
1136
+ process.on(signal, () => void shutdown(signal));
1137
+ }
1138
+ process.on("beforeExit", () => void shutdown());
1139
+ registered = true;
1140
+ }
1141
+ return shutdown;
1142
+ }
1143
+
1028
1144
  // src/logger.ts
1029
1145
  var Logger = class _Logger {
1030
1146
  constructor(state, context) {
@@ -1035,7 +1151,12 @@ var Logger = class _Logger {
1035
1151
  static create(config, store) {
1036
1152
  const validatedConfig = validateConfig(config);
1037
1153
  const state = createState(validatedConfig, store);
1038
- return new _Logger(state, "APP");
1154
+ const logger = new _Logger(state, "APP");
1155
+ if (validatedConfig.autoShutdown) {
1156
+ const shutdownConfig = typeof validatedConfig.autoShutdown === "object" ? validatedConfig.autoShutdown : {};
1157
+ registerShutdown(logger, shutdownConfig);
1158
+ }
1159
+ return logger;
1039
1160
  }
1040
1161
  for(context) {
1041
1162
  return new _Logger(this.state, context);
@@ -1077,6 +1198,15 @@ var Logger = class _Logger {
1077
1198
  getLevelOverrides() {
1078
1199
  return Array.from(this.state.levelOverrides.values());
1079
1200
  }
1201
+ // Shutdown
1202
+ /**
1203
+ * Gracefully shutdown the logger, flushing all pending messages.
1204
+ * Should be called before process exit to ensure no logs are lost.
1205
+ */
1206
+ async shutdown() {
1207
+ const closePromises = this.state.transports.map((transport) => transport.close());
1208
+ await Promise.all(closePromises);
1209
+ }
1080
1210
  // Profiling
1081
1211
  profile(id, meta) {
1082
1212
  const existing = this.profileTimers.get(id);
@@ -1343,16 +1473,28 @@ function createMasker(options = {}) {
1343
1473
  }
1344
1474
 
1345
1475
  // src/formatters.ts
1346
- function flattenObject(obj, prefix = "") {
1476
+ var MAX_FLATTEN_DEPTH = 10;
1477
+ var CIRCULAR_REF = "[Circular]";
1478
+ function flattenObject(obj, prefix = "", ancestors = /* @__PURE__ */ new WeakSet(), depth = 0) {
1347
1479
  const result = {};
1480
+ if (depth > MAX_FLATTEN_DEPTH) {
1481
+ result[prefix || "value"] = "[Max depth exceeded]";
1482
+ return result;
1483
+ }
1484
+ ancestors.add(obj);
1348
1485
  for (const [key, value] of Object.entries(obj)) {
1349
1486
  const newKey = prefix ? `${prefix}.${key}` : key;
1350
1487
  if (value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Error)) {
1351
- Object.assign(result, flattenObject(value, newKey));
1488
+ if (ancestors.has(value)) {
1489
+ result[newKey] = CIRCULAR_REF;
1490
+ } else {
1491
+ Object.assign(result, flattenObject(value, newKey, ancestors, depth + 1));
1492
+ }
1352
1493
  } else {
1353
1494
  result[newKey] = value;
1354
1495
  }
1355
1496
  }
1497
+ ancestors.delete(obj);
1356
1498
  return result;
1357
1499
  }
1358
1500
  function formatLogfmtValue(value) {
@@ -1381,6 +1523,6 @@ function formatLogfmt(data) {
1381
1523
  return Object.entries(flattened).filter(([, value]) => value !== void 0 && value !== null).map(([key, value]) => `${key}=${formatLogfmtValue(value)}`).join(" ");
1382
1524
  }
1383
1525
 
1384
- export { BaseHttpTransport, CallerConfigSchema, CloudWatchConfigSchema, CloudWatchTransport, ConsoleConfigSchema, DiscordConfigSchema, DiscordTransport, FileConfigSchema, HttpTransportBaseConfigSchema, LOG_LEVELS, LevelConfigObjectSchema, LevelConfigSchema, LevelRuleSchema, LogFormatSchema, LogLevelSchema, Logger, LoggerConfigSchema, LoggerStore, MessageBuffer, TelegramConfigSchema, TelegramTransport, assertLogLevel, createMasker, createSingletonLogger, extractRequestId, flattenObject, formatCallerInfo, formatLogfmt, formatLogfmtValue, generateRequestId, getCallerInfo, getOrGenerateRequestId, isValidLogLevel, maskSecrets, matchesContext, measureAsync, measureSync, safeValidateConfig, validateConfig };
1526
+ export { AutoShutdownConfigSchema, BaseHttpTransport, CallerConfigSchema, CloudWatchConfigSchema, CloudWatchTransport, ConsoleConfigSchema, DiscordConfigSchema, DiscordTransport, FileConfigSchema, HttpTransportBaseConfigSchema, LOG_LEVELS, LevelConfigObjectSchema, LevelConfigSchema, LevelRuleSchema, LogFormatSchema, LogLevelSchema, LogStreamNameSchema, LogStreamPatternConfigSchema, LogStreamPatternSchema, LogStreamTemplateConfigSchema, Logger, LoggerConfigSchema, LoggerStore, MessageBuffer, TelegramConfigSchema, TelegramTransport, assertLogLevel, createMasker, createSingletonLogger, extractRequestId, flattenObject, formatCallerInfo, formatLogfmt, formatLogfmtValue, generateRequestId, getCallerInfo, getOrGenerateRequestId, isValidLogLevel, maskSecrets, matchesContext, measureAsync, measureSync, registerShutdown, safeValidateConfig, validateConfig };
1385
1527
  //# sourceMappingURL=index.mjs.map
1386
1528
  //# sourceMappingURL=index.mjs.map