@rawnodes/logger 2.5.0 → 2.7.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
@@ -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,176 @@ 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
+
1150
+ // src/utils/error.ts
1151
+ function isAxiosError(error) {
1152
+ return error !== null && typeof error === "object" && "isAxiosError" in error && error.isAxiosError === true;
1153
+ }
1154
+ function sanitizeResponseData(data, maxStringLength = 1e4) {
1155
+ if (data === null || data === void 0) {
1156
+ return data;
1157
+ }
1158
+ if (typeof data === "string") {
1159
+ return data.length > maxStringLength ? `${data.slice(0, maxStringLength)}...[truncated]` : data;
1160
+ }
1161
+ if (typeof data === "number" || typeof data === "boolean") {
1162
+ return data;
1163
+ }
1164
+ if (Array.isArray(data)) {
1165
+ return data.slice(0, 100).map((item) => sanitizeResponseData(item, maxStringLength));
1166
+ }
1167
+ if (typeof data === "object") {
1168
+ const result = {};
1169
+ const entries = Object.entries(data);
1170
+ for (const [key, value] of entries.slice(0, 50)) {
1171
+ result[key] = sanitizeResponseData(value, maxStringLength);
1172
+ }
1173
+ return result;
1174
+ }
1175
+ return String(data);
1176
+ }
1177
+ function extractAxiosHttpData(error) {
1178
+ const httpData = {};
1179
+ if (error.code) {
1180
+ httpData.code = error.code;
1181
+ }
1182
+ if (error.response) {
1183
+ httpData.status = error.response.status;
1184
+ httpData.statusText = error.response.statusText;
1185
+ if (error.response.data !== void 0) {
1186
+ httpData.responseData = sanitizeResponseData(error.response.data);
1187
+ }
1188
+ }
1189
+ if (error.config) {
1190
+ const { url, baseURL, method } = error.config;
1191
+ if (url) {
1192
+ httpData.url = baseURL && !url.startsWith("http") ? `${baseURL}${url}` : url;
1193
+ }
1194
+ if (method) {
1195
+ httpData.method = method.toUpperCase();
1196
+ }
1197
+ }
1198
+ return httpData;
1199
+ }
1200
+ function extractGenericHttpData(error) {
1201
+ const httpData = {};
1202
+ let hasData = false;
1203
+ if (typeof error.code === "string") {
1204
+ httpData.code = error.code;
1205
+ hasData = true;
1206
+ }
1207
+ const response = error.response;
1208
+ if (response && typeof response === "object") {
1209
+ if (typeof response.status === "number") {
1210
+ httpData.status = response.status;
1211
+ hasData = true;
1212
+ }
1213
+ if (typeof response.statusText === "string") {
1214
+ httpData.statusText = response.statusText;
1215
+ hasData = true;
1216
+ }
1217
+ if (response.data !== void 0) {
1218
+ httpData.responseData = sanitizeResponseData(response.data);
1219
+ hasData = true;
1220
+ }
1221
+ }
1222
+ const config = error.config;
1223
+ if (config && typeof config === "object") {
1224
+ const url = config.url;
1225
+ const baseURL = config.baseURL;
1226
+ if (typeof url === "string") {
1227
+ httpData.url = baseURL && !url.startsWith("http") ? `${baseURL}${url}` : url;
1228
+ hasData = true;
1229
+ }
1230
+ if (typeof config.method === "string") {
1231
+ httpData.method = config.method.toUpperCase();
1232
+ hasData = true;
1233
+ }
1234
+ }
1235
+ return hasData ? httpData : void 0;
1236
+ }
1237
+ function serializeError(error) {
1238
+ if (!(error instanceof Error)) {
1239
+ if (error === null || error === void 0) {
1240
+ return { errorMessage: "Unknown error" };
1241
+ }
1242
+ if (typeof error === "string") {
1243
+ return { errorMessage: error };
1244
+ }
1245
+ const httpData2 = typeof error === "object" ? extractGenericHttpData(error) : void 0;
1246
+ return {
1247
+ errorMessage: String(error),
1248
+ http: httpData2
1249
+ };
1250
+ }
1251
+ const serialized = {
1252
+ errorMessage: error.message,
1253
+ stack: error.stack
1254
+ };
1255
+ if (error.name && error.name !== "Error") {
1256
+ serialized.errorName = error.name;
1257
+ }
1258
+ if (isAxiosError(error)) {
1259
+ serialized.http = extractAxiosHttpData(error);
1260
+ return serialized;
1261
+ }
1262
+ const errWithCode = error;
1263
+ if (typeof errWithCode.code === "string") {
1264
+ serialized.code = errWithCode.code;
1265
+ }
1266
+ const httpData = extractGenericHttpData(error);
1267
+ if (httpData) {
1268
+ serialized.http = httpData;
1269
+ }
1270
+ return serialized;
1271
+ }
1272
+
1090
1273
  // src/logger.ts
1091
1274
  var Logger = class _Logger {
1092
1275
  constructor(state, context) {
@@ -1097,7 +1280,12 @@ var Logger = class _Logger {
1097
1280
  static create(config, store) {
1098
1281
  const validatedConfig = validateConfig(config);
1099
1282
  const state = createState(validatedConfig, store);
1100
- return new _Logger(state, "APP");
1283
+ const logger = new _Logger(state, "APP");
1284
+ if (validatedConfig.autoShutdown) {
1285
+ const shutdownConfig = typeof validatedConfig.autoShutdown === "object" ? validatedConfig.autoShutdown : {};
1286
+ registerShutdown(logger, shutdownConfig);
1287
+ }
1288
+ return logger;
1101
1289
  }
1102
1290
  for(context) {
1103
1291
  return new _Logger(this.state, context);
@@ -1139,6 +1327,15 @@ var Logger = class _Logger {
1139
1327
  getLevelOverrides() {
1140
1328
  return Array.from(this.state.levelOverrides.values());
1141
1329
  }
1330
+ // Shutdown
1331
+ /**
1332
+ * Gracefully shutdown the logger, flushing all pending messages.
1333
+ * Should be called before process exit to ensure no logs are lost.
1334
+ */
1335
+ async shutdown() {
1336
+ const closePromises = this.state.transports.map((transport) => transport.close());
1337
+ await Promise.all(closePromises);
1338
+ }
1142
1339
  // Profiling
1143
1340
  profile(id, meta) {
1144
1341
  const existing = this.profileTimers.get(id);
@@ -1201,11 +1398,19 @@ var Logger = class _Logger {
1201
1398
  logMeta.caller = formatCallerInfo(callerInfo);
1202
1399
  }
1203
1400
  }
1204
- if (error instanceof Error) {
1205
- logMeta.errorMessage = error.message;
1206
- logMeta.stack = error.stack;
1207
- } else if (error !== void 0) {
1208
- logMeta.error = error;
1401
+ if (error !== void 0) {
1402
+ const serialized = serializeError(error);
1403
+ logMeta.errorMessage = serialized.errorMessage;
1404
+ logMeta.stack = serialized.stack;
1405
+ if (serialized.errorName) {
1406
+ logMeta.errorName = serialized.errorName;
1407
+ }
1408
+ if (serialized.code) {
1409
+ logMeta.errorCode = serialized.code;
1410
+ }
1411
+ if (serialized.http) {
1412
+ logMeta.http = serialized.http;
1413
+ }
1209
1414
  }
1210
1415
  const pinoMethod = this.getPinoMethod(level);
1211
1416
  pinoMethod.call(this.state.pino, logMeta, message);
@@ -1405,16 +1610,28 @@ function createMasker(options = {}) {
1405
1610
  }
1406
1611
 
1407
1612
  // src/formatters.ts
1408
- function flattenObject(obj, prefix = "") {
1613
+ var MAX_FLATTEN_DEPTH = 10;
1614
+ var CIRCULAR_REF = "[Circular]";
1615
+ function flattenObject(obj, prefix = "", ancestors = /* @__PURE__ */ new WeakSet(), depth = 0) {
1409
1616
  const result = {};
1617
+ if (depth > MAX_FLATTEN_DEPTH) {
1618
+ result[prefix || "value"] = "[Max depth exceeded]";
1619
+ return result;
1620
+ }
1621
+ ancestors.add(obj);
1410
1622
  for (const [key, value] of Object.entries(obj)) {
1411
1623
  const newKey = prefix ? `${prefix}.${key}` : key;
1412
1624
  if (value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Error)) {
1413
- Object.assign(result, flattenObject(value, newKey));
1625
+ if (ancestors.has(value)) {
1626
+ result[newKey] = CIRCULAR_REF;
1627
+ } else {
1628
+ Object.assign(result, flattenObject(value, newKey, ancestors, depth + 1));
1629
+ }
1414
1630
  } else {
1415
1631
  result[newKey] = value;
1416
1632
  }
1417
1633
  }
1634
+ ancestors.delete(obj);
1418
1635
  return result;
1419
1636
  }
1420
1637
  function formatLogfmtValue(value) {
@@ -1443,6 +1660,7 @@ function formatLogfmt(data) {
1443
1660
  return Object.entries(flattened).filter(([, value]) => value !== void 0 && value !== null).map(([key, value]) => `${key}=${formatLogfmtValue(value)}`).join(" ");
1444
1661
  }
1445
1662
 
1663
+ exports.AutoShutdownConfigSchema = AutoShutdownConfigSchema;
1446
1664
  exports.BaseHttpTransport = BaseHttpTransport;
1447
1665
  exports.CallerConfigSchema = CallerConfigSchema;
1448
1666
  exports.CloudWatchConfigSchema = CloudWatchConfigSchema;
@@ -1484,7 +1702,9 @@ exports.maskSecrets = maskSecrets;
1484
1702
  exports.matchesContext = matchesContext;
1485
1703
  exports.measureAsync = measureAsync;
1486
1704
  exports.measureSync = measureSync;
1705
+ exports.registerShutdown = registerShutdown;
1487
1706
  exports.safeValidateConfig = safeValidateConfig;
1707
+ exports.serializeError = serializeError;
1488
1708
  exports.validateConfig = validateConfig;
1489
1709
  //# sourceMappingURL=index.js.map
1490
1710
  //# sourceMappingURL=index.js.map