@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.
- package/LICENSE +21 -0
- package/README.md +1255 -0
- package/dist/app-info.d.ts +25 -0
- package/dist/app-info.d.ts.map +1 -0
- package/dist/app-info.js +179 -0
- package/dist/app-info.js.map +1 -0
- package/dist/formatters/table-formatter.d.ts +23 -0
- package/dist/formatters/table-formatter.d.ts.map +1 -0
- package/dist/formatters/table-formatter.js +218 -0
- package/dist/formatters/table-formatter.js.map +1 -0
- package/dist/formatters/yaml-formatter.d.ts +14 -0
- package/dist/formatters/yaml-formatter.d.ts.map +1 -0
- package/dist/formatters/yaml-formatter.js +164 -0
- package/dist/formatters/yaml-formatter.js.map +1 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +219 -0
- package/dist/index.js.map +1 -0
- package/dist/logxer.d.ts +145 -0
- package/dist/logxer.d.ts.map +1 -0
- package/dist/logxer.js +811 -0
- package/dist/logxer.js.map +1 -0
- package/dist/outputs/shadow-sink.d.ts +83 -0
- package/dist/outputs/shadow-sink.d.ts.map +1 -0
- package/dist/outputs/shadow-sink.js +380 -0
- package/dist/outputs/shadow-sink.js.map +1 -0
- package/dist/outputs/unified-logger-output.d.ts +30 -0
- package/dist/outputs/unified-logger-output.d.ts.map +1 -0
- package/dist/outputs/unified-logger-output.js +125 -0
- package/dist/outputs/unified-logger-output.js.map +1 -0
- package/dist/sanitizer.d.ts +69 -0
- package/dist/sanitizer.d.ts.map +1 -0
- package/dist/sanitizer.js +507 -0
- package/dist/sanitizer.js.map +1 -0
- package/dist/trails/headers.d.ts +21 -0
- package/dist/trails/headers.d.ts.map +1 -0
- package/dist/trails/headers.js +121 -0
- package/dist/trails/headers.js.map +1 -0
- package/dist/trails/index.d.ts +59 -0
- package/dist/trails/index.d.ts.map +1 -0
- package/dist/trails/index.js +197 -0
- package/dist/trails/index.js.map +1 -0
- package/dist/types.d.ts +411 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/debug-config.d.ts +19 -0
- package/dist/utils/debug-config.d.ts.map +1 -0
- package/dist/utils/debug-config.js +172 -0
- package/dist/utils/debug-config.js.map +1 -0
- package/dist/utils/package-logs-level.d.ts +40 -0
- package/dist/utils/package-logs-level.d.ts.map +1 -0
- package/dist/utils/package-logs-level.js +79 -0
- package/dist/utils/package-logs-level.js.map +1 -0
- package/docs/package-usage.md +155 -0
- package/docs/package.md +48 -0
- package/docs/upgrade-for-package-authors.md +85 -0
- 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
|