@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/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
+ };