@crimsonsunset/jsg-logger 1.6.0 → 1.7.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/index.js CHANGED
@@ -6,13 +6,38 @@
6
6
  import pino from 'pino';
7
7
  import {configManager} from './config/config-manager.js';
8
8
  import {COMPONENT_SCHEME} from './config/component-schemes.js';
9
- import defaultConfig from './config/default-config.json' with { type: 'json' };
10
- import {getEnvironment, isBrowser, isCLI, forceEnvironment} from './utils/environment-detector.js';
9
+ import defaultConfig from './config/default-config.json' with {type: 'json'};
10
+ import {forceEnvironment, getEnvironment, isBrowser, isCLI} from './utils/environment-detector.js';
11
11
  import {createBrowserFormatter} from './formatters/browser-formatter.js';
12
12
  import {createCLIFormatter} from './formatters/cli-formatter.js';
13
13
  import {createServerFormatter, getServerConfig} from './formatters/server-formatter.js';
14
14
  import {LogStore} from './stores/log-store.js';
15
15
 
16
+ // Check default config for devtools at module load time
17
+ // This allows bundlers to tree-shake if disabled
18
+ const defaultDevtoolsEnabled = defaultConfig.devtools?.enabled ?? false;
19
+
20
+ // Conditional static import - bundlers can eliminate if condition is false
21
+ // If defaultDevtoolsEnabled is false, bundler removes this entire block
22
+ // Use lazy initialization to avoid top-level await (which would make module async)
23
+ let devtoolsModule = null;
24
+ let devtoolsModulePromise = null;
25
+ if (defaultDevtoolsEnabled) {
26
+ console.log('[JSG-LOGGER] DevTools module pre-loading started (default config enabled)');
27
+ // Start loading immediately but don't await (non-blocking)
28
+ // Bundlers can still analyze this static import for tree-shaking
29
+ devtoolsModulePromise = import('./devtools/dist/panel-entry.js').then(module => {
30
+ devtoolsModule = module;
31
+ console.log('[JSG-LOGGER] DevTools module pre-loaded successfully');
32
+ return module;
33
+ }).catch(error => {
34
+ console.error('[JSG-LOGGER] DevTools module pre-load failed:', error);
35
+ return null;
36
+ });
37
+ } else {
38
+ console.log('[JSG-LOGGER] DevTools module NOT pre-loaded (default config disabled - will tree-shake)');
39
+ }
40
+
16
41
  /**
17
42
  * Main Logger Class
18
43
  * Manages logger instances and provides the public API
@@ -36,21 +61,39 @@ class JSGLogger {
36
61
  * @returns {Promise<Object>} Enhanced logger exports with controls API
37
62
  */
38
63
  static async getInstance(options = {}) {
64
+ const hasOptions = options && Object.keys(options).length > 0;
65
+
39
66
  if (!JSGLogger._instance) {
67
+ // First time initialization
40
68
  JSGLogger._instance = new JSGLogger();
41
69
  JSGLogger._enhancedLoggers = await JSGLogger._instance.init(options);
70
+
71
+ // Make runtime controls available globally in browser for debugging
72
+ if (isBrowser() && typeof window !== 'undefined' && JSGLogger._enhancedLoggers?.controls) {
73
+ window.JSG_Logger = JSGLogger._enhancedLoggers.controls;
74
+ }
75
+ } else if (hasOptions) {
76
+ // Instance exists but new options provided - reinitialize
77
+ JSGLogger._enhancedLoggers = await JSGLogger._instance.init(options);
78
+
79
+ // Make runtime controls available globally in browser for debugging
80
+ // (same as getInstanceSync behavior)
81
+ if (isBrowser() && typeof window !== 'undefined' && JSGLogger._enhancedLoggers?.controls) {
82
+ window.JSG_Logger = JSGLogger._enhancedLoggers.controls;
83
+ }
42
84
  }
85
+
43
86
  return JSGLogger._enhancedLoggers;
44
87
  }
45
88
 
46
89
  /**
47
90
  * Get singleton instance synchronously (for environments without async support)
48
- * @param {Object} options - Initialization options (only used on first call)
91
+ * @param {Object} options - Initialization options (only used on first call)
49
92
  * @returns {Object} Enhanced logger exports with controls API
50
93
  */
