mastercontroller 1.2.12 → 1.2.14
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/.claude/settings.local.json +12 -0
- package/MasterAction.js +297 -73
- package/MasterControl.js +112 -19
- package/MasterHtml.js +101 -14
- package/MasterRouter.js +281 -66
- package/MasterTemplate.js +96 -3
- package/README.md +0 -44
- package/error/ErrorBoundary.js +353 -0
- package/error/HydrationMismatch.js +265 -0
- package/error/MasterBackendErrorHandler.js +769 -0
- package/{MasterError.js → error/MasterError.js} +2 -2
- package/error/MasterErrorHandler.js +487 -0
- package/error/MasterErrorLogger.js +360 -0
- package/error/MasterErrorMiddleware.js +407 -0
- package/error/SSRErrorHandler.js +273 -0
- package/monitoring/MasterCache.js +400 -0
- package/monitoring/MasterMemoryMonitor.js +188 -0
- package/monitoring/MasterProfiler.js +409 -0
- package/monitoring/PerformanceMonitor.js +233 -0
- package/package.json +3 -3
- package/security/CSPConfig.js +319 -0
- package/security/EventHandlerValidator.js +464 -0
- package/security/MasterSanitizer.js +429 -0
- package/security/MasterValidator.js +546 -0
- package/security/SecurityMiddleware.js +486 -0
- package/security/SessionSecurity.js +416 -0
- package/ssr/hydration-client.js +93 -0
- package/ssr/runtime-ssr.cjs +553 -0
- package/ssr/ssr-shims.js +73 -0
- package/examples/FileServingExample.js +0 -88
|
@@ -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
|
+
};
|