@wgtechlabs/log-engine 2.0.0 → 2.1.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 -21
- package/README.md +869 -608
- package/dist/formatter/data-formatter.d.ts +2 -1
- package/dist/formatter/data-formatter.d.ts.map +1 -1
- package/dist/formatter/data-formatter.js +1 -1
- package/dist/formatter/message-formatter.d.ts +23 -23
- package/dist/formatter/message-formatter.d.ts.map +1 -1
- package/dist/formatter/message-formatter.js +23 -23
- package/dist/formatter/timestamp.d.ts.map +1 -1
- package/dist/index.d.ts +130 -136
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +108 -108
- package/dist/logger/advanced-outputs.d.ts +159 -0
- package/dist/logger/advanced-outputs.d.ts.map +1 -0
- package/dist/logger/advanced-outputs.js +586 -0
- package/dist/logger/config.d.ts +18 -18
- package/dist/logger/config.d.ts.map +1 -1
- package/dist/logger/config.js +32 -29
- package/dist/logger/core.d.ts +128 -84
- package/dist/logger/core.d.ts.map +1 -1
- package/dist/logger/core.js +259 -74
- package/dist/logger/environment.d.ts +15 -15
- package/dist/logger/environment.d.ts.map +1 -1
- package/dist/logger/environment.js +15 -15
- package/dist/logger/filtering.d.ts +16 -16
- package/dist/logger/filtering.d.ts.map +1 -1
- package/dist/logger/filtering.js +37 -22
- package/dist/redaction/config.d.ts +8 -8
- package/dist/redaction/config.d.ts.map +1 -1
- package/dist/redaction/config.js +8 -8
- package/dist/redaction/redactor.d.ts +60 -60
- package/dist/redaction/redactor.d.ts.map +1 -1
- package/dist/redaction/redactor.js +101 -96
- package/dist/types/index.d.ts +98 -16
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +80 -68
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Advanced output handlers for log-engine
|
|
4
|
+
* Provides file, HTTP, and other production-ready output handlers
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.SAFE_BASE_DIRS = exports.HttpOutputHandler = exports.FileOutputHandler = void 0;
|
|
41
|
+
exports.createBuiltInHandler = createBuiltInHandler;
|
|
42
|
+
exports.secureExistsSync = secureExistsSync;
|
|
43
|
+
exports.secureMkdirSync = secureMkdirSync;
|
|
44
|
+
exports.secureStatSync = secureStatSync;
|
|
45
|
+
exports.secureWriteFileSync = secureWriteFileSync;
|
|
46
|
+
exports.secureUnlinkSync = secureUnlinkSync;
|
|
47
|
+
exports.secureRenameSync = secureRenameSync;
|
|
48
|
+
exports.validatePath = validatePath;
|
|
49
|
+
const fs = __importStar(require("fs"));
|
|
50
|
+
const path = __importStar(require("path"));
|
|
51
|
+
const os = __importStar(require("os"));
|
|
52
|
+
/**
|
|
53
|
+
* Secure filesystem operations for logging operations
|
|
54
|
+
*
|
|
55
|
+
* SECURITY NOTE: These functions implement comprehensive path validation and access controls
|
|
56
|
+
* to prevent path traversal attacks, directory injection, and unauthorized file access.
|
|
57
|
+
* ESLint security rules are disabled for specific fs operations because:
|
|
58
|
+
*
|
|
59
|
+
* 1. All paths are validated through validatePath() which:
|
|
60
|
+
* - Prevents directory traversal (../)
|
|
61
|
+
* - Restricts access to predefined safe directories
|
|
62
|
+
* - Blocks access to system directories
|
|
63
|
+
* - Normalizes and resolves paths securely
|
|
64
|
+
*
|
|
65
|
+
* 2. The logging library requires dynamic file paths by design (user-configurable log files)
|
|
66
|
+
* 3. All operations are wrapped in try-catch with comprehensive error handling
|
|
67
|
+
* 4. File operations are restricted to log and temp directories only
|
|
68
|
+
*/
|
|
69
|
+
/**
|
|
70
|
+
* Predefined safe base directories for different operation types
|
|
71
|
+
* Restricted to specific subdirectories to prevent unauthorized access
|
|
72
|
+
*/
|
|
73
|
+
const SAFE_BASE_DIRS = {
|
|
74
|
+
LOG_FILES: [path.resolve('./logs'), path.resolve('./var/log'), path.resolve('./data/logs')],
|
|
75
|
+
TEMP_FILES: [path.resolve('./temp'), path.resolve('./logs'), path.resolve('./tmp'), os.tmpdir()],
|
|
76
|
+
CONFIG_FILES: [path.resolve('./config'), path.resolve('./etc'), path.resolve('./logs')]
|
|
77
|
+
};
|
|
78
|
+
exports.SAFE_BASE_DIRS = SAFE_BASE_DIRS;
|
|
79
|
+
/**
|
|
80
|
+
* Validates file path with comprehensive security checks
|
|
81
|
+
* Prevents path traversal, restricts to safe directories, blocks system paths
|
|
82
|
+
*/
|
|
83
|
+
function validatePath(filePath) {
|
|
84
|
+
if (!filePath || typeof filePath !== 'string') {
|
|
85
|
+
throw new Error('File path must be a non-empty string');
|
|
86
|
+
}
|
|
87
|
+
// Resolve and normalize the path to handle relative paths and traversal attempts
|
|
88
|
+
const resolvedPath = path.resolve(filePath);
|
|
89
|
+
const normalizedPath = path.normalize(resolvedPath);
|
|
90
|
+
// Check for path traversal attempts in original path - reject ANY use of '..'
|
|
91
|
+
if (filePath.includes('..')) {
|
|
92
|
+
throw new Error(`Path traversal detected: ${filePath}`);
|
|
93
|
+
}
|
|
94
|
+
// Ensure path is within safe directories (logs, current directory, or temp)
|
|
95
|
+
const safeBaseDirs = [
|
|
96
|
+
...SAFE_BASE_DIRS.LOG_FILES,
|
|
97
|
+
...SAFE_BASE_DIRS.TEMP_FILES,
|
|
98
|
+
...SAFE_BASE_DIRS.CONFIG_FILES
|
|
99
|
+
];
|
|
100
|
+
const isInSafeDir = safeBaseDirs.some(safeDir => normalizedPath.startsWith(safeDir));
|
|
101
|
+
if (!isInSafeDir) {
|
|
102
|
+
throw new Error(`File path outside allowed directories: ${filePath}`);
|
|
103
|
+
}
|
|
104
|
+
// Block access to dangerous system directories
|
|
105
|
+
const dangerousPaths = [
|
|
106
|
+
'/etc', '/sys', '/proc', '/dev', '/root', '/bin', '/sbin',
|
|
107
|
+
'C:\\Windows', 'C:\\System32', 'C:\\Program Files', 'C:\\Users\\All Users'
|
|
108
|
+
];
|
|
109
|
+
if (dangerousPaths.some(dangerous => normalizedPath.startsWith(dangerous))) {
|
|
110
|
+
throw new Error(`Access denied to system directory: ${filePath}`);
|
|
111
|
+
}
|
|
112
|
+
return normalizedPath;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Secure file existence check
|
|
116
|
+
* Uses fs.accessSync instead of fs.existsSync for better security practices
|
|
117
|
+
*/
|
|
118
|
+
function secureExistsSync(filePath) {
|
|
119
|
+
try {
|
|
120
|
+
const safePath = validatePath(filePath);
|
|
121
|
+
// SECURITY: Path has been validated and restricted to safe directories
|
|
122
|
+
fs.accessSync(safePath, fs.constants.F_OK);
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Secure directory creation with recursive option support
|
|
131
|
+
* Restricted to log and temp directories only
|
|
132
|
+
*/
|
|
133
|
+
function secureMkdirSync(dirPath, options) {
|
|
134
|
+
const safePath = validatePath(dirPath);
|
|
135
|
+
try {
|
|
136
|
+
const mkdirOptions = { recursive: Boolean(options?.recursive) };
|
|
137
|
+
// SECURITY: Path has been validated and restricted to safe directories
|
|
138
|
+
fs.mkdirSync(safePath, mkdirOptions);
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
142
|
+
throw new Error(`Failed to create directory ${dirPath}: ${errorMessage}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Secure file stat operation
|
|
147
|
+
* Returns file system statistics for validated paths only
|
|
148
|
+
*/
|
|
149
|
+
function secureStatSync(filePath) {
|
|
150
|
+
const safePath = validatePath(filePath);
|
|
151
|
+
try {
|
|
152
|
+
// SECURITY: Path has been validated and restricted to safe directories
|
|
153
|
+
return fs.statSync(safePath);
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
157
|
+
throw new Error(`Failed to stat file ${filePath}: ${errorMessage}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Secure file write operation
|
|
162
|
+
* Validates path and data before writing to prevent injection attacks
|
|
163
|
+
*/
|
|
164
|
+
function secureWriteFileSync(filePath, data, options) {
|
|
165
|
+
const safePath = validatePath(filePath);
|
|
166
|
+
// Validate data parameter
|
|
167
|
+
if (typeof data !== 'string') {
|
|
168
|
+
throw new Error('Data must be a string for security');
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
const writeOptions = options || {};
|
|
172
|
+
// SECURITY: Path has been validated and restricted to safe directories
|
|
173
|
+
fs.writeFileSync(safePath, data, writeOptions);
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
177
|
+
throw new Error(`Failed to write file ${filePath}: ${errorMessage}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Secure file deletion
|
|
182
|
+
* Restricted to log and temp files only for safety
|
|
183
|
+
*/
|
|
184
|
+
function secureUnlinkSync(filePath) {
|
|
185
|
+
const safePath = validatePath(filePath);
|
|
186
|
+
// Additional safety check: only allow deletion of log and temp files
|
|
187
|
+
const logDirs = [...SAFE_BASE_DIRS.LOG_FILES, ...SAFE_BASE_DIRS.TEMP_FILES];
|
|
188
|
+
const isInLogDir = logDirs.some(logDir => safePath.startsWith(logDir));
|
|
189
|
+
if (!isInLogDir) {
|
|
190
|
+
throw new Error(`File deletion not allowed outside log/temp directories: ${filePath}`);
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
// SECURITY: Path has been validated and restricted to log/temp directories
|
|
194
|
+
fs.unlinkSync(safePath);
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
198
|
+
throw new Error(`Failed to delete file ${filePath}: ${errorMessage}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Secure file rename/move operation
|
|
203
|
+
* Both source and destination must be in safe directories
|
|
204
|
+
*/
|
|
205
|
+
function secureRenameSync(oldPath, newPath) {
|
|
206
|
+
const safeOldPath = validatePath(oldPath);
|
|
207
|
+
const safeNewPath = validatePath(newPath);
|
|
208
|
+
// Ensure both paths are in allowed directories (log/temp only for safety)
|
|
209
|
+
const allowedDirs = [...SAFE_BASE_DIRS.LOG_FILES, ...SAFE_BASE_DIRS.TEMP_FILES];
|
|
210
|
+
const oldInAllowed = allowedDirs.some(dir => safeOldPath.startsWith(dir));
|
|
211
|
+
const newInAllowed = allowedDirs.some(dir => safeNewPath.startsWith(dir));
|
|
212
|
+
if (!oldInAllowed || !newInAllowed) {
|
|
213
|
+
throw new Error(`File rename not allowed outside safe directories: ${oldPath} -> ${newPath}`);
|
|
214
|
+
}
|
|
215
|
+
try {
|
|
216
|
+
// SECURITY: Both paths have been validated and restricted to safe directories
|
|
217
|
+
fs.renameSync(safeOldPath, safeNewPath);
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
221
|
+
throw new Error(`Failed to rename file ${oldPath} to ${newPath}: ${errorMessage}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* File output handler with rotation support and concurrency protection
|
|
226
|
+
* Implements atomic file operations and write queuing to prevent corruption
|
|
227
|
+
*/
|
|
228
|
+
class FileOutputHandler {
|
|
229
|
+
constructor(config) {
|
|
230
|
+
this.currentFileSize = 0;
|
|
231
|
+
this.rotationInProgress = false;
|
|
232
|
+
this.writeQueue = [];
|
|
233
|
+
/**
|
|
234
|
+
* Default formatter for file output
|
|
235
|
+
*/
|
|
236
|
+
this.defaultFormatter = (level, message, data) => {
|
|
237
|
+
const timestamp = new Date().toISOString();
|
|
238
|
+
const dataStr = data ? ` ${JSON.stringify(data)}` : '';
|
|
239
|
+
return `${timestamp} [${level.toUpperCase()}] ${message}${dataStr}\n`;
|
|
240
|
+
};
|
|
241
|
+
/**
|
|
242
|
+
* Write log to file with rotation support and concurrency protection
|
|
243
|
+
* Queues writes during rotation to prevent file corruption
|
|
244
|
+
*/
|
|
245
|
+
this.write = (level, message, data) => {
|
|
246
|
+
// If rotation is in progress, queue the write
|
|
247
|
+
if (this.rotationInProgress) {
|
|
248
|
+
this.writeQueue.push({ level, message, data });
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
try {
|
|
252
|
+
this.writeToFile(level, message, data);
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
// Fallback to console if file writing fails
|
|
256
|
+
console.error('File output handler failed:', error);
|
|
257
|
+
console.log(`[${level.toUpperCase()}] ${message}`, data);
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
// Set defaults
|
|
261
|
+
this.config = {
|
|
262
|
+
filePath: config.filePath,
|
|
263
|
+
append: config.append ?? true,
|
|
264
|
+
maxFileSize: config.maxFileSize ?? 0, // 0 means no rotation
|
|
265
|
+
maxBackupFiles: config.maxBackupFiles ?? 3,
|
|
266
|
+
formatter: config.formatter ?? this.defaultFormatter
|
|
267
|
+
};
|
|
268
|
+
// Ensure directory exists and validate paths
|
|
269
|
+
try {
|
|
270
|
+
const dir = path.dirname(this.config.filePath);
|
|
271
|
+
if (!secureExistsSync(dir)) {
|
|
272
|
+
secureMkdirSync(dir, { recursive: true });
|
|
273
|
+
}
|
|
274
|
+
// Get current file size if it exists
|
|
275
|
+
if (secureExistsSync(this.config.filePath)) {
|
|
276
|
+
this.currentFileSize = secureStatSync(this.config.filePath).size;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
// Re-throw with context for better error handling
|
|
281
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
282
|
+
throw new Error(`Failed to initialize file output handler: ${errorMessage}`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Write to file with concurrency protection and rotation check
|
|
287
|
+
* If rotation is in progress, messages are queued to prevent corruption
|
|
288
|
+
*/
|
|
289
|
+
writeToFile(level, message, data) {
|
|
290
|
+
const formattedMessage = this.config.formatter(level, message, data);
|
|
291
|
+
// Check if rotation is needed
|
|
292
|
+
if (this.config.maxFileSize > 0 &&
|
|
293
|
+
this.currentFileSize + Buffer.byteLength(formattedMessage) > this.config.maxFileSize) {
|
|
294
|
+
this.rotateFile();
|
|
295
|
+
}
|
|
296
|
+
// Write to file using secure filesystem wrapper
|
|
297
|
+
const writeOptions = this.config.append ? { flag: 'a' } : { flag: 'w' };
|
|
298
|
+
secureWriteFileSync(this.config.filePath, formattedMessage, writeOptions);
|
|
299
|
+
this.currentFileSize += Buffer.byteLength(formattedMessage);
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Process queued writes after rotation completes
|
|
303
|
+
*/
|
|
304
|
+
processWriteQueue() {
|
|
305
|
+
while (this.writeQueue.length > 0) {
|
|
306
|
+
const queuedWrite = this.writeQueue.shift();
|
|
307
|
+
if (queuedWrite) {
|
|
308
|
+
try {
|
|
309
|
+
this.writeToFile(queuedWrite.level, queuedWrite.message, queuedWrite.data);
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
console.error('Failed to process queued write:', error);
|
|
313
|
+
console.log(`[${queuedWrite.level.toUpperCase()}] ${queuedWrite.message}`, queuedWrite.data);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Rotate log files when size limit is reached
|
|
320
|
+
* Implements concurrency protection to prevent corruption during rotation
|
|
321
|
+
*/
|
|
322
|
+
rotateFile() {
|
|
323
|
+
// Prevent concurrent rotations
|
|
324
|
+
if (this.rotationInProgress) {
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
this.rotationInProgress = true;
|
|
328
|
+
try {
|
|
329
|
+
// Move backup files
|
|
330
|
+
for (let i = this.config.maxBackupFiles - 1; i >= 1; i--) {
|
|
331
|
+
const oldFile = `${this.config.filePath}.${i}`;
|
|
332
|
+
const newFile = `${this.config.filePath}.${i + 1}`;
|
|
333
|
+
if (secureExistsSync(oldFile)) {
|
|
334
|
+
if (i === this.config.maxBackupFiles - 1) {
|
|
335
|
+
// Delete the oldest file
|
|
336
|
+
secureUnlinkSync(oldFile);
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
secureRenameSync(oldFile, newFile);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
// Move current file to .1
|
|
344
|
+
if (secureExistsSync(this.config.filePath)) {
|
|
345
|
+
const backupFile = `${this.config.filePath}.1`;
|
|
346
|
+
secureRenameSync(this.config.filePath, backupFile);
|
|
347
|
+
}
|
|
348
|
+
this.currentFileSize = 0;
|
|
349
|
+
}
|
|
350
|
+
catch (error) {
|
|
351
|
+
console.error('File rotation failed:', error);
|
|
352
|
+
}
|
|
353
|
+
finally {
|
|
354
|
+
// Always reset rotation flag and process queued writes
|
|
355
|
+
this.rotationInProgress = false;
|
|
356
|
+
this.processWriteQueue();
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Clean up resources and process any remaining queued writes
|
|
361
|
+
*/
|
|
362
|
+
destroy() {
|
|
363
|
+
// Process any remaining queued writes
|
|
364
|
+
if (this.writeQueue.length > 0) {
|
|
365
|
+
this.processWriteQueue();
|
|
366
|
+
}
|
|
367
|
+
// Clear the write queue
|
|
368
|
+
this.writeQueue = [];
|
|
369
|
+
this.rotationInProgress = false;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
exports.FileOutputHandler = FileOutputHandler;
|
|
373
|
+
/**
|
|
374
|
+
* HTTP output handler for sending logs to remote endpoints
|
|
375
|
+
*/
|
|
376
|
+
class HttpOutputHandler {
|
|
377
|
+
constructor(config) {
|
|
378
|
+
this.logBuffer = [];
|
|
379
|
+
this.flushTimeout = null;
|
|
380
|
+
/**
|
|
381
|
+
* Default formatter for HTTP output
|
|
382
|
+
*/
|
|
383
|
+
this.defaultFormatter = (logs) => {
|
|
384
|
+
return {
|
|
385
|
+
logs: logs.map(log => ({
|
|
386
|
+
timestamp: log.timestamp,
|
|
387
|
+
level: log.level,
|
|
388
|
+
message: log.message,
|
|
389
|
+
data: log.data
|
|
390
|
+
}))
|
|
391
|
+
};
|
|
392
|
+
};
|
|
393
|
+
/**
|
|
394
|
+
* Write log to HTTP endpoint with batching support
|
|
395
|
+
*/
|
|
396
|
+
this.write = (level, message, data) => {
|
|
397
|
+
try {
|
|
398
|
+
// Add to buffer
|
|
399
|
+
this.logBuffer.push({
|
|
400
|
+
level,
|
|
401
|
+
message,
|
|
402
|
+
data,
|
|
403
|
+
timestamp: new Date().toISOString()
|
|
404
|
+
});
|
|
405
|
+
// Flush if batch size reached
|
|
406
|
+
if (this.logBuffer.length >= this.config.batchSize) {
|
|
407
|
+
this.flush();
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
// Schedule a flush if not already scheduled
|
|
411
|
+
if (!this.flushTimeout) {
|
|
412
|
+
this.flushTimeout = setTimeout(() => {
|
|
413
|
+
this.flush();
|
|
414
|
+
}, 1000); // Flush after 1 second if batch isn't full
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
catch (error) {
|
|
419
|
+
// Fallback to console if HTTP fails
|
|
420
|
+
console.error('HTTP output handler failed:', error);
|
|
421
|
+
console.log(`[${level.toUpperCase()}] ${message}`, data);
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
// Set defaults
|
|
425
|
+
this.config = {
|
|
426
|
+
url: config.url,
|
|
427
|
+
method: config.method ?? 'POST',
|
|
428
|
+
headers: config.headers ?? { 'Content-Type': 'application/json' },
|
|
429
|
+
batchSize: config.batchSize ?? 1,
|
|
430
|
+
timeout: config.timeout ?? 5000,
|
|
431
|
+
formatter: config.formatter ?? this.defaultFormatter
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Flush buffered logs to HTTP endpoint
|
|
436
|
+
*/
|
|
437
|
+
flush() {
|
|
438
|
+
if (this.logBuffer.length === 0) {
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
try {
|
|
442
|
+
const payload = this.config.formatter([...this.logBuffer]);
|
|
443
|
+
this.logBuffer = []; // Clear buffer
|
|
444
|
+
if (this.flushTimeout) {
|
|
445
|
+
clearTimeout(this.flushTimeout);
|
|
446
|
+
this.flushTimeout = null;
|
|
447
|
+
}
|
|
448
|
+
// Send HTTP request (using fetch if available, otherwise fall back)
|
|
449
|
+
this.sendHttpRequest(payload);
|
|
450
|
+
}
|
|
451
|
+
catch (error) {
|
|
452
|
+
console.error('HTTP flush failed:', error);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Send HTTP request with appropriate method based on environment
|
|
457
|
+
*/
|
|
458
|
+
sendHttpRequest(payload) {
|
|
459
|
+
// Try to use fetch (Node.js 18+ or browser)
|
|
460
|
+
if (typeof fetch !== 'undefined') {
|
|
461
|
+
fetch(this.config.url, {
|
|
462
|
+
method: this.config.method,
|
|
463
|
+
headers: this.config.headers,
|
|
464
|
+
body: JSON.stringify(payload),
|
|
465
|
+
signal: AbortSignal.timeout(this.config.timeout)
|
|
466
|
+
}).catch(error => {
|
|
467
|
+
console.error('HTTP request failed:', error);
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
// Fallback for older Node.js versions
|
|
472
|
+
this.sendHttpRequestNodeJS(payload);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Fallback HTTP implementation for Node.js environments without fetch
|
|
477
|
+
*/
|
|
478
|
+
sendHttpRequestNodeJS(payload) {
|
|
479
|
+
try {
|
|
480
|
+
const https = require('https');
|
|
481
|
+
const parsedUrl = new URL(this.config.url);
|
|
482
|
+
const isHttps = parsedUrl.protocol === 'https:';
|
|
483
|
+
// Security: Block HTTP (cleartext) connections by default
|
|
484
|
+
if (!isHttps) {
|
|
485
|
+
throw new Error('SECURITY ERROR: HTTP (cleartext) connections are not allowed for log transmission. Use HTTPS URLs only.');
|
|
486
|
+
}
|
|
487
|
+
const postData = JSON.stringify(payload);
|
|
488
|
+
const options = {
|
|
489
|
+
hostname: parsedUrl.hostname,
|
|
490
|
+
port: parsedUrl.port ? parseInt(parsedUrl.port, 10) : 443,
|
|
491
|
+
path: parsedUrl.pathname + parsedUrl.search,
|
|
492
|
+
method: this.config.method,
|
|
493
|
+
headers: {
|
|
494
|
+
...this.config.headers,
|
|
495
|
+
'Content-Length': Buffer.byteLength(postData)
|
|
496
|
+
},
|
|
497
|
+
timeout: this.config.timeout
|
|
498
|
+
};
|
|
499
|
+
const req = https.request(options, (res) => {
|
|
500
|
+
// Handle response (optional: log success/failure)
|
|
501
|
+
res.on('data', () => { }); // Consume response
|
|
502
|
+
res.on('end', () => { });
|
|
503
|
+
});
|
|
504
|
+
req.on('error', (error) => {
|
|
505
|
+
console.error('HTTP request failed:', error);
|
|
506
|
+
});
|
|
507
|
+
req.on('timeout', () => {
|
|
508
|
+
req.destroy();
|
|
509
|
+
console.error('HTTP request timed out');
|
|
510
|
+
});
|
|
511
|
+
req.write(postData);
|
|
512
|
+
req.end();
|
|
513
|
+
}
|
|
514
|
+
catch (error) {
|
|
515
|
+
console.error('HTTP request setup failed:', error);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Cleanup method to prevent memory leaks
|
|
520
|
+
*/
|
|
521
|
+
destroy() {
|
|
522
|
+
if (this.flushTimeout) {
|
|
523
|
+
clearTimeout(this.flushTimeout);
|
|
524
|
+
this.flushTimeout = null;
|
|
525
|
+
}
|
|
526
|
+
// Flush any remaining logs
|
|
527
|
+
this.flush();
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
exports.HttpOutputHandler = HttpOutputHandler;
|
|
531
|
+
/**
|
|
532
|
+
* Returns a logging handler function based on the specified type and configuration.
|
|
533
|
+
*
|
|
534
|
+
* Supported types are:
|
|
535
|
+
* - `'console'`: Logs to the console using the appropriate method for the log level.
|
|
536
|
+
* - `'silent'`: Returns a no-op handler that discards all logs.
|
|
537
|
+
* - `'file'`: Writes logs to a file with optional rotation; requires `filePath` in config.
|
|
538
|
+
* - `'http'`: Sends logs to a remote HTTP endpoint; requires `url` in config.
|
|
539
|
+
*
|
|
540
|
+
* If required configuration is missing or initialization fails, logs an error and returns either a fallback handler or `null`.
|
|
541
|
+
*
|
|
542
|
+
* @param type - The type of output handler to create (`'console'`, `'silent'`, `'file'`, or `'http'`)
|
|
543
|
+
* @returns A log handler function or `null` if the handler cannot be created
|
|
544
|
+
*/
|
|
545
|
+
function createBuiltInHandler(type, config) {
|
|
546
|
+
switch (type) {
|
|
547
|
+
case 'console':
|
|
548
|
+
return (level, message, data) => {
|
|
549
|
+
const method = level === 'error' ? 'error' : level === 'warn' ? 'warn' : 'log';
|
|
550
|
+
// Use safe method call to prevent object injection
|
|
551
|
+
if (Object.prototype.hasOwnProperty.call(console, method) && typeof console[method] === 'function') {
|
|
552
|
+
console[method](message, data);
|
|
553
|
+
}
|
|
554
|
+
else {
|
|
555
|
+
console.log(message, data);
|
|
556
|
+
}
|
|
557
|
+
};
|
|
558
|
+
case 'silent':
|
|
559
|
+
return () => { }; // No-op handler
|
|
560
|
+
case 'file':
|
|
561
|
+
if (config && typeof config.filePath === 'string') {
|
|
562
|
+
try {
|
|
563
|
+
const handler = new FileOutputHandler(config);
|
|
564
|
+
return handler.write;
|
|
565
|
+
}
|
|
566
|
+
catch (error) {
|
|
567
|
+
// Return a handler that logs the expected error message and falls back to console
|
|
568
|
+
return (level, message, data) => {
|
|
569
|
+
console.error('File output handler failed:', error);
|
|
570
|
+
console.log(`[${level.toUpperCase()}] ${message}`, data);
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
console.error('File output handler requires filePath in config');
|
|
575
|
+
return null;
|
|
576
|
+
case 'http':
|
|
577
|
+
if (config && typeof config.url === 'string') {
|
|
578
|
+
const handler = new HttpOutputHandler(config);
|
|
579
|
+
return handler.write;
|
|
580
|
+
}
|
|
581
|
+
console.error('HTTP output handler requires url in config');
|
|
582
|
+
return null;
|
|
583
|
+
default:
|
|
584
|
+
return null;
|
|
585
|
+
}
|
|
586
|
+
}
|
package/dist/logger/config.d.ts
CHANGED
|
@@ -11,32 +11,32 @@ export declare class LoggerConfigManager {
|
|
|
11
11
|
private config;
|
|
12
12
|
constructor();
|
|
13
13
|
/**
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
* Get current configuration
|
|
15
|
+
* @returns Current logger configuration
|
|
16
|
+
*/
|
|
17
17
|
getConfig(): LoggerConfig;
|
|
18
18
|
/**
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
* Updates logger configuration with new settings
|
|
20
|
+
* Merges provided config with existing settings (partial update)
|
|
21
|
+
* Supports backwards compatibility by mapping level to mode with deprecation warnings
|
|
22
|
+
* @param config - Partial configuration object to apply
|
|
23
|
+
*/
|
|
24
24
|
updateConfig(config: Partial<LoggerConfig>): void;
|
|
25
25
|
/**
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
* Handle legacy level-based configuration with deprecation warnings
|
|
27
|
+
* @param config - Configuration containing legacy level property
|
|
28
|
+
*/
|
|
29
29
|
private handleLegacyLevelConfig;
|
|
30
30
|
/**
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
* Map legacy LogLevel values to LogMode values
|
|
32
|
+
* @param levelValue - Legacy level value
|
|
33
|
+
* @returns Corresponding LogMode or undefined if invalid
|
|
34
|
+
*/
|
|
35
35
|
private mapLevelToMode;
|
|
36
36
|
/**
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
* Create deprecation warning message using LogFormatter
|
|
38
|
+
* Outputs formatted deprecation warning messages to console
|
|
39
|
+
*/
|
|
40
40
|
private createDeprecationWarning;
|
|
41
41
|
}
|
|
42
42
|
//# sourceMappingURL=config.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/logger/config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,YAAY,EAAqB,MAAM,UAAU,CAAC;AAG3D;;;GAGG;AACH,qBAAa,mBAAmB;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/logger/config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,YAAY,EAAqB,MAAM,UAAU,CAAC;AAG3D;;;GAGG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,MAAM,CAAe;;IAS7B;;;SAGK;IACL,SAAS,IAAI,YAAY;IAIzB;;;;;SAKK;IACL,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,IAAI;IAiBjD;;;SAGK;IACL,OAAO,CAAC,uBAAuB;IAsB/B;;;;SAIK;IACL,OAAO,CAAC,cAAc;IActB;;;SAGK;IACL,OAAO,CAAC,wBAAwB;CAQjC"}
|