diff --git a/doc/appdev-logging.rst b/doc/appdev-logging.rst new file mode 100644 index 0000000..0be1d62 --- /dev/null +++ b/doc/appdev-logging.rst @@ -0,0 +1,218 @@ +.. appdev-logging + +.. _appdevlogging: + +Logging System +============== + +The *x0-framework* provides a configurable logging mechanism through the ``sysLogger`` class. +This logging system replaces direct ``console.debug()`` calls and allows fine-grained control +over log output based on configurable debug levels. + +Overview +-------- + +The logging system is designed to: + +* Reduce performance impact of debug statements in production +* Provide configurable log levels from the PostgreSQL backend +* Support standard logging levels (ERROR, WARN, INFO, DEBUG) +* Maintain backward compatibility with existing code + +Configuration +------------- + +The logging level is configured in the PostgreSQL database via the ``debug_level`` configuration: + +.. code-block:: sql + + INSERT INTO system.config (config_group, "value") + VALUES ('debug_level', '10'); + +Log Levels +---------- + +The following log levels are available: + +=============== ======= ===================================================== +Level Name Value Description +=============== ======= ===================================================== +LOG_LEVEL_NONE 0 No logging (production) +LOG_LEVEL_ERROR 1 Only error messages +LOG_LEVEL_WARN 5 Warnings and errors +LOG_LEVEL_INFO 8 Informational messages, warnings, and errors +LOG_LEVEL_DEBUG 10 All messages including debug output +=============== ======= ===================================================== + +Usage +----- + +The logger is accessible globally through ``sysFactory.Logger`` after initialization. + +Basic Logging +^^^^^^^^^^^^^ + +.. code-block:: javascript + + // Debug messages (level 10) + sysFactory.Logger.debug('Debug message:', objectData); + + // Info messages (level 8) + sysFactory.Logger.info('User logged in:', userId); + + // Warning messages (level 5) + sysFactory.Logger.warn('Deprecated function called:', functionName); + + // Error messages (level 1) + sysFactory.Logger.error('Operation failed:', errorDetails); + +Custom Log Levels +^^^^^^^^^^^^^^^^^ + +You can also use custom log levels: + +.. code-block:: javascript + + // Log with custom level (7) + sysFactory.Logger.log(7, 'Custom level message:', data); + +Getting/Setting Log Level +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: javascript + + // Get current log level + var currentLevel = sysFactory.Logger.getLogLevel(); + + // Set log level programmatically (not recommended, use config instead) + sysFactory.Logger.setLogLevel(5); // Set to WARN level + +Examples +-------- + +Basic Usage +^^^^^^^^^^^ + +.. code-block:: javascript + + function sysReactor() { + this.Events = new Array(); + sysFactory.Logger.debug('Reactor initialized with events array'); + } + + sysReactor.prototype.registerEvent = function(Attributes, ProcessObject, Type) { + sysFactory.Logger.debug('registerEvent Attributes:%o ProcessObject:%o, Type:%s', + Attributes, ProcessObject, Type); + // ... rest of implementation + } + +Conditional Logging +^^^^^^^^^^^^^^^^^^^ + +.. code-block:: javascript + + function processData(data) { + try { + // Process data + sysFactory.Logger.info('Data processed successfully'); + } catch (err) { + sysFactory.Logger.error('Data processing failed:', err); + } + } + +Migration Guide +--------------- + +Existing ``console.debug()`` calls can be gradually migrated to use the logger: + +Before: + +.. code-block:: javascript + + console.debug('TreeSimple JSONConfig:%o', Attributes); + +After: + +.. code-block:: javascript + + sysFactory.Logger.debug('TreeSimple JSONConfig:%o', Attributes); + +Benefits +-------- + +Using the logger class provides several advantages: + +1. **Performance**: Logging can be completely disabled in production (level 0) +2. **Flexibility**: Different log levels can be enabled without code changes +3. **Consistency**: Standardized logging interface across the framework +4. **Control**: Centralized configuration through the database + +Best Practices +-------------- + +1. Use appropriate log levels: + + * ``debug()`` for detailed debugging information + * ``info()`` for general informational messages + * ``warn()`` for warning messages about potential issues + * ``error()`` for error conditions + +2. Include context in log messages: + + .. code-block:: javascript + + // Good - includes context + sysFactory.Logger.debug('User validation failed for ID:%s', userId); + + // Poor - lacks context + sysFactory.Logger.debug('Validation failed'); + +3. Use structured logging with objects: + + .. code-block:: javascript + + sysFactory.Logger.debug('Request data:', { + url: requestUrl, + method: requestMethod, + params: requestParams + }); + +4. Avoid logging sensitive information: + + .. code-block:: javascript + + // Avoid logging passwords, tokens, or personal data + sysFactory.Logger.debug('User logged in', { userId: user.id }); // OK + // NOT: sysFactory.Logger.debug('Login', { password: user.password }); // NEVER! + +Technical Details +----------------- + +Implementation +^^^^^^^^^^^^^^ + +The ``sysLogger`` class is implemented in ``/www/sysLogger.js`` and provides: + +* Constructor: ``sysLogger()`` +* Methods: + + * ``setLogLevel(level)`` - Set the logging level + * ``getLogLevel()`` - Get the current logging level + * ``debug(...)`` - Log debug messages + * ``info(...)`` - Log info messages + * ``warn(...)`` - Log warning messages + * ``error(...)`` - Log error messages + * ``log(level, ...)`` - Log with custom level + +Initialization +^^^^^^^^^^^^^^ + +The logger is initialized in ``sysInitOnLoad.js`` during application startup: + +.. code-block:: javascript + + sysFactory.Logger = new sysLogger(); + sysFactory.Logger.setLogLevel(sysVarDebugLevel); + +The ``sysVarDebugLevel`` value is injected from the Python backend (``Index.py``) +based on the PostgreSQL configuration. diff --git a/doc/index.rst b/doc/index.rst index 98897f8..be805be 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -15,6 +15,7 @@ x0 Framework Documentation :caption: Application Development Guide appdev-config + appdev-logging appdev-global-css appdev-grid-system appdev-objects diff --git a/python/Index.py b/python/Index.py index 9930fbd..7b0546a 100755 --- a/python/Index.py +++ b/python/Index.py @@ -20,6 +20,7 @@ + diff --git a/test/integration/test_logger.py b/test/integration/test_logger.py new file mode 100644 index 0000000..c093733 --- /dev/null +++ b/test/integration/test_logger.py @@ -0,0 +1,187 @@ +import os +import json +import time +import pytest +import logging + +import globalconf + +from selenium import webdriver +from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC + +logging.basicConfig(level=logging.DEBUG) +logger = logging.getLogger() + +wd_options = webdriver.ChromeOptions() +wd_options.add_argument('ignore-certificate-errors') +wd_options.add_argument('headless') + + +@pytest.fixture(scope='module') +def config(): + + try: + run_namespace = os.environ['RUN_NAMESPACE'] + except Exception as e: + run_namespace = None + + try: + run_kube_env = os.environ['KUBERNETES_SERVICE_HOST'] + except Exception as e: + run_kube_env = None + + try: + domain_suffix = '.' + run_namespace + except Exception as e: + domain_suffix = '' + + if run_kube_env is not None: + domain_suffix += '.svc.cluster.local' + + vhost_test_urls = globalconf.setup() + + logger.info('test urls:{}'.format(vhost_test_urls)) + + selenium_server_url = 'http://selenium-server-0{}:4444'.format(domain_suffix) + + logger.info('selenium server url:{}'.format(selenium_server_url)) + + config = { + 'vhost_test_urls': vhost_test_urls, + 'selenium_server_url': selenium_server_url, + 'timeout': 60 + } + + return config + + +@pytest.fixture(scope='module') +def driver(config): + + selenium_server_url = config['selenium_server_url'] + + driver = webdriver.Remote( + command_executor=selenium_server_url, + options=wd_options + ) + + driver.implicitly_wait(config['timeout']) + + yield driver + + driver.quit() + + +def test_logger_initialization(driver, config): + """Test that sysLogger is properly initialized""" + + test_url = config['vhost_test_urls'][0] + logger.info('Test URL: {}'.format(test_url)) + + driver.get(test_url) + + # Wait for page to load + time.sleep(3) + + # Check that sysFactory exists + result = driver.execute_script("return typeof sysFactory !== 'undefined';") + assert result == True, "sysFactory should be defined" + + # Check that Logger exists on sysFactory + result = driver.execute_script("return typeof sysFactory.Logger !== 'undefined';") + assert result == True, "sysFactory.Logger should be defined" + + # Check that Logger is an instance of sysLogger + result = driver.execute_script("return sysFactory.Logger instanceof sysLogger;") + assert result == True, "sysFactory.Logger should be an instance of sysLogger" + + logger.info('Logger initialization test passed') + + +def test_logger_methods_exist(driver, config): + """Test that all logger methods exist""" + + test_url = config['vhost_test_urls'][0] + driver.get(test_url) + time.sleep(3) + + # Check for debug method + result = driver.execute_script("return typeof sysFactory.Logger.debug === 'function';") + assert result == True, "Logger should have debug method" + + # Check for info method + result = driver.execute_script("return typeof sysFactory.Logger.info === 'function';") + assert result == True, "Logger should have info method" + + # Check for warn method + result = driver.execute_script("return typeof sysFactory.Logger.warn === 'function';") + assert result == True, "Logger should have warn method" + + # Check for error method + result = driver.execute_script("return typeof sysFactory.Logger.error === 'function';") + assert result == True, "Logger should have error method" + + # Check for log method + result = driver.execute_script("return typeof sysFactory.Logger.log === 'function';") + assert result == True, "Logger should have log method" + + # Check for setLogLevel method + result = driver.execute_script("return typeof sysFactory.Logger.setLogLevel === 'function';") + assert result == True, "Logger should have setLogLevel method" + + # Check for getLogLevel method + result = driver.execute_script("return typeof sysFactory.Logger.getLogLevel === 'function';") + assert result == True, "Logger should have getLogLevel method" + + logger.info('Logger methods test passed') + + +def test_logger_level_configuration(driver, config): + """Test that logger respects debug level from configuration""" + + test_url = config['vhost_test_urls'][0] + driver.get(test_url) + time.sleep(3) + + # Get the configured log level + result = driver.execute_script("return sysFactory.Logger.getLogLevel();") + logger.info('Current log level: {}'.format(result)) + + # The default configuration should be 10 (DEBUG) + assert result >= 0, "Log level should be >= 0" + + # Test that we can change the log level + driver.execute_script("sysFactory.Logger.setLogLevel(5);") + result = driver.execute_script("return sysFactory.Logger.getLogLevel();") + assert result == 5, "Log level should be 5 after setting" + + logger.info('Logger level configuration test passed') + + +def test_logger_constants(driver, config): + """Test that logger constants are defined""" + + test_url = config['vhost_test_urls'][0] + driver.get(test_url) + time.sleep(3) + + # Check log level constants + result = driver.execute_script("return sysLogger.LOG_LEVEL_NONE;") + assert result == 0, "LOG_LEVEL_NONE should be 0" + + result = driver.execute_script("return sysLogger.LOG_LEVEL_ERROR;") + assert result == 1, "LOG_LEVEL_ERROR should be 1" + + result = driver.execute_script("return sysLogger.LOG_LEVEL_WARN;") + assert result == 5, "LOG_LEVEL_WARN should be 5" + + result = driver.execute_script("return sysLogger.LOG_LEVEL_INFO;") + assert result == 8, "LOG_LEVEL_INFO should be 8" + + result = driver.execute_script("return sysLogger.LOG_LEVEL_DEBUG;") + assert result == 10, "LOG_LEVEL_DEBUG should be 10" + + logger.info('Logger constants test passed') diff --git a/www/sysInitOnLoad.js b/www/sysInitOnLoad.js index fd109ae..e1d7a39 100644 --- a/www/sysInitOnLoad.js +++ b/www/sysInitOnLoad.js @@ -137,6 +137,14 @@ function InitOk(XHR) { sysFactory.ParentWindowURL = sysVarParentWindowURL; + //---------------------------------------------------------------------------- + //- Initialize Logger + //---------------------------------------------------------------------------- + + sysFactory.Logger = new sysLogger(); + sysFactory.Logger.setLogLevel(sysVarDebugLevel); + + //---------------------------------------------------------------------------- //- Style Defaults //---------------------------------------------------------------------------- diff --git a/www/sysLogger.README.md b/www/sysLogger.README.md new file mode 100644 index 0000000..d4a15fd --- /dev/null +++ b/www/sysLogger.README.md @@ -0,0 +1,126 @@ +# sysLogger - Configurable Logging for x0 Framework + +## Overview + +The `sysLogger` class provides a configurable logging mechanism for the x0 JavaScript framework. It replaces direct `console.debug()` calls and allows fine-grained control over log output based on configurable debug levels. + +## Quick Start + +The logger is available globally after initialization: + +```javascript +// Basic usage +sysFactory.Logger.debug('Debug message'); +sysFactory.Logger.info('Info message'); +sysFactory.Logger.warn('Warning message'); +sysFactory.Logger.error('Error message'); + +// With formatting +sysFactory.Logger.debug('User data: %o', userData); +sysFactory.Logger.info('Processing item %d of %d', current, total); +``` + +## Configuration + +The logging level is configured in the PostgreSQL database: + +```sql +-- Default configuration +INSERT INTO system.config (config_group, "value") +VALUES ('debug_level', '10'); +``` + +## Log Levels + +| Level | Name | Value | Description | +|-------|------|-------|-------------| +| NONE | `LOG_LEVEL_NONE` | 0 | No logging (production) | +| ERROR | `LOG_LEVEL_ERROR` | 1 | Only error messages | +| WARN | `LOG_LEVEL_WARN` | 5 | Warnings and errors | +| INFO | `LOG_LEVEL_INFO` | 8 | Info, warnings, and errors | +| DEBUG | `LOG_LEVEL_DEBUG` | 10 | All messages (development) | + +## Migration from console.debug() + +Replace existing console calls: + +```javascript +// Before +console.debug('TreeSimple JSONConfig:%o', Attributes); + +// After +sysFactory.Logger.debug('TreeSimple JSONConfig:%o', Attributes); +``` + +## Benefits + +1. **Performance**: Logging can be completely disabled in production (level 0) +2. **Flexibility**: Different log levels without code changes +3. **Consistency**: Standardized logging interface +4. **Control**: Centralized configuration through database + +## Documentation + +For complete documentation, see `/doc/appdev-logging.rst` + +## Testing + +Run the integration tests: + +```bash +pytest test/integration/test_logger.py +``` + +## Examples + +### Basic Logging + +```javascript +function processData(data) { + sysFactory.Logger.debug('Processing data:', data); + + try { + // Process data + sysFactory.Logger.info('Data processed successfully'); + } catch (err) { + sysFactory.Logger.error('Data processing failed:', err); + } +} +``` + +### Conditional Logging with Objects + +```javascript +sysReactor.prototype.registerEvent = function(Attributes, ProcessObject, Type) { + sysFactory.Logger.debug('registerEvent - Attributes:%o ProcessObject:%o Type:%s', + Attributes, ProcessObject, Type); + // ... implementation +} +``` + +### Custom Log Levels + +```javascript +// Log only if level >= 7 +sysFactory.Logger.log(7, 'Custom level message', data); +``` + +## API Reference + +### Methods + +- `debug(...)` - Log debug messages (level 10) +- `info(...)` - Log info messages (level 8) +- `warn(...)` - Log warning messages (level 5) +- `error(...)` - Log error messages (level 1) +- `log(level, ...)` - Log with custom level +- `setLogLevel(level)` - Set the logging level +- `getLogLevel()` - Get the current logging level + +### Constants + +- `sysLogger.LOG_LEVEL_NONE` = 0 +- `sysLogger.LOG_LEVEL_ERROR` = 1 +- `sysLogger.LOG_LEVEL_WARN` = 5 +- `sysLogger.LOG_LEVEL_INFO` = 8 +- `sysLogger.LOG_LEVEL_DEBUG` = 10 diff --git a/www/sysLogger.js b/www/sysLogger.js new file mode 100644 index 0000000..ee4d595 --- /dev/null +++ b/www/sysLogger.js @@ -0,0 +1,114 @@ +//-------1---------2---------3---------4---------5---------6---------7--------// +//- Copyright WEB/codeX, clickIT 2011 - 2025 -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- SYSTEM "Logger" -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- Configurable logging mechanism with debug level support -// +//- -// +//- -// +//-------1---------2---------3---------4---------5---------6---------7--------// + + +//------------------------------------------------------------------------------ +//- CONSTRUCTOR "sysLogger" +//------------------------------------------------------------------------------ + +function sysLogger() { + this.logLevel = 0; // Default: no logging + this.enabled = false; +} + + +//------------------------------------------------------------------------------ +//- Log Level Constants +//------------------------------------------------------------------------------ + +sysLogger.LOG_LEVEL_NONE = 0; +sysLogger.LOG_LEVEL_ERROR = 1; +sysLogger.LOG_LEVEL_WARN = 5; +sysLogger.LOG_LEVEL_INFO = 8; +sysLogger.LOG_LEVEL_DEBUG = 10; + + +//------------------------------------------------------------------------------ +//- METHOD "setLogLevel" +//- Sets the current logging level +//------------------------------------------------------------------------------ + +sysLogger.prototype.setLogLevel = function(level) { + this.logLevel = parseInt(level) || 0; + this.enabled = this.logLevel > 0; +} + + +//------------------------------------------------------------------------------ +//- METHOD "getLogLevel" +//- Gets the current logging level +//------------------------------------------------------------------------------ + +sysLogger.prototype.getLogLevel = function() { + return this.logLevel; +} + + +//------------------------------------------------------------------------------ +//- METHOD "debug" +//- Logs debug messages (level 10) +//------------------------------------------------------------------------------ + +sysLogger.prototype.debug = function() { + if (this.enabled && this.logLevel >= sysLogger.LOG_LEVEL_DEBUG) { + console.debug.apply(console, arguments); + } +} + + +//------------------------------------------------------------------------------ +//- METHOD "info" +//- Logs info messages (level 8) +//------------------------------------------------------------------------------ + +sysLogger.prototype.info = function() { + if (this.enabled && this.logLevel >= sysLogger.LOG_LEVEL_INFO) { + console.info.apply(console, arguments); + } +} + + +//------------------------------------------------------------------------------ +//- METHOD "warn" +//- Logs warning messages (level 5) +//------------------------------------------------------------------------------ + +sysLogger.prototype.warn = function() { + if (this.enabled && this.logLevel >= sysLogger.LOG_LEVEL_WARN) { + console.warn.apply(console, arguments); + } +} + + +//------------------------------------------------------------------------------ +//- METHOD "error" +//- Logs error messages (level 1) +//------------------------------------------------------------------------------ + +sysLogger.prototype.error = function() { + if (this.enabled && this.logLevel >= sysLogger.LOG_LEVEL_ERROR) { + console.error.apply(console, arguments); + } +} + + +//------------------------------------------------------------------------------ +//- METHOD "log" +//- Generic log method with custom level +//------------------------------------------------------------------------------ + +sysLogger.prototype.log = function(level) { + if (this.enabled && this.logLevel >= level) { + var args = Array.prototype.slice.call(arguments, 1); + console.log.apply(console, args); + } +}