@crimsonsunset/jsg-logger 1.0.8
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/CHANGELOG.md +55 -0
- package/CONTRIBUTING.md +58 -0
- package/LICENSE +15 -0
- package/README.md +428 -0
- package/config/component-schemes.js +26 -0
- package/config/config-manager.js +378 -0
- package/config/default-config.json +92 -0
- package/docs/next-session.md +136 -0
- package/docs/roadmap.md +404 -0
- package/examples/advanced-config.json +151 -0
- package/formatters/browser-formatter.js +227 -0
- package/formatters/cli-formatter.js +43 -0
- package/formatters/server-formatter.js +42 -0
- package/index.js +413 -0
- package/package.json +74 -0
- package/stores/log-store.js +148 -0
- package/utils/environment-detector.js +38 -0
package/index.js
ADDED
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CACP Portable Logger System
|
|
3
|
+
* Main entry point for the smart adaptive logging system
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import pino from 'pino';
|
|
7
|
+
import {configManager} from './config/config-manager.js';
|
|
8
|
+
import {COMPONENT_SCHEME} from './config/component-schemes.js';
|
|
9
|
+
import defaultConfig from './config/default-config.json';
|
|
10
|
+
import {getEnvironment, isBrowser, isCLI} from './utils/environment-detector.js';
|
|
11
|
+
import {createBrowserFormatter} from './formatters/browser-formatter.js';
|
|
12
|
+
import {createCLIFormatter} from './formatters/cli-formatter.js';
|
|
13
|
+
import {createServerFormatter, getServerConfig} from './formatters/server-formatter.js';
|
|
14
|
+
import {LogStore} from './stores/log-store.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Main Logger Class
|
|
18
|
+
* Manages logger instances and provides the public API
|
|
19
|
+
*/
|
|
20
|
+
class CACPLogger {
|
|
21
|
+
constructor() {
|
|
22
|
+
this.loggers = {};
|
|
23
|
+
this.logStore = new LogStore();
|
|
24
|
+
this.environment = getEnvironment();
|
|
25
|
+
this.initialized = false;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Initialize the logger system
|
|
30
|
+
* @param {Object} options - Initialization options
|
|
31
|
+
* @returns {Promise<Object>} Logger instance with all components
|
|
32
|
+
*/
|
|
33
|
+
async init(options = {}) {
|
|
34
|
+
try {
|
|
35
|
+
// Load configuration
|
|
36
|
+
if (options.configPath || options.config) {
|
|
37
|
+
await configManager.loadConfig(options.configPath || options.config);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Create loggers for all available components
|
|
41
|
+
const components = configManager.getAvailableComponents();
|
|
42
|
+
|
|
43
|
+
components.forEach(componentName => {
|
|
44
|
+
this.loggers[componentName] = this.createLogger(componentName);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Create legacy compatibility aliases
|
|
48
|
+
this.createAliases();
|
|
49
|
+
|
|
50
|
+
// Add utility methods
|
|
51
|
+
this.addUtilityMethods();
|
|
52
|
+
|
|
53
|
+
this.initialized = true;
|
|
54
|
+
|
|
55
|
+
// Log initialization success
|
|
56
|
+
if (this.loggers.cacp) {
|
|
57
|
+
this.loggers.cacp.info('CACP Logger initialized', {
|
|
58
|
+
environment: this.environment,
|
|
59
|
+
components: components.length,
|
|
60
|
+
projectName: configManager.getProjectName(),
|
|
61
|
+
configPaths: configManager.loadedPaths,
|
|
62
|
+
fileOverrides: Object.keys(configManager.config.fileOverrides || {}).length
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return this.getLoggerExports();
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.error('CACP Logger initialization failed:', error);
|
|
69
|
+
// Return minimal fallback logger
|
|
70
|
+
return this.createFallbackLogger();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Initialize synchronously with default configuration
|
|
76
|
+
* @returns {Object} Logger instance with all components
|
|
77
|
+
*/
|
|
78
|
+
initSync() {
|
|
79
|
+
try {
|
|
80
|
+
// Create loggers for all available components using default config
|
|
81
|
+
const components = configManager.getAvailableComponents();
|
|
82
|
+
|
|
83
|
+
components.forEach(componentName => {
|
|
84
|
+
this.loggers[componentName] = this.createLogger(componentName);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Create legacy compatibility aliases
|
|
88
|
+
this.createAliases();
|
|
89
|
+
|
|
90
|
+
// Add utility methods
|
|
91
|
+
this.addUtilityMethods();
|
|
92
|
+
|
|
93
|
+
this.initialized = true;
|
|
94
|
+
|
|
95
|
+
// Log initialization success
|
|
96
|
+
if (this.loggers.cacp) {
|
|
97
|
+
this.loggers.cacp.info('CACP Logger initialized (sync)', {
|
|
98
|
+
environment: this.environment,
|
|
99
|
+
components: components.length,
|
|
100
|
+
projectName: configManager.getProjectName(),
|
|
101
|
+
fileOverrides: Object.keys(configManager.config.fileOverrides || {}).length,
|
|
102
|
+
timestampMode: configManager.getTimestampMode()
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return this.getLoggerExports();
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.error('CACP Logger sync initialization failed:', error);
|
|
109
|
+
// Return minimal fallback logger
|
|
110
|
+
return this.createFallbackLogger();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Create a logger for a specific component
|
|
116
|
+
* @param {string} componentName - Component identifier
|
|
117
|
+
* @returns {Object} Pino logger instance
|
|
118
|
+
*/
|
|
119
|
+
createLogger(componentName) {
|
|
120
|
+
const component = configManager.getComponentConfig(componentName);
|
|
121
|
+
|
|
122
|
+
let stream;
|
|
123
|
+
let config = {
|
|
124
|
+
name: componentName,
|
|
125
|
+
level: configManager.getEffectiveLevel(componentName) // Use smart level resolution
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
if (isBrowser()) {
|
|
129
|
+
// Browser environment - bypass Pino, use direct console formatting
|
|
130
|
+
return this.createDirectBrowserLogger(componentName);
|
|
131
|
+
} else if (isCLI()) {
|
|
132
|
+
// CLI environment - use pino-colada or pino-pretty
|
|
133
|
+
stream = createCLIFormatter();
|
|
134
|
+
config.prettyPrint = true;
|
|
135
|
+
} else {
|
|
136
|
+
// Server/production environment - structured JSON
|
|
137
|
+
stream = createServerFormatter();
|
|
138
|
+
config = {...config, ...getServerConfig()};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const logger = stream ? pino(config, stream) : pino(config);
|
|
142
|
+
|
|
143
|
+
// Add component emoji to logger for easy identification
|
|
144
|
+
logger._componentEmoji = component.emoji;
|
|
145
|
+
logger._componentName = component.name;
|
|
146
|
+
logger._effectiveLevel = configManager.getEffectiveLevel(componentName);
|
|
147
|
+
|
|
148
|
+
return logger;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Create legacy compatibility aliases
|
|
153
|
+
* @private
|
|
154
|
+
*/
|
|
155
|
+
createAliases() {
|
|
156
|
+
// Legacy compatibility for existing codebase
|
|
157
|
+
this.loggers.siteDetector = this.loggers['site-detector'];
|
|
158
|
+
this.loggers.priorityManager = this.loggers['priority-manager'];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Add utility methods to the logger exports
|
|
163
|
+
* @private
|
|
164
|
+
*/
|
|
165
|
+
addUtilityMethods() {
|
|
166
|
+
// Create logger on demand
|
|
167
|
+
this.createLogger = (componentName) => {
|
|
168
|
+
if (!this.loggers[componentName]) {
|
|
169
|
+
this.loggers[componentName] = this.createLogger(componentName);
|
|
170
|
+
}
|
|
171
|
+
return this.loggers[componentName];
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Get the object to export with all loggers and utilities
|
|
177
|
+
* @returns {Object} Complete logger exports
|
|
178
|
+
*/
|
|
179
|
+
getLoggerExports() {
|
|
180
|
+
return {
|
|
181
|
+
// All component loggers
|
|
182
|
+
...this.loggers,
|
|
183
|
+
|
|
184
|
+
// Utility methods
|
|
185
|
+
createLogger: (componentName) => {
|
|
186
|
+
if (!this.loggers[componentName]) {
|
|
187
|
+
this.loggers[componentName] = this.createLogger(componentName);
|
|
188
|
+
}
|
|
189
|
+
return this.loggers[componentName];
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
// Configuration and debugging
|
|
193
|
+
config: {
|
|
194
|
+
environment: this.environment,
|
|
195
|
+
components: COMPONENT_SCHEME,
|
|
196
|
+
summary: configManager.getSummary()
|
|
197
|
+
},
|
|
198
|
+
|
|
199
|
+
// Expose config manager for runtime configuration
|
|
200
|
+
configManager: configManager,
|
|
201
|
+
|
|
202
|
+
// Log store for popup/debugging
|
|
203
|
+
logStore: this.logStore,
|
|
204
|
+
|
|
205
|
+
// Enhanced runtime controls with all new features
|
|
206
|
+
controls: {
|
|
207
|
+
// Level controls
|
|
208
|
+
setLevel: (component, level) => {
|
|
209
|
+
if (this.loggers[component]) {
|
|
210
|
+
this.loggers[component].level = level;
|
|
211
|
+
this.loggers[component]._effectiveLevel = level;
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
getLevel: (component) => {
|
|
215
|
+
return this.loggers[component]?._effectiveLevel;
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
// Component controls
|
|
219
|
+
listComponents: () => Object.keys(this.loggers),
|
|
220
|
+
enableDebugMode: () => {
|
|
221
|
+
Object.keys(this.loggers).forEach(component => {
|
|
222
|
+
this.controls.setLevel(component, 'debug');
|
|
223
|
+
});
|
|
224
|
+
},
|
|
225
|
+
enableTraceMode: () => {
|
|
226
|
+
Object.keys(this.loggers).forEach(component => {
|
|
227
|
+
this.controls.setLevel(component, 'trace');
|
|
228
|
+
});
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
// File override controls
|
|
232
|
+
addFileOverride: (filePath, overrideConfig) => {
|
|
233
|
+
configManager.addFileOverride(filePath, overrideConfig);
|
|
234
|
+
// Refresh affected loggers
|
|
235
|
+
this.refreshLoggers();
|
|
236
|
+
},
|
|
237
|
+
removeFileOverride: (filePath) => {
|
|
238
|
+
if (configManager.config.fileOverrides) {
|
|
239
|
+
delete configManager.config.fileOverrides[filePath];
|
|
240
|
+
this.refreshLoggers();
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
listFileOverrides: () => {
|
|
244
|
+
return Object.keys(configManager.config.fileOverrides || {});
|
|
245
|
+
},
|
|
246
|
+
|
|
247
|
+
// Timestamp controls
|
|
248
|
+
setTimestampMode: (mode) => {
|
|
249
|
+
configManager.config.timestampMode = mode;
|
|
250
|
+
},
|
|
251
|
+
getTimestampMode: () => {
|
|
252
|
+
return configManager.getTimestampMode();
|
|
253
|
+
},
|
|
254
|
+
getTimestampModes: () => {
|
|
255
|
+
return ['absolute', 'readable', 'relative', 'disable'];
|
|
256
|
+
},
|
|
257
|
+
|
|
258
|
+
// Display controls
|
|
259
|
+
setDisplayOption: (option, enabled) => {
|
|
260
|
+
if (!configManager.config.display) {
|
|
261
|
+
configManager.config.display = {};
|
|
262
|
+
}
|
|
263
|
+
configManager.config.display[option] = enabled;
|
|
264
|
+
},
|
|
265
|
+
getDisplayConfig: () => {
|
|
266
|
+
return configManager.getDisplayConfig();
|
|
267
|
+
},
|
|
268
|
+
toggleDisplayOption: (option) => {
|
|
269
|
+
const current = configManager.getDisplayConfig();
|
|
270
|
+
this.controls.setDisplayOption(option, !current[option]);
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
// Statistics and debugging
|
|
274
|
+
getStats: () => this.logStore.getStats(),
|
|
275
|
+
getConfigSummary: () => configManager.getSummary(),
|
|
276
|
+
|
|
277
|
+
// Advanced configuration
|
|
278
|
+
setComponentLevel: (component, level) => {
|
|
279
|
+
if (!configManager.config.components[component]) {
|
|
280
|
+
configManager.config.components[component] = {};
|
|
281
|
+
}
|
|
282
|
+
configManager.config.components[component].level = level;
|
|
283
|
+
this.refreshLoggers();
|
|
284
|
+
},
|
|
285
|
+
getComponentLevel: (component) => {
|
|
286
|
+
return configManager.config.components?.[component]?.level;
|
|
287
|
+
},
|
|
288
|
+
|
|
289
|
+
// System controls
|
|
290
|
+
refresh: () => this.refreshLoggers(),
|
|
291
|
+
reset: () => {
|
|
292
|
+
configManager.config = {...defaultConfig};
|
|
293
|
+
this.refreshLoggers();
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Create a direct browser logger that bypasses Pino for full control
|
|
301
|
+
* @param {string} componentName - Component identifier
|
|
302
|
+
* @returns {Object} Logger-like object with standard methods
|
|
303
|
+
* @private
|
|
304
|
+
*/
|
|
305
|
+
createDirectBrowserLogger(componentName) {
|
|
306
|
+
const formatter = createBrowserFormatter(componentName, this.logStore);
|
|
307
|
+
const levels = ['trace', 'debug', 'info', 'warn', 'error', 'fatal'];
|
|
308
|
+
const levelMap = { trace: 10, debug: 20, info: 30, warn: 40, error: 50, fatal: 60 };
|
|
309
|
+
|
|
310
|
+
const logger = {};
|
|
311
|
+
|
|
312
|
+
levels.forEach(level => {
|
|
313
|
+
logger[level] = (first, ...args) => {
|
|
314
|
+
const logLevel = levelMap[level];
|
|
315
|
+
|
|
316
|
+
// Check if level should be logged
|
|
317
|
+
const effectiveLevel = configManager.getEffectiveLevel(componentName);
|
|
318
|
+
const minLevel = levelMap[effectiveLevel] || 30;
|
|
319
|
+
if (logLevel < minLevel) return;
|
|
320
|
+
|
|
321
|
+
// Create log data object
|
|
322
|
+
let logData = {
|
|
323
|
+
level: logLevel,
|
|
324
|
+
time: Date.now(),
|
|
325
|
+
name: componentName,
|
|
326
|
+
v: 1
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
// Handle different argument patterns
|
|
330
|
+
if (typeof first === 'string') {
|
|
331
|
+
logData.msg = first;
|
|
332
|
+
// Add additional args as context
|
|
333
|
+
if (args.length === 1 && typeof args[0] === 'object') {
|
|
334
|
+
Object.assign(logData, args[0]);
|
|
335
|
+
} else if (args.length > 0) {
|
|
336
|
+
logData.args = args;
|
|
337
|
+
}
|
|
338
|
+
} else if (typeof first === 'object') {
|
|
339
|
+
Object.assign(logData, first);
|
|
340
|
+
if (args.length > 0 && typeof args[0] === 'string') {
|
|
341
|
+
logData.msg = args[0];
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Use our beautiful formatter
|
|
346
|
+
formatter.write(JSON.stringify(logData));
|
|
347
|
+
};
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// Add Pino-compatible properties
|
|
351
|
+
logger._componentEmoji = configManager.getComponentConfig(componentName).emoji;
|
|
352
|
+
logger._componentName = componentName;
|
|
353
|
+
logger._effectiveLevel = configManager.getEffectiveLevel(componentName);
|
|
354
|
+
logger.level = configManager.getEffectiveLevel(componentName);
|
|
355
|
+
|
|
356
|
+
return logger;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Refresh all loggers with updated configuration
|
|
361
|
+
* @private
|
|
362
|
+
*/
|
|
363
|
+
refreshLoggers() {
|
|
364
|
+
Object.keys(this.loggers).forEach(componentName => {
|
|
365
|
+
this.loggers[componentName] = this.createLogger(componentName);
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Create fallback logger for error scenarios
|
|
371
|
+
* @private
|
|
372
|
+
*/
|
|
373
|
+
createFallbackLogger() {
|
|
374
|
+
const fallback = {
|
|
375
|
+
info: console.log,
|
|
376
|
+
debug: console.log,
|
|
377
|
+
trace: console.log,
|
|
378
|
+
warn: console.warn,
|
|
379
|
+
error: console.error,
|
|
380
|
+
fatal: console.error
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
return {
|
|
384
|
+
cacp: fallback,
|
|
385
|
+
createLogger: () => fallback,
|
|
386
|
+
config: {environment: 'fallback'},
|
|
387
|
+
logStore: {getRecent: () => [], clear: () => {}},
|
|
388
|
+
controls: {
|
|
389
|
+
setLevel: () => {},
|
|
390
|
+
getLevel: () => 'info',
|
|
391
|
+
listComponents: () => [],
|
|
392
|
+
enableDebugMode: () => {},
|
|
393
|
+
getStats: () => ({total: 0})
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Create singleton instance
|
|
400
|
+
const logger = new CACPLogger();
|
|
401
|
+
|
|
402
|
+
// Initialize synchronously with default config for immediate use
|
|
403
|
+
// (Chrome extensions and other environments that don't support top-level await)
|
|
404
|
+
const enhancedLoggers = logger.initSync();
|
|
405
|
+
|
|
406
|
+
// Make runtime controls available globally in browser for debugging
|
|
407
|
+
if (isBrowser() && typeof window !== 'undefined') {
|
|
408
|
+
window.CACP_Logger = enhancedLoggers.controls;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Export both the initialized loggers and the init function for advanced usage
|
|
412
|
+
export default enhancedLoggers;
|
|
413
|
+
export {logger as CACPLogger};
|
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@crimsonsunset/jsg-logger",
|
|
3
|
+
"version": "1.0.8",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "JSG Logger - Multi-environment logger with smart detection, file-level overrides, and beautiful console formatting",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"logging",
|
|
9
|
+
"pino",
|
|
10
|
+
"browser",
|
|
11
|
+
"cli",
|
|
12
|
+
"server",
|
|
13
|
+
"chrome-extension",
|
|
14
|
+
"adaptive",
|
|
15
|
+
"jsg-logger",
|
|
16
|
+
"environment-detection",
|
|
17
|
+
"file-overrides"
|
|
18
|
+
],
|
|
19
|
+
"author": "Joe Sangiorgio <jsangio1@gmail.com>",
|
|
20
|
+
"license": "ISC",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/crimsonsunset/jsg-logger.git"
|
|
24
|
+
},
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/crimsonsunset/jsg-logger/issues"
|
|
27
|
+
},
|
|
28
|
+
"homepage": "https://github.com/crimsonsunset/jsg-logger#readme",
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"pino": "^9.7.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"pino-colada": "^2.2.2"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"pino-colada": "^2.2.2"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"index.js",
|
|
40
|
+
"config/",
|
|
41
|
+
"formatters/",
|
|
42
|
+
"stores/",
|
|
43
|
+
"utils/",
|
|
44
|
+
"examples/",
|
|
45
|
+
"docs/",
|
|
46
|
+
"README.md",
|
|
47
|
+
"CHANGELOG.md",
|
|
48
|
+
"CONTRIBUTING.md",
|
|
49
|
+
"LICENSE"
|
|
50
|
+
],
|
|
51
|
+
"exports": {
|
|
52
|
+
".": "./index.js",
|
|
53
|
+
"./config": "./config/config-manager.js",
|
|
54
|
+
"./config/manager": "./config/config-manager.js",
|
|
55
|
+
"./config/schemes": "./config/component-schemes.js",
|
|
56
|
+
"./config/default": "./config/default-config.json",
|
|
57
|
+
"./formatters/browser": "./formatters/browser-formatter.js",
|
|
58
|
+
"./formatters/cli": "./formatters/cli-formatter.js",
|
|
59
|
+
"./formatters/server": "./formatters/server-formatter.js",
|
|
60
|
+
"./stores": "./stores/log-store.js",
|
|
61
|
+
"./stores/log-store": "./stores/log-store.js",
|
|
62
|
+
"./utils": "./utils/environment-detector.js",
|
|
63
|
+
"./utils/environment": "./utils/environment-detector.js",
|
|
64
|
+
"./examples/*": "./examples/*"
|
|
65
|
+
},
|
|
66
|
+
"scripts": {
|
|
67
|
+
"release:patch": "npm version patch && npm publish --access public",
|
|
68
|
+
"release:minor": "npm version minor && npm publish --access public",
|
|
69
|
+
"release:major": "npm version major && npm publish --access public",
|
|
70
|
+
"release": "npm run release:patch",
|
|
71
|
+
"publish:public": "npm publish --access public",
|
|
72
|
+
"check": "npm run test && echo 'Package ready for publishing'"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Log Store for In-Memory Log Management
|
|
3
|
+
* Particularly useful for Chrome extension popup debugging
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export class LogStore {
|
|
7
|
+
constructor(maxLogs = 100) {
|
|
8
|
+
this.logs = [];
|
|
9
|
+
this.maxLogs = maxLogs;
|
|
10
|
+
this.subscribers = [];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Add a log entry to the store
|
|
15
|
+
* @param {Object} logData - Structured log data
|
|
16
|
+
*/
|
|
17
|
+
add(logData) {
|
|
18
|
+
const logEntry = {
|
|
19
|
+
...logData,
|
|
20
|
+
id: Date.now() + Math.random(),
|
|
21
|
+
timestamp: logData.time || Date.now()
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
this.logs.unshift(logEntry);
|
|
25
|
+
|
|
26
|
+
// Maintain max size
|
|
27
|
+
if (this.logs.length > this.maxLogs) {
|
|
28
|
+
this.logs.pop();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Notify subscribers
|
|
32
|
+
this.subscribers.forEach(callback => {
|
|
33
|
+
try {
|
|
34
|
+
callback(logEntry, this.logs);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error('LogStore subscriber error:', error);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get recent log entries
|
|
43
|
+
* @param {number} count - Number of logs to return
|
|
44
|
+
* @returns {Array} Recent log entries
|
|
45
|
+
*/
|
|
46
|
+
getRecent(count = 20) {
|
|
47
|
+
return this.logs.slice(0, count);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get logs filtered by component
|
|
52
|
+
* @param {string} component - Component name to filter by
|
|
53
|
+
* @param {number} count - Max number of logs to return
|
|
54
|
+
* @returns {Array} Filtered log entries
|
|
55
|
+
*/
|
|
56
|
+
getByComponent(component, count = 20) {
|
|
57
|
+
return this.logs
|
|
58
|
+
.filter(log => log.name === component)
|
|
59
|
+
.slice(0, count);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get logs filtered by level
|
|
64
|
+
* @param {number} level - Minimum log level (10=trace, 60=fatal)
|
|
65
|
+
* @param {number} count - Max number of logs to return
|
|
66
|
+
* @returns {Array} Filtered log entries
|
|
67
|
+
*/
|
|
68
|
+
getByLevel(level, count = 20) {
|
|
69
|
+
return this.logs
|
|
70
|
+
.filter(log => log.level >= level)
|
|
71
|
+
.slice(0, count);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Clear all stored logs
|
|
76
|
+
*/
|
|
77
|
+
clear() {
|
|
78
|
+
this.logs = [];
|
|
79
|
+
this.subscribers.forEach(callback => {
|
|
80
|
+
try {
|
|
81
|
+
callback(null, []);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.error('LogStore subscriber error:', error);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Subscribe to log updates
|
|
90
|
+
* @param {Function} callback - Called when new logs are added
|
|
91
|
+
* @returns {Function} Unsubscribe function
|
|
92
|
+
*/
|
|
93
|
+
subscribe(callback) {
|
|
94
|
+
this.subscribers.push(callback);
|
|
95
|
+
|
|
96
|
+
// Return unsubscribe function
|
|
97
|
+
return () => {
|
|
98
|
+
const index = this.subscribers.indexOf(callback);
|
|
99
|
+
if (index > -1) {
|
|
100
|
+
this.subscribers.splice(index, 1);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Get summary statistics
|
|
107
|
+
* @returns {Object} Log statistics
|
|
108
|
+
*/
|
|
109
|
+
getStats() {
|
|
110
|
+
const stats = {
|
|
111
|
+
total: this.logs.length,
|
|
112
|
+
byLevel: {},
|
|
113
|
+
byComponent: {},
|
|
114
|
+
timeRange: null
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
if (this.logs.length > 0) {
|
|
118
|
+
stats.timeRange = {
|
|
119
|
+
start: this.logs[this.logs.length - 1].timestamp,
|
|
120
|
+
end: this.logs[0].timestamp
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
this.logs.forEach(log => {
|
|
124
|
+
// Level stats
|
|
125
|
+
const levelName = this.getLevelName(log.level);
|
|
126
|
+
stats.byLevel[levelName] = (stats.byLevel[levelName] || 0) + 1;
|
|
127
|
+
|
|
128
|
+
// Component stats
|
|
129
|
+
const component = log.name || 'unknown';
|
|
130
|
+
stats.byComponent[component] = (stats.byComponent[component] || 0) + 1;
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return stats;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Helper to get level name from level number
|
|
139
|
+
* @private
|
|
140
|
+
*/
|
|
141
|
+
getLevelName(level) {
|
|
142
|
+
const levelMap = {
|
|
143
|
+
10: 'trace', 20: 'debug', 30: 'info',
|
|
144
|
+
40: 'warn', 50: 'error', 60: 'fatal'
|
|
145
|
+
};
|
|
146
|
+
return levelMap[level] || 'unknown';
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment Detection Utilities
|
|
3
|
+
* Smart detection of browser, CLI, and server environments
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Check if running in browser environment
|
|
8
|
+
* @returns {boolean}
|
|
9
|
+
*/
|
|
10
|
+
export const isBrowser = () => {
|
|
11
|
+
return typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Check if running in CLI/terminal environment
|
|
16
|
+
* @returns {boolean}
|
|
17
|
+
*/
|
|
18
|
+
export const isCLI = () => {
|
|
19
|
+
return typeof process !== 'undefined' && process.stdout && process.stdout.isTTY;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Check if running in server/production environment
|
|
24
|
+
* @returns {boolean}
|
|
25
|
+
*/
|
|
26
|
+
export const isServer = () => {
|
|
27
|
+
return !isBrowser() && !isCLI();
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get current environment type
|
|
32
|
+
* @returns {'browser'|'cli'|'server'}
|
|
33
|
+
*/
|
|
34
|
+
export const getEnvironment = () => {
|
|
35
|
+
if (isBrowser()) return 'browser';
|
|
36
|
+
if (isCLI()) return 'cli';
|
|
37
|
+
return 'server';
|
|
38
|
+
};
|