From 901676c05211bfa87ad4ef45a480117892914655 Mon Sep 17 00:00:00 2001 From: David Enete Date: Thu, 2 Apr 2026 16:07:23 -0400 Subject: [PATCH 1/5] [improvement-854] Added variable debug message functionality for messages sent to the console. This overrides current console.* and also provides for future scoped logger.* functionality. --- src/debug/DebugProvider.jsx | 18 ++++ src/debug/README.md | 161 +++++++++++++++++++++++++++++++++++ src/debug/consoleOverride.js | 50 +++++++++++ src/debug/debugConfig.js | 25 ++++++ src/debug/hooks.js | 29 +++++++ src/debug/index.js | 2 + src/debug/logger.js | 29 +++++++ src/main.jsx | 9 +- 8 files changed, 320 insertions(+), 3 deletions(-) create mode 100644 src/debug/DebugProvider.jsx create mode 100644 src/debug/README.md create mode 100644 src/debug/consoleOverride.js create mode 100644 src/debug/debugConfig.js create mode 100644 src/debug/hooks.js create mode 100644 src/debug/index.js create mode 100644 src/debug/logger.js diff --git a/src/debug/DebugProvider.jsx b/src/debug/DebugProvider.jsx new file mode 100644 index 00000000..ba6b23ae --- /dev/null +++ b/src/debug/DebugProvider.jsx @@ -0,0 +1,18 @@ +import React, { createContext, useMemo, useEffect } from 'react'; +import { getDebugConfig } from './debugConfig'; +import { createLogger } from './logger'; +import { overrideConsole } from './consoleOverride'; + +export const DebugContext = createContext(null); + +export function DebugProvider({ children }) { + const config = useMemo(() => getDebugConfig(), []); + const logger = useMemo(() => createLogger(config), [config]); + + // Hybrid strategy: override console globally + useEffect(() => { + overrideConsole(config); + }, [config]); + + return {children}; +} diff --git a/src/debug/README.md b/src/debug/README.md new file mode 100644 index 00000000..e1ae6ce8 --- /dev/null +++ b/src/debug/README.md @@ -0,0 +1,161 @@ +# Debug Logging System (Querystring + Scoped Logging) + +## Overview + +Logging is controlled via querystring parameters: + +``` +?log=&scope= +``` + +A **page reload is required** after changing values. + +--- + +# Supported Log Levels + +| Level | Behavior | +| ----- | ------------------------ | +| none | No logs | +| error | Errors only | +| warn | Warnings + errors | +| info | Info + warn + error | +| debug | Debug + everything above | +| all | Everything | + +--- + +# Scoped Logging + +Scope allow you to filter logs by component or module. + +``` +?log=debug&scope=UserProfile +``` + +Multiple scopes: + +``` +?log=debug&scope=UserProfile,Navbar +``` + +--- + +# Querystring Examples + +## Log Levels + +``` +?log=none +?log=error +?log=warn +?log=info +?log=debug +?log=all +``` + +## Scoped Examples + +``` +?log=debug&scope=UserProfile +``` + +``` +?log=debug&scope=Navbar,Sidebar +``` + +## Practical Combinations + +### Debug one component only + +``` +?log=debug&scope=UserProfile +``` + +### Debug multiple UI areas + +``` +?log=debug&scope=Navbar,Dashboard +``` + +### Only show API errors + +``` +?log=error&scope=API +``` + +### Default (silent) + +``` +(no params) → behaves like ?log=none +``` + +--- + +# Usage in Components + +## Basic Logger + +```jsx +import { useDebug } from '../debug'; + +function Example() { + const { logger } = useDebug(); + + logger.info('App started'); + + return null; +} +``` + +--- + +## Scoped Logger (Recommended) + +```jsx +import { useLogger } from '../debug'; + +function UserProfile({ user }) { + const logger = useLogger('UserProfile'); + + logger.debug('render start'); + + if (!user) { + logger.warn('missing user'); + return null; + } + + logger.info('rendering user', user.id); + + return
{user.name}
; +} +``` + +--- + +# Replacing console.\* + +| Before | After | +| --------------------------------- | ------------------------------ | +| `jsconsole.log('loaded');` | `logger.debug('loaded');` | +| `jsconsole.warn('missing data');` | `logger.warn('missing data');` | +| `jsconsole.error('failed');` | `logger.error('failed');` | + +--- + +# Scoped Logging Example Output + +With: + +``` +?log=debug&scope=UserProfile +``` + +Console output: + +``` +[UserProfile] render start +[UserProfile] rendering user 123 +``` + +Other components will NOT log. diff --git a/src/debug/consoleOverride.js b/src/debug/consoleOverride.js new file mode 100644 index 00000000..1b8267b5 --- /dev/null +++ b/src/debug/consoleOverride.js @@ -0,0 +1,50 @@ +let originalConsole = {}; + +export function overrideConsole(config) { + // Save original console once + if (!originalConsole.log) { + originalConsole = { + log: console.log, + info: console.info, + warn: console.warn, + error: console.error, + }; + } + + const noop = () => {}; + + // Allow everything + if (config.logLevel === 'all') { + restoreConsole(); + return; + } + + // Disable everything + if (config.logLevel === 'none') { + console.log = noop; + console.info = noop; + console.warn = noop; + console.error = noop; + return; + } + + const order = ['error', 'warn', 'info', 'log']; + const allowedIndex = ['none', 'error', 'warn', 'info', 'debug', 'all'].indexOf(config.logLevel); + + order.forEach((method, index) => { + if (index > allowedIndex) { + console[method] = noop; + } else { + console[method] = originalConsole[method]; + } + }); +} + +export function restoreConsole() { + if (!originalConsole.log) return; + + console.log = originalConsole.log; + console.info = originalConsole.info; + console.warn = originalConsole.warn; + console.error = originalConsole.error; +} diff --git a/src/debug/debugConfig.js b/src/debug/debugConfig.js new file mode 100644 index 00000000..aa024e7a --- /dev/null +++ b/src/debug/debugConfig.js @@ -0,0 +1,25 @@ +export function getDebugConfig() { + const params = new URLSearchParams(window.location.search); + + const parseSet = (key) => + new Set( + (params.get(key) || '') + .split(',') + .map((v) => v.trim()) + .filter(Boolean), + ); + + return { + logLevel: params.get('log') || 'none', + logScopes: parseSet('logScope'), + + features: parseSet('feature'), + disable: parseSet('disable'), + debug: parseSet('debug'), + + role: params.get('role') || null, + state: params.get('state') || null, + + perf: params.get('perf') === 'true', + }; +} diff --git a/src/debug/hooks.js b/src/debug/hooks.js new file mode 100644 index 00000000..4db43c68 --- /dev/null +++ b/src/debug/hooks.js @@ -0,0 +1,29 @@ +import { useContext } from 'react'; +import { DebugContext } from './DebugProvider.jsx'; + +export function useDebug() { + const ctx = useContext(DebugContext); + if (!ctx) throw new Error('Must be used inside DebugProvider'); + return ctx; +} + +export function useLogger() { + return useDebug().logger; +} + +export function useFeature(name) { + const { config } = useDebug(); + return config.features.has(name) && !config.disable.has(name); +} + +export function useDebugFlag(name) { + return useDebug().config.debug.has(name); +} + +export function useRole() { + return useDebug().config.role; +} + +export function useForcedState() { + return useDebug().config.state; +} diff --git a/src/debug/index.js b/src/debug/index.js new file mode 100644 index 00000000..d0049a26 --- /dev/null +++ b/src/debug/index.js @@ -0,0 +1,2 @@ +export * from './DebugProvider.jsx'; +export * from './hooks.js'; diff --git a/src/debug/logger.js b/src/debug/logger.js new file mode 100644 index 00000000..ffc4f41c --- /dev/null +++ b/src/debug/logger.js @@ -0,0 +1,29 @@ +export function createLogger(config) { + const levels = ['error', 'warn', 'info', 'debug']; + + const shouldLog = (level, scope) => { + if (config.logLevel === 'none') return false; + + if (config.logScopes.size && scope && !config.logScopes.has(scope)) { + return false; + } + + if (config.logLevel === 'all') return true; + + return levels.indexOf(level) <= levels.indexOf(config.logLevel); + }; + + const log = + (level) => + (scope, ...args) => { + if (!shouldLog(level, scope)) return; + console[level](`[${scope}]`, ...args); + }; + + return { + error: log('error'), + warn: log('warn'), + info: log('info'), + debug: log('debug'), + }; +} diff --git a/src/main.jsx b/src/main.jsx index d20721cf..79ae4553 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -5,6 +5,7 @@ import ErrorBoundary from './components/ErrorBoundary'; import { QRZProvider, setupMapQRZHandler } from './components/CallsignLink'; import './styles/main.css'; import './lang/i18n'; +import { DebugProvider } from './debug'; // Global click handler for QRZ links in Leaflet HTML popups setupMapQRZHandler(); @@ -12,9 +13,11 @@ setupMapQRZHandler(); ReactDOM.createRoot(document.getElementById('root')).render( - - - + + + + + , ); From 01f3269530ccd650d2eef1af8abba6a115a1dd5c Mon Sep 17 00:00:00 2001 From: David Enete Date: Fri, 3 Apr 2026 09:53:23 -0400 Subject: [PATCH 2/5] [improvement-854] quick edit on readme --- src/debug/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/debug/README.md b/src/debug/README.md index e1ae6ce8..2850b86c 100644 --- a/src/debug/README.md +++ b/src/debug/README.md @@ -135,11 +135,11 @@ function UserProfile({ user }) { # Replacing console.\* -| Before | After | -| --------------------------------- | ------------------------------ | -| `jsconsole.log('loaded');` | `logger.debug('loaded');` | -| `jsconsole.warn('missing data');` | `logger.warn('missing data');` | -| `jsconsole.error('failed');` | `logger.error('failed');` | +| Before | After | +| ------------------------------- | ------------------------------ | +| `console.log('loaded');` | `logger.debug('loaded');` | +| `console.warn('missing data');` | `logger.warn('missing data');` | +| `console.error('failed');` | `logger.error('failed');` | --- From adc8a2b27f57a2e7a795b8f994c329228974fac6 Mon Sep 17 00:00:00 2001 From: David Enete Date: Fri, 3 Apr 2026 11:42:14 -0400 Subject: [PATCH 3/5] [improvement-854] PR feedback changes, removed logger functionality, addressed other comments --- src/debug/DebugProvider.jsx | 15 +--- src/debug/README.md | 157 ++++++----------------------------- src/debug/consoleOverride.js | 68 ++++++--------- src/debug/debugConfig.js | 23 ++--- src/debug/hooks.js | 29 ------- src/debug/index.js | 2 - src/debug/logger.js | 29 ------- 7 files changed, 59 insertions(+), 264 deletions(-) delete mode 100644 src/debug/hooks.js delete mode 100644 src/debug/index.js delete mode 100644 src/debug/logger.js diff --git a/src/debug/DebugProvider.jsx b/src/debug/DebugProvider.jsx index ba6b23ae..625f9d44 100644 --- a/src/debug/DebugProvider.jsx +++ b/src/debug/DebugProvider.jsx @@ -1,18 +1,11 @@ -import React, { createContext, useMemo, useEffect } from 'react'; +import React from 'react'; import { getDebugConfig } from './debugConfig'; -import { createLogger } from './logger'; import { overrideConsole } from './consoleOverride'; -export const DebugContext = createContext(null); - export function DebugProvider({ children }) { - const config = useMemo(() => getDebugConfig(), []); - const logger = useMemo(() => createLogger(config), [config]); + const config = getDebugConfig(); - // Hybrid strategy: override console globally - useEffect(() => { - overrideConsole(config); - }, [config]); + overrideConsole(config); - return {children}; + return children; } diff --git a/src/debug/README.md b/src/debug/README.md index 2850b86c..97a95439 100644 --- a/src/debug/README.md +++ b/src/debug/README.md @@ -1,161 +1,54 @@ -# Debug Logging System (Querystring + Scoped Logging) +# Debug Logging in the Console ## Overview -Logging is controlled via querystring parameters: +Logging is controlled via a querystring parameter: ``` -?log=&scope= +?log= ``` -A **page reload is required** after changing values. +This system works by overriding `console.*` methods at app startup. --- # Supported Log Levels -| Level | Behavior | -| ----- | ------------------------ | -| none | No logs | -| error | Errors only | -| warn | Warnings + errors | -| info | Info + warn + error | -| debug | Debug + everything above | -| all | Everything | +| Level | Behavior | +| ----- | --------------------------- | +| none | No logs | +| error | Errors only | +| warn | Warnings + errors (default) | +| info | Info + warn + error | +| debug | All logs | +| all | All logs | --- -# Scoped Logging +# Notes -Scope allow you to filter logs by component or module. +## Default Behavior -``` -?log=debug&scope=UserProfile -``` - -Multiple scopes: - -``` -?log=debug&scope=UserProfile,Navbar -``` - ---- - -# Querystring Examples - -## Log Levels - -``` -?log=none -?log=error -?log=warn -?log=info -?log=debug -?log=all -``` - -## Scoped Examples - -``` -?log=debug&scope=UserProfile -``` - -``` -?log=debug&scope=Navbar,Sidebar -``` - -## Practical Combinations - -### Debug one component only - -``` -?log=debug&scope=UserProfile -``` - -### Debug multiple UI areas - -``` -?log=debug&scope=Navbar,Dashboard -``` - -### Only show API errors - -``` -?log=error&scope=API -``` - -### Default (silent) - -``` -(no params) → behaves like ?log=none -``` - ---- - -# Usage in Components - -## Basic Logger - -```jsx -import { useDebug } from '../debug'; - -function Example() { - const { logger } = useDebug(); - - logger.info('App started'); - - return null; -} -``` +- Defaults to `warn` +- Ensures important issues are always visible --- -## Scoped Logger (Recommended) - -```jsx -import { useLogger } from '../debug'; - -function UserProfile({ user }) { - const logger = useLogger('UserProfile'); - - logger.debug('render start'); +## Page Reload Required - if (!user) { - logger.warn('missing user'); - return null; - } - - logger.info('rendering user', user.id); - - return
{user.name}
; -} -``` +Changing the querystring requires a manual refresh. --- -# Replacing console.\* +## Global Impact -| Before | After | -| ------------------------------- | ------------------------------ | -| `console.log('loaded');` | `logger.debug('loaded');` | -| `console.warn('missing data');` | `logger.warn('missing data');` | -| `console.error('failed');` | `logger.error('failed');` | +This override affects **all console calls globally**, including third-party libraries. --- -# Scoped Logging Example Output - -With: - -``` -?log=debug&scope=UserProfile -``` - -Console output: - -``` -[UserProfile] render start -[UserProfile] rendering user 123 -``` +# `console.*` considerations -Other components will NOT log. +- Use `console.error` for real failures +- Use `console.warn` for unexpected but non-breaking issues +- Use `console.log` for debugging only +- Avoid leaving excessive logs in production code diff --git a/src/debug/consoleOverride.js b/src/debug/consoleOverride.js index 1b8267b5..a5c69a83 100644 --- a/src/debug/consoleOverride.js +++ b/src/debug/consoleOverride.js @@ -1,50 +1,32 @@ -let originalConsole = {}; +const LEVEL_MAP = { + none: 0, + error: 1, + warn: 2, + info: 3, + debug: 4, + all: 4, +}; + +const METHOD_LEVEL = { + error: 1, + warn: 2, + info: 3, + log: 4, +}; export function overrideConsole(config) { - // Save original console once - if (!originalConsole.log) { - originalConsole = { - log: console.log, - info: console.info, - warn: console.warn, - error: console.error, - }; - } + const allowedLevel = LEVEL_MAP[config.logLevel]; - const noop = () => {}; + const originalConsole = { + error: console.error.bind(console), + warn: console.warn.bind(console), + info: console.info.bind(console), + log: console.log.bind(console), + }; - // Allow everything - if (config.logLevel === 'all') { - restoreConsole(); - return; - } + Object.keys(METHOD_LEVEL).forEach((method) => { + const methodLevel = METHOD_LEVEL[method]; - // Disable everything - if (config.logLevel === 'none') { - console.log = noop; - console.info = noop; - console.warn = noop; - console.error = noop; - return; - } - - const order = ['error', 'warn', 'info', 'log']; - const allowedIndex = ['none', 'error', 'warn', 'info', 'debug', 'all'].indexOf(config.logLevel); - - order.forEach((method, index) => { - if (index > allowedIndex) { - console[method] = noop; - } else { - console[method] = originalConsole[method]; - } + console[method] = methodLevel <= allowedLevel ? originalConsole[method] : () => {}; }); } - -export function restoreConsole() { - if (!originalConsole.log) return; - - console.log = originalConsole.log; - console.info = originalConsole.info; - console.warn = originalConsole.warn; - console.error = originalConsole.error; -} diff --git a/src/debug/debugConfig.js b/src/debug/debugConfig.js index aa024e7a..8b3d4c1a 100644 --- a/src/debug/debugConfig.js +++ b/src/debug/debugConfig.js @@ -1,25 +1,12 @@ export function getDebugConfig() { const params = new URLSearchParams(window.location.search); - const parseSet = (key) => - new Set( - (params.get(key) || '') - .split(',') - .map((v) => v.trim()) - .filter(Boolean), - ); + const level = params.get('log'); - return { - logLevel: params.get('log') || 'none', - logScopes: parseSet('logScope'), - - features: parseSet('feature'), - disable: parseSet('disable'), - debug: parseSet('debug'), + const validLevels = ['none', 'error', 'warn', 'info', 'debug', 'all']; - role: params.get('role') || null, - state: params.get('state') || null, - - perf: params.get('perf') === 'true', + // warn will be the default + return { + logLevel: validLevels.includes(level) ? level : 'warn', }; } diff --git a/src/debug/hooks.js b/src/debug/hooks.js deleted file mode 100644 index 4db43c68..00000000 --- a/src/debug/hooks.js +++ /dev/null @@ -1,29 +0,0 @@ -import { useContext } from 'react'; -import { DebugContext } from './DebugProvider.jsx'; - -export function useDebug() { - const ctx = useContext(DebugContext); - if (!ctx) throw new Error('Must be used inside DebugProvider'); - return ctx; -} - -export function useLogger() { - return useDebug().logger; -} - -export function useFeature(name) { - const { config } = useDebug(); - return config.features.has(name) && !config.disable.has(name); -} - -export function useDebugFlag(name) { - return useDebug().config.debug.has(name); -} - -export function useRole() { - return useDebug().config.role; -} - -export function useForcedState() { - return useDebug().config.state; -} diff --git a/src/debug/index.js b/src/debug/index.js deleted file mode 100644 index d0049a26..00000000 --- a/src/debug/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export * from './DebugProvider.jsx'; -export * from './hooks.js'; diff --git a/src/debug/logger.js b/src/debug/logger.js deleted file mode 100644 index ffc4f41c..00000000 --- a/src/debug/logger.js +++ /dev/null @@ -1,29 +0,0 @@ -export function createLogger(config) { - const levels = ['error', 'warn', 'info', 'debug']; - - const shouldLog = (level, scope) => { - if (config.logLevel === 'none') return false; - - if (config.logScopes.size && scope && !config.logScopes.has(scope)) { - return false; - } - - if (config.logLevel === 'all') return true; - - return levels.indexOf(level) <= levels.indexOf(config.logLevel); - }; - - const log = - (level) => - (scope, ...args) => { - if (!shouldLog(level, scope)) return; - console[level](`[${scope}]`, ...args); - }; - - return { - error: log('error'), - warn: log('warn'), - info: log('info'), - debug: log('debug'), - }; -} From 1be258c3643b25a90bcc696bc524678a615b9922 Mon Sep 17 00:00:00 2001 From: David Enete Date: Fri, 3 Apr 2026 11:45:34 -0400 Subject: [PATCH 4/5] [improvement-854] fixed path to debug provider --- src/main.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.jsx b/src/main.jsx index 79ae4553..7f6dd464 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -5,7 +5,7 @@ import ErrorBoundary from './components/ErrorBoundary'; import { QRZProvider, setupMapQRZHandler } from './components/CallsignLink'; import './styles/main.css'; import './lang/i18n'; -import { DebugProvider } from './debug'; +import { DebugProvider } from './debug/DebugProvider'; // Global click handler for QRZ links in Leaflet HTML popups setupMapQRZHandler(); From 337af96ee90f1a73d80a627c8ca23fc0fc0f2b00 Mon Sep 17 00:00:00 2001 From: David Enete Date: Sun, 5 Apr 2026 15:40:55 -0400 Subject: [PATCH 5/5] [improvement-854] removed provider pattern, added console.debug and console.trace to overridden methods --- src/debug/DebugProvider.jsx | 11 ----------- src/debug/consoleOverride.js | 4 ++++ src/debug/debugConfig.js | 2 -- src/main.jsx | 13 +++++++------ 4 files changed, 11 insertions(+), 19 deletions(-) delete mode 100644 src/debug/DebugProvider.jsx diff --git a/src/debug/DebugProvider.jsx b/src/debug/DebugProvider.jsx deleted file mode 100644 index 625f9d44..00000000 --- a/src/debug/DebugProvider.jsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import { getDebugConfig } from './debugConfig'; -import { overrideConsole } from './consoleOverride'; - -export function DebugProvider({ children }) { - const config = getDebugConfig(); - - overrideConsole(config); - - return children; -} diff --git a/src/debug/consoleOverride.js b/src/debug/consoleOverride.js index a5c69a83..326941e1 100644 --- a/src/debug/consoleOverride.js +++ b/src/debug/consoleOverride.js @@ -12,6 +12,8 @@ const METHOD_LEVEL = { warn: 2, info: 3, log: 4, + debug: 4, + trace: 4, }; export function overrideConsole(config) { @@ -22,6 +24,8 @@ export function overrideConsole(config) { warn: console.warn.bind(console), info: console.info.bind(console), log: console.log.bind(console), + debug: console.debug ? console.debug.bind(console) : console.log.bind(console), + trace: console.trace ? console.trace.bind(console) : console.log.bind(console), }; Object.keys(METHOD_LEVEL).forEach((method) => { diff --git a/src/debug/debugConfig.js b/src/debug/debugConfig.js index 8b3d4c1a..ac5c7401 100644 --- a/src/debug/debugConfig.js +++ b/src/debug/debugConfig.js @@ -2,10 +2,8 @@ export function getDebugConfig() { const params = new URLSearchParams(window.location.search); const level = params.get('log'); - const validLevels = ['none', 'error', 'warn', 'info', 'debug', 'all']; - // warn will be the default return { logLevel: validLevels.includes(level) ? level : 'warn', }; diff --git a/src/main.jsx b/src/main.jsx index 7f6dd464..209762dd 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -5,19 +5,20 @@ import ErrorBoundary from './components/ErrorBoundary'; import { QRZProvider, setupMapQRZHandler } from './components/CallsignLink'; import './styles/main.css'; import './lang/i18n'; -import { DebugProvider } from './debug/DebugProvider'; +import { getDebugConfig } from './debug/debugConfig'; +import { overrideConsole } from './debug/consoleOverride'; // Global click handler for QRZ links in Leaflet HTML popups setupMapQRZHandler(); +overrideConsole(getDebugConfig()); + ReactDOM.createRoot(document.getElementById('root')).render( - - - - - + + + , );