From d02e2d3ff46cdb766d0eb636fc94375ef21aebd6 Mon Sep 17 00:00:00 2001 From: renakas <107467323+renakas@users.noreply.github.com> Date: Mon, 6 Oct 2025 20:15:28 +0200 Subject: [PATCH 1/5] Added log rotation with python --- agent360/agent360.py | 94 +++++++++++++++++++++++++++++++++----------- 1 file changed, 71 insertions(+), 23 deletions(-) diff --git a/agent360/agent360.py b/agent360/agent360.py index e20b626..614c19b 100755 --- a/agent360/agent360.py +++ b/agent360/agent360.py @@ -42,6 +42,9 @@ import time import types from optparse import OptionParser +from logging import Formatter, getLogger, StreamHandler +from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler + try: from urllib.parse import urlparse, urlencode @@ -366,38 +369,83 @@ def _config_section_create(self, section): if not self.config.has_section(section): self.config.add_section(section) - def _logging_init(self): - ''' - Initialize logging faculty - ''' - level = self.config.getint('agent', 'logging_level') - if os.name == 'nt': - log_file = os.path.expandvars(self.config.get('agent', 'log_file')) - else: - log_file = self.config.get('agent', 'log_file') +def _logging_init(self): + """ + Initialize logging with optional in-app log rotation. + Supported rotation modes: + - size (default): RotatingFileHandler + - time: TimedRotatingFileHandler + - none: plain FileHandler + """ + level = self.config.getint('agent', 'logging_level') + + # Resolve target log file path + if os.name == 'nt': + log_file = os.path.expandvars(self.config.get('agent', 'log_file')) + else: + log_file = self.config.get('agent', 'log_file') - log_file_mode = self.config.get('agent', 'log_file_mode') - if log_file_mode in ('w', 'a'): - pass - elif log_file_mode == 'truncate': + # Normalize legacy 'log_file_mode' (kept for compatibility) + log_file_mode = self.config.get('agent', 'log_file_mode') + if log_file_mode not in ('w', 'a'): + if log_file_mode == 'truncate': log_file_mode = 'w' - elif log_file_mode == 'append': - log_file_mode = 'a' else: log_file_mode = 'a' + # Rotation config (all optional) + rotation = self.config.get('agent', 'rotation') if self.config.has_option('agent', 'rotation') else 'size' + # Size-based defaults (10MB, keep 7 files) + rotation_max_bytes = self.config.getint('agent', 'rotation_max_bytes') if self.config.has_option('agent', 'rotation_max_bytes') else 10 * 1024 * 1024 + rotation_backup_count = self.config.getint('agent', 'rotation_backup_count') if self.config.has_option('agent', 'rotation_backup_count') else 7 + # Time-based defaults (roll at midnight, keep 7 files) + rotation_when = self.config.get('agent', 'rotation_when') if self.config.has_option('agent', 'rotation_when') else 'midnight' + rotation_interval = self.config.getint('agent', 'rotation_interval') if self.config.has_option('agent', 'rotation_interval') else 1 + rotation_utc = self.config.getboolean('agent', 'rotation_utc') if self.config.has_option('agent', 'rotation_utc') else False + + # Configure root logger explicitly (avoid basicConfig) + root = getLogger() + root.setLevel(level) + fmt = Formatter("%(asctime)-15s %(levelname)s %(message)s") + + try: if log_file == '-': - logging.basicConfig(level=level) # Log to sys.stderr by default + # Container/journald-friendly: write to stderr + handler = StreamHandler() else: - try: - logging.basicConfig(filename=log_file, filemode=log_file_mode, level=level, format="%(asctime)-15s %(levelname)s %(message)s") - except IOError as e: - logging.basicConfig(level=level) - logging.info('IOError: %s', e) - logging.info('Drop logging to stderr') + if rotation == 'none': + # No rotation at all + handler = logging.FileHandler(log_file, mode=log_file_mode) + elif rotation == 'time': + # Time-based rotation + handler = TimedRotatingFileHandler( + log_file, + when=rotation_when, # e.g., 'S', 'M', 'H', 'D', 'midnight', 'W0' + interval=rotation_interval, + backupCount=rotation_backup_count, + utc=rotation_utc + ) + else: + # Default: size-based rotation + handler = RotatingFileHandler( + log_file, + mode=log_file_mode, + maxBytes=rotation_max_bytes, + backupCount=rotation_backup_count + ) + + handler.setFormatter(fmt) + root.handlers = [handler] + except IOError as e: + # Fall back to stderr if file cannot be opened + root.handlers = [StreamHandler()] + root.handlers[0].setFormatter(fmt) + logging.info('IOError: %s', e) + logging.info('Drop logging to stderr') + + logging.info('Agent logging_level %i', level) - logging.info('Agent logging_level %i', level) def _plugins_init(self): ''' From c62f96989ad45be97bef337b7cec0583ccd99037 Mon Sep 17 00:00:00 2001 From: Renat Kasimov Date: Thu, 15 Jan 2026 10:05:11 +0100 Subject: [PATCH 2/5] Update mysql.py --- agent360/plugins/mysql.py | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/agent360/plugins/mysql.py b/agent360/plugins/mysql.py index 468e86c..b213e63 100644 --- a/agent360/plugins/mysql.py +++ b/agent360/plugins/mysql.py @@ -129,29 +129,37 @@ def run(self, config): pass cursor = db.cursor(MySQLdb.cursors.DictCursor) - cursor.execute('SHOW SLAVE STATUS') - query_result_slave = cursor.fetchone() + # Try MySQL 8.4+ syntax first, fall back to older syntax for backward compatibility + try: + cursor.execute('SHOW REPLICA STATUS') + query_result_slave = cursor.fetchone() + except MySQLdb.OperationalError: + cursor.execute('SHOW SLAVE STATUS') + query_result_slave = cursor.fetchone() + + # Support both old and new column names non_delta_slave = ( - 'slave_io_state', - 'master_host', - 'seconds_behind_master', - 'read_master_log_pos', + 'slave_io_state', 'replica_io_state', + 'master_host', 'source_host', + 'seconds_behind_master', 'seconds_behind_source', + 'read_master_log_pos', 'read_source_log_pos', 'relay_log_pos', - 'slave_io_running', - 'slave_sql_running', + 'slave_io_running', 'replica_io_running', + 'slave_sql_running', 'replica_sql_running', 'last_error', - 'exec_master_log_pos', + 'exec_master_log_pos', 'exec_source_log_pos', 'relay_log_space', - 'slave_sql_running_state', - 'master_retry_count' + 'slave_sql_running_state', 'replica_sql_running_state', + 'master_retry_count', 'source_retry_count' ) if query_result_slave is None: query_result_slave = dict() for key, value in query_result_slave.items(): key = key.lower().strip() - if key == 'slave_sql_running': + # Handle both old and new column names + if key in ('slave_sql_running', 'replica_sql_running'): value = 1 if value == 'Yes' else 0 - if key == 'slave_io_running': + if key in ('slave_io_running', 'replica_io_running'): value = 1 if value == 'Yes' else 0 for c in constructors: From 077eb926c38b834b4cd89febebccdd9e48272705 Mon Sep 17 00:00:00 2001 From: Renat Kasimov Date: Wed, 21 Jan 2026 10:33:25 +0100 Subject: [PATCH 3/5] Revert "Added log rotation with python" This reverts commit d02e2d3ff46cdb766d0eb636fc94375ef21aebd6. --- agent360/agent360.py | 94 +++++++++++--------------------------------- 1 file changed, 23 insertions(+), 71 deletions(-) diff --git a/agent360/agent360.py b/agent360/agent360.py index 614c19b..e20b626 100755 --- a/agent360/agent360.py +++ b/agent360/agent360.py @@ -42,9 +42,6 @@ import time import types from optparse import OptionParser -from logging import Formatter, getLogger, StreamHandler -from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler - try: from urllib.parse import urlparse, urlencode @@ -369,83 +366,38 @@ def _config_section_create(self, section): if not self.config.has_section(section): self.config.add_section(section) + def _logging_init(self): + ''' + Initialize logging faculty + ''' + level = self.config.getint('agent', 'logging_level') -def _logging_init(self): - """ - Initialize logging with optional in-app log rotation. - Supported rotation modes: - - size (default): RotatingFileHandler - - time: TimedRotatingFileHandler - - none: plain FileHandler - """ - level = self.config.getint('agent', 'logging_level') - - # Resolve target log file path - if os.name == 'nt': - log_file = os.path.expandvars(self.config.get('agent', 'log_file')) - else: - log_file = self.config.get('agent', 'log_file') + if os.name == 'nt': + log_file = os.path.expandvars(self.config.get('agent', 'log_file')) + else: + log_file = self.config.get('agent', 'log_file') - # Normalize legacy 'log_file_mode' (kept for compatibility) - log_file_mode = self.config.get('agent', 'log_file_mode') - if log_file_mode not in ('w', 'a'): - if log_file_mode == 'truncate': + log_file_mode = self.config.get('agent', 'log_file_mode') + if log_file_mode in ('w', 'a'): + pass + elif log_file_mode == 'truncate': log_file_mode = 'w' + elif log_file_mode == 'append': + log_file_mode = 'a' else: log_file_mode = 'a' - # Rotation config (all optional) - rotation = self.config.get('agent', 'rotation') if self.config.has_option('agent', 'rotation') else 'size' - # Size-based defaults (10MB, keep 7 files) - rotation_max_bytes = self.config.getint('agent', 'rotation_max_bytes') if self.config.has_option('agent', 'rotation_max_bytes') else 10 * 1024 * 1024 - rotation_backup_count = self.config.getint('agent', 'rotation_backup_count') if self.config.has_option('agent', 'rotation_backup_count') else 7 - # Time-based defaults (roll at midnight, keep 7 files) - rotation_when = self.config.get('agent', 'rotation_when') if self.config.has_option('agent', 'rotation_when') else 'midnight' - rotation_interval = self.config.getint('agent', 'rotation_interval') if self.config.has_option('agent', 'rotation_interval') else 1 - rotation_utc = self.config.getboolean('agent', 'rotation_utc') if self.config.has_option('agent', 'rotation_utc') else False - - # Configure root logger explicitly (avoid basicConfig) - root = getLogger() - root.setLevel(level) - fmt = Formatter("%(asctime)-15s %(levelname)s %(message)s") - - try: if log_file == '-': - # Container/journald-friendly: write to stderr - handler = StreamHandler() + logging.basicConfig(level=level) # Log to sys.stderr by default else: - if rotation == 'none': - # No rotation at all - handler = logging.FileHandler(log_file, mode=log_file_mode) - elif rotation == 'time': - # Time-based rotation - handler = TimedRotatingFileHandler( - log_file, - when=rotation_when, # e.g., 'S', 'M', 'H', 'D', 'midnight', 'W0' - interval=rotation_interval, - backupCount=rotation_backup_count, - utc=rotation_utc - ) - else: - # Default: size-based rotation - handler = RotatingFileHandler( - log_file, - mode=log_file_mode, - maxBytes=rotation_max_bytes, - backupCount=rotation_backup_count - ) - - handler.setFormatter(fmt) - root.handlers = [handler] - except IOError as e: - # Fall back to stderr if file cannot be opened - root.handlers = [StreamHandler()] - root.handlers[0].setFormatter(fmt) - logging.info('IOError: %s', e) - logging.info('Drop logging to stderr') - - logging.info('Agent logging_level %i', level) + try: + logging.basicConfig(filename=log_file, filemode=log_file_mode, level=level, format="%(asctime)-15s %(levelname)s %(message)s") + except IOError as e: + logging.basicConfig(level=level) + logging.info('IOError: %s', e) + logging.info('Drop logging to stderr') + logging.info('Agent logging_level %i', level) def _plugins_init(self): ''' From c44d04a5af784e6f7909456d86e887afc2c92403 Mon Sep 17 00:00:00 2001 From: Renat Kasimov Date: Tue, 27 Jan 2026 13:59:28 +0100 Subject: [PATCH 4/5] Adds log rotation Turned off by default --- agent360-example.ini | 32 +++++++++++++++ agent360/agent360.py | 94 +++++++++++++++++++++++++++++++++----------- 2 files changed, 103 insertions(+), 23 deletions(-) diff --git a/agent360-example.ini b/agent360-example.ini index 9166546..672cfda 100644 --- a/agent360-example.ini +++ b/agent360-example.ini @@ -15,6 +15,38 @@ ###################### Default sections # [agent] ; Main thread # +###################### Log rotation settings +# rotation = none ; Rotation mode: 'size', 'time', or 'none' (default: none) +# ; 'none' = no rotation (recommended for external logrotate) +# ; 'size' = rotate when file reaches max size +# ; 'time' = rotate at specified time intervals +# +# Size-based rotation (when rotation = size): +# rotation_max_bytes = 10485760 ; Max file size before rotation (default: 10MB = 10485760 bytes) +# rotation_backup_count = 7 ; Number of backup files to keep (default: 7) +# +# Time-based rotation (when rotation = time): +# rotation_when = midnight ; When to rotate: 'S'=seconds, 'M'=minutes, 'H'=hours, +# ; 'D'=days, 'midnight', 'W0'-'W6'=weekday (0=Monday) +# rotation_interval = 1 ; Interval for rotation (default: 1) +# rotation_utc = False ; Use UTC for time rotation (default: False) +# +# Examples: +# Daily rotation with 30 day retention: +# rotation = time +# rotation_when = midnight +# rotation_interval = 1 +# rotation_backup_count = 30 +# +# Rotate every 50MB, keep 10 files: +# rotation = size +# rotation_max_bytes = 52428800 +# rotation_backup_count = 10 +# +# External logrotate (recommended for Linux): +# rotation = none +# # Then configure /etc/logrotate.d/agent360 +# # [execution] ; Plugin execution threads # ttl = 15 ; Example: plugins will be killed after 15 sec # diff --git a/agent360/agent360.py b/agent360/agent360.py index e20b626..fa0b3b8 100755 --- a/agent360/agent360.py +++ b/agent360/agent360.py @@ -42,6 +42,9 @@ import time import types from optparse import OptionParser +from logging import Formatter, getLogger, StreamHandler +from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler + try: from urllib.parse import urlparse, urlencode @@ -366,38 +369,83 @@ def _config_section_create(self, section): if not self.config.has_section(section): self.config.add_section(section) - def _logging_init(self): - ''' - Initialize logging faculty - ''' - level = self.config.getint('agent', 'logging_level') - if os.name == 'nt': - log_file = os.path.expandvars(self.config.get('agent', 'log_file')) - else: - log_file = self.config.get('agent', 'log_file') +def _logging_init(self): + """ + Initialize logging with optional in-app log rotation. + Supported rotation modes: + - size (default): RotatingFileHandler + - time: TimedRotatingFileHandler + - none: plain FileHandler + """ + level = self.config.getint('agent', 'logging_level') + + # Resolve target log file path + if os.name == 'nt': + log_file = os.path.expandvars(self.config.get('agent', 'log_file')) + else: + log_file = self.config.get('agent', 'log_file') - log_file_mode = self.config.get('agent', 'log_file_mode') - if log_file_mode in ('w', 'a'): - pass - elif log_file_mode == 'truncate': + # Normalize legacy 'log_file_mode' (kept for compatibility) + log_file_mode = self.config.get('agent', 'log_file_mode') + if log_file_mode not in ('w', 'a'): + if log_file_mode == 'truncate': log_file_mode = 'w' - elif log_file_mode == 'append': - log_file_mode = 'a' else: log_file_mode = 'a' + # Rotation config (all optional) + rotation = self.config.get('agent', 'rotation') if self.config.has_option('agent', 'rotation') else 'none' + # Size-based defaults (10MB, keep 7 files) + rotation_max_bytes = self.config.getint('agent', 'rotation_max_bytes') if self.config.has_option('agent', 'rotation_max_bytes') else 10 * 1024 * 1024 + rotation_backup_count = self.config.getint('agent', 'rotation_backup_count') if self.config.has_option('agent', 'rotation_backup_count') else 7 + # Time-based defaults (roll at midnight, keep 7 files) + rotation_when = self.config.get('agent', 'rotation_when') if self.config.has_option('agent', 'rotation_when') else 'midnight' + rotation_interval = self.config.getint('agent', 'rotation_interval') if self.config.has_option('agent', 'rotation_interval') else 1 + rotation_utc = self.config.getboolean('agent', 'rotation_utc') if self.config.has_option('agent', 'rotation_utc') else False + + # Configure root logger explicitly (avoid basicConfig) + root = getLogger() + root.setLevel(level) + fmt = Formatter("%(asctime)-15s %(levelname)s %(message)s") + + try: if log_file == '-': - logging.basicConfig(level=level) # Log to sys.stderr by default + # Container/journald-friendly: write to stderr + handler = StreamHandler() else: - try: - logging.basicConfig(filename=log_file, filemode=log_file_mode, level=level, format="%(asctime)-15s %(levelname)s %(message)s") - except IOError as e: - logging.basicConfig(level=level) - logging.info('IOError: %s', e) - logging.info('Drop logging to stderr') + if rotation == 'none': + # No rotation at all + handler = logging.FileHandler(log_file, mode=log_file_mode) + elif rotation == 'time': + # Time-based rotation + handler = TimedRotatingFileHandler( + log_file, + when=rotation_when, # e.g., 'S', 'M', 'H', 'D', 'midnight', 'W0' + interval=rotation_interval, + backupCount=rotation_backup_count, + utc=rotation_utc + ) + else: + # Default: size-based rotation + handler = RotatingFileHandler( + log_file, + mode=log_file_mode, + maxBytes=rotation_max_bytes, + backupCount=rotation_backup_count + ) + + handler.setFormatter(fmt) + root.handlers = [handler] + except IOError as e: + # Fall back to stderr if file cannot be opened + root.handlers = [StreamHandler()] + root.handlers[0].setFormatter(fmt) + logging.info('IOError: %s', e) + logging.info('Drop logging to stderr') + + logging.info('Agent logging_level %i', level) - logging.info('Agent logging_level %i', level) def _plugins_init(self): ''' From de7a169a9eaa826abc389c422abc80a1fcf0e09e Mon Sep 17 00:00:00 2001 From: Renat Kasimov Date: Tue, 27 Jan 2026 14:07:23 +0100 Subject: [PATCH 5/5] Revert "Adds log rotation" This reverts commit c44d04a5af784e6f7909456d86e887afc2c92403. --- agent360-example.ini | 32 --------------- agent360/agent360.py | 94 +++++++++++--------------------------------- 2 files changed, 23 insertions(+), 103 deletions(-) diff --git a/agent360-example.ini b/agent360-example.ini index 672cfda..9166546 100644 --- a/agent360-example.ini +++ b/agent360-example.ini @@ -15,38 +15,6 @@ ###################### Default sections # [agent] ; Main thread # -###################### Log rotation settings -# rotation = none ; Rotation mode: 'size', 'time', or 'none' (default: none) -# ; 'none' = no rotation (recommended for external logrotate) -# ; 'size' = rotate when file reaches max size -# ; 'time' = rotate at specified time intervals -# -# Size-based rotation (when rotation = size): -# rotation_max_bytes = 10485760 ; Max file size before rotation (default: 10MB = 10485760 bytes) -# rotation_backup_count = 7 ; Number of backup files to keep (default: 7) -# -# Time-based rotation (when rotation = time): -# rotation_when = midnight ; When to rotate: 'S'=seconds, 'M'=minutes, 'H'=hours, -# ; 'D'=days, 'midnight', 'W0'-'W6'=weekday (0=Monday) -# rotation_interval = 1 ; Interval for rotation (default: 1) -# rotation_utc = False ; Use UTC for time rotation (default: False) -# -# Examples: -# Daily rotation with 30 day retention: -# rotation = time -# rotation_when = midnight -# rotation_interval = 1 -# rotation_backup_count = 30 -# -# Rotate every 50MB, keep 10 files: -# rotation = size -# rotation_max_bytes = 52428800 -# rotation_backup_count = 10 -# -# External logrotate (recommended for Linux): -# rotation = none -# # Then configure /etc/logrotate.d/agent360 -# # [execution] ; Plugin execution threads # ttl = 15 ; Example: plugins will be killed after 15 sec # diff --git a/agent360/agent360.py b/agent360/agent360.py index fa0b3b8..e20b626 100755 --- a/agent360/agent360.py +++ b/agent360/agent360.py @@ -42,9 +42,6 @@ import time import types from optparse import OptionParser -from logging import Formatter, getLogger, StreamHandler -from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler - try: from urllib.parse import urlparse, urlencode @@ -369,83 +366,38 @@ def _config_section_create(self, section): if not self.config.has_section(section): self.config.add_section(section) + def _logging_init(self): + ''' + Initialize logging faculty + ''' + level = self.config.getint('agent', 'logging_level') -def _logging_init(self): - """ - Initialize logging with optional in-app log rotation. - Supported rotation modes: - - size (default): RotatingFileHandler - - time: TimedRotatingFileHandler - - none: plain FileHandler - """ - level = self.config.getint('agent', 'logging_level') - - # Resolve target log file path - if os.name == 'nt': - log_file = os.path.expandvars(self.config.get('agent', 'log_file')) - else: - log_file = self.config.get('agent', 'log_file') + if os.name == 'nt': + log_file = os.path.expandvars(self.config.get('agent', 'log_file')) + else: + log_file = self.config.get('agent', 'log_file') - # Normalize legacy 'log_file_mode' (kept for compatibility) - log_file_mode = self.config.get('agent', 'log_file_mode') - if log_file_mode not in ('w', 'a'): - if log_file_mode == 'truncate': + log_file_mode = self.config.get('agent', 'log_file_mode') + if log_file_mode in ('w', 'a'): + pass + elif log_file_mode == 'truncate': log_file_mode = 'w' + elif log_file_mode == 'append': + log_file_mode = 'a' else: log_file_mode = 'a' - # Rotation config (all optional) - rotation = self.config.get('agent', 'rotation') if self.config.has_option('agent', 'rotation') else 'none' - # Size-based defaults (10MB, keep 7 files) - rotation_max_bytes = self.config.getint('agent', 'rotation_max_bytes') if self.config.has_option('agent', 'rotation_max_bytes') else 10 * 1024 * 1024 - rotation_backup_count = self.config.getint('agent', 'rotation_backup_count') if self.config.has_option('agent', 'rotation_backup_count') else 7 - # Time-based defaults (roll at midnight, keep 7 files) - rotation_when = self.config.get('agent', 'rotation_when') if self.config.has_option('agent', 'rotation_when') else 'midnight' - rotation_interval = self.config.getint('agent', 'rotation_interval') if self.config.has_option('agent', 'rotation_interval') else 1 - rotation_utc = self.config.getboolean('agent', 'rotation_utc') if self.config.has_option('agent', 'rotation_utc') else False - - # Configure root logger explicitly (avoid basicConfig) - root = getLogger() - root.setLevel(level) - fmt = Formatter("%(asctime)-15s %(levelname)s %(message)s") - - try: if log_file == '-': - # Container/journald-friendly: write to stderr - handler = StreamHandler() + logging.basicConfig(level=level) # Log to sys.stderr by default else: - if rotation == 'none': - # No rotation at all - handler = logging.FileHandler(log_file, mode=log_file_mode) - elif rotation == 'time': - # Time-based rotation - handler = TimedRotatingFileHandler( - log_file, - when=rotation_when, # e.g., 'S', 'M', 'H', 'D', 'midnight', 'W0' - interval=rotation_interval, - backupCount=rotation_backup_count, - utc=rotation_utc - ) - else: - # Default: size-based rotation - handler = RotatingFileHandler( - log_file, - mode=log_file_mode, - maxBytes=rotation_max_bytes, - backupCount=rotation_backup_count - ) - - handler.setFormatter(fmt) - root.handlers = [handler] - except IOError as e: - # Fall back to stderr if file cannot be opened - root.handlers = [StreamHandler()] - root.handlers[0].setFormatter(fmt) - logging.info('IOError: %s', e) - logging.info('Drop logging to stderr') - - logging.info('Agent logging_level %i', level) + try: + logging.basicConfig(filename=log_file, filemode=log_file_mode, level=level, format="%(asctime)-15s %(levelname)s %(message)s") + except IOError as e: + logging.basicConfig(level=level) + logging.info('IOError: %s', e) + logging.info('Drop logging to stderr') + logging.info('Agent logging_level %i', level) def _plugins_init(self): '''