@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.mjs CHANGED
@@ -683,6 +683,7 @@ function createFormattedFilterStream(format, level, rules, store, destination) {
683
683
  }
684
684
  function createStreams(config, store) {
685
685
  const streams = [];
686
+ const transports = [];
686
687
  const consoleStream = createFormattedFilterStream(
687
688
  config.console.format,
688
689
  config.console.level,
@@ -730,6 +731,7 @@ function createStreams(config, store) {
730
731
  }
731
732
  for (const discordConfig of toArray(config.discord)) {
732
733
  const transport = new DiscordTransport(discordConfig);
734
+ transports.push(transport);
733
735
  const discordStream = createHttpTransportStream(transport, discordConfig.level, discordConfig.rules, store);
734
736
  streams.push({
735
737
  level: "trace",
@@ -738,6 +740,7 @@ function createStreams(config, store) {
738
740
  }
739
741
  for (const telegramConfig of toArray(config.telegram)) {
740
742
  const transport = new TelegramTransport(telegramConfig);
743
+ transports.push(transport);
741
744
  const telegramStream = createHttpTransportStream(transport, telegramConfig.level, telegramConfig.rules, store);
742
745
  streams.push({
743
746
  level: "trace",
@@ -746,13 +749,17 @@ function createStreams(config, store) {
746
749
  }
747
750
  for (const cloudwatchConfig of toArray(config.cloudwatch)) {
748
751
  const transport = new CloudWatchTransport(cloudwatchConfig, config.hostname);
752
+ transports.push(transport);
749
753
  const cwStream = createHttpTransportStream(transport, cloudwatchConfig.level, cloudwatchConfig.rules, store);
750
754
  streams.push({
751
755
  level: "trace",
752
756
  stream: cwStream
753
757
  });
754
758
  }
755
- return pino.multistream(streams);
759
+ return {
760
+ destination: pino.multistream(streams),
761
+ transports
762
+ };
756
763
  }
757
764
  function toArray(value) {
758
765
  if (!value) return [];
@@ -837,14 +844,14 @@ function createState(config, store) {
837
844
  levelOverrides.set(key, rule);
838
845
  }
839
846
  const { contextIndex, complexRules } = buildIndexes(levelOverrides);
840
- const streams = createStreams(config, loggerStore);
847
+ const { destination, transports } = createStreams(config, loggerStore);
841
848
  const options = {
842
849
  level: "trace",
843
850
  // Accept all, we filter in shouldLog()
844
851
  customLevels: CUSTOM_LEVELS,
845
852
  base: { hostname: config.hostname ?? hostname() }
846
853
  };
847
- const pinoLogger = pino(options, streams);
854
+ const pinoLogger = pino(options, destination);
848
855
  let callerConfig;
849
856
  if (config.caller === true) {
850
857
  callerConfig = {};
@@ -858,7 +865,8 @@ function createState(config, store) {
858
865
  levelOverrides,
859
866
  contextIndex,
860
867
  complexRules,
861
- callerConfig
868
+ callerConfig,
869
+ transports
862
870
  };
863
871
  }
864
872
  function rebuildIndexes(state) {
@@ -1004,6 +1012,10 @@ var CallerConfigSchema = z.object({
1004
1012
  depth: z.number().int().nonnegative().optional(),
1005
1013
  fullPath: z.boolean().optional()
1006
1014
  });
1015
+ var AutoShutdownConfigSchema = z.object({
1016
+ timeout: z.number().int().positive().optional(),
1017
+ signals: z.array(z.string()).optional()
1018
+ });
1007
1019
  var LoggerConfigSchema = z.object({
1008
1020
  level: LevelConfigSchema,
1009
1021
  console: ConsoleConfigSchema,
@@ -1012,7 +1024,8 @@ var LoggerConfigSchema = z.object({
1012
1024
  telegram: z.union([TelegramConfigSchema, z.array(TelegramConfigSchema)]).optional(),
1013
1025
  cloudwatch: z.union([CloudWatchConfigSchema, z.array(CloudWatchConfigSchema)]).optional(),
1014
1026
  caller: z.union([z.boolean(), CallerConfigSchema]).optional(),
1015
- hostname: z.string().optional()
1027
+ hostname: z.string().optional(),
1028
+ autoShutdown: z.union([z.boolean(), AutoShutdownConfigSchema]).optional()
1016
1029
  });
1017
1030
  function validateConfig(config) {
1018
1031
  return LoggerConfigSchema.parse(config);
@@ -1081,6 +1094,176 @@ function formatCallerInfo(info) {
1081
1094
  return location;
1082
1095
  }
1083
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
+
1144
+ // src/utils/error.ts
1145
+ function isAxiosError(error) {
1146
+ return error !== null && typeof error === "object" && "isAxiosError" in error && error.isAxiosError === true;
1147
+ }
1148
+ function sanitizeResponseData(data, maxStringLength = 1e4) {
1149
+ if (data === null || data === void 0) {
1150
+ return data;
1151
+ }
1152
+ if (typeof data === "string") {
1153
+ return data.length > maxStringLength ? `${data.slice(0, maxStringLength)}...[truncated]` : data;
1154
+ }
1155
+ if (typeof data === "number" || typeof data === "boolean") {
1156
+ return data;
1157
+ }
1158
+ if (Array.isArray(data)) {
1159
+ return data.slice(0, 100).map((item) => sanitizeResponseData(item, maxStringLength));
1160
+ }
1161
+ if (typeof data === "object") {
1162
+ const result = {};
1163
+ const entries = Object.entries(data);
1164
+ for (const [key, value] of entries.slice(0, 50)) {
1165
+ result[key] = sanitizeResponseData(value, maxStringLength);
1166
+ }
1167
+ return result;
1168
+ }
1169
+ return String(data);
1170
+ }
1171
+ function extractAxiosHttpData(error) {
1172
+ const httpData = {};
1173
+ if (error.code) {
1174
+ httpData.code = error.code;
1175
+ }
1176
+ if (error.response) {
1177
+ httpData.status = error.response.status;
1178
+ httpData.statusText = error.response.statusText;
1179
+ if (error.response.data !== void 0) {
1180
+ httpData.responseData = sanitizeResponseData(error.response.data);
1181
+ }
1182
+ }
1183
+ if (error.config) {
1184
+ const { url, baseURL, method } = error.config;
1185
+ if (url) {
1186
+ httpData.url = baseURL && !url.startsWith("http") ? `${baseURL}${url}` : url;
1187
+ }
1188
+ if (method) {
1189
+ httpData.method = method.toUpperCase();
1190
+ }
1191
+ }
1192
+ return httpData;
1193
+ }
1194
+ function extractGenericHttpData(error) {
1195
+ const httpData = {};
1196
+ let hasData = false;
1197
+ if (typeof error.code === "string") {
1198
+ httpData.code = error.code;
1199
+ hasData = true;
1200
+ }
1201
+ const response = error.response;
1202
+ if (response && typeof response === "object") {
1203
+ if (typeof response.status === "number") {
1204
+ httpData.status = response.status;
1205
+ hasData = true;
1206
+ }
1207
+ if (typeof response.statusText === "string") {
1208
+ httpData.statusText = response.statusText;
1209
+ hasData = true;
1210
+ }
1211
+ if (response.data !== void 0) {
1212
+ httpData.responseData = sanitizeResponseData(response.data);
1213
+ hasData = true;
1214
+ }
1215
+ }
1216
+ const config = error.config;
1217
+ if (config && typeof config === "object") {
1218
+ const url = config.url;
1219
+ const baseURL = config.baseURL;
1220
+ if (typeof url === "string") {
1221
+ httpData.url = baseURL && !url.startsWith("http") ? `${baseURL}${url}` : url;
1222
+ hasData = true;
1223
+ }
1224
+ if (typeof config.method === "string") {
1225
+ httpData.method = config.method.toUpperCase();
1226
+ hasData = true;
1227
+ }
1228
+ }
1229
+ return hasData ? httpData : void 0;
1230
+ }
1231
+ function serializeError(error) {
1232
+ if (!(error instanceof Error)) {
1233
+ if (error === null || error === void 0) {
1234
+ return { errorMessage: "Unknown error" };
1235
+ }
1236
+ if (typeof error === "string") {
1237
+ return { errorMessage: error };
1238
+ }
1239
+ const httpData2 = typeof error === "object" ? extractGenericHttpData(error) : void 0;
1240
+ return {
1241
+ errorMessage: String(error),
1242
+ http: httpData2
1243
+ };
1244
+ }
1245
+ const serialized = {
1246
+ errorMessage: error.message,
1247
+ stack: error.stack
1248
+ };
1249
+ if (error.name && error.name !== "Error") {
1250
+ serialized.errorName = error.name;
1251
+ }
1252
+ if (isAxiosError(error)) {
1253
+ serialized.http = extractAxiosHttpData(error);
1254
+ return serialized;
1255
+ }
1256
+ const errWithCode = error;
1257
+ if (typeof errWithCode.code === "string") {
1258
+ serialized.code = errWithCode.code;
1259
+ }
1260
+ const httpData = extractGenericHttpData(error);
1261
+ if (httpData) {
1262
+ serialized.http = httpData;
1263
+ }
1264
+ return serialized;
1265
+ }
1266
+
1084
1267
  // src/logger.ts
1085
1268
  var Logger = class _Logger {
1086
1269
  constructor(state, context) {
@@ -1091,7 +1274,12 @@ var Logger = class _Logger {
1091
1274
  static create(config, store) {
1092
1275
  const validatedConfig = validateConfig(config);
1093
1276
  const state = createState(validatedConfig, store);
1094
- return new _Logger(state, "APP");
1277
+ const logger = new _Logger(state, "APP");
1278
+ if (validatedConfig.autoShutdown) {
1279
+ const shutdownConfig = typeof validatedConfig.autoShutdown === "object" ? validatedConfig.autoShutdown : {};
1280
+ registerShutdown(logger, shutdownConfig);
1281
+ }
1282
+ return logger;
1095
1283
  }
1096
1284
  for(context) {
1097
1285
  return new _Logger(this.state, context);
@@ -1133,6 +1321,15 @@ var Logger = class _Logger {
1133
1321
  getLevelOverrides() {
1134
1322
  return Array.from(this.state.levelOverrides.values());
1135
1323
  }
1324
+ // Shutdown
1325
+ /**
1326
+ * Gracefully shutdown the logger, flushing all pending messages.
1327
+ * Should be called before process exit to ensure no logs are lost.
1328
+ */
1329
+ async shutdown() {
1330
+ const closePromises = this.state.transports.map((transport) => transport.close());
1331
+ await Promise.all(closePromises);
1332
+ }
1136
1333
  // Profiling
1137
1334
  profile(id, meta) {
1138
1335
  const existing = this.profileTimers.get(id);
@@ -1195,11 +1392,19 @@ var Logger = class _Logger {
1195
1392
  logMeta.caller = formatCallerInfo(callerInfo);
1196
1393
  }
1197
1394
  }
1198
- if (error instanceof Error) {
1199
- logMeta.errorMessage = error.message;
1200
- logMeta.stack = error.stack;
1201
- } else if (error !== void 0) {
1202
- logMeta.error = error;
1395
+ if (error !== void 0) {
1396
+ const serialized = serializeError(error);
1397
+ logMeta.errorMessage = serialized.errorMessage;
1398
+ logMeta.stack = serialized.stack;
1399
+ if (serialized.errorName) {
1400
+ logMeta.errorName = serialized.errorName;
1401
+ }
1402
+ if (serialized.code) {
1403
+ logMeta.errorCode = serialized.code;
1404
+ }
1405
+ if (serialized.http) {
1406
+ logMeta.http = serialized.http;
1407
+ }
1203
1408
  }
1204
1409
  const pinoMethod = this.getPinoMethod(level);
1205
1410
  pinoMethod.call(this.state.pino, logMeta, message);
@@ -1399,16 +1604,28 @@ function createMasker(options = {}) {
1399
1604
  }
1400
1605
 
1401
1606
  // src/formatters.ts
1402
- function flattenObject(obj, prefix = "") {
1607
+ var MAX_FLATTEN_DEPTH = 10;
1608
+ var CIRCULAR_REF = "[Circular]";
1609
+ function flattenObject(obj, prefix = "", ancestors = /* @__PURE__ */ new WeakSet(), depth = 0) {
1403
1610
  const result = {};
1611
+ if (depth > MAX_FLATTEN_DEPTH) {
1612
+ result[prefix || "value"] = "[Max depth exceeded]";
1613
+ return result;
1614
+ }
1615
+ ancestors.add(obj);
1404
1616
  for (const [key, value] of Object.entries(obj)) {
1405
1617
  const newKey = prefix ? `${prefix}.${key}` : key;
1406
1618
  if (value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Error)) {
1407
- Object.assign(result, flattenObject(value, newKey));
1619
+ if (ancestors.has(value)) {
1620
+ result[newKey] = CIRCULAR_REF;
1621
+ } else {
1622
+ Object.assign(result, flattenObject(value, newKey, ancestors, depth + 1));
1623
+ }
1408
1624
  } else {
1409
1625
  result[newKey] = value;
1410
1626
  }
1411
1627
  }
1628
+ ancestors.delete(obj);
1412
1629
  return result;
1413
1630
  }
1414
1631
  function formatLogfmtValue(value) {
@@ -1437,6 +1654,6 @@ function formatLogfmt(data) {
1437
1654
  return Object.entries(flattened).filter(([, value]) => value !== void 0 && value !== null).map(([key, value]) => `${key}=${formatLogfmtValue(value)}`).join(" ");
1438
1655
  }
1439
1656
 
1440
- export { 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, safeValidateConfig, validateConfig };
1657
+ 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, serializeError, validateConfig };
1441
1658
  //# sourceMappingURL=index.mjs.map
1442
1659
  //# sourceMappingURL=index.mjs.map