From ce04001f69d0bb8a6c1643d5c546f333bfacac17 Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Tue, 6 Jan 2026 15:17:55 -0500 Subject: [PATCH 1/2] Add centralized plugin logging for Python and native plugins MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit introduces a centralized logging mechanism for both Python and native (C/C++) plugins to ensure all plugin log messages are routed through the central logging system and visible in the OpenPLC Editor. Python plugins: - Add PluginLogger class in shared/plugin_logger.py - Update modbus_slave to use PluginLogger (replace ~70 print() calls) - Update modbus_master to use PluginLogger (replace ~50 print() calls) - Plugin names are automatically prefixed to messages Native plugins: - Add plugin_logger.h/c in plugins/native/ - Update test_plugin.c example to demonstrate proper logging usage - Update Makefile to compile with plugin_logger Both implementations: - Auto-prefix messages with plugin name (e.g., "[MODBUS_SLAVE]") - Fall back to print/printf if central logging unavailable - Thread-safe logging support 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../drivers/plugins/native/examples/Makefile | 7 +- .../plugins/native/examples/test_plugin.c | 212 ++++++++------ .../drivers/plugins/native/plugin_logger.c | 184 +++++++++++++ .../drivers/plugins/native/plugin_logger.h | 110 ++++++++ .../modbus_master/modbus_master_plugin.py | 176 ++++++------ .../python/modbus_slave/simple_modbus.py | 260 ++++++++++-------- .../drivers/plugins/python/shared/__init__.py | 6 + .../plugins/python/shared/plugin_logger.py | 171 ++++++++++++ 8 files changed, 835 insertions(+), 291 deletions(-) create mode 100644 core/src/drivers/plugins/native/plugin_logger.c create mode 100644 core/src/drivers/plugins/native/plugin_logger.h create mode 100644 core/src/drivers/plugins/python/shared/plugin_logger.py diff --git a/core/src/drivers/plugins/native/examples/Makefile b/core/src/drivers/plugins/native/examples/Makefile index 398ac301..24b9989e 100644 --- a/core/src/drivers/plugins/native/examples/Makefile +++ b/core/src/drivers/plugins/native/examples/Makefile @@ -1,19 +1,20 @@ # Makefile for test plugin CC = gcc -CFLAGS = -fPIC -shared -I../../../../lib -I../../../../drivers +CFLAGS = -fPIC -shared -I../../../../lib -I../../../../drivers -I.. LDFLAGS = -lpthread TARGET = test_plugin.so SOURCE = test_plugin.c +PLUGIN_LOGGER = ../plugin_logger.c LOADER_TARGET = test_plugin_loader LOADER_SOURCE = test_plugin_loader.c all: $(TARGET) $(LOADER_TARGET) -$(TARGET): $(SOURCE) - $(CC) $(CFLAGS) $(LDFLAGS) -o $(TARGET) $(SOURCE) +$(TARGET): $(SOURCE) $(PLUGIN_LOGGER) + $(CC) $(CFLAGS) $(LDFLAGS) -o $(TARGET) $(SOURCE) $(PLUGIN_LOGGER) $(LOADER_TARGET): $(LOADER_SOURCE) $(CC) -o $(LOADER_TARGET) $(LOADER_SOURCE) -ldl -lpthread diff --git a/core/src/drivers/plugins/native/examples/test_plugin.c b/core/src/drivers/plugins/native/examples/test_plugin.c index d857dc3b..b6ecdce8 100644 --- a/core/src/drivers/plugins/native/examples/test_plugin.c +++ b/core/src/drivers/plugins/native/examples/test_plugin.c @@ -1,152 +1,184 @@ +/** + * @file test_plugin.c + * @brief Example native plugin demonstrating proper logging usage + * + * This plugin demonstrates how to use the centralized plugin logger + * to ensure all log messages are visible in the OpenPLC Editor. + */ + #include #include #include #include -// Include IEC types +/* Include the plugin logger */ +#include "../plugin_logger.h" + +/* Include IEC types */ #include "../../../../lib/iec_types.h" -// Define plugin_runtime_args_t structure locally to avoid Python dependencies -typedef struct -{ - // Buffer pointers - IEC_BOOL *(*bool_input)[8]; - IEC_BOOL *(*bool_output)[8]; - IEC_BYTE **byte_input; - IEC_BYTE **byte_output; - IEC_UINT **int_input; - IEC_UINT **int_output; - IEC_UDINT **dint_input; - IEC_UDINT **dint_output; - IEC_ULINT **lint_input; - IEC_ULINT **lint_output; - IEC_UINT **int_memory; - IEC_UDINT **dint_memory; - IEC_ULINT **lint_memory; - - // Mutex functions - int (*mutex_take)(pthread_mutex_t *mutex); - int (*mutex_give)(pthread_mutex_t *mutex); - pthread_mutex_t *buffer_mutex; - char plugin_specific_config_file_path[256]; - - // Buffer size information - int buffer_size; - int bits_per_buffer; -} plugin_runtime_args_t; - -// Global variable to track plugin state +/* Include the actual plugin_runtime_args_t definition */ +#include "../../../plugin_driver.h" + +/* Global logger instance */ +static plugin_logger_t g_logger; + +/* Global variable to track plugin state */ static int plugin_initialized = 0; static int plugin_running = 0; -// Required init function -// This function is called when the plugin is loaded -// args: pointer to plugin_runtime_args_t structure containing runtime buffers and mutex functions +/* Store runtime args for mutex access */ +static plugin_runtime_args_t *g_runtime_args = NULL; + +/** + * @brief Initialize the plugin + * + * This function is called when the plugin is loaded. + * @param args Pointer to plugin_runtime_args_t containing runtime buffers, + * mutex functions, and logging function pointers + * @return 0 on success, -1 on failure + */ int init(void *args) { - printf("[TEST_PLUGIN]: Initializing test plugin...\n"); + /* Initialize logger first (before we have runtime_args) */ + plugin_logger_init(&g_logger, "TEST_PLUGIN", NULL); + plugin_logger_info(&g_logger, "Initializing test plugin..."); - if (!args) { - fprintf(stderr, "[TEST_PLUGIN]: Error - init args is NULL\n"); + if (!args) + { + plugin_logger_error(&g_logger, "init args is NULL"); return -1; } - plugin_runtime_args_t *runtime_args = (plugin_runtime_args_t *)args; - - // Print some information about the runtime args - printf("[TEST_PLUGIN]: Buffer size: %d\n", runtime_args->buffer_size); - printf("[TEST_PLUGIN]: Bits per buffer: %d\n", runtime_args->bits_per_buffer); - printf("[TEST_PLUGIN]: Plugin config path: %s\n", runtime_args->plugin_specific_config_file_path); - - // Test mutex functions if available - if (runtime_args->mutex_take && runtime_args->mutex_give && runtime_args->buffer_mutex) { - printf("[TEST_PLUGIN]: Testing mutex functions...\n"); - if (runtime_args->mutex_take(runtime_args->buffer_mutex) == 0) { - printf("[TEST_PLUGIN]: Mutex acquired successfully\n"); - runtime_args->mutex_give(runtime_args->buffer_mutex); - printf("[TEST_PLUGIN]: Mutex released successfully\n"); - } else { - fprintf(stderr, "[TEST_PLUGIN]: Failed to acquire mutex\n"); + g_runtime_args = (plugin_runtime_args_t *)args; + + /* Re-initialize logger with runtime_args for central logging */ + plugin_logger_init(&g_logger, "TEST_PLUGIN", args); + + /* Print some information about the runtime args */ + plugin_logger_info(&g_logger, "Buffer size: %d", g_runtime_args->buffer_size); + plugin_logger_info(&g_logger, "Bits per buffer: %d", g_runtime_args->bits_per_buffer); + plugin_logger_debug(&g_logger, "Plugin config path: %s", + g_runtime_args->plugin_specific_config_file_path); + + /* Test mutex functions if available */ + if (g_runtime_args->mutex_take && g_runtime_args->mutex_give && g_runtime_args->buffer_mutex) + { + plugin_logger_debug(&g_logger, "Testing mutex functions..."); + if (g_runtime_args->mutex_take(g_runtime_args->buffer_mutex) == 0) + { + plugin_logger_debug(&g_logger, "Mutex acquired successfully"); + g_runtime_args->mutex_give(g_runtime_args->buffer_mutex); + plugin_logger_debug(&g_logger, "Mutex released successfully"); + } + else + { + plugin_logger_warn(&g_logger, "Failed to acquire mutex"); } } plugin_initialized = 1; - printf("[TEST_PLUGIN]: Test plugin initialized successfully!\n"); + plugin_logger_info(&g_logger, "Test plugin initialized successfully!"); return 0; } -// Optional start_loop function -// This function is called when the plugin should start its main loop -void start_loop() +/** + * @brief Start the plugin main loop + * + * This function is called when the plugin should start its main loop. + */ +void start_loop(void) { - if (!plugin_initialized) { - fprintf(stderr, "[TEST_PLUGIN]: Cannot start - plugin not initialized\n"); + if (!plugin_initialized) + { + plugin_logger_error(&g_logger, "Cannot start - plugin not initialized"); return; } - printf("[TEST_PLUGIN]: Starting test plugin loop...\n"); + plugin_logger_info(&g_logger, "Starting test plugin loop..."); plugin_running = 1; - printf("[TEST_PLUGIN]: Test plugin loop started!\n"); + plugin_logger_info(&g_logger, "Test plugin loop started!"); } -// Optional stop_loop function -// This function is called when the plugin should stop its main loop -void stop_loop() +/** + * @brief Stop the plugin main loop + * + * This function is called when the plugin should stop its main loop. + */ +void stop_loop(void) { - if (!plugin_running) { - printf("[TEST_PLUGIN]: Plugin loop already stopped\n"); + if (!plugin_running) + { + plugin_logger_info(&g_logger, "Plugin loop already stopped"); return; } - printf("[TEST_PLUGIN]: Stopping test plugin loop...\n"); + plugin_logger_info(&g_logger, "Stopping test plugin loop..."); plugin_running = 0; - printf("[TEST_PLUGIN]: Test plugin loop stopped!\n"); + plugin_logger_info(&g_logger, "Test plugin loop stopped!"); } -// Optional cycle_start function -// This function is called at the start of each PLC cycle if the plugin needs to run synchronously -void cycle_start() +/** + * @brief Called at the start of each PLC scan cycle + * + * This function is called at the start of each PLC cycle if the plugin + * needs to run synchronously with the scan cycle. + */ +void cycle_start(void) { - if (!plugin_initialized || !plugin_running) { - return; // Silent if not running + if (!plugin_initialized || !plugin_running) + { + return; /* Silent if not running */ } - // Simple test - just print a message occasionally + /* Simple test - log a message occasionally */ static int cycle_count = 0; cycle_count++; - if (cycle_count % 1000 == 0) { // Print every 1000 cycles - printf("[TEST_PLUGIN]: Starting cycle %d\n", cycle_count); + if (cycle_count % 1000 == 0) + { /* Log every 1000 cycles */ + plugin_logger_debug(&g_logger, "Starting cycle %d", cycle_count); } } -// Optional cycle_end function -// This function is called at the end of each PLC cycle if the plugin needs to run synchronously -void cycle_end() +/** + * @brief Called at the end of each PLC scan cycle + * + * This function is called at the end of each PLC cycle if the plugin + * needs to run synchronously with the scan cycle. + */ +void cycle_end(void) { - if (!plugin_initialized || !plugin_running) { - return; // Silent if not running + if (!plugin_initialized || !plugin_running) + { + return; /* Silent if not running */ } - // Simple test - just print a message occasionally + /* Simple test - log a message occasionally */ static int cycle_count = 0; cycle_count++; - if (cycle_count % 1000 == 0) { // Print every 1000 cycles - printf("[TEST_PLUGIN]: Ending cycle %d\n", cycle_count); + if (cycle_count % 1000 == 0) + { /* Log every 1000 cycles */ + plugin_logger_debug(&g_logger, "Ending cycle %d", cycle_count); } } -// Optional cleanup function -// This function is called when the plugin is being unloaded -void cleanup() +/** + * @brief Cleanup plugin resources + * + * This function is called when the plugin is being unloaded. + */ +void cleanup(void) { - printf("[TEST_PLUGIN]: Cleaning up test plugin...\n"); + plugin_logger_info(&g_logger, "Cleaning up test plugin..."); - if (plugin_running) { + if (plugin_running) + { stop_loop(); } plugin_initialized = 0; - printf("[TEST_PLUGIN]: Test plugin cleaned up successfully!\n"); + g_runtime_args = NULL; + plugin_logger_info(&g_logger, "Test plugin cleaned up successfully!"); } diff --git a/core/src/drivers/plugins/native/plugin_logger.c b/core/src/drivers/plugins/native/plugin_logger.c new file mode 100644 index 00000000..98a8aa4e --- /dev/null +++ b/core/src/drivers/plugins/native/plugin_logger.c @@ -0,0 +1,184 @@ +/** + * @file plugin_logger.c + * @brief Centralized Plugin Logger Implementation for Native OpenPLC Plugins + */ + +#include "plugin_logger.h" +#include +#include +#include + +/* Maximum size for formatted log messages */ +#define MAX_LOG_MESSAGE_SIZE 1024 + +/** + * @brief Runtime args structure (must match plugin_driver.h) + * + * We only need the logging function pointers from this structure. + */ +typedef struct +{ + /* Buffer pointers - not used by logger */ + void *bool_input; + void *bool_output; + void *byte_input; + void *byte_output; + void *int_input; + void *int_output; + void *dint_input; + void *dint_output; + void *lint_input; + void *lint_output; + void *int_memory; + void *dint_memory; + void *lint_memory; + void *bool_memory; + + /* Mutex functions - not used by logger */ + void *mutex_take; + void *mutex_give; + void *buffer_mutex; + char plugin_specific_config_file_path[256]; + + /* Buffer size information - not used by logger */ + int buffer_size; + int bits_per_buffer; + + /* Logging functions - these are what we need */ + plugin_log_func_t log_info; + plugin_log_func_t log_debug; + plugin_log_func_t log_warn; + plugin_log_func_t log_error; +} plugin_runtime_args_internal_t; + +bool plugin_logger_init(plugin_logger_t *logger, const char *plugin_name, void *runtime_args) +{ + if (!logger) + { + return false; + } + + /* Initialize to invalid state */ + logger->is_valid = false; + logger->log_info = NULL; + logger->log_debug = NULL; + logger->log_warn = NULL; + logger->log_error = NULL; + logger->plugin_name[0] = '\0'; + + if (!plugin_name) + { + fprintf(stderr, "[PLUGIN_LOGGER] Error: plugin_name is NULL\n"); + return false; + } + + /* Copy plugin name (with bounds checking) */ + strncpy(logger->plugin_name, plugin_name, sizeof(logger->plugin_name) - 1); + logger->plugin_name[sizeof(logger->plugin_name) - 1] = '\0'; + + if (!runtime_args) + { + fprintf(stderr, "[%s] Warning: runtime_args is NULL, logging will fall back to printf\n", + logger->plugin_name); + return true; /* Still return true - logger will fall back to printf */ + } + + /* Extract logging function pointers from runtime_args */ + plugin_runtime_args_internal_t *args = (plugin_runtime_args_internal_t *)runtime_args; + + logger->log_info = args->log_info; + logger->log_debug = args->log_debug; + logger->log_warn = args->log_warn; + logger->log_error = args->log_error; + + /* Validate that we have at least the basic logging functions */ + if (logger->log_info && logger->log_error) + { + logger->is_valid = true; + } + else + { + fprintf(stderr, "[%s] Warning: Some log functions are NULL, falling back to printf\n", + logger->plugin_name); + } + + return true; +} + +/** + * @brief Internal helper to format and send log message + */ +static void plugin_logger_log(plugin_logger_t *logger, plugin_log_func_t log_func, + const char *level, const char *fmt, va_list args) +{ + char message[MAX_LOG_MESSAGE_SIZE]; + char prefixed_message[MAX_LOG_MESSAGE_SIZE]; + + /* Format the user's message */ + vsnprintf(message, sizeof(message), fmt, args); + + /* Add plugin name prefix */ + snprintf(prefixed_message, sizeof(prefixed_message), "[%s] %s", logger->plugin_name, message); + + /* Use central logging if available, otherwise fall back to printf */ + if (log_func) + { + log_func("%s", prefixed_message); + } + else + { + printf("[%s] [%s] %s\n", logger->plugin_name, level, message); + } +} + +void plugin_logger_info(plugin_logger_t *logger, const char *fmt, ...) +{ + if (!logger || !fmt) + { + return; + } + + va_list args; + va_start(args, fmt); + plugin_logger_log(logger, logger->log_info, "INFO", fmt, args); + va_end(args); +} + +void plugin_logger_debug(plugin_logger_t *logger, const char *fmt, ...) +{ + if (!logger || !fmt) + { + return; + } + + va_list args; + va_start(args, fmt); + plugin_logger_log(logger, logger->log_debug, "DEBUG", fmt, args); + va_end(args); +} + +void plugin_logger_warn(plugin_logger_t *logger, const char *fmt, ...) +{ + if (!logger || !fmt) + { + return; + } + + va_list args; + va_start(args, fmt); + plugin_logger_log(logger, logger->log_warn, "WARN", fmt, args); + va_end(args); +} + +void plugin_logger_error(plugin_logger_t *logger, const char *fmt, ...) +{ + if (!logger || !fmt) + { + return; + } + + va_list args; + va_start(args, fmt); + plugin_logger_log(logger, logger->log_error, "ERROR", fmt, args); + va_end(args); +} diff --git a/core/src/drivers/plugins/native/plugin_logger.h b/core/src/drivers/plugins/native/plugin_logger.h new file mode 100644 index 00000000..6f46d97d --- /dev/null +++ b/core/src/drivers/plugins/native/plugin_logger.h @@ -0,0 +1,110 @@ +/** + * @file plugin_logger.h + * @brief Centralized Plugin Logger for Native OpenPLC Plugins + * + * Provides a simple, consistent logging interface for native (C/C++) OpenPLC plugins + * that routes log messages through the central logging system: + * + * C runtime log functions -> Unix socket -> Python log server -> + * REST API -> OpenPLC Editor + * + * This ensures all plugin logs are visible in the Editor's log viewer. + * + * Usage: + * #include "plugin_logger.h" + * + * // In plugin init(): + * plugin_logger_t logger; + * plugin_logger_init(&logger, "MY_PLUGIN", runtime_args); + * + * // Throughout plugin: + * plugin_logger_info(&logger, "Server started on port %d", port); + * plugin_logger_error(&logger, "Connection failed: %s", error_msg); + * plugin_logger_warn(&logger, "Retrying connection..."); + * plugin_logger_debug(&logger, "Processing request %d", req_id); + * + * The plugin name is automatically prefixed to all messages, e.g.: + * "[MY_PLUGIN] Server started on port 502" + */ + +#ifndef PLUGIN_LOGGER_H +#define PLUGIN_LOGGER_H + +#include +#include + +/** + * @brief Logging function pointer types (matching plugin_driver.h) + */ +typedef void (*plugin_log_func_t)(const char *fmt, ...); + +/** + * @brief Plugin logger structure + * + * Stores the plugin name and logging function pointers for use throughout + * the plugin lifecycle. + */ +typedef struct +{ + char plugin_name[64]; /**< Plugin name used as prefix in log messages */ + plugin_log_func_t log_info; /**< Info level logging function */ + plugin_log_func_t log_debug; /**< Debug level logging function */ + plugin_log_func_t log_warn; /**< Warning level logging function */ + plugin_log_func_t log_error; /**< Error level logging function */ + bool is_valid; /**< True if logger is properly initialized */ +} plugin_logger_t; + +/** + * @brief Initialize the plugin logger + * + * @param logger Pointer to the logger structure to initialize + * @param plugin_name Name of the plugin (will be used as prefix in messages) + * @param runtime_args Pointer to plugin_runtime_args_t containing log function pointers + * @return true if initialization succeeded, false otherwise + * + * Example: + * plugin_logger_t logger; + * if (!plugin_logger_init(&logger, "MODBUS_SLAVE", runtime_args)) { + * fprintf(stderr, "Failed to initialize logger\n"); + * return -1; + * } + */ +bool plugin_logger_init(plugin_logger_t *logger, const char *plugin_name, void *runtime_args); + +/** + * @brief Log an informational message + * + * @param logger Pointer to initialized plugin logger + * @param fmt Printf-style format string + * @param ... Format arguments + */ +void plugin_logger_info(plugin_logger_t *logger, const char *fmt, ...); + +/** + * @brief Log a debug message + * + * @param logger Pointer to initialized plugin logger + * @param fmt Printf-style format string + * @param ... Format arguments + */ +void plugin_logger_debug(plugin_logger_t *logger, const char *fmt, ...); + +/** + * @brief Log a warning message + * + * @param logger Pointer to initialized plugin logger + * @param fmt Printf-style format string + * @param ... Format arguments + */ +void plugin_logger_warn(plugin_logger_t *logger, const char *fmt, ...); + +/** + * @brief Log an error message + * + * @param logger Pointer to initialized plugin logger + * @param fmt Printf-style format string + * @param ... Format arguments + */ +void plugin_logger_error(plugin_logger_t *logger, const char *fmt, ...); + +#endif /* PLUGIN_LOGGER_H */ diff --git a/core/src/drivers/plugins/python/modbus_master/modbus_master_plugin.py b/core/src/drivers/plugins/python/modbus_master/modbus_master_plugin.py index 3c07f47a..b494b70e 100644 --- a/core/src/drivers/plugins/python/modbus_master/modbus_master_plugin.py +++ b/core/src/drivers/plugins/python/modbus_master/modbus_master_plugin.py @@ -14,8 +14,8 @@ # Import the correct type definitions # pylint: disable=wrong-import-position from shared import ( + PluginLogger, SafeBufferAccess, - SafeLoggingAccess, safe_extract_runtime_args_from_capsule, ) @@ -61,16 +61,17 @@ runtime_args = None modbus_master_config: ModbusMasterConfig = None safe_buffer_accessor: SafeBufferAccess = None -_safe_logging_access: SafeLoggingAccess = None +logger: PluginLogger = None slave_threads: List[threading.Thread] = [] # pylint: enable=invalid-name class ModbusSlaveDevice(threading.Thread): - def __init__(self, device_config: Any, sba: SafeBufferAccess): + def __init__(self, device_config: Any, sba: SafeBufferAccess, plugin_logger: PluginLogger): super().__init__(daemon=True) self.device_config = device_config self.sba = sba + self.logger = plugin_logger self._stop_event = threading.Event() self.connection_manager = ModbusConnectionManager( device_config.host, device_config.port, device_config.timeout_ms @@ -79,7 +80,7 @@ def __init__(self, device_config: Any, sba: SafeBufferAccess): # Calculate GCD of all I/O point cycle times for this device self.gcd_cycle_time_ms = calculate_gcd_of_cycle_times(device_config.io_points) - print(f"[{self.name}] Calculated GCD cycle time: {self.gcd_cycle_time_ms}ms") + self.logger.info(f"[{self.name}] Calculated GCD cycle time: {self.gcd_cycle_time_ms}ms") def _ensure_connection(self) -> bool: """ @@ -91,18 +92,18 @@ def _ensure_connection(self) -> bool: return self.connection_manager.ensure_connection(self._stop_event) def run(self): # pylint: disable=too-many-locals - print(f"[{self.name}] Thread started.") + self.logger.info(f"[{self.name}] Thread started.") io_points = self.device_config.io_points gcd_cycle_time_seconds = self.gcd_cycle_time_ms / 1000.0 if not io_points: - print(f"[{self.name}] No I/O points defined. Stopping thread.") + self.logger.warn(f"[{self.name}] No I/O points defined. Stopping thread.") return # Connect with infinite retry if not self.connection_manager.connect_with_retry(self._stop_event): - print(f"[{self.name}] Thread stopped before connection could be established.") + self.logger.info(f"[{self.name}] Thread stopped before connection could be established.") return # Initialize cycle counter @@ -166,21 +167,21 @@ def run(self): # pylint: disable=too-many-locals address, count=count ) else: - print(f"[{self.name}] Unsupported read FC: {point.fc}") + self.logger.warn(f"[{self.name}] Unsupported read FC: {point.fc}") continue # Check if response is valid if isinstance(response, (ModbusIOException, ExceptionResponse)): - print( - f"[{self.name}] (FAIL) Modbus read error " + self.logger.error( + f"[{self.name}] Modbus read error " f"(FC {point.fc}, addr {address}): {response}" ) # Mark as disconnected to force reconnection on next cycle self.connection_manager.mark_disconnected() continue if response.isError(): - print( - f"[{self.name}] (FAIL) Modbus read failed " + self.logger.error( + f"[{self.name}] Modbus read failed " f"(FC {point.fc}, addr {address}): {response}" ) # Mark as disconnected to force reconnection on next cycle @@ -199,20 +200,20 @@ def run(self): # pylint: disable=too-many-locals ) except ValueError as ve: - print( - f"[{self.name}] (FAIL) Invalid offset " + self.logger.error( + f"[{self.name}] Invalid offset " f"'{point.offset}' for FC {point.fc}: {ve}" ) except ConnectionException as ce: - print( - f"[{self.name}] (FAIL) Connection error reading " + self.logger.error( + f"[{self.name}] Connection error reading " f"FC {point.fc}, offset {point.offset}: {ce}" ) # Mark as disconnected to force reconnection self.connection_manager.mark_disconnected() except Exception as e: - print( - f"[{self.name}] (FAIL) Error reading " + self.logger.error( + f"[{self.name}] Error reading " f"FC {point.fc}, offset {point.offset}: {e}" ) # For other errors also mark disconnected as precaution @@ -231,8 +232,8 @@ def run(self): # pylint: disable=too-many-locals if converted_values is not None and details is not None: preconverted_updates.append((converted_values, details)) else: - print( - f"[{self.name}] (FAIL) Data conversion failed " + self.logger.error( + f"[{self.name}] Data conversion failed " f"for IEC address {iec_addr}, length={length}" ) @@ -248,8 +249,8 @@ def run(self): # pylint: disable=too-many-locals finally: self.sba.release_mutex() else: - print( - f"[{self.name}] (FAIL) Failed to acquire mutex " + self.logger.error( + f"[{self.name}] Failed to acquire mutex " f"for read updates: {lock_msg}" ) @@ -275,8 +276,8 @@ def run(self): # pylint: disable=too-many-locals address = parse_modbus_offset(point.offset) write_points_due.append((point, address)) except ValueError as ve: - print( - f"[{self.name}] (FAIL) Invalid offset " + self.logger.error( + f"[{self.name}] Invalid offset " f"'{point.offset}' for FC {point.fc}: {ve}" ) @@ -295,15 +296,15 @@ def run(self): # pylint: disable=too-many-locals (point, address, raw_values, details, iec_size) ) else: - print( - f"[{self.name}] (FAIL) Failed to read raw IEC data " + self.logger.error( + f"[{self.name}] Failed to read raw IEC data " f"for write (FC {point.fc}, offset {point.offset})" ) finally: self.sba.release_mutex() else: - print( - f"[{self.name}] (FAIL) Failed to acquire mutex " + self.logger.error( + f"[{self.name}] Failed to acquire mutex " f"for batch write prep: {lock_msg}" ) @@ -318,8 +319,8 @@ def run(self): # pylint: disable=too-many-locals values_to_write = convert_raw_iec_to_modbus(raw_values, details, iec_size) if values_to_write is None: - print( - f"[{self.name}] (FAIL) Failed to convert data " + self.logger.error( + f"[{self.name}] Failed to convert data " f"for Modbus write (FC {point.fc}, " f"offset {point.offset})" ) @@ -332,8 +333,8 @@ def run(self): # pylint: disable=too-many-locals address, values_to_write[0] ) else: - print( - f"[{self.name}] (FAIL) No data to write " + self.logger.error( + f"[{self.name}] No data to write " f"for FC 5, offset {address}" ) continue @@ -343,8 +344,8 @@ def run(self): # pylint: disable=too-many-locals address, values_to_write[0] ) else: - print( - f"[{self.name}] (FAIL) No data to write " + self.logger.error( + f"[{self.name}] No data to write " f"for FC 6, offset {address}" ) continue @@ -357,32 +358,32 @@ def run(self): # pylint: disable=too-many-locals address, values_to_write ) else: - print(f"[{self.name}] Unsupported write FC: {point.fc}") + self.logger.warn(f"[{self.name}] Unsupported write FC: {point.fc}") continue # Check write response if isinstance(response, (ModbusIOException, ExceptionResponse)): - print( - f"[{self.name}] (FAIL) Modbus write error " + self.logger.error( + f"[{self.name}] Modbus write error " f"(FC {point.fc}, addr {address}): {response}" ) self.connection_manager.mark_disconnected() elif response.isError(): - print( - f"[{self.name}] (FAIL) Modbus write failed " + self.logger.error( + f"[{self.name}] Modbus write failed " f"(FC {point.fc}, addr {address}): {response}" ) self.connection_manager.mark_disconnected() except ConnectionException as ce: - print( - f"[{self.name}] (FAIL) Connection error writing " + self.logger.error( + f"[{self.name}] Connection error writing " f"FC {point.fc}, offset {point.offset}: {ce}" ) self.connection_manager.mark_disconnected() except Exception as e: - print( - f"[{self.name}] (FAIL) Error writing " + self.logger.error( + f"[{self.name}] Error writing " f"FC {point.fc}, offset {point.offset}: {e}" ) self.connection_manager.mark_disconnected() @@ -404,18 +405,18 @@ def run(self): # pylint: disable=too-many-locals cycle_counter += 1 except ConnectionException as ce: - print(f"[{self.name}] (FAIL) Connection failed: {ce}") + self.logger.error(f"[{self.name}] Connection failed: {ce}") # Mark as disconnected to force reconnection self.connection_manager.mark_disconnected() except Exception as e: - print(f"[{self.name}] (FAIL) Unexpected error in thread: {e}") + self.logger.error(f"[{self.name}] Unexpected error in thread: {e}") traceback.print_exc() finally: self.connection_manager.disconnect() - print(f"[{self.name}] Thread finished and connection closed.") + self.logger.info(f"[{self.name}] Thread finished and connection closed.") def stop(self): - print(f"[{self.name}] Stop signal received.") + self.logger.info(f"[{self.name}] Stop signal received.") self._stop_event.set() @@ -424,50 +425,50 @@ def init(args_capsule): Initialize the Modbus Master plugin. This function is called once when the plugin is loaded. """ - global runtime_args, modbus_master_config, safe_buffer_accessor, _safe_logging_access + global runtime_args, modbus_master_config, safe_buffer_accessor, logger - print(" Modbus Master Plugin - Initializing...") + # Initialize logger early (without runtime_args for now) + logger = PluginLogger("MODBUS_MASTER", None) + logger.info("Initializing...") try: # Extract runtime arguments from capsule runtime_args, error_msg = safe_extract_runtime_args_from_capsule(args_capsule) if not runtime_args: - print(f"(FAIL) Failed to extract runtime args: {error_msg}") + logger.error(f"Failed to extract runtime args: {error_msg}") return False - print("(PASS) Runtime arguments extracted successfully") - - _safe_logging_access = SafeLoggingAccess(runtime_args) + # Re-initialize logger with runtime_args for central logging + logger = PluginLogger("MODBUS_MASTER", runtime_args) + logger.info("Runtime arguments extracted successfully") # Create safe buffer accessor safe_buffer_accessor = SafeBufferAccess(runtime_args) if not safe_buffer_accessor.is_valid: - print(f"(FAIL) Failed to create SafeBufferAccess: {safe_buffer_accessor.error_msg}") + logger.error(f"Failed to create SafeBufferAccess: {safe_buffer_accessor.error_msg}") return False - print("(PASS) SafeBufferAccess created successfully") + logger.info("SafeBufferAccess created successfully") # Load configuration config_path, config_error = safe_buffer_accessor.get_config_path() if not config_path: - print(f"(FAIL) Failed to get config path: {config_error}") + logger.error(f"Failed to get config path: {config_error}") return False - _safe_logging_access.log_debug(f" Loading configuration from: {config_path}") + logger.debug(f"Loading configuration from: {config_path}") modbus_master_config = ModbusMasterConfig() modbus_master_config.import_config_from_file(config_path) modbus_master_config.validate() device_count = len(modbus_master_config.devices) - _safe_logging_access.log_info( - f"(PASS) Configuration loaded successfully: {device_count} device(s)" - ) + logger.info(f"Configuration loaded successfully: {device_count} device(s)") return True except Exception as e: - print(f"(FAIL) Error during initialization: {e}") + logger.error(f"Error during initialization: {e}") traceback.print_exc() return False @@ -478,42 +479,38 @@ def start_loop(): This function is called after successful initialization. """ # pylint: disable=global-variable-not-assigned - global slave_threads, modbus_master_config, safe_buffer_accessor, _safe_logging_access + global slave_threads, modbus_master_config, safe_buffer_accessor, logger # pylint: enable=global-variable-not-assigned - _safe_logging_access.log_info(" Modbus Master Plugin - Starting main loop...") + logger.info("Starting main loop...") try: if not modbus_master_config or not safe_buffer_accessor: - _safe_logging_access.log_error("(FAIL) Plugin not properly initialized") + logger.error("Plugin not properly initialized") return False # Start a thread for each configured device for device_config in modbus_master_config.devices: try: - device_thread = ModbusSlaveDevice(device_config, safe_buffer_accessor) + device_thread = ModbusSlaveDevice(device_config, safe_buffer_accessor, logger) device_thread.start() slave_threads.append(device_thread) - _safe_logging_access.log_info( - f"(PASS) Started thread for device: {device_config.name} " + logger.info( + f"Started thread for device: {device_config.name} " f"({device_config.host}:{device_config.port})" ) except Exception as e: - _safe_logging_access.log_error( - f"(FAIL) Failed to start thread for device {device_config.name}: {e}" - ) + logger.error(f"Failed to start thread for device {device_config.name}: {e}") if slave_threads: - _safe_logging_access.log_info( - f"(PASS) Successfully started {len(slave_threads)} device thread(s)" - ) + logger.info(f"Successfully started {len(slave_threads)} device thread(s)") return True else: - _safe_logging_access.log_error("(FAIL) No device threads started") + logger.error("No device threads started") return False except Exception as e: - _safe_logging_access.log_error(f"(FAIL) Error starting main loop: {e}") + logger.error(f"Error starting main loop: {e}") traceback.print_exc() return False @@ -523,13 +520,13 @@ def stop_loop(): Stop the main loop and all running device threads. This function is called when the plugin needs to be stopped. """ - global slave_threads, _safe_logging_access # pylint: disable=global-variable-not-assigned + global slave_threads, logger # pylint: disable=global-variable-not-assigned - _safe_logging_access.log_info(" Modbus Master Plugin - Stopping main loop...") + logger.info("Stopping main loop...") try: if not slave_threads: - _safe_logging_access.log_info(" No threads to stop") + logger.info("No threads to stop") return True # Signal all threads to stop @@ -538,9 +535,9 @@ def stop_loop(): if hasattr(thread, "stop"): thread.stop() else: - print(f" Thread {thread.name} does not have a stop method") + logger.warn(f"Thread {thread.name} does not have a stop method") except Exception as e: - print(f"(FAIL) Error stopping thread {thread.name}: {e}") + logger.error(f"Error stopping thread {thread.name}: {e}") # Wait for all threads to finish (with timeout) timeout_per_thread = 5.0 # seconds @@ -548,17 +545,17 @@ def stop_loop(): try: thread.join(timeout=timeout_per_thread) if thread.is_alive(): - print(f" Thread {thread.name} did not stop within timeout") + logger.warn(f"Thread {thread.name} did not stop within timeout") else: - print(f"(PASS) Thread {thread.name} stopped successfully") + logger.info(f"Thread {thread.name} stopped successfully") except Exception as e: - _safe_logging_access.log_error(f"(FAIL) Error joining thread {thread.name}: {e}") + logger.error(f"Error joining thread {thread.name}: {e}") - _safe_logging_access.log_info("(PASS) Main loop stopped") + logger.info("Main loop stopped") return True except Exception as e: - _safe_logging_access.log_error(f"(FAIL) Error stopping main loop: {e}") + logger.error(f"Error stopping main loop: {e}") traceback.print_exc() return False @@ -570,10 +567,10 @@ def cleanup(): """ # pylint: disable=global-variable-not-assigned global runtime_args, modbus_master_config, safe_buffer_accessor - global slave_threads, _safe_logging_access + global slave_threads, logger # pylint: enable=global-variable-not-assigned - _safe_logging_access.log_info(" Modbus Master Plugin - Cleaning up...") + logger.info("Cleaning up...") try: # Stop all threads if not already stopped @@ -586,13 +583,12 @@ def cleanup(): runtime_args = None modbus_master_config = None safe_buffer_accessor = None - _safe_logging_access = None - print("(PASS) Cleanup completed successfully") + logger.info("Cleanup completed successfully") return True except Exception as e: - print(f"(FAIL) Error during cleanup: {e}") + logger.error(f"Error during cleanup: {e}") traceback.print_exc() return False diff --git a/core/src/drivers/plugins/python/modbus_slave/simple_modbus.py b/core/src/drivers/plugins/python/modbus_slave/simple_modbus.py index 4b1e286e..b467c953 100644 --- a/core/src/drivers/plugins/python/modbus_slave/simple_modbus.py +++ b/core/src/drivers/plugins/python/modbus_slave/simple_modbus.py @@ -53,6 +53,7 @@ # Import the correct type definitions (must be after sys.path modification) from shared import ( # noqa: E402 + PluginLogger, SafeBufferAccess, safe_extract_runtime_args_from_capsule, ) @@ -68,9 +69,11 @@ def __init__(self, runtime_args, num_coils=64): # Create safe buffer access wrapper self.safe_buffer_access = SafeBufferAccess(runtime_args) if not self.safe_buffer_access.is_valid: - print( - f"[MODBUS] Warning: Failed to create safe buffer access for coils: {self.safe_buffer_access.error_msg}" - ) + if logger: + logger.warn( + f"Failed to create safe buffer access for coils: " + f"{self.safe_buffer_access.error_msg}" + ) # Initialize with zeros super().__init__([0] * num_coils) @@ -80,9 +83,10 @@ def getValues(self, address, count=1): address = address - 1 # Modbus addresses are 0-based if not self.safe_buffer_access.is_valid: - print( - f"[MODBUS] Error: Safe buffer access not valid: {self.safe_buffer_access.error_msg}" - ) + if logger: + logger.error( + f"Safe buffer access not valid: {self.safe_buffer_access.error_msg}" + ) return [0] * count # Ensure thread-safe access @@ -103,7 +107,8 @@ def getValues(self, address, count=1): if error_msg == "Success": values.append(1 if value else 0) else: - print(f"[MODBUS] Error reading coil {coil_addr}: {error_msg}") + if logger: + logger.error(f"Error reading coil {coil_addr}: {error_msg}") values.append(0) else: values.append(0) @@ -118,9 +123,10 @@ def setValues(self, address, values): address = address - 1 # Modbus addresses are 0-based if not self.safe_buffer_access.is_valid: - print( - f"[MODBUS] Error: Safe buffer access not valid: {self.safe_buffer_access.error_msg}" - ) + if logger: + logger.error( + f"Safe buffer access not valid: {self.safe_buffer_access.error_msg}" + ) return # Ensure thread-safe access @@ -138,7 +144,8 @@ def setValues(self, address, values): buffer_idx, bit_idx, bool(value), thread_safe=False ) if error_msg != "Success": - print(f"[MODBUS] Error setting coil {coil_addr}: {error_msg}") + if logger: + logger.error(f"Error setting coil {coil_addr}: {error_msg}") # Release mutex after access self.safe_buffer_access.release_mutex() @@ -154,10 +161,11 @@ def __init__(self, runtime_args, num_inputs=64): # Create safe buffer access wrapper self.safe_buffer_access = SafeBufferAccess(runtime_args) if not self.safe_buffer_access.is_valid: - print( - f"[MODBUS] Warning: Failed to create safe buffer access for " - f"discrete inputs: {self.safe_buffer_access.error_msg}" - ) + if logger: + logger.warn( + f"Failed to create safe buffer access for " + f"discrete inputs: {self.safe_buffer_access.error_msg}" + ) # Initialize with zeros super().__init__([0] * num_inputs) @@ -167,9 +175,10 @@ def getValues(self, address, count=1): address = address - 1 # Modbus addresses are 0-based if not self.safe_buffer_access.is_valid: - print( - f"[MODBUS] Error: Safe buffer access not valid: {self.safe_buffer_access.error_msg}" - ) + if logger: + logger.error( + f"Safe buffer access not valid: {self.safe_buffer_access.error_msg}" + ) return [0] * count # Ensure thread-safe access @@ -190,7 +199,8 @@ def getValues(self, address, count=1): if error_msg == "Success": values.append(1 if value else 0) else: - print(f"[MODBUS] Error reading discrete input {input_addr}: {error_msg}") + if logger: + logger.error(f"Error reading discrete input {input_addr}: {error_msg}") values.append(0) else: values.append(0) @@ -215,10 +225,11 @@ def __init__(self, runtime_args, num_registers=32): # Create safe buffer access wrapper self.safe_buffer_access = SafeBufferAccess(runtime_args) if not self.safe_buffer_access.is_valid: - print( - f"[MODBUS] Warning: Failed to create safe buffer access for " - f"input registers: {self.safe_buffer_access.error_msg}" - ) + if logger: + logger.warn( + f"Failed to create safe buffer access for " + f"input registers: {self.safe_buffer_access.error_msg}" + ) # Initialize with zeros super().__init__([0] * num_registers) @@ -228,9 +239,10 @@ def getValues(self, address, count=1): address = address - 1 # Modbus addresses are 0-based if not self.safe_buffer_access.is_valid: - print( - f"[MODBUS] Error: Safe buffer access not valid: {self.safe_buffer_access.error_msg}" - ) + if logger: + logger.error( + f"Safe buffer access not valid: {self.safe_buffer_access.error_msg}" + ) return [0] * count # Ensure buffer mutex @@ -247,7 +259,8 @@ def getValues(self, address, count=1): if error_msg == "Success": values.append(value) else: - print(f"[MODBUS] Error reading input register {reg_addr}: {error_msg}") + if logger: + logger.error(f"Error reading input register {reg_addr}: {error_msg}") values.append(0) else: values.append(0) @@ -272,10 +285,11 @@ def __init__(self, runtime_args, num_registers=32): # Create safe buffer access wrapper self.safe_buffer_access = SafeBufferAccess(runtime_args) if not self.safe_buffer_access.is_valid: - print( - f"[MODBUS] Warning: Failed to create safe buffer access for " - f"holding registers: {self.safe_buffer_access.error_msg}" - ) + if logger: + logger.warn( + f"Failed to create safe buffer access for " + f"holding registers: {self.safe_buffer_access.error_msg}" + ) # Initialize with zeros super().__init__([0] * num_registers) @@ -285,9 +299,10 @@ def getValues(self, address, count=1): address = address - 1 # Modbus addresses are 0-based if not self.safe_buffer_access.is_valid: - print( - f"[MODBUS] Error: Safe buffer access not valid: {self.safe_buffer_access.error_msg}" - ) + if logger: + logger.error( + f"Safe buffer access not valid: {self.safe_buffer_access.error_msg}" + ) return [0] * count # Ensure buffer mutex @@ -304,7 +319,8 @@ def getValues(self, address, count=1): if error_msg == "Success": values.append(value) else: - print(f"[MODBUS] Error reading holding register {reg_addr}: {error_msg}") + if logger: + logger.error(f"Error reading holding register {reg_addr}: {error_msg}") values.append(0) else: values.append(0) @@ -318,9 +334,10 @@ def setValues(self, address, values): address = address - 1 # Modbus addresses are 0-based if not self.safe_buffer_access.is_valid: - print( - f"[MODBUS] Error: Safe buffer access not valid: {self.safe_buffer_access.error_msg}" - ) + if logger: + logger.error( + f"Safe buffer access not valid: {self.safe_buffer_access.error_msg}" + ) return # Ensure buffer mutex @@ -334,7 +351,8 @@ def setValues(self, address, values): reg_addr, value, thread_safe=False ) if error_msg != "Success": - print(f"[MODBUS] Error setting holding register {reg_addr}: {error_msg}") + if logger: + logger.error(f"Error setting holding register {reg_addr}: {error_msg}") # Release mutex after access self.safe_buffer_access.release_mutex() @@ -358,16 +376,18 @@ def __init__(self, runtime_args, qx_bits=8192, mx_bits=0): # Create safe buffer access wrapper self.safe_buffer_access = SafeBufferAccess(runtime_args) if not self.safe_buffer_access.is_valid: - print( - f"[MODBUS] Warning: Failed to create safe buffer access for segmented coils: " - f"{self.safe_buffer_access.error_msg}" - ) + if logger: + logger.warn( + f"Failed to create safe buffer access for segmented coils: " + f"{self.safe_buffer_access.error_msg}" + ) # Initialize with zeros super().__init__([0] * self.total_bits) - print( - f"[MODBUS] Segmented coils: %QX={qx_bits} bits, %MX={mx_bits} bits, total={self.total_bits}" - ) + if logger: + logger.info( + f"Segmented coils: %QX={qx_bits} bits, %MX={mx_bits} bits, total={self.total_bits}" + ) def _get_segment_info(self, coil_addr): """ @@ -397,9 +417,10 @@ def getValues(self, address, count=1): address = address - 1 # Modbus addresses are 1-based if not self.safe_buffer_access.is_valid: - print( - f"[MODBUS] Error: Safe buffer access not valid: {self.safe_buffer_access.error_msg}" - ) + if logger: + logger.error( + f"Safe buffer access not valid: {self.safe_buffer_access.error_msg}" + ) return [0] * count self.safe_buffer_access.acquire_mutex() @@ -416,7 +437,8 @@ def getValues(self, address, count=1): if error_msg == "Success": values.append(1 if value else 0) else: - print(f"[MODBUS] Error reading coil %QX{coil_addr}: {error_msg}") + if logger: + logger.error(f"Error reading coil %QX{coil_addr}: {error_msg}") values.append(0) elif segment == "mx": mx_addr = coil_addr - self.qx_bits @@ -426,7 +448,8 @@ def getValues(self, address, count=1): if error_msg == "Success": values.append(1 if value else 0) else: - print(f"[MODBUS] Error reading coil %MX{mx_addr}: {error_msg}") + if logger: + logger.error(f"Error reading coil %MX{mx_addr}: {error_msg}") values.append(0) else: values.append(0) @@ -440,9 +463,10 @@ def setValues(self, address, values): address = address - 1 # Modbus addresses are 1-based if not self.safe_buffer_access.is_valid: - print( - f"[MODBUS] Error: Safe buffer access not valid: {self.safe_buffer_access.error_msg}" - ) + if logger: + logger.error( + f"Safe buffer access not valid: {self.safe_buffer_access.error_msg}" + ) return self.safe_buffer_access.acquire_mutex() @@ -456,14 +480,16 @@ def setValues(self, address, values): buffer_idx, bit_idx, bool(value), thread_safe=False ) if error_msg != "Success": - print(f"[MODBUS] Error setting coil %QX{coil_addr}: {error_msg}") + if logger: + logger.error(f"Error setting coil %QX{coil_addr}: {error_msg}") elif segment == "mx": mx_addr = coil_addr - self.qx_bits _, error_msg = self.safe_buffer_access.write_bool_memory( buffer_idx, bit_idx, bool(value), thread_safe=False ) if error_msg != "Success": - print(f"[MODBUS] Error setting coil %MX{mx_addr}: {error_msg}") + if logger: + logger.error(f"Error setting coil %MX{mx_addr}: {error_msg}") finally: self.safe_buffer_access.release_mutex() @@ -513,18 +539,20 @@ def __init__( # Create safe buffer access wrapper self.safe_buffer_access = SafeBufferAccess(runtime_args) if not self.safe_buffer_access.is_valid: - print( - f"[MODBUS] Warning: Failed to create safe buffer access for segmented holding registers: " - f"{self.safe_buffer_access.error_msg}" - ) + if logger: + logger.warn( + f"Failed to create safe buffer access for segmented holding registers: " + f"{self.safe_buffer_access.error_msg}" + ) # Initialize with zeros super().__init__([0] * self.total_registers) - print( - f"[MODBUS] Segmented holding registers: %QW=0-{self.qw_end - 1}, " - f"%MW={self.mw_start}-{self.mw_end - 1}, %MD={self.md_start}-{self.md_end - 1}, " - f"%ML={self.ml_start}-{self.ml_end - 1}, word_order={word_order}" - ) + if logger: + logger.info( + f"Segmented holding registers: %QW=0-{self.qw_end - 1}, " + f"%MW={self.mw_start}-{self.mw_end - 1}, %MD={self.md_start}-{self.md_end - 1}, " + f"%ML={self.ml_start}-{self.ml_end - 1}, word_order={word_order}" + ) def _get_segment_info(self, reg_addr): """ @@ -609,9 +637,10 @@ def getValues(self, address, count=1): address = address - 1 # Modbus addresses are 1-based if not self.safe_buffer_access.is_valid: - print( - f"[MODBUS] Error: Safe buffer access not valid: {self.safe_buffer_access.error_msg}" - ) + if logger: + logger.error( + f"Safe buffer access not valid: {self.safe_buffer_access.error_msg}" + ) return [0] * count self.safe_buffer_access.acquire_mutex() @@ -628,7 +657,8 @@ def getValues(self, address, count=1): if error_msg == "Success": values.append(value & 0xFFFF) else: - print(f"[MODBUS] Error reading %QW{value_idx}: {error_msg}") + if logger: + logger.error(f"Error reading %QW{value_idx}: {error_msg}") values.append(0) elif segment == "mw": @@ -638,7 +668,8 @@ def getValues(self, address, count=1): if error_msg == "Success": values.append(value & 0xFFFF) else: - print(f"[MODBUS] Error reading %MW{value_idx}: {error_msg}") + if logger: + logger.error(f"Error reading %MW{value_idx}: {error_msg}") values.append(0) elif segment == "md": @@ -649,7 +680,8 @@ def getValues(self, address, count=1): words = self._split_dint_to_words(dint_value) values.append(words[word_offset]) else: - print(f"[MODBUS] Error reading %MD{value_idx}: {error_msg}") + if logger: + logger.error(f"Error reading %MD{value_idx}: {error_msg}") values.append(0) elif segment == "ml": @@ -660,7 +692,8 @@ def getValues(self, address, count=1): words = self._split_lint_to_words(lint_value) values.append(words[word_offset]) else: - print(f"[MODBUS] Error reading %ML{value_idx}: {error_msg}") + if logger: + logger.error(f"Error reading %ML{value_idx}: {error_msg}") values.append(0) else: @@ -675,9 +708,10 @@ def setValues(self, address, values): address = address - 1 # Modbus addresses are 1-based if not self.safe_buffer_access.is_valid: - print( - f"[MODBUS] Error: Safe buffer access not valid: {self.safe_buffer_access.error_msg}" - ) + if logger: + logger.error( + f"Safe buffer access not valid: {self.safe_buffer_access.error_msg}" + ) return self.safe_buffer_access.acquire_mutex() @@ -696,14 +730,16 @@ def setValues(self, address, values): value_idx, value & 0xFFFF, thread_safe=False ) if error_msg != "Success": - print(f"[MODBUS] Error setting %QW{value_idx}: {error_msg}") + if logger: + logger.error(f"Error setting %QW{value_idx}: {error_msg}") elif segment == "mw": _, error_msg = self.safe_buffer_access.write_int_memory( value_idx, value & 0xFFFF, thread_safe=False ) if error_msg != "Success": - print(f"[MODBUS] Error setting %MW{value_idx}: {error_msg}") + if logger: + logger.error(f"Error setting %MW{value_idx}: {error_msg}") elif segment == "md": # Collect words for this DINT @@ -736,7 +772,8 @@ def setValues(self, address, values): value_idx, dint_value, thread_safe=False ) if error_msg != "Success": - print(f"[MODBUS] Error setting %MD{value_idx}: {error_msg}") + if logger: + logger.error(f"Error setting %MD{value_idx}: {error_msg}") # Write pending LINT values for value_idx, words in pending_lint.items(): @@ -745,7 +782,8 @@ def setValues(self, address, values): value_idx, lint_value, thread_safe=False ) if error_msg != "Success": - print(f"[MODBUS] Error setting %ML{value_idx}: {error_msg}") + if logger: + logger.error(f"Error setting %ML{value_idx}: {error_msg}") finally: self.safe_buffer_access.release_mutex() @@ -844,6 +882,7 @@ def parse_buffer_mapping_config(config_map): server_context = None runtime_args = None running = False +logger: PluginLogger = None server_loop = None # Reference to the server's event loop for cross-thread operations server_started_event = threading.Event() # Signals successful server startup server_error = None # Stores any startup error message @@ -857,27 +896,31 @@ def parse_buffer_mapping_config(config_map): def init(args_capsule): """Initialize the Modbus plugin""" - global runtime_args, server_context, gIp, gPort + global runtime_args, server_context, gIp, gPort, logger - print("[MODBUS] Python plugin 'simple_modbus' initializing...") + # Initialize logger early (without runtime_args for now) + logger = PluginLogger("MODBUS_SLAVE", None) + logger.info("Python plugin 'simple_modbus' initializing...") try: # Print structure validation info for debugging - print("[MODBUS] Validating plugin structure alignment...") + logger.debug("Validating plugin structure alignment...") # Extract runtime args from capsule using safe method if hasattr(args_capsule, "__class__") and "PyCapsule" in str(type(args_capsule)): # This is a PyCapsule from C - use safe extraction runtime_args, error_msg = safe_extract_runtime_args_from_capsule(args_capsule) if runtime_args is None: - print(f"[MODBUS] Failed to extract runtime args: {error_msg}") + logger.error(f"Failed to extract runtime args: {error_msg}") return False - print("[MODBUS] Runtime arguments extracted successfully") + # Re-initialize logger with runtime_args for central logging + logger = PluginLogger("MODBUS_SLAVE", runtime_args) + logger.info("Runtime arguments extracted successfully") else: # This is a direct object (for testing) runtime_args = args_capsule - print("[MODBUS] Using direct runtime args for testing") + logger.info("Using direct runtime args for testing") # Try to load configuration from plugin_specific_config_file_path config_map = None @@ -890,20 +933,21 @@ def init(args_capsule): if network_config and "host" in network_config and "port" in network_config: gIp = str(network_config["host"]) gPort = int(network_config["port"]) - print(f"[MODBUS] Configuration loaded - Host: {gIp}, Port: {gPort}") + logger.info(f"Configuration loaded - Host: {gIp}, Port: {gPort}") else: - print( - "[MODBUS] Config file loaded but network_configuration section missing or incomplete - using defaults" + logger.warn( + "Config file loaded but network_configuration section missing or " + "incomplete - using defaults" ) - print(f"[MODBUS] Available config sections: {list(config_map.keys())}") + logger.debug(f"Available config sections: {list(config_map.keys())}") # Parse buffer mapping configuration buffer_config = parse_buffer_mapping_config(config_map) - print(f"[MODBUS] Buffer mapping format: {buffer_config['format']}") + logger.info(f"Buffer mapping format: {buffer_config['format']}") else: - print(f"[MODBUS] Failed to load configuration file: {status} - using defaults") + logger.warn(f"Failed to load configuration file: {status} - using defaults") except Exception as config_error: - print(f"[MODBUS] Exception while loading config: {config_error} - using defaults") + logger.warn(f"Exception while loading config: {config_error} - using defaults") import traceback traceback.print_exc() @@ -911,12 +955,12 @@ def init(args_capsule): # Use default configuration if not loaded from file if buffer_config is None: buffer_config = parse_buffer_mapping_config({}) - print("[MODBUS] Using default buffer mapping configuration") + logger.info("Using default buffer mapping configuration") # Safely access buffer size using validation buffer_size, size_error = runtime_args.safe_access_buffer_size() if buffer_size == -1: - print(f"[MODBUS] Failed to access buffer size: {size_error}") + logger.error(f"Failed to access buffer size: {size_error}") return False # Create OpenPLC-connected data blocks based on configuration @@ -954,11 +998,11 @@ def init(args_capsule): ) server_context = ModbusServerContext(devices={1: device}, single=False) - print(f"[MODBUS] Plugin initialized successfully - Host: {gIp}, Port: {gPort}") + logger.info(f"Plugin initialized successfully - Host: {gIp}, Port: {gPort}") return True except Exception as e: - print(f"[MODBUS] Plugin initialization failed: {e}") + logger.error(f"Plugin initialization failed: {e}") import traceback traceback.print_exc() @@ -970,12 +1014,12 @@ def start_loop(): global server_task, running, server_loop, server_started_event, server_error if server_context is None: - print("[MODBUS] Error: Plugin not initialized") + logger.error("Plugin not initialized") return False # Prevent double-start if server_task is not None and server_task.is_alive(): - print("[MODBUS] Warning: Server already running") + logger.warn("Server already running") return True running = True @@ -1011,7 +1055,7 @@ async def server_runner(): # If we get here, server is listening if first_attempt: - print(f"[MODBUS] Server listening on {gIp}:{gPort}") + logger.info(f"Server listening on {gIp}:{gPort}") server_started_event.set() first_attempt = False @@ -1031,13 +1075,13 @@ async def server_runner(): if first_attempt: # Signal startup failure on first attempt - print(f"[MODBUS] Failed to start server on {gIp}:{gPort}: {error_msg}") + logger.error(f"Failed to start server on {gIp}:{gPort}: {error_msg}") server_started_event.set() # Unblock start_loop if not running: break # Stop requested, don't retry - print(f"[MODBUS] Server error, will retry in {backoff:.1f}s: {error_msg}") + logger.warn(f"Server error, will retry in {backoff:.1f}s: {error_msg}") # Wait before retry (check running flag periodically) wait_time = 0 @@ -1052,7 +1096,7 @@ async def server_runner(): try: loop.run_until_complete(server_runner()) except Exception as e: - print(f"[MODBUS] Fatal error in server thread: {e}") + logger.error(f"Fatal error in server thread: {e}") finally: server_loop = None loop.close() @@ -1064,11 +1108,11 @@ async def server_runner(): startup_timeout = 5.0 if server_started_event.wait(timeout=startup_timeout): if server_error is not None: - print(f"[MODBUS] Server startup failed: {server_error}") + logger.error(f"Server startup failed: {server_error}") return False return True else: - print(f"[MODBUS] Timeout waiting for server to start on {gIp}:{gPort}") + logger.error(f"Timeout waiting for server to start on {gIp}:{gPort}") return False @@ -1085,14 +1129,14 @@ def stop_loop(): ServerStop() except RuntimeError as e: # Server may not be running or already stopped - print(f"[MODBUS] ServerStop warning: {e}") + logger.warn(f"ServerStop warning: {e}") server_task.join(timeout=5.0) if server_task.is_alive(): - print("[MODBUS] Warning: Server thread did not stop within timeout") + logger.warn("Server thread did not stop within timeout") server_task = None - print("[MODBUS] Server stopped") + logger.info("Server stopped") return True @@ -1103,7 +1147,7 @@ def cleanup(): server_context = None runtime_args = None - print("[MODBUS] Plugin cleaned up") + logger.info("Plugin cleaned up") return True diff --git a/core/src/drivers/plugins/python/shared/__init__.py b/core/src/drivers/plugins/python/shared/__init__.py index deecb5e4..049786f9 100644 --- a/core/src/drivers/plugins/python/shared/__init__.py +++ b/core/src/drivers/plugins/python/shared/__init__.py @@ -11,6 +11,9 @@ # Safe logging access functionality from .safe_logging_access import SafeLoggingAccess +# Plugin logger (high-level logging interface for plugins) +from .plugin_logger import PluginLogger + # Core type definitions from .iec_types import IEC_BOOL, IEC_BYTE, IEC_UINT, IEC_UDINT, IEC_ULINT from .plugin_runtime_args import PluginRuntimeArgs @@ -34,6 +37,9 @@ # Safe logging access functionality 'SafeLoggingAccess', + # Plugin logger (high-level interface) + 'PluginLogger', + # IEC type definitions 'IEC_BOOL', 'IEC_BYTE', 'IEC_UINT', 'IEC_UDINT', 'IEC_ULINT', diff --git a/core/src/drivers/plugins/python/shared/plugin_logger.py b/core/src/drivers/plugins/python/shared/plugin_logger.py new file mode 100644 index 00000000..ec7aeedd --- /dev/null +++ b/core/src/drivers/plugins/python/shared/plugin_logger.py @@ -0,0 +1,171 @@ +""" +Centralized Plugin Logger Module + +Provides a simple, consistent logging interface for OpenPLC Python plugins that +routes log messages through the central logging system: + + C runtime log functions -> Unix socket -> Python log server -> + REST API -> OpenPLC Editor + +This ensures all plugin logs are visible in the Editor's log viewer. + +Usage: + from shared import PluginLogger + + # In plugin init(): + logger = PluginLogger("MODBUS_SLAVE", runtime_args) + + # Throughout plugin: + logger.info("Server started on port 502") + logger.error("Connection failed: timeout") + logger.warn("Retrying connection...") + logger.debug("Processing request...") + +The plugin name is automatically prefixed to all messages, e.g.: + "[MODBUS_SLAVE] Server started on port 502" +""" + +from typing import Optional +from .safe_logging_access import SafeLoggingAccess + + +class PluginLogger: + """ + Thread-safe logger for OpenPLC plugins that routes messages to the + central logging system. + + Attributes: + plugin_name: Name of the plugin (used as prefix in log messages) + is_valid: True if the logger is properly connected to central logging + """ + + def __init__(self, plugin_name: str, runtime_args=None): + """ + Initialize the plugin logger. + + Args: + plugin_name: Name of the plugin (e.g., "MODBUS_SLAVE", "MODBUS_MASTER"). + This will be used as a prefix in all log messages. + runtime_args: PluginRuntimeArgs instance containing logging function + pointers. If None, logger will fall back to print(). + """ + self.plugin_name = plugin_name + self._prefix = f"[{plugin_name}]" + self._logging_access: Optional[SafeLoggingAccess] = None + self._is_valid = False + + if runtime_args is not None: + self._logging_access = SafeLoggingAccess(runtime_args) + if self._logging_access.is_valid: + self._is_valid = True + else: + # Log the validation failure via fallback + self._fallback_print( + "WARN", + f"SafeLoggingAccess not valid: {self._logging_access.error_msg}. " + "Falling back to print()." + ) + self._logging_access = None + + @property + def is_valid(self) -> bool: + """Returns True if logger is connected to central logging system.""" + return self._is_valid + + def _format_message(self, message: str) -> str: + """Format message with plugin name prefix.""" + return f"{self._prefix} {message}" + + def _fallback_print(self, level: str, message: str): + """ + Fallback to print when logging access is unavailable. + + This ensures messages are still visible in stdout even if the + central logging system is not available. + """ + print(f"{self._prefix} [{level}] {message}") + + def info(self, message: str) -> bool: + """ + Log an informational message. + + Args: + message: The message to log + + Returns: + True if message was sent to central logging, False if fallback was used + """ + formatted = self._format_message(message) + if self._logging_access: + success, error_msg = self._logging_access.log_info(formatted) + if success: + return True + # If logging failed, fall back to print + self._fallback_print("INFO", message) + return False + else: + self._fallback_print("INFO", message) + return False + + def error(self, message: str) -> bool: + """ + Log an error message. + + Args: + message: The message to log + + Returns: + True if message was sent to central logging, False if fallback was used + """ + formatted = self._format_message(message) + if self._logging_access: + success, error_msg = self._logging_access.log_error(formatted) + if success: + return True + self._fallback_print("ERROR", message) + return False + else: + self._fallback_print("ERROR", message) + return False + + def warn(self, message: str) -> bool: + """ + Log a warning message. + + Args: + message: The message to log + + Returns: + True if message was sent to central logging, False if fallback was used + """ + formatted = self._format_message(message) + if self._logging_access: + success, error_msg = self._logging_access.log_warn(formatted) + if success: + return True + self._fallback_print("WARN", message) + return False + else: + self._fallback_print("WARN", message) + return False + + def debug(self, message: str) -> bool: + """ + Log a debug message. + + Args: + message: The message to log + + Returns: + True if message was sent to central logging, False if fallback was used + """ + formatted = self._format_message(message) + if self._logging_access: + success, error_msg = self._logging_access.log_debug(formatted) + if success: + return True + self._fallback_print("DEBUG", message) + return False + else: + self._fallback_print("DEBUG", message) + return False From dac94b989036ff3219a022bf688d5804a72ec68d Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Tue, 6 Jan 2026 15:30:50 -0500 Subject: [PATCH 2/2] Refactor: Extract plugin_runtime_args_t to plugin_types.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Moved plugin_runtime_args_t and logging function pointer typedefs from plugin_driver.h to a new plugin_types.h header. This allows native plugins to include only the essential types without pulling in unnecessary dependencies (plcapp_manager.h, plugin_config.h, python_plugin_bridge.h). - Created plugin_types.h with plugin_runtime_args_t and log function types - Updated plugin_driver.h to include plugin_types.h - Updated plugin_logger.c to use plugin_types.h instead of plugin_driver.h - Updated test_plugin.c to use plugin_types.h 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- core/src/drivers/plugin_driver.h | 45 +--------- core/src/drivers/plugin_types.h | 84 +++++++++++++++++++ .../plugins/native/examples/test_plugin.c | 4 +- .../drivers/plugins/native/plugin_logger.c | 43 +--------- 4 files changed, 89 insertions(+), 87 deletions(-) create mode 100644 core/src/drivers/plugin_types.h diff --git a/core/src/drivers/plugin_driver.h b/core/src/drivers/plugin_driver.h index b773419a..864f0467 100644 --- a/core/src/drivers/plugin_driver.h +++ b/core/src/drivers/plugin_driver.h @@ -1,11 +1,10 @@ #ifndef PLUGIN_DRIVER_H #define PLUGIN_DRIVER_H -#include "../lib/iec_types.h" #include "../plc_app/plcapp_manager.h" #include "plugin_config.h" +#include "plugin_types.h" #include "python_plugin_bridge.h" -#include // Maximum number of plugins #define MAX_PLUGINS 16 @@ -23,12 +22,6 @@ typedef void (*plugin_cycle_start_func_t)(void); typedef void (*plugin_cycle_end_func_t)(void); typedef void (*plugin_cleanup_func_t)(void); -// Logging function pointer types -typedef void (*plugin_log_info_func_t)(const char *fmt, ...); -typedef void (*plugin_log_debug_func_t)(const char *fmt, ...); -typedef void (*plugin_log_warn_func_t)(const char *fmt, ...); -typedef void (*plugin_log_error_func_t)(const char *fmt, ...); - typedef struct { void *handle; // Handle to the loaded shared library @@ -40,42 +33,6 @@ typedef struct plugin_cleanup_func_t cleanup; } plugin_funct_bundle_t; -// Runtime buffer access structure for plugins -typedef struct -{ - // Buffer pointers - IEC_BOOL *(*bool_input)[8]; - IEC_BOOL *(*bool_output)[8]; - IEC_BYTE **byte_input; - IEC_BYTE **byte_output; - IEC_UINT **int_input; - IEC_UINT **int_output; - IEC_UDINT **dint_input; - IEC_UDINT **dint_output; - IEC_ULINT **lint_input; - IEC_ULINT **lint_output; - IEC_UINT **int_memory; - IEC_UDINT **dint_memory; - IEC_ULINT **lint_memory; - IEC_BOOL *(*bool_memory)[8]; - - // Mutex functions - int (*mutex_take)(pthread_mutex_t *mutex); - int (*mutex_give)(pthread_mutex_t *mutex); - pthread_mutex_t *buffer_mutex; - char plugin_specific_config_file_path[256]; - - // Buffer size information - int buffer_size; - int bits_per_buffer; - - // Logging functions - plugin_log_info_func_t log_info; - plugin_log_debug_func_t log_debug; - plugin_log_warn_func_t log_warn; - plugin_log_error_func_t log_error; -} plugin_runtime_args_t; - // Plugin instance structure typedef struct plugin_instance_s { diff --git a/core/src/drivers/plugin_types.h b/core/src/drivers/plugin_types.h new file mode 100644 index 00000000..0c8d9aca --- /dev/null +++ b/core/src/drivers/plugin_types.h @@ -0,0 +1,84 @@ +/** + * @file plugin_types.h + * @brief Common type definitions for OpenPLC plugins + * + * This header defines the essential types and structures shared between + * the plugin driver system and native plugins. It provides: + * - Logging function pointer types + * - The plugin_runtime_args_t structure for runtime buffer access + * + * Both Python and native plugins receive a pointer to plugin_runtime_args_t + * during initialization, giving them access to PLC I/O buffers, mutex + * functions, and centralized logging. + */ + +#ifndef PLUGIN_TYPES_H +#define PLUGIN_TYPES_H + +#include "../lib/iec_types.h" +#include + +/** + * @brief Logging function pointer types + * + * These function pointers are provided to plugins for routing log messages + * through the central OpenPLC logging system. Messages logged through these + * functions will appear in the OpenPLC Editor's log viewer. + */ +typedef void (*plugin_log_info_func_t)(const char *fmt, ...); +typedef void (*plugin_log_debug_func_t)(const char *fmt, ...); +typedef void (*plugin_log_warn_func_t)(const char *fmt, ...); +typedef void (*plugin_log_error_func_t)(const char *fmt, ...); + +/** + * @brief Runtime buffer access structure for plugins + * + * This structure is passed to plugins during initialization, providing + * access to: + * - PLC I/O buffers (bool, byte, int, dint, lint for inputs/outputs/memory) + * - Mutex functions for thread-safe buffer access + * - Plugin-specific configuration file path + * - Buffer size information + * - Centralized logging functions + * + * Plugins should use mutex_take/mutex_give when accessing buffers to ensure + * thread safety with the PLC scan cycle. + */ +typedef struct +{ + /* Buffer pointers */ + IEC_BOOL *(*bool_input)[8]; + IEC_BOOL *(*bool_output)[8]; + IEC_BYTE **byte_input; + IEC_BYTE **byte_output; + IEC_UINT **int_input; + IEC_UINT **int_output; + IEC_UDINT **dint_input; + IEC_UDINT **dint_output; + IEC_ULINT **lint_input; + IEC_ULINT **lint_output; + IEC_UINT **int_memory; + IEC_UDINT **dint_memory; + IEC_ULINT **lint_memory; + IEC_BOOL *(*bool_memory)[8]; + + /* Mutex functions for thread-safe buffer access */ + int (*mutex_take)(pthread_mutex_t *mutex); + int (*mutex_give)(pthread_mutex_t *mutex); + pthread_mutex_t *buffer_mutex; + + /* Plugin configuration */ + char plugin_specific_config_file_path[256]; + + /* Buffer size information */ + int buffer_size; + int bits_per_buffer; + + /* Logging functions - route messages through central logging system */ + plugin_log_info_func_t log_info; + plugin_log_debug_func_t log_debug; + plugin_log_warn_func_t log_warn; + plugin_log_error_func_t log_error; +} plugin_runtime_args_t; + +#endif /* PLUGIN_TYPES_H */ diff --git a/core/src/drivers/plugins/native/examples/test_plugin.c b/core/src/drivers/plugins/native/examples/test_plugin.c index b6ecdce8..0b443dd2 100644 --- a/core/src/drivers/plugins/native/examples/test_plugin.c +++ b/core/src/drivers/plugins/native/examples/test_plugin.c @@ -17,8 +17,8 @@ /* Include IEC types */ #include "../../../../lib/iec_types.h" -/* Include the actual plugin_runtime_args_t definition */ -#include "../../../plugin_driver.h" +/* Include the plugin types (runtime args, logging function types) */ +#include "../../../plugin_types.h" /* Global logger instance */ static plugin_logger_t g_logger; diff --git a/core/src/drivers/plugins/native/plugin_logger.c b/core/src/drivers/plugins/native/plugin_logger.c index 98a8aa4e..ac87353a 100644 --- a/core/src/drivers/plugins/native/plugin_logger.c +++ b/core/src/drivers/plugins/native/plugin_logger.c @@ -4,6 +4,7 @@ */ #include "plugin_logger.h" +#include "../../plugin_types.h" #include #include #include @@ -11,46 +12,6 @@ /* Maximum size for formatted log messages */ #define MAX_LOG_MESSAGE_SIZE 1024 -/** - * @brief Runtime args structure (must match plugin_driver.h) - * - * We only need the logging function pointers from this structure. - */ -typedef struct -{ - /* Buffer pointers - not used by logger */ - void *bool_input; - void *bool_output; - void *byte_input; - void *byte_output; - void *int_input; - void *int_output; - void *dint_input; - void *dint_output; - void *lint_input; - void *lint_output; - void *int_memory; - void *dint_memory; - void *lint_memory; - void *bool_memory; - - /* Mutex functions - not used by logger */ - void *mutex_take; - void *mutex_give; - void *buffer_mutex; - char plugin_specific_config_file_path[256]; - - /* Buffer size information - not used by logger */ - int buffer_size; - int bits_per_buffer; - - /* Logging functions - these are what we need */ - plugin_log_func_t log_info; - plugin_log_func_t log_debug; - plugin_log_func_t log_warn; - plugin_log_func_t log_error; -} plugin_runtime_args_internal_t; - bool plugin_logger_init(plugin_logger_t *logger, const char *plugin_name, void *runtime_args) { if (!logger) @@ -84,7 +45,7 @@ bool plugin_logger_init(plugin_logger_t *logger, const char *plugin_name, void * } /* Extract logging function pointers from runtime_args */ - plugin_runtime_args_internal_t *args = (plugin_runtime_args_internal_t *)runtime_args; + plugin_runtime_args_t *args = (plugin_runtime_args_t *)runtime_args; logger->log_info = args->log_info; logger->log_debug = args->log_debug;