mastercontroller 1.3.6 → 1.3.7

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.
@@ -0,0 +1,360 @@
1
+ /**
2
+ * MasterErrorLogger - Error logging infrastructure
3
+ * Supports multiple logging backends and monitoring service integration
4
+ * Version: 1.0.1
5
+ */
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+
10
+ // Log levels
11
+ const LOG_LEVELS = {
12
+ DEBUG: 0,
13
+ INFO: 1,
14
+ WARN: 2,
15
+ ERROR: 3,
16
+ FATAL: 4
17
+ };
18
+
19
+ const LOG_LEVEL_NAMES = {
20
+ 0: 'DEBUG',
21
+ 1: 'INFO',
22
+ 2: 'WARN',
23
+ 3: 'ERROR',
24
+ 4: 'FATAL'
25
+ };
26
+
27
+ class MasterErrorLogger {
28
+ constructor(options = {}) {
29
+ this.options = {
30
+ level: options.level || (process.env.NODE_ENV === 'production' ? LOG_LEVELS.WARN : LOG_LEVELS.DEBUG),
31
+ console: options.console !== false,
32
+ file: options.file || null,
33
+ sampleRate: options.sampleRate || 1.0, // Log 100% by default
34
+ maxFileSize: options.maxFileSize || 10 * 1024 * 1024, // 10MB
35
+ ...options
36
+ };
37
+
38
+ this.backends = [];
39
+ this.errorCount = 0;
40
+ this.sessionId = this._generateSessionId();
41
+
42
+ // Setup default backends
43
+ if (this.options.console) {
44
+ this.backends.push(this._consoleBackend.bind(this));
45
+ }
46
+
47
+ if (this.options.file) {
48
+ this.backends.push(this._fileBackend.bind(this));
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Add custom logging backend
54
+ */
55
+ addBackend(backend) {
56
+ if (typeof backend === 'function') {
57
+ this.backends.push(backend);
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Log an error
63
+ */
64
+ log(data = {}) {
65
+ const level = typeof data.level === 'string'
66
+ ? LOG_LEVELS[data.level.toUpperCase()] || LOG_LEVELS.ERROR
67
+ : data.level || LOG_LEVELS.ERROR;
68
+
69
+ // Check log level threshold
70
+ if (level < this.options.level) {
71
+ return;
72
+ }
73
+
74
+ // Apply sampling (don't log everything in production)
75
+ if (Math.random() > this.options.sampleRate) {
76
+ return;
77
+ }
78
+
79
+ const entry = this._formatLogEntry(data, level);
80
+
81
+ // Send to all backends
82
+ this.backends.forEach(backend => {
83
+ try {
84
+ backend(entry);
85
+ } catch (error) {
86
+ console.error('[MasterErrorLogger] Backend failed:', error.message);
87
+ }
88
+ });
89
+
90
+ this.errorCount++;
91
+ }
92
+
93
+ /**
94
+ * Convenience methods for different log levels
95
+ */
96
+ debug(data) {
97
+ this.log({ ...data, level: LOG_LEVELS.DEBUG });
98
+ }
99
+
100
+ info(data) {
101
+ this.log({ ...data, level: LOG_LEVELS.INFO });
102
+ }
103
+
104
+ warn(data) {
105
+ this.log({ ...data, level: LOG_LEVELS.WARN });
106
+ }
107
+
108
+ error(data) {
109
+ this.log({ ...data, level: LOG_LEVELS.ERROR });
110
+ }
111
+
112
+ fatal(data) {
113
+ this.log({ ...data, level: LOG_LEVELS.FATAL });
114
+ }
115
+
116
+ /**
117
+ * Format log entry with metadata
118
+ */
119
+ _formatLogEntry(data, level) {
120
+ return {
121
+ timestamp: new Date().toISOString(),
122
+ sessionId: this.sessionId,
123
+ level: LOG_LEVEL_NAMES[level],
124
+ code: data.code || 'UNKNOWN',
125
+ message: data.message || 'No message provided',
126
+ component: data.component || null,
127
+ file: data.file || null,
128
+ line: data.line || null,
129
+ route: data.route || null,
130
+ context: data.context || {},
131
+ stack: data.stack || null,
132
+ originalError: data.originalError ? {
133
+ message: data.originalError.message,
134
+ stack: data.originalError.stack
135
+ } : null,
136
+ environment: process.env.NODE_ENV || 'development',
137
+ nodeVersion: process.version,
138
+ platform: process.platform,
139
+ memory: process.memoryUsage(),
140
+ uptime: process.uptime()
141
+ };
142
+ }
143
+
144
+ /**
145
+ * Console backend
146
+ */
147
+ _consoleBackend(entry) {
148
+ const levelColors = {
149
+ DEBUG: '\x1b[36m', // Cyan
150
+ INFO: '\x1b[32m', // Green
151
+ WARN: '\x1b[33m', // Yellow
152
+ ERROR: '\x1b[31m', // Red
153
+ FATAL: '\x1b[35m' // Magenta
154
+ };
155
+
156
+ const color = levelColors[entry.level] || '';
157
+ const reset = '\x1b[0m';
158
+
159
+ const logFn = entry.level === 'DEBUG' || entry.level === 'INFO' ? console.log :
160
+ entry.level === 'WARN' ? console.warn : console.error;
161
+
162
+ logFn(
163
+ `${color}[${entry.timestamp}] [${entry.level}]${reset} ${entry.code}:`,
164
+ entry.message
165
+ );
166
+
167
+ if (entry.component) {
168
+ logFn(` Component: ${entry.component}`);
169
+ }
170
+
171
+ if (entry.file) {
172
+ logFn(` File: ${entry.file}${entry.line ? `:${entry.line}` : ''}`);
173
+ }
174
+
175
+ if (entry.stack && process.env.NODE_ENV !== 'production') {
176
+ logFn(` Stack: ${entry.stack}`);
177
+ }
178
+ }
179
+
180
+ /**
181
+ * File backend
182
+ */
183
+ _fileBackend(entry) {
184
+ if (!this.options.file) return;
185
+
186
+ try {
187
+ const logDir = path.dirname(this.options.file);
188
+
189
+ // Create log directory if it doesn't exist
190
+ if (!fs.existsSync(logDir)) {
191
+ fs.mkdirSync(logDir, { recursive: true });
192
+ }
193
+
194
+ // Check file size and rotate if needed
195
+ if (fs.existsSync(this.options.file)) {
196
+ const stats = fs.statSync(this.options.file);
197
+ if (stats.size > this.options.maxFileSize) {
198
+ this._rotateLogFile();
199
+ }
200
+ }
201
+
202
+ // Append log entry as JSON line
203
+ const logLine = JSON.stringify(entry) + '\n';
204
+ fs.appendFileSync(this.options.file, logLine, 'utf8');
205
+
206
+ } catch (error) {
207
+ console.error('[MasterErrorLogger] File logging failed:', error.message);
208
+ }
209
+ }
210
+
211
+ /**
212
+ * Rotate log file when it gets too large
213
+ */
214
+ _rotateLogFile() {
215
+ try {
216
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
217
+ const ext = path.extname(this.options.file);
218
+ const base = path.basename(this.options.file, ext);
219
+ const dir = path.dirname(this.options.file);
220
+ const rotatedFile = path.join(dir, `${base}-${timestamp}${ext}`);
221
+
222
+ fs.renameSync(this.options.file, rotatedFile);
223
+
224
+ // Keep only last 5 rotated files
225
+ const files = fs.readdirSync(dir)
226
+ .filter(f => f.startsWith(base) && f !== path.basename(this.options.file))
227
+ .sort()
228
+ .reverse();
229
+
230
+ files.slice(5).forEach(f => {
231
+ try {
232
+ fs.unlinkSync(path.join(dir, f));
233
+ } catch (_) {}
234
+ });
235
+
236
+ } catch (error) {
237
+ console.error('[MasterErrorLogger] Log rotation failed:', error.message);
238
+ }
239
+ }
240
+
241
+ /**
242
+ * Generate unique session ID
243
+ */
244
+ _generateSessionId() {
245
+ return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
246
+ }
247
+
248
+ /**
249
+ * Get logger statistics
250
+ */
251
+ getStats() {
252
+ return {
253
+ sessionId: this.sessionId,
254
+ errorCount: this.errorCount,
255
+ sampleRate: this.options.sampleRate,
256
+ backends: this.backends.length,
257
+ uptime: process.uptime()
258
+ };
259
+ }
260
+
261
+ /**
262
+ * Reset error count
263
+ */
264
+ resetStats() {
265
+ this.errorCount = 0;
266
+ this.sessionId = this._generateSessionId();
267
+ }
268
+ }
269
+
270
+ /**
271
+ * Sentry integration helper
272
+ */
273
+ function createSentryBackend(sentryInstance) {
274
+ return (entry) => {
275
+ if (entry.level === 'ERROR' || entry.level === 'FATAL') {
276
+ sentryInstance.captureException(new Error(entry.message), {
277
+ level: entry.level.toLowerCase(),
278
+ tags: {
279
+ code: entry.code,
280
+ component: entry.component,
281
+ sessionId: entry.sessionId
282
+ },
283
+ extra: {
284
+ file: entry.file,
285
+ line: entry.line,
286
+ route: entry.route,
287
+ context: entry.context,
288
+ originalError: entry.originalError
289
+ }
290
+ });
291
+ }
292
+ };
293
+ }
294
+
295
+ /**
296
+ * LogRocket integration helper
297
+ */
298
+ function createLogRocketBackend(logRocketInstance) {
299
+ return (entry) => {
300
+ if (entry.level === 'ERROR' || entry.level === 'FATAL') {
301
+ logRocketInstance.captureException(new Error(entry.message), {
302
+ tags: {
303
+ code: entry.code,
304
+ component: entry.component
305
+ },
306
+ extra: entry
307
+ });
308
+ }
309
+ };
310
+ }
311
+
312
+ /**
313
+ * Custom webhook backend
314
+ */
315
+ function createWebhookBackend(webhookUrl) {
316
+ return async (entry) => {
317
+ try {
318
+ const https = require('https');
319
+ const http = require('http');
320
+ const url = new URL(webhookUrl);
321
+ const client = url.protocol === 'https:' ? https : http;
322
+
323
+ const data = JSON.stringify(entry);
324
+
325
+ const options = {
326
+ hostname: url.hostname,
327
+ port: url.port,
328
+ path: url.pathname + url.search,
329
+ method: 'POST',
330
+ headers: {
331
+ 'Content-Type': 'application/json',
332
+ 'Content-Length': data.length
333
+ }
334
+ };
335
+
336
+ const req = client.request(options);
337
+ req.write(data);
338
+ req.end();
339
+
340
+ } catch (error) {
341
+ console.error('[MasterErrorLogger] Webhook failed:', error.message);
342
+ }
343
+ };
344
+ }
345
+
346
+ // Singleton instance
347
+ const logger = new MasterErrorLogger({
348
+ console: true,
349
+ file: process.env.MC_LOG_FILE || path.join(process.cwd(), 'log', 'mastercontroller.log'),
350
+ sampleRate: parseFloat(process.env.MC_LOG_SAMPLE_RATE || '1.0')
351
+ });
352
+
353
+ module.exports = {
354
+ MasterErrorLogger,
355
+ logger,
356
+ LOG_LEVELS,
357
+ createSentryBackend,
358
+ createLogRocketBackend,
359
+ createWebhookBackend
360
+ };