51
94
  static getInstanceSync(options = {}) {
52
95
  const hasOptions = options && Object.keys(options).length > 0;
53
-
96
+
54
97
  if (!JSGLogger._instance) {
55
98
  // First time initialization
56
99
  JSGLogger._instance = new JSGLogger();
@@ -59,7 +102,7 @@ class JSGLogger {
59
102
  // Instance exists but new options provided - reinitialize
60
103
  JSGLogger._enhancedLoggers = JSGLogger._instance.initSync(options);
61
104
  }
62
-
105
+
63
106
  return JSGLogger._enhancedLoggers;
64
107
  }
65
108
 
@@ -70,6 +113,8 @@ class JSGLogger {
70
113
  */
71
114
  async init(options = {}) {
72
115
  try {
116
+ console.log('[JSG-LOGGER] Initializing logger...', options.configPath ? `configPath: ${options.configPath}` : options.config ? 'inline config provided' : 'using defaults');
117
+
73
118
  // Load configuration FIRST (before environment detection)
74
119
  if (options.configPath || options.config) {
75
120
  await configManager.loadConfig(options.configPath || options.config);
@@ -132,8 +177,14 @@ class JSGLogger {
132
177
  try {
133
178
  // Load inline config if provided (sync loading for objects)
134
179
  if (options && Object.keys(options).length > 0) {
135
- // Merge inline config with existing config
136
- configManager.config = configManager.mergeConfigs(configManager.config, options);
180
+ // Reset to default config for clean reinitialization
181
+ // This ensures each reinit starts from a known state
182
+ configManager.config = {...defaultConfig};
183
+
184
+ // Normalize the inline config first
185
+ const normalizedOptions = configManager._normalizeConfigStructure(options);
186
+ // Merge inline config with default config
187
+ configManager.config = configManager.mergeConfigs(configManager.config, normalizedOptions);
137
188
  }
138
189
 
139
190
  // Apply forceEnvironment if specified in config
@@ -144,6 +195,10 @@ class JSGLogger {
144
195
  // NOW determine environment (after config is loaded and forceEnvironment is applied)
145
196
  this.environment = getEnvironment();
146
197
 
198
+ // Clear existing loggers for clean reinitialization
199
+ this.loggers = {};
200
+ this.components = {};
201
+
147
202
  // Create loggers for all available components using default config
148
203
  const components = configManager.getAvailableComponents();
149
204
 
@@ -287,7 +342,7 @@ class JSGLogger {
287
342
  addUtilityMethods() {
288
343
  // Store reference to original createLogger before overriding
289
344
  const originalCreateLogger = this.createLogger.bind(this);
290
-
345
+
291
346
  // Create logger on demand (reuses existing loggers)
292
347
  this.createLogger = (componentName) => {
293
348
  if (!this.loggers[componentName]) {
@@ -295,7 +350,7 @@ class JSGLogger {
295
350
  }
296
351
  return this.loggers[componentName];
297
352
  };
298
-
353
+
299
354
  // Store the original for internal use
300
355
  this._createLoggerOriginal = originalCreateLogger;
301
356
  }
@@ -412,6 +467,8 @@ class JSGLogger {
412
467
 
413
468
  // Statistics and debugging
414
469
  getStats: () => this.logStore.getStats(),
470
+ subscribe: (callback) => this.logStore.subscribe(callback),
471
+ clearLogs: () => this.logStore.clear(),
415
472
  getConfigSummary: () => configManager.getSummary(),
416
473
 
417
474
  // Advanced configuration
@@ -428,42 +485,64 @@ class JSGLogger {
428
485
 
429
486
  // DevTools panel controls
430
487
  enableDevPanel: async () => {
488
+ // Early config check - uses consumer's runtime config
489
+ const runtimeDevtoolsEnabled = configManager.config.devtools?.enabled ?? false;
490
+
491
+ console.log(`[JSG-LOGGER] enableDevPanel() called - runtime config: ${runtimeDevtoolsEnabled ? 'ENABLED' : 'DISABLED'}`);
492
+
493
+ if (!runtimeDevtoolsEnabled) {
494
+ console.warn('[JSG-LOGGER] DevTools disabled via config. Set devtools.enabled: true to enable.');
495
+ return null;
496
+ }
497
+
431
498
  if (typeof window === 'undefined') {
432
499
  console.warn('[JSG-LOGGER] DevTools panel only available in browser environments');
433
500
  return null;
434
501
  }
435
502
 
436
503
  try {
437
- // In development: import source files directly for hot reload
438
- // In production: import built bundle
439
- const isDev = import.meta.env?.DEV || window.location.hostname === 'localhost';
440
-
441
- let module;
442
- if (isDev) {
443
- console.log('🔥 DEV MODE: Attempting to load DevTools from SOURCE for hot reload');
444
- try {
445
- // Fix the import path for Vite dev server
446
- const importPath = '/src/panel-entry.jsx';
447
- console.log('🔍 Importing:', importPath);
448
- module = await import(importPath);
449
- console.log('✅ Source import successful:', module);
450
- } catch (sourceError) {
451
- console.error('❌ Source import failed, falling back to bundle:', sourceError);
452
- const cacheBuster = Date.now();
453
- module = await import(`./devtools/dist/panel-entry.js?v=${cacheBuster}`);
504
+ // Use pre-loaded module (from default config) or load dynamically (consumer's runtime config override)
505
+ if (!devtoolsModule) {
506
+ // Check if we have a promise for pre-loading
507
+ if (devtoolsModulePromise) {
508
+ console.log('[JSG-LOGGER] Waiting for pre-loaded DevTools module...');
509
+ // Wait for pre-load to complete
510
+ devtoolsModule = await devtoolsModulePromise;
511
+ } else {
512
+ // Runtime config override: consumer enabled devtools but default was disabled
513
+ // Load on demand via dynamic import
514
+ console.log('[JSG-LOGGER] Loading DevTools module dynamically (runtime config override)...');
515
+ devtoolsModule = await import('./devtools/dist/panel-entry.js');
454
516
  }
455
517
  } else {
456
- console.log('📦 PROD MODE: Loading DevTools from built bundle');
457
- const cacheBuster = Date.now();
458
- module = await import(`./devtools/dist/panel-entry.js?v=${cacheBuster}`);
518
+ console.log('[JSG-LOGGER] Using pre-loaded DevTools module');
459
519
  }
460
- return module.initializePanel();
520
+
521
+ if (!devtoolsModule || !devtoolsModule.initializePanel) {
522
+ throw new Error('DevTools panel module missing initializePanel export');
523
+ }
524
+
525
+ console.log('[JSG-LOGGER] Initializing DevTools panel...');
526
+ const panel = devtoolsModule.initializePanel();
527
+ console.log('[JSG-LOGGER] DevTools panel initialized successfully');
528
+ return panel;
461
529
  } catch (error) {
462
530
  console.error('[JSG-LOGGER] Failed to load DevTools panel:', error);
531
+ console.error('[JSG-LOGGER] If using npm link, ensure Vite config has: server.fs.allow: [\'..\']');
463
532
  return null;
464
533
  }
465
534
  },
466
535
 
536
+ disableDevPanel: () => {
537
+ if (typeof window !== 'undefined' && window.JSG_DevTools?.destroy) {
538
+ window.JSG_DevTools.destroy();
539
+ console.log('[JSG-LOGGER] DevTools panel disabled and destroyed');
540
+ return true;
541
+ }
542
+ console.warn('[JSG-LOGGER] DevTools panel not loaded or already destroyed');
543
+ return false;
544
+ },
545
+
467
546
  // System controls
468
547
  refresh: () => this.refreshLoggers(),
469
548
  reset: () => {
@@ -483,19 +562,19 @@ class JSGLogger {
483
562
  createDirectBrowserLogger(componentName) {
484
563
  const formatter = createBrowserFormatter(componentName, this.logStore);
485
564
  const levels = ['trace', 'debug', 'info', 'warn', 'error', 'fatal'];
486
- const levelMap = { trace: 10, debug: 20, info: 30, warn: 40, error: 50, fatal: 60 };
487
-
565
+ const levelMap = {trace: 10, debug: 20, info: 30, warn: 40, error: 50, fatal: 60};
566
+
488
567
  const logger = {};
489
-
568
+
490
569
  levels.forEach(level => {
491
570
  logger[level] = (first, ...args) => {
492
571
  const logLevel = levelMap[level];
493
-
572
+
494
573
  // Check if level should be logged
495
574
  const effectiveLevel = configManager.getEffectiveLevel(componentName);
496
575
  const minLevel = levelMap[effectiveLevel] || 30;
497
576
  if (logLevel < minLevel) return;
498
-
577
+
499
578
  // Create log data object
500
579
  let logData = {
501
580
  level: logLevel,
@@ -503,7 +582,7 @@ class JSGLogger {
503
582
  name: componentName,
504
583
  v: 1
505
584
  };
506
-
585
+
507
586
  // Handle different argument patterns
508
587
  if (typeof first === 'string') {
509
588
  logData.msg = first;
@@ -519,18 +598,18 @@ class JSGLogger {
519
598
  logData.msg = args[0];
520
599
  }
521
600
  }
522
-
601
+
523
602
  // Use our beautiful formatter
524
603
  formatter.write(JSON.stringify(logData));
525
604
  };
526
605
  });
527
-
606
+
528
607
  // Add Pino-compatible properties
529
608
  logger._componentEmoji = configManager.getComponentConfig(componentName).emoji;
530
609
  logger._componentName = componentName;
531
610
  logger._effectiveLevel = configManager.getEffectiveLevel(componentName);
532
611
  logger.level = configManager.getEffectiveLevel(componentName);
533
-
612
+
534
613
  return logger;
535
614
  }
536
615
 
@@ -580,11 +659,11 @@ class JSGLogger {
580
659
  */
581
660
  _createAutoDiscoveryGetters() {
582
661
  this.components = {};
583
-
662
+
584
663
  Object.keys(this.loggers).forEach(name => {
585
664
  // Original kebab-case name
586
665
  this.components[name] = () => this.getComponent(name);
587
-
666
+
588
667
  // camelCase convenience getter
589
668
  const camelName = name.replace(/-([a-z])/g, (match, letter) => letter.toUpperCase());
590
669
  if (camelName !== name) {
@@ -604,17 +683,17 @@ class JSGLogger {
604
683
  // Check if this is a configured component or custom component
605
684
  const hasConfig = configManager.config.components?.[componentName];
606
685
  const hasScheme = COMPONENT_SCHEME[componentName];
607
-
686
+
608
687
  if (!hasConfig && !hasScheme) {
609
688
  // Custom component - log info and auto-create
610
689
  if (this.loggers.core) {
611
690
  this.loggers.core.debug(`Auto-creating custom component logger: ${componentName}`);
612
691
  }
613
692
  }
614
-
693
+
615
694
  // Create the logger (getComponentConfig will auto-generate config for custom components)
616
695
  this.loggers[componentName] = this.createLogger(componentName);
617
-
696
+
618
697
  // Also add to auto-discovery getters
619
698
  this.components[componentName] = () => this.getComponent(componentName);
620
699
  const camelName = componentName.replace(/-([a-z])/g, (match, letter) => letter.toUpperCase());
@@ -622,7 +701,7 @@ class JSGLogger {
622
701
  this.components[camelName] = () => this.getComponent(componentName);
623
702
  }
624
703
  }
625
-
704
+
626
705
  return this.loggers[componentName];
627
706
  }
628
707
 
@@ -635,7 +714,7 @@ class JSGLogger {
635
714
  _createErrorLogger(componentName) {
636
715
  const prefix = `[${componentName.toUpperCase()}]`;
637
716
  const errorMsg = '⚠️ Component not configured -';
638
-
717
+
639
718
  return {
640
719
  trace: (msg, ...args) => console.log(`${prefix} ${errorMsg}`, msg, ...args),
641
720
  debug: (msg, ...args) => console.log(`${prefix} ${errorMsg}`, msg, ...args),
@@ -658,7 +737,7 @@ class JSGLogger {
658
737
  const instance = await JSGLogger.getInstance();
659
738
  const logger = instance.getComponent(component);
660
739
  const duration = performance.now() - startTime;
661
-
740
+
662
741
  if (duration > 1000) {
663
742
  logger.warn(`${operation} took ${duration.toFixed(2)}ms (slow)`);
664
743
  } else if (duration > 100) {
@@ -666,7 +745,7 @@ class JSGLogger {
666
745
  } else {
667
746
  logger.debug(`${operation} took ${duration.toFixed(2)}ms (fast)`);
668
747
  }
669
-
748
+
670
749
  return duration;
671
750
  } catch (error) {
672
751
  // Fallback to console if logger fails
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@crimsonsunset/jsg-logger",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "type": "module",
5
- "description": "JSG Logger - Multi-environment logger with smart detection, file-level overrides, and beautiful console formatting",
5
+ "description": "Multi-environment logger with smart detection, file-level overrides, and beautiful console formatting. Test it live: https://logger.joesangiorgio.com/",
6
6
  "main": "index.js",
7
7
  "keywords": [
8
8
  "logging",
@@ -27,13 +27,25 @@
27
27
  },
28
28
  "homepage": "https://github.com/crimsonsunset/jsg-logger#readme",
29
29
  "dependencies": {
30
- "@preact/preset-vite": "^2.10.2",
31
- "evergreen-ui": "^7.1.9",
32
30
  "lodash.merge": "^4.6.2",
33
- "pino": "^9.7.0",
34
- "preact": "^10.27.1"
31
+ "pino": "^9.7.0"
32
+ },
33
+ "peerDependencies": {
34
+ "preact": "^10.27.1",
35
+ "evergreen-ui": "^7.1.9"
36
+ },
37
+ "peerDependenciesMeta": {
38
+ "preact": {
39
+ "optional": true
40
+ },
41
+ "evergreen-ui": {
42
+ "optional": true
43
+ }
35
44
  },
36
45
  "devDependencies": {
46
+ "@preact/preset-vite": "^2.10.2",
47
+ "evergreen-ui": "^7.1.9",
48
+ "preact": "^10.27.1",
37
49
  "vite": "^5.1.3"
38
50
  },
39
51
  "files": [
@@ -44,6 +56,7 @@
44
56
  "utils/",
45
57
  "examples/",
46
58
  "docs/",
59
+ "devtools/dist/",
47
60
  "README.md",
48
61
  "CHANGELOG.md",
49
62
  "CONTRIBUTING.md",
@@ -62,25 +75,23 @@
62
75
  "./stores/log-store": "./stores/log-store.js",
63
76
  "./utils": "./utils/environment-detector.js",
64
77
  "./utils/environment": "./utils/environment-detector.js",
78
+ "./devtools": "./devtools/dist/panel-entry.js",
65
79
  "./examples/*": "./examples/*"
66
80
  },
67
81
  "scripts": {
68
82
  "test": "node tests/run-all.js",
69
83
  "test:watch": "nodemon --watch . --ext js --exec 'npm test'",
70
84
  "dev": "vite",
71
- "dev:devtools": "vite",
85
+ "dev:devtools": "cd devtools && npm run dev",
72
86
  "test:devtools": "vite",
73
87
  "build:devtools": "cd devtools && npm run build",
74
- "release:patch": "npm version patch && npm publish --access public",
75
- "release:minor": "npm version minor && npm publish --access public",
76
- "release:major": "npm version major && npm publish --access public",
77
- "release": "npm run release:patch",
88
+ "build:devtools:netlify": "npm install && cd devtools && npm install && npm run build:netlify",
89
+ "preview:devtools": "cd devtools && npm run preview",
90
+ "release:patch": "node scripts/release.js patch",
91
+ "release:minor": "node scripts/release.js minor",
92
+ "release:major": "node scripts/release.js major",
78
93
  "publish:public": "npm publish --access public",
79
94
  "publish:github": "npm publish --registry=https://npm.pkg.github.com/ --access public",
80
- "publish:dual": "npm publish --access public && npm publish --registry=https://npm.pkg.github.com/ --access public",
81
- "release:dual:patch": "npm version patch && npm run publish:dual",
82
- "release:dual:minor": "npm version minor && npm run publish:dual",
83
- "release:dual:major": "npm version major && npm run publish:dual",
84
95
  "check": "npm run test && echo 'Package ready for publishing'"
85
96
  }
86
97
  }