@x12i/logxer 4.0.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.
Files changed (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1255 -0
  3. package/dist/app-info.d.ts +25 -0
  4. package/dist/app-info.d.ts.map +1 -0
  5. package/dist/app-info.js +179 -0
  6. package/dist/app-info.js.map +1 -0
  7. package/dist/formatters/table-formatter.d.ts +23 -0
  8. package/dist/formatters/table-formatter.d.ts.map +1 -0
  9. package/dist/formatters/table-formatter.js +218 -0
  10. package/dist/formatters/table-formatter.js.map +1 -0
  11. package/dist/formatters/yaml-formatter.d.ts +14 -0
  12. package/dist/formatters/yaml-formatter.d.ts.map +1 -0
  13. package/dist/formatters/yaml-formatter.js +164 -0
  14. package/dist/formatters/yaml-formatter.js.map +1 -0
  15. package/dist/index.d.ts +44 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +219 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/logxer.d.ts +145 -0
  20. package/dist/logxer.d.ts.map +1 -0
  21. package/dist/logxer.js +811 -0
  22. package/dist/logxer.js.map +1 -0
  23. package/dist/outputs/shadow-sink.d.ts +83 -0
  24. package/dist/outputs/shadow-sink.d.ts.map +1 -0
  25. package/dist/outputs/shadow-sink.js +380 -0
  26. package/dist/outputs/shadow-sink.js.map +1 -0
  27. package/dist/outputs/unified-logger-output.d.ts +30 -0
  28. package/dist/outputs/unified-logger-output.d.ts.map +1 -0
  29. package/dist/outputs/unified-logger-output.js +125 -0
  30. package/dist/outputs/unified-logger-output.js.map +1 -0
  31. package/dist/sanitizer.d.ts +69 -0
  32. package/dist/sanitizer.d.ts.map +1 -0
  33. package/dist/sanitizer.js +507 -0
  34. package/dist/sanitizer.js.map +1 -0
  35. package/dist/trails/headers.d.ts +21 -0
  36. package/dist/trails/headers.d.ts.map +1 -0
  37. package/dist/trails/headers.js +121 -0
  38. package/dist/trails/headers.js.map +1 -0
  39. package/dist/trails/index.d.ts +59 -0
  40. package/dist/trails/index.d.ts.map +1 -0
  41. package/dist/trails/index.js +197 -0
  42. package/dist/trails/index.js.map +1 -0
  43. package/dist/types.d.ts +411 -0
  44. package/dist/types.d.ts.map +1 -0
  45. package/dist/types.js +8 -0
  46. package/dist/types.js.map +1 -0
  47. package/dist/utils/debug-config.d.ts +19 -0
  48. package/dist/utils/debug-config.d.ts.map +1 -0
  49. package/dist/utils/debug-config.js +172 -0
  50. package/dist/utils/debug-config.js.map +1 -0
  51. package/dist/utils/package-logs-level.d.ts +40 -0
  52. package/dist/utils/package-logs-level.d.ts.map +1 -0
  53. package/dist/utils/package-logs-level.js +79 -0
  54. package/dist/utils/package-logs-level.js.map +1 -0
  55. package/docs/package-usage.md +155 -0
  56. package/docs/package.md +48 -0
  57. package/docs/upgrade-for-package-authors.md +85 -0
  58. package/package.json +98 -0
package/dist/logxer.js ADDED
@@ -0,0 +1,811 @@
1
+ "use strict";
2
+ /**
3
+ * logxer - Core Logxer Implementation
4
+ *
5
+ * This file contains the main Logxer class that handles all logging functionality.
6
+ */
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.Logxer = void 0;
42
+ const fs = __importStar(require("fs"));
43
+ const path = __importStar(require("path"));
44
+ const sanitizer_1 = require("./sanitizer");
45
+ const yaml_formatter_1 = require("./formatters/yaml-formatter");
46
+ const table_formatter_1 = require("./formatters/table-formatter");
47
+ const shadow_sink_1 = require("./outputs/shadow-sink");
48
+ const app_info_1 = require("./app-info");
49
+ const debug_config_1 = require("./utils/debug-config");
50
+ const package_logs_level_1 = require("./utils/package-logs-level");
51
+ /**
52
+ * Main Logxer class for handling all logging operations
53
+ */
54
+ class Logxer {
55
+ constructor(packageConfig, userConfig, sinks) {
56
+ this.levelPriority = {
57
+ verbose: 0,
58
+ debug: 1,
59
+ info: 2,
60
+ warn: 3,
61
+ error: 4
62
+ };
63
+ this.activeBetweenRanges = new Set(); // Tracks indices of active between rules
64
+ this.packageConfig = packageConfig;
65
+ this.sinks = sinks || {};
66
+ // Automatically detect consuming application's package name and version
67
+ this.appInfo = (0, app_info_1.detectAppInfo)();
68
+ // Load debug scoping configuration from logxer-debug.json
69
+ this.debugScopingConfig = (0, debug_config_1.loadDebugConfig)();
70
+ const envPrefix = packageConfig.envPrefix || packageConfig.packageName;
71
+ const resolvedLevel = (0, package_logs_level_1.resolvePackageLogsLevel)({
72
+ envPrefix,
73
+ ...(userConfig?.logLevel !== undefined ? { userLogLevel: userConfig.logLevel } : {}),
74
+ env: process.env
75
+ });
76
+ // Parse console package filtering from env vars
77
+ const parsePackageList = (envVar) => {
78
+ if (!envVar)
79
+ return undefined;
80
+ const packages = envVar.split(',').map(p => p.trim()).filter(p => p.length > 0);
81
+ return packages.length > 0 ? packages : undefined;
82
+ };
83
+ // Build configuration with precedence: user config > env vars > defaults
84
+ this.config = {
85
+ logToConsole: userConfig?.logToConsole ??
86
+ (process.env[`${envPrefix}_LOG_TO_CONSOLE`] !== 'false'),
87
+ logToFile: userConfig?.logToFile ??
88
+ (process.env[`${envPrefix}_LOG_TO_FILE`] === 'true'),
89
+ logFilePath: userConfig?.logFilePath ??
90
+ process.env[`${envPrefix}_LOG_FILE`] ??
91
+ '',
92
+ logLevel: resolvedLevel.logLevel,
93
+ packageLogsDisabled: resolvedLevel.packageLogsDisabled,
94
+ logFormat: userConfig?.logFormat ??
95
+ process.env[`${envPrefix}_LOG_FORMAT`] ??
96
+ 'table',
97
+ showFullTimestamp: userConfig?.showFullTimestamp ??
98
+ (process.env[`${envPrefix}_SHOW_FULL_TIMESTAMP`] === 'true'),
99
+ consolePackagesShow: userConfig?.consolePackagesShow ?? parsePackageList(process.env[`${envPrefix}_CONSOLE_PACKAGES_SHOW`]),
100
+ consolePackagesHide: userConfig?.consolePackagesHide ?? parsePackageList(process.env[`${envPrefix}_CONSOLE_PACKAGES_HIDE`]),
101
+ enableUnifiedLogger: userConfig?.enableUnifiedLogger ??
102
+ (process.env[`${envPrefix}_LOG_TO_UNIFIED`] === 'true'),
103
+ unifiedLogger: userConfig?.unifiedLogger ?? {},
104
+ defaultSource: userConfig?.defaultSource ?? 'application',
105
+ sanitization: {
106
+ // Default: completely disabled
107
+ enabled: userConfig?.sanitization?.enabled ??
108
+ (process.env[`${envPrefix}_SANITIZE_ENABLED`] === 'true'),
109
+ // Only set other options if sanitization is enabled
110
+ ...(userConfig?.sanitization?.enabled || process.env[`${envPrefix}_SANITIZE_ENABLED`] === 'true' ? {
111
+ maskWith: userConfig?.sanitization?.maskWith ??
112
+ process.env[`${envPrefix}_SANITIZE_MASK`] ??
113
+ '[REDACTED]',
114
+ partialMaskRatio: userConfig?.sanitization?.partialMaskRatio ??
115
+ parseFloat(process.env[`${envPrefix}_SANITIZE_PARTIAL_RATIO`] ?? '1.0'),
116
+ maxDepth: userConfig?.sanitization?.maxDepth ??
117
+ parseInt(process.env[`${envPrefix}_SANITIZE_MAX_DEPTH`] ?? '5'),
118
+ keysDenylist: userConfig?.sanitization?.keysDenylist ??
119
+ (process.env[`${envPrefix}_SANITIZE_KEYS_DENYLIST`]?.split(',').map(k => k.trim()) ??
120
+ ['authorization', 'token', 'secret', 'api_key', 'passwd', 'password']),
121
+ keysAllowlist: userConfig?.sanitization?.keysAllowlist ??
122
+ (process.env[`${envPrefix}_SANITIZE_KEYS_ALLOWLIST`]?.split(',').map(k => k.trim()) ?? []),
123
+ fieldsHashInsteadOfMask: userConfig?.sanitization?.fieldsHashInsteadOfMask ??
124
+ (process.env[`${envPrefix}_SANITIZE_FIELDS_HASH`]?.split(',').map(k => k.trim()) ?? []),
125
+ detectEmails: userConfig?.sanitization?.detectEmails ??
126
+ (process.env[`${envPrefix}_SANITIZE_DETECT_EMAILS`] !== 'false'),
127
+ detectIPs: userConfig?.sanitization?.detectIPs ??
128
+ (process.env[`${envPrefix}_SANITIZE_DETECT_IPS`] !== 'false'),
129
+ detectPhoneNumbers: userConfig?.sanitization?.detectPhoneNumbers ??
130
+ (process.env[`${envPrefix}_SANITIZE_DETECT_PHONES`] !== 'false'),
131
+ detectJWTs: userConfig?.sanitization?.detectJWTs ??
132
+ (process.env[`${envPrefix}_SANITIZE_DETECT_JWTS`] !== 'false'),
133
+ detectAPIKeys: userConfig?.sanitization?.detectAPIKeys ??
134
+ (process.env[`${envPrefix}_SANITIZE_DETECT_APIKEYS`] !== 'false'),
135
+ detectAWSCreds: userConfig?.sanitization?.detectAWSCreds ??
136
+ (process.env[`${envPrefix}_SANITIZE_DETECT_AWSCREDS`] !== 'false'),
137
+ detectAzureKeys: userConfig?.sanitization?.detectAzureKeys ??
138
+ (process.env[`${envPrefix}_SANITIZE_DETECT_AZUREKEYS`] !== 'false'),
139
+ detectGCPKeys: userConfig?.sanitization?.detectGCPKeys ??
140
+ (process.env[`${envPrefix}_SANITIZE_DETECT_GCPKEYS`] !== 'false'),
141
+ detectPasswords: userConfig?.sanitization?.detectPasswords ??
142
+ (process.env[`${envPrefix}_SANITIZE_DETECT_PASSWORDS`] !== 'false'),
143
+ detectCreditCards: userConfig?.sanitization?.detectCreditCards ??
144
+ (process.env[`${envPrefix}_SANITIZE_DETECT_CREDITCARDS`] !== 'false')
145
+ } : {})
146
+ },
147
+ packageName: packageConfig.packageName,
148
+ ...(userConfig?.customLogger && { customLogger: userConfig.customLogger }),
149
+ shadow: this.parseShadowConfig(envPrefix, userConfig?.shadow),
150
+ ...(userConfig?.debugScoping || this.debugScopingConfig ? {
151
+ debugScoping: (userConfig?.debugScoping ?? this.debugScopingConfig)
152
+ } : {})
153
+ };
154
+ // Validate configuration
155
+ if (this.config.logToFile && !this.config.logFilePath) {
156
+ throw new Error(`[logxer] ${packageConfig.packageName}: logFilePath is required when logToFile is true. ` +
157
+ `Set it via config or ${envPrefix}_LOG_FILE environment variable.`);
158
+ }
159
+ // Ensure log directory exists if file logging is enabled
160
+ if (this.config.logToFile && this.config.logFilePath) {
161
+ this.ensureLogDirectory(this.config.logFilePath);
162
+ }
163
+ // Initialize sanitizer
164
+ this.sanitizer = new sanitizer_1.LogSanitizer(this.config.sanitization);
165
+ // Initialize shadow sink if enabled
166
+ if (this.config.shadow?.enabled) {
167
+ this.shadowSink = new shadow_sink_1.ShadowSink(this.config.shadow, packageConfig.packageName);
168
+ this.shadow = this.shadowSink;
169
+ }
170
+ else {
171
+ // No-op controller when shadow is disabled
172
+ this.shadow = this.createNoOpController();
173
+ }
174
+ }
175
+ // --------------------------------------------------------------------------
176
+ // PRIVATE METHODS
177
+ // --------------------------------------------------------------------------
178
+ /**
179
+ * Parse shadow configuration from user config and environment variables
180
+ */
181
+ parseShadowConfig(envPrefix, userShadow) {
182
+ if (!userShadow && process.env[`${envPrefix}_SHADOW_ENABLED`] !== 'true') {
183
+ return undefined;
184
+ }
185
+ return {
186
+ enabled: userShadow?.enabled ?? (process.env[`${envPrefix}_SHADOW_ENABLED`] === 'true'),
187
+ format: userShadow?.format ?? process.env[`${envPrefix}_SHADOW_FORMAT`] ?? 'json',
188
+ directory: userShadow?.directory ?? process.env[`${envPrefix}_SHADOW_DIR`] ?? './logs/shadow',
189
+ ttlMs: userShadow?.ttlMs ?? parseInt(process.env[`${envPrefix}_SHADOW_TTL_MS`] ?? '86400000'),
190
+ respectRoutingBlocks: userShadow?.respectRoutingBlocks ?? (process.env[`${envPrefix}_SHADOW_RESPECT_ROUTING`] !== 'false'),
191
+ rollingBuffer: {
192
+ maxEntries: userShadow?.rollingBuffer?.maxEntries ?? parseInt(process.env[`${envPrefix}_SHADOW_BUFFER_ENTRIES`] ?? '0'),
193
+ maxAgeMs: userShadow?.rollingBuffer?.maxAgeMs ?? parseInt(process.env[`${envPrefix}_SHADOW_BUFFER_AGE_MS`] ?? '0')
194
+ }
195
+ };
196
+ }
197
+ /**
198
+ * Create a no-op shadow controller for when shadow is disabled
199
+ */
200
+ createNoOpController() {
201
+ return {
202
+ enable: () => { },
203
+ disable: () => { },
204
+ isEnabled: () => false,
205
+ listActive: () => [],
206
+ export: async () => { throw new Error('Shadow logging is not enabled'); },
207
+ cleanupExpired: async () => 0
208
+ };
209
+ }
210
+ /**
211
+ * Ensure the log directory exists, creating it if necessary
212
+ */
213
+ ensureLogDirectory(filePath) {
214
+ try {
215
+ const dir = path.dirname(filePath);
216
+ if (!fs.existsSync(dir)) {
217
+ fs.mkdirSync(dir, { recursive: true });
218
+ }
219
+ }
220
+ catch (err) {
221
+ console.error(`[logxer] ${this.packageConfig.packageName}: Failed to create log directory:`, err);
222
+ }
223
+ }
224
+ /**
225
+ * Check if a log level should be output based on current configuration
226
+ */
227
+ shouldLog(level) {
228
+ if (this.config.packageLogsDisabled) {
229
+ return false;
230
+ }
231
+ // Check DEBUG environment variable override (does not apply when package is silent via _LOGS_LEVEL contract)
232
+ const debugEnabled = process.env.DEBUG?.includes(this.packageConfig.debugNamespace || this.packageConfig.packageName.toLowerCase());
233
+ if (debugEnabled && (level === 'verbose' || level === 'debug')) {
234
+ return true;
235
+ }
236
+ const currentLevelPriority = this.levelPriority[this.config.logLevel];
237
+ const messageLevelPriority = this.levelPriority[level];
238
+ return messageLevelPriority >= currentLevelPriority;
239
+ }
240
+ /**
241
+ * Check if a specific output should receive the log based on routing metadata
242
+ */
243
+ shouldSend(outputName, meta) {
244
+ // Block/allow by metadata
245
+ if (meta?._routing?.blockOutputs?.includes(outputName)) {
246
+ return false;
247
+ }
248
+ if (meta?._routing?.allowedOutputs && !meta._routing.allowedOutputs.includes(outputName)) {
249
+ return false;
250
+ }
251
+ // Safety: gateway internal logs never escape to unified-logger
252
+ if (meta?.source === 'logxer-internal' && outputName === 'unified-logger') {
253
+ return false;
254
+ }
255
+ return true;
256
+ }
257
+ /**
258
+ * Check if a search string matches any identity in a list
259
+ *
260
+ * @param searchString - The string to search for
261
+ * @param identityList - List of identity patterns to match against
262
+ * @param exactMatch - If true: exact match (case sensitive), if false: partial match (case insensitive)
263
+ * @returns true if any identity in the list matches
264
+ */
265
+ matchesIdentity(searchString, identityList, exactMatch) {
266
+ if (!searchString || identityList.length === 0) {
267
+ return false;
268
+ }
269
+ for (const identity of identityList) {
270
+ if (exactMatch) {
271
+ // Exact match (case sensitive)
272
+ if (searchString === identity) {
273
+ return true;
274
+ }
275
+ }
276
+ else {
277
+ // Partial match (case insensitive)
278
+ if (searchString.toLowerCase().includes(identity.toLowerCase())) {
279
+ return true;
280
+ }
281
+ }
282
+ }
283
+ return false;
284
+ }
285
+ /**
286
+ * Extract searchable text from log entry
287
+ * Combines message, identity, and all meta fields (recursively stringified)
288
+ *
289
+ * @param message - Log message
290
+ * @param identity - Log identity
291
+ * @param meta - Log metadata
292
+ * @returns Combined searchable string
293
+ */
294
+ getSearchableLogText(message, identity, meta) {
295
+ const parts = [];
296
+ if (message) {
297
+ parts.push(message);
298
+ }
299
+ if (identity) {
300
+ parts.push(identity);
301
+ }
302
+ if (meta) {
303
+ // Recursively stringify all meta fields
304
+ try {
305
+ const metaString = JSON.stringify(meta);
306
+ parts.push(metaString);
307
+ }
308
+ catch (e) {
309
+ // If stringification fails, try to stringify individual fields
310
+ for (const [key, value] of Object.entries(meta)) {
311
+ try {
312
+ parts.push(`${key}:${JSON.stringify(value)}`);
313
+ }
314
+ catch (e2) {
315
+ parts.push(`${key}:${String(value)}`);
316
+ }
317
+ }
318
+ }
319
+ }
320
+ return parts.join(' ');
321
+ }
322
+ /**
323
+ * Update the state of between ranges based on current log entry
324
+ *
325
+ * @param message - Log message
326
+ * @param identity - Log identity
327
+ * @param meta - Log metadata
328
+ */
329
+ updateBetweenRangeState(message, identity, meta) {
330
+ const scopingConfig = this.config.debugScoping || this.debugScopingConfig;
331
+ // If no config or scoping is disabled, no between rules to process
332
+ if (!scopingConfig || scopingConfig.scoping.status !== 'enabled') {
333
+ return;
334
+ }
335
+ const betweenRules = scopingConfig.scoping.between;
336
+ if (!betweenRules || betweenRules.length === 0) {
337
+ return;
338
+ }
339
+ // Initialize ranges with empty startIdentities (ranges that start from beginning)
340
+ // This ensures ranges with empty startIdentities are active from the start
341
+ for (let i = 0; i < betweenRules.length; i++) {
342
+ const rule = betweenRules[i];
343
+ if (rule && rule.startIdentities.length === 0) {
344
+ this.activeBetweenRanges.add(i);
345
+ }
346
+ }
347
+ // Process each rule
348
+ for (let i = 0; i < betweenRules.length; i++) {
349
+ const rule = betweenRules[i];
350
+ if (!rule) {
351
+ continue;
352
+ }
353
+ const exactMatch = rule.exactMatch ?? false;
354
+ const searchLog = rule.searchLog ?? false;
355
+ // Determine what to search
356
+ const searchText = searchLog
357
+ ? this.getSearchableLogText(message, identity, meta)
358
+ : (identity || '');
359
+ // Check if search text matches start identities
360
+ const matchesStart = rule.startIdentities.length > 0
361
+ ? this.matchesIdentity(searchText, rule.startIdentities, exactMatch)
362
+ : false;
363
+ // Check if search text matches end identities
364
+ const matchesEnd = rule.endIdentities.length > 0
365
+ ? this.matchesIdentity(searchText, rule.endIdentities, exactMatch)
366
+ : false;
367
+ // Handle matching both start and end (treat as both start and end)
368
+ if (matchesStart && matchesEnd) {
369
+ // Toggle the range state
370
+ if (this.activeBetweenRanges.has(i)) {
371
+ this.activeBetweenRanges.delete(i);
372
+ }
373
+ else {
374
+ this.activeBetweenRanges.add(i);
375
+ }
376
+ }
377
+ else if (matchesStart) {
378
+ // Activate range
379
+ this.activeBetweenRanges.add(i);
380
+ }
381
+ else if (matchesEnd) {
382
+ // Deactivate range (only if endIdentities is not empty)
383
+ if (rule.endIdentities.length > 0) {
384
+ this.activeBetweenRanges.delete(i);
385
+ }
386
+ }
387
+ // Note: Empty endIdentities means range never closes, so we don't remove it
388
+ }
389
+ }
390
+ /**
391
+ * Check if a log should be included based on debug scoping configuration
392
+ * Filters logs at runtime based on identity and application name
393
+ *
394
+ * @param message - Log message
395
+ * @param identity - Log identity (file:function format)
396
+ * @param appName - Application name
397
+ * @param meta - Log metadata
398
+ * @returns true if log should be included, false if it should be filtered out
399
+ */
400
+ shouldIncludeLog(message, identity, appName, meta) {
401
+ // Use user-provided config if available, otherwise use loaded debug config
402
+ const scopingConfig = this.config.debugScoping || this.debugScopingConfig;
403
+ // If no config or scoping is disabled, include all logs
404
+ if (!scopingConfig || scopingConfig.scoping.status !== 'enabled') {
405
+ return true;
406
+ }
407
+ const { filterIdentities, filteredApplications, between } = scopingConfig.scoping;
408
+ // Check existing filterIdentities/filteredApplications (OR logic)
409
+ let matchesExistingFilter = false;
410
+ const hasIdentityFilter = filterIdentities && filterIdentities.length > 0;
411
+ const hasAppFilter = filteredApplications && filteredApplications.length > 0;
412
+ if (hasIdentityFilter && identity) {
413
+ if (filterIdentities.includes(identity)) {
414
+ matchesExistingFilter = true;
415
+ }
416
+ }
417
+ if (hasAppFilter && appName) {
418
+ if (filteredApplications.includes(appName)) {
419
+ matchesExistingFilter = true;
420
+ }
421
+ }
422
+ // If no filters are configured (including between), include all logs
423
+ if (!hasIdentityFilter && !hasAppFilter && (!between || between.length === 0)) {
424
+ return true;
425
+ }
426
+ // Update between range state
427
+ if (between && between.length > 0) {
428
+ this.updateBetweenRangeState(message, identity, meta);
429
+ // Check active between ranges
430
+ let hasActiveIncludeRule = false;
431
+ let hasActiveExcludeRule = false;
432
+ for (const ruleIndex of this.activeBetweenRanges) {
433
+ const rule = between[ruleIndex];
434
+ if (!rule) {
435
+ continue;
436
+ }
437
+ if (rule.action === 'include') {
438
+ hasActiveIncludeRule = true;
439
+ }
440
+ else if (rule.action === 'exclude') {
441
+ hasActiveExcludeRule = true;
442
+ }
443
+ }
444
+ // Between rules use OR logic with existing filters
445
+ // If ANY include rule is active, include the log
446
+ if (hasActiveIncludeRule) {
447
+ return true;
448
+ }
449
+ // If ANY exclude rule is active, exclude the log
450
+ if (hasActiveExcludeRule) {
451
+ return false;
452
+ }
453
+ }
454
+ // If existing filters match, include the log
455
+ if (matchesExistingFilter) {
456
+ return true;
457
+ }
458
+ // No matches found - filter out the log
459
+ return false;
460
+ }
461
+ /**
462
+ * Check if a package should be shown in console output based on filtering rules
463
+ * Only affects console output, not file or unified-logger
464
+ */
465
+ shouldShowPackageInConsole(packageName) {
466
+ // If show list is defined, only show packages in that list
467
+ if (this.config.consolePackagesShow && this.config.consolePackagesShow.length > 0) {
468
+ return this.config.consolePackagesShow.includes(packageName);
469
+ }
470
+ // If hide list is defined, hide packages in that list
471
+ if (this.config.consolePackagesHide && this.config.consolePackagesHide.length > 0) {
472
+ return !this.config.consolePackagesHide.includes(packageName);
473
+ }
474
+ // Default: show all packages
475
+ return true;
476
+ }
477
+ /**
478
+ * Get call site identity from stack trace
479
+ * Extracts file path and function name from the call stack
480
+ * Returns a clean identity string like "src/file.ts:functionName" or "src/file.ts:lineNumber"
481
+ */
482
+ getCallSiteIdentity() {
483
+ try {
484
+ const stack = new Error().stack;
485
+ if (!stack)
486
+ return undefined;
487
+ const stackLines = stack.split('\n');
488
+ // Skip the first line (Error message) and find the first caller outside logxer
489
+ for (let i = 1; i < stackLines.length; i++) {
490
+ const line = stackLines[i]?.trim();
491
+ if (!line)
492
+ continue;
493
+ // Skip logxer internal files
494
+ if (line.includes('logxer.ts') ||
495
+ line.includes('index.ts') ||
496
+ line.includes('logxer') ||
497
+ line.includes('node_modules')) {
498
+ continue;
499
+ }
500
+ // Parse stack line format: "at FunctionName (file:line:column)" or "at file:line:column"
501
+ // Match patterns like:
502
+ // - "at functionName (file:///path/to/file.ts:123:45)"
503
+ // - "at file:///path/to/file.ts:123:45"
504
+ // - "at Object.functionName (file:///path/to/file.ts:123:45)"
505
+ const match = line.match(/at\s+(?:[^(]+\.)?([^(]+)\s*\(?([^:]+):(\d+):(\d+)\)?/);
506
+ if (match && match[1] && match[2] && match[3]) {
507
+ const functionName = match[1].trim();
508
+ let filePath = match[2].trim();
509
+ const lineNumber = match[3];
510
+ // Clean up file path - remove file:// protocol and normalize
511
+ filePath = filePath.replace(/^file:\/\/\//, '').replace(/^file:\/\//, '');
512
+ // Try to make path relative to process.cwd() for readability
513
+ const cwd = process.cwd();
514
+ if (filePath.startsWith(cwd)) {
515
+ filePath = filePath.substring(cwd.length + 1);
516
+ }
517
+ // Remove node_modules from path if present
518
+ const nodeModulesIndex = filePath.indexOf('node_modules');
519
+ if (nodeModulesIndex !== -1) {
520
+ // Keep only the package name and file after node_modules
521
+ const afterNodeModules = filePath.substring(nodeModulesIndex + 'node_modules'.length + 1);
522
+ const parts = afterNodeModules.split(/[/\\]/);
523
+ if (parts.length >= 2) {
524
+ filePath = `${parts[0]}/${parts.slice(1).join('/')}`;
525
+ }
526
+ else {
527
+ filePath = afterNodeModules;
528
+ }
529
+ }
530
+ // Normalize path separators
531
+ filePath = filePath.replace(/\\/g, '/');
532
+ // Format identity
533
+ if (functionName && functionName !== 'Object' && functionName !== 'Module' && !functionName.startsWith('<')) {
534
+ return `${filePath}:${functionName}`;
535
+ }
536
+ else {
537
+ return `${filePath}:${lineNumber}`;
538
+ }
539
+ }
540
+ }
541
+ return undefined;
542
+ }
543
+ catch (error) {
544
+ // If stack trace parsing fails, return undefined (no identity)
545
+ return undefined;
546
+ }
547
+ }
548
+ /**
549
+ * Format a log entry according to the configured format
550
+ */
551
+ formatLogEntry(level, message, meta) {
552
+ const timestamp = new Date().toISOString();
553
+ const logEntry = {
554
+ timestamp,
555
+ package: this.packageConfig.packageName,
556
+ level: level.toUpperCase(),
557
+ message,
558
+ ...(meta !== undefined && { data: meta }),
559
+ ...(this.appInfo.name && { appName: this.appInfo.name }),
560
+ ...(this.appInfo.version && { appVersion: this.appInfo.version })
561
+ };
562
+ if (this.config.logFormat === 'json') {
563
+ // Include identity in JSON output if present
564
+ const jsonEntry = { ...logEntry };
565
+ if (meta?.identity) {
566
+ jsonEntry.identity = meta.identity;
567
+ }
568
+ return JSON.stringify(jsonEntry);
569
+ }
570
+ else if (this.config.logFormat === 'yaml') {
571
+ // Convert LogEntry to LogEnvelope format for YAML
572
+ const envelope = {
573
+ timestamp: logEntry.timestamp,
574
+ package: logEntry.package,
575
+ level: logEntry.level,
576
+ message: logEntry.message,
577
+ source: meta?.source ?? this.config.defaultSource ?? 'application',
578
+ ...(logEntry.data && { data: logEntry.data }),
579
+ ...(this.appInfo.name && { appName: this.appInfo.name }),
580
+ ...(this.appInfo.version && { appVersion: this.appInfo.version }),
581
+ // Include other metadata fields if present
582
+ ...(meta?.identity && { identity: meta.identity }),
583
+ ...(meta?.correlationId && { correlationId: meta.correlationId }),
584
+ ...(meta?.tags && { tags: meta.tags }),
585
+ ...(meta?._routing && { _routing: meta._routing })
586
+ };
587
+ return (0, yaml_formatter_1.formatLogEntryAsYaml)(envelope);
588
+ }
589
+ else if (this.config.logFormat === 'table') {
590
+ // Table format is handled directly in writeToConsole, but provide a fallback string
591
+ // This should not be called for console output, but may be used for file output
592
+ let formatted = `[${timestamp}] [${this.packageConfig.packageName}] [${level.toUpperCase()}] ${message}`;
593
+ if (meta !== undefined) {
594
+ formatted += ` ${typeof meta === 'object' ? JSON.stringify(meta) : meta}`;
595
+ }
596
+ return formatted;
597
+ }
598
+ else {
599
+ // Text format: [timestamp] [PACKAGE] [LEVEL] message
600
+ let formatted = `[${timestamp}] [${this.packageConfig.packageName}] [${level.toUpperCase()}] ${message}`;
601
+ if (meta !== undefined) {
602
+ // Include identity in text format if present
603
+ if (meta.identity) {
604
+ formatted += ` [identity:${meta.identity}]`;
605
+ }
606
+ formatted += ` ${typeof meta === 'object' ? JSON.stringify(meta) : meta}`;
607
+ }
608
+ return formatted;
609
+ }
610
+ }
611
+ /**
612
+ * Write formatted message to console
613
+ */
614
+ writeToConsole(level, formattedMessage, meta) {
615
+ if (!this.config.logToConsole)
616
+ return;
617
+ // Handle table format specially
618
+ if (this.config.logFormat === 'table') {
619
+ const timestamp = new Date().toISOString();
620
+ const envelope = {
621
+ timestamp,
622
+ package: this.packageConfig.packageName,
623
+ level: level.toUpperCase(),
624
+ message: formattedMessage,
625
+ source: meta?.source ?? this.config.defaultSource ?? 'application',
626
+ ...(meta !== undefined && { data: meta }),
627
+ ...(this.appInfo.name && { appName: this.appInfo.name }),
628
+ ...(this.appInfo.version && { appVersion: this.appInfo.version }),
629
+ ...(meta?.correlationId && { correlationId: meta.correlationId }),
630
+ ...(meta?.jobId && { jobId: meta.jobId }),
631
+ ...(meta?.runId && { runId: meta.runId }),
632
+ ...(meta?.sessionId && { sessionId: meta.sessionId }),
633
+ ...(meta?.tags && { tags: meta.tags })
634
+ };
635
+ (0, table_formatter_1.outputLogAsTable)(envelope, this.config.showFullTimestamp);
636
+ return;
637
+ }
638
+ if (level === 'error') {
639
+ console.error(formattedMessage);
640
+ }
641
+ else {
642
+ console.log(formattedMessage);
643
+ }
644
+ }
645
+ /**
646
+ * Write formatted message to log file
647
+ */
648
+ writeToFile(formattedMessage) {
649
+ if (!this.config.logToFile || !this.config.logFilePath)
650
+ return;
651
+ try {
652
+ fs.appendFileSync(this.config.logFilePath, formattedMessage + '\n', 'utf8');
653
+ }
654
+ catch (err) {
655
+ // Fallback to console if file write fails
656
+ console.error(`[logxer] ${this.packageConfig.packageName}: Failed to write to log file (${this.config.logFilePath}):`, err);
657
+ }
658
+ }
659
+ /**
660
+ * Core logging method that handles all log output
661
+ */
662
+ emit(level, message, meta) {
663
+ if (this.config.packageLogsDisabled) {
664
+ return;
665
+ }
666
+ // Get identity - use provided identity or auto-generate from call site
667
+ const identity = meta?.identity ?? this.getCallSiteIdentity();
668
+ // Apply debug scoping filter - if log doesn't match filters, skip all outputs
669
+ if (!this.shouldIncludeLog(message, identity, this.appInfo.name, meta)) {
670
+ return; // Filter out this log completely
671
+ }
672
+ // Build envelope for shadow capture BEFORE sanitization and level filtering
673
+ const timestamp = new Date().toISOString();
674
+ const rawEnvelope = {
675
+ timestamp,
676
+ package: this.packageConfig.packageName,
677
+ level: level.toUpperCase(),
678
+ message,
679
+ source: meta?.source ?? this.config.defaultSource ?? 'application',
680
+ ...(meta && { data: meta }),
681
+ ...(this.appInfo.name && { appName: this.appInfo.name }),
682
+ ...(this.appInfo.version && { appVersion: this.appInfo.version }),
683
+ // Extract known fields from meta
684
+ ...(identity && { identity }),
685
+ ...(meta?.correlationId && { correlationId: meta.correlationId }),
686
+ ...(meta?.jobId && { jobId: meta.jobId }),
687
+ ...(meta?.runId && { runId: meta.runId }),
688
+ ...(meta?.sessionId && { sessionId: meta.sessionId }),
689
+ ...(meta?._routing && { _routing: meta._routing }),
690
+ ...(meta?.tags && { tags: meta.tags })
691
+ };
692
+ // Shadow capture (raw, before sanitization, all levels)
693
+ if (this.shadowSink) {
694
+ this.shadowSink.write(rawEnvelope, meta);
695
+ }
696
+ // Check if we should log this level (for normal outputs)
697
+ if (!this.shouldLog(level))
698
+ return;
699
+ // Only sanitize if explicitly enabled
700
+ let sanitizedMessage = message;
701
+ let sanitizedMeta = meta;
702
+ let sanitizationResult = {
703
+ sanitized: { message, data: meta },
704
+ redactionCount: 0,
705
+ truncated: false
706
+ };
707
+ if (this.config.sanitization.enabled) {
708
+ sanitizationResult = this.sanitizer.sanitize(message, meta);
709
+ sanitizedMessage = sanitizationResult.sanitized.message;
710
+ sanitizedMeta = sanitizationResult.sanitized.data;
711
+ }
712
+ // Use custom logxer if provided
713
+ if (this.config.customLogger) {
714
+ this.config.customLogger[level](sanitizedMessage, sanitizedMeta);
715
+ return;
716
+ }
717
+ // Fill defaults without mutating caller object
718
+ const enriched = {
719
+ ...sanitizedMeta,
720
+ source: sanitizedMeta?.source ?? this.config.defaultSource ?? 'application',
721
+ ...(identity && { identity })
722
+ };
723
+ // Add sanitization metadata if any redactions occurred
724
+ if (sanitizationResult.redactionCount > 0) {
725
+ enriched._sanitization = {
726
+ redactionCount: sanitizationResult.redactionCount,
727
+ truncated: sanitizationResult.truncated
728
+ };
729
+ }
730
+ // Console output
731
+ // Apply package filtering for console (only affects console, not file/unified-logger)
732
+ if (this.config.logToConsole && this.shouldSend('console', enriched) && this.shouldShowPackageInConsole(this.packageConfig.packageName)) {
733
+ if (this.sinks.console) {
734
+ this.sinks.console(level, sanitizedMessage, enriched);
735
+ }
736
+ else {
737
+ // Fallback to default console behavior
738
+ const formattedMessage = this.formatLogEntry(level, sanitizedMessage, enriched);
739
+ this.writeToConsole(level, formattedMessage, enriched);
740
+ }
741
+ }
742
+ // File output
743
+ if (this.config.logToFile && this.shouldSend('file', enriched)) {
744
+ if (this.sinks.file) {
745
+ this.sinks.file(level, sanitizedMessage, enriched);
746
+ }
747
+ else {
748
+ // Fallback to default file behavior
749
+ const formattedMessage = this.formatLogEntry(level, sanitizedMessage, enriched);
750
+ this.writeToFile(formattedMessage);
751
+ }
752
+ }
753
+ // Unified logxer output
754
+ if (this.config.enableUnifiedLogger && this.sinks.unified && this.shouldSend('unified-logger', enriched)) {
755
+ this.sinks.unified.write(level, sanitizedMessage, enriched);
756
+ }
757
+ }
758
+ // --------------------------------------------------------------------------
759
+ // PUBLIC API
760
+ // --------------------------------------------------------------------------
761
+ /**
762
+ * Log verbose message (only shown when logLevel is 'verbose' or 'debug')
763
+ */
764
+ verbose(message, data) {
765
+ this.emit('verbose', message, data);
766
+ }
767
+ /**
768
+ * Log debug message (only shown when logLevel is 'debug' or 'verbose')
769
+ */
770
+ debug(message, data) {
771
+ this.emit('debug', message, data);
772
+ }
773
+ /**
774
+ * Log informational message
775
+ */
776
+ info(message, data) {
777
+ this.emit('info', message, data);
778
+ }
779
+ /**
780
+ * Log warning message
781
+ */
782
+ warn(message, data) {
783
+ this.emit('warn', message, data);
784
+ }
785
+ /**
786
+ * Log error message
787
+ */
788
+ error(message, data) {
789
+ this.emit('error', message, data);
790
+ }
791
+ /**
792
+ * Log success message (maps to INFO level)
793
+ */
794
+ success(message, data) {
795
+ this.emit('info', message, data);
796
+ }
797
+ /**
798
+ * Get current logxer configuration (for debugging/testing)
799
+ */
800
+ getConfig() {
801
+ return { ...this.config };
802
+ }
803
+ /**
804
+ * Check if a specific log level is enabled
805
+ */
806
+ isLevelEnabled(level) {
807
+ return this.shouldLog(level);
808
+ }
809
+ }
810
+ exports.Logxer = Logxer;
811
+ //# sourceMappingURL=logxer.js.map