@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.js CHANGED
@@ -8,9 +8,9 @@ var promises = require('fs/promises');
8
8
  var rotatingFileStream = require('rotating-file-stream');
9
9
  var events = require('events');
10
10
  var clientCloudwatchLogs = require('@aws-sdk/client-cloudwatch-logs');
11
+ var crypto = require('crypto');
11
12
  var zod = require('zod');
12
13
  var path = require('path');
13
- var crypto = require('crypto');
14
14
 
15
15
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
16
16
 
@@ -422,13 +422,50 @@ var TelegramTransport = class extends BaseHttpTransport {
422
422
  return text.replace(/[&<>"']/g, (c) => entities[c] || c);
423
423
  }
424
424
  };
425
+ var instanceUuid = crypto.randomUUID().slice(0, 8);
426
+ function formatDate() {
427
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
428
+ }
429
+ function formatDateTime() {
430
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace(/[T:]/g, "-");
431
+ }
432
+ function resolveLogStreamName(config, configHostname) {
433
+ const hostname = configHostname || process.env.HOSTNAME || os.hostname();
434
+ if (!config) {
435
+ return hostname;
436
+ }
437
+ if (typeof config === "string") {
438
+ return config;
439
+ }
440
+ if ("pattern" in config) {
441
+ switch (config.pattern) {
442
+ case "hostname":
443
+ return hostname;
444
+ case "hostname-date":
445
+ return `${hostname}/${formatDate()}`;
446
+ case "hostname-uuid":
447
+ return `${hostname}-${instanceUuid}`;
448
+ case "date":
449
+ return formatDate();
450
+ case "uuid":
451
+ return crypto.randomUUID();
452
+ default:
453
+ return hostname;
454
+ }
455
+ }
456
+ if ("template" in config) {
457
+ return config.template.replace(/\{hostname\}/g, hostname).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");
458
+ }
459
+ return hostname;
460
+ }
425
461
  var CloudWatchTransport = class extends BaseHttpTransport {
426
462
  config;
427
463
  client;
428
464
  sequenceToken;
429
465
  initialized = false;
430
466
  initPromise = null;
431
- constructor(config) {
467
+ resolvedLogStreamName;
468
+ constructor(config, configHostname) {
432
469
  super({
433
470
  batchSize: config.batchSize ?? 100,
434
471
  flushInterval: config.flushInterval ?? 1e3,
@@ -436,6 +473,7 @@ var CloudWatchTransport = class extends BaseHttpTransport {
436
473
  retryDelay: config.retryDelay
437
474
  });
438
475
  this.config = config;
476
+ this.resolvedLogStreamName = resolveLogStreamName(config.logStreamName, configHostname);
439
477
  this.client = new clientCloudwatchLogs.CloudWatchLogsClient({
440
478
  region: config.region,
441
479
  credentials: {
@@ -458,7 +496,7 @@ var CloudWatchTransport = class extends BaseHttpTransport {
458
496
  logEvents.sort((a, b) => (a.timestamp ?? 0) - (b.timestamp ?? 0));
459
497
  const command = new clientCloudwatchLogs.PutLogEventsCommand({
460
498
  logGroupName: this.config.logGroupName,
461
- logStreamName: this.config.logStreamName,
499
+ logStreamName: this.resolvedLogStreamName,
462
500
  logEvents,
463
501
  sequenceToken: this.sequenceToken
464
502
  });
@@ -470,7 +508,7 @@ var CloudWatchTransport = class extends BaseHttpTransport {
470
508
  await this.fetchSequenceToken();
471
509
  const retryCommand = new clientCloudwatchLogs.PutLogEventsCommand({
472
510
  logGroupName: this.config.logGroupName,
473
- logStreamName: this.config.logStreamName,
511
+ logStreamName: this.resolvedLogStreamName,
474
512
  logEvents,
475
513
  sequenceToken: this.sequenceToken
476
514
  });
@@ -516,7 +554,7 @@ var CloudWatchTransport = class extends BaseHttpTransport {
516
554
  await this.client.send(
517
555
  new clientCloudwatchLogs.CreateLogStreamCommand({
518
556
  logGroupName: this.config.logGroupName,
519
- logStreamName: this.config.logStreamName
557
+ logStreamName: this.resolvedLogStreamName
520
558
  })
521
559
  );
522
560
  } catch (error) {
@@ -529,11 +567,11 @@ var CloudWatchTransport = class extends BaseHttpTransport {
529
567
  const response = await this.client.send(
530
568
  new clientCloudwatchLogs.DescribeLogStreamsCommand({
531
569
  logGroupName: this.config.logGroupName,
532
- logStreamNamePrefix: this.config.logStreamName,
570
+ logStreamNamePrefix: this.resolvedLogStreamName,
533
571
  limit: 1
534
572
  })
535
573
  );
536
- const stream = response.logStreams?.find((s) => s.logStreamName === this.config.logStreamName);
574
+ const stream = response.logStreams?.find((s) => s.logStreamName === this.resolvedLogStreamName);
537
575
  this.sequenceToken = stream?.uploadSequenceToken;
538
576
  }
539
577
  isResourceAlreadyExistsError(error) {
@@ -651,6 +689,7 @@ function createFormattedFilterStream(format, level, rules, store, destination) {
651
689
  }
652
690
  function createStreams(config, store) {
653
691
  const streams = [];
692
+ const transports = [];
654
693
  const consoleStream = createFormattedFilterStream(
655
694
  config.console.format,
656
695
  config.console.level,
@@ -698,6 +737,7 @@ function createStreams(config, store) {
698
737
  }
699
738
  for (const discordConfig of toArray(config.discord)) {
700
739
  const transport = new DiscordTransport(discordConfig);
740
+ transports.push(transport);
701
741
  const discordStream = createHttpTransportStream(transport, discordConfig.level, discordConfig.rules, store);
702
742
  streams.push({
703
743
  level: "trace",
@@ -706,6 +746,7 @@ function createStreams(config, store) {
706
746
  }
707
747
  for (const telegramConfig of toArray(config.telegram)) {
708
748
  const transport = new TelegramTransport(telegramConfig);
749
+ transports.push(transport);
709
750
  const telegramStream = createHttpTransportStream(transport, telegramConfig.level, telegramConfig.rules, store);
710
751
  streams.push({
711
752
  level: "trace",
@@ -713,14 +754,18 @@ function createStreams(config, store) {
713
754
  });
714
755
  }
715
756
  for (const cloudwatchConfig of toArray(config.cloudwatch)) {
716
- const transport = new CloudWatchTransport(cloudwatchConfig);
757
+ const transport = new CloudWatchTransport(cloudwatchConfig, config.hostname);
758
+ transports.push(transport);
717
759
  const cwStream = createHttpTransportStream(transport, cloudwatchConfig.level, cloudwatchConfig.rules, store);
718
760
  streams.push({
719
761
  level: "trace",
720
762
  stream: cwStream
721
763
  });
722
764
  }
723
- return pino__default.default.multistream(streams);
765
+ return {
766
+ destination: pino__default.default.multistream(streams),
767
+ transports
768
+ };
724
769
  }
725
770
  function toArray(value) {
726
771
  if (!value) return [];
@@ -805,14 +850,14 @@ function createState(config, store) {
805
850
  levelOverrides.set(key, rule);
806
851
  }
807
852
  const { contextIndex, complexRules } = buildIndexes(levelOverrides);
808
- const streams = createStreams(config, loggerStore);
853
+ const { destination, transports } = createStreams(config, loggerStore);
809
854
  const options = {
810
855
  level: "trace",
811
856
  // Accept all, we filter in shouldLog()
812
857
  customLevels: CUSTOM_LEVELS,
813
858
  base: { hostname: config.hostname ?? os.hostname() }
814
859
  };
815
- const pinoLogger = pino__default.default(options, streams);
860
+ const pinoLogger = pino__default.default(options, destination);
816
861
  let callerConfig;
817
862
  if (config.caller === true) {
818
863
  callerConfig = {};
@@ -826,7 +871,8 @@ function createState(config, store) {
826
871
  levelOverrides,
827
872
  contextIndex,
828
873
  complexRules,
829
- callerConfig
874
+ callerConfig,
875
+ transports
830
876
  };
831
877
  }
832
878
  function rebuildIndexes(state) {
@@ -927,6 +973,24 @@ var TelegramConfigSchema = zod.z.object({
927
973
  threadId: zod.z.number().int().optional(),
928
974
  replyToMessageId: zod.z.number().int().optional()
929
975
  });
976
+ var LogStreamPatternSchema = zod.z.enum([
977
+ "hostname",
978
+ "hostname-date",
979
+ "hostname-uuid",
980
+ "date",
981
+ "uuid"
982
+ ]);
983
+ var LogStreamPatternConfigSchema = zod.z.object({
984
+ pattern: LogStreamPatternSchema
985
+ });
986
+ var LogStreamTemplateConfigSchema = zod.z.object({
987
+ template: zod.z.string().min(1, "template is required")
988
+ });
989
+ var LogStreamNameSchema = zod.z.union([
990
+ zod.z.string().min(1, "logStreamName string must not be empty"),
991
+ LogStreamPatternConfigSchema,
992
+ LogStreamTemplateConfigSchema
993
+ ]);
930
994
  var CloudWatchConfigSchema = zod.z.object({
931
995
  level: LogLevelSchema.optional(),
932
996
  rules: zod.z.array(LevelRuleSchema).optional(),
@@ -935,7 +999,7 @@ var CloudWatchConfigSchema = zod.z.object({
935
999
  maxRetries: zod.z.number().int().nonnegative().optional(),
936
1000
  retryDelay: zod.z.number().int().positive().optional(),
937
1001
  logGroupName: zod.z.string().min(1, "logGroupName is required"),
938
- logStreamName: zod.z.string().min(1, "logStreamName is required"),
1002
+ logStreamName: LogStreamNameSchema.optional(),
939
1003
  region: zod.z.string().min(1, "region is required"),
940
1004
  accessKeyId: zod.z.string().min(1, "accessKeyId is required"),
941
1005
  secretAccessKey: zod.z.string().min(1, "secretAccessKey is required"),
@@ -954,6 +1018,10 @@ var CallerConfigSchema = zod.z.object({
954
1018
  depth: zod.z.number().int().nonnegative().optional(),
955
1019
  fullPath: zod.z.boolean().optional()
956
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
+ });
957
1025
  var LoggerConfigSchema = zod.z.object({
958
1026
  level: LevelConfigSchema,
959
1027
  console: ConsoleConfigSchema,
@@ -962,7 +1030,8 @@ var LoggerConfigSchema = zod.z.object({
962
1030
  telegram: zod.z.union([TelegramConfigSchema, zod.z.array(TelegramConfigSchema)]).optional(),
963
1031
  cloudwatch: zod.z.union([CloudWatchConfigSchema, zod.z.array(CloudWatchConfigSchema)]).optional(),
964
1032
  caller: zod.z.union([zod.z.boolean(), CallerConfigSchema]).optional(),
965
- hostname: zod.z.string().optional()
1033
+ hostname: zod.z.string().optional(),
1034
+ autoShutdown: zod.z.union([zod.z.boolean(), AutoShutdownConfigSchema]).optional()
966
1035
  });
967
1036
  function validateConfig(config) {
968
1037
  return LoggerConfigSchema.parse(config);
@@ -1031,6 +1100,53 @@ function formatCallerInfo(info) {
1031
1100
  return location;
1032
1101
  }
1033
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
+
1034
1150
  // src/logger.ts
1035
1151
  var Logger = class _Logger {
1036
1152
  constructor(state, context) {
@@ -1041,7 +1157,12 @@ var Logger = class _Logger {
1041
1157
  static create(config, store) {
1042
1158
  const validatedConfig = validateConfig(config);
1043
1159
  const state = createState(validatedConfig, store);
1044
- 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;
1045
1166
  }
1046
1167
  for(context) {
1047
1168
  return new _Logger(this.state, context);
@@ -1083,6 +1204,15 @@ var Logger = class _Logger {
1083
1204
  getLevelOverrides() {
1084
1205
  return Array.from(this.state.levelOverrides.values());
1085
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
+ }
1086
1216
  // Profiling
1087
1217
  profile(id, meta) {
1088
1218
  const existing = this.profileTimers.get(id);
@@ -1349,16 +1479,28 @@ function createMasker(options = {}) {
1349
1479
  }
1350
1480
 
1351
1481
  // src/formatters.ts
1352
- 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) {
1353
1485
  const result = {};
1486
+ if (depth > MAX_FLATTEN_DEPTH) {
1487
+ result[prefix || "value"] = "[Max depth exceeded]";
1488
+ return result;
1489
+ }
1490
+ ancestors.add(obj);
1354
1491
  for (const [key, value] of Object.entries(obj)) {
1355
1492
  const newKey = prefix ? `${prefix}.${key}` : key;
1356
1493
  if (value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Error)) {
1357
- 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
+ }
1358
1499
  } else {
1359
1500
  result[newKey] = value;
1360
1501
  }
1361
1502
  }
1503
+ ancestors.delete(obj);
1362
1504
  return result;
1363
1505
  }
1364
1506
  function formatLogfmtValue(value) {
@@ -1387,6 +1529,7 @@ function formatLogfmt(data) {
1387
1529
  return Object.entries(flattened).filter(([, value]) => value !== void 0 && value !== null).map(([key, value]) => `${key}=${formatLogfmtValue(value)}`).join(" ");
1388
1530
  }
1389
1531
 
1532
+ exports.AutoShutdownConfigSchema = AutoShutdownConfigSchema;
1390
1533
  exports.BaseHttpTransport = BaseHttpTransport;
1391
1534
  exports.CallerConfigSchema = CallerConfigSchema;
1392
1535
  exports.CloudWatchConfigSchema = CloudWatchConfigSchema;
@@ -1402,6 +1545,10 @@ exports.LevelConfigSchema = LevelConfigSchema;
1402
1545
  exports.LevelRuleSchema = LevelRuleSchema;
1403
1546
  exports.LogFormatSchema = LogFormatSchema;
1404
1547
  exports.LogLevelSchema = LogLevelSchema;
1548
+ exports.LogStreamNameSchema = LogStreamNameSchema;
1549
+ exports.LogStreamPatternConfigSchema = LogStreamPatternConfigSchema;
1550
+ exports.LogStreamPatternSchema = LogStreamPatternSchema;
1551
+ exports.LogStreamTemplateConfigSchema = LogStreamTemplateConfigSchema;
1405
1552
  exports.Logger = Logger;
1406
1553
  exports.LoggerConfigSchema = LoggerConfigSchema;
1407
1554
  exports.LoggerStore = LoggerStore;
@@ -1424,6 +1571,7 @@ exports.maskSecrets = maskSecrets;
1424
1571
  exports.matchesContext = matchesContext;
1425
1572
  exports.measureAsync = measureAsync;
1426
1573
  exports.measureSync = measureSync;
1574
+ exports.registerShutdown = registerShutdown;
1427
1575
  exports.safeValidateConfig = safeValidateConfig;
1428
1576
  exports.validateConfig = validateConfig;
1429
1577
  //# sourceMappingURL=index.js.map