logging.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. import logging
  2. from logging.handlers import RotatingFileHandler
  3. import os
  4. from typing import Optional
  5. from .config import RobotConfig
  6. import time
  7. THROTTLING_FILTER_ENABLED = False
  8. # TODO [SG]: Currently not used due to multiple problems:
  9. # No individual logger naming for each module
  10. # Throttling filtered too much
  11. # Coul not be created without config
  12. class LoggerSingleton:
  13. _instance: Optional[logging.Logger] = None
  14. @classmethod
  15. def get_logger(cls, config: Optional[RobotConfig] = None) -> logging.Logger:
  16. # if config is None:
  17. # config = RobotConfig()
  18. if cls._instance is None and config is not None:
  19. cls._instance = cls._setup_logger(config)
  20. return cls._instance
  21. @staticmethod
  22. def _setup_logger(config: RobotConfig) -> logging.Logger:
  23. log_config = config.logging
  24. logger = logging.getLogger('robot_control')
  25. logger.setLevel(getattr(logging, log_config.level))
  26. os.makedirs(os.path.dirname(log_config.file_path), exist_ok=True)
  27. file_handler = RotatingFileHandler(
  28. log_config.file_path,
  29. maxBytes=log_config.max_file_size_mb * 1024 * 1024,
  30. backupCount=log_config.backup_count
  31. )
  32. file_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
  33. if THROTTLING_FILTER_ENABLED:
  34. file_handler.addFilter(ThrottlingFilter())
  35. file_handler.setFormatter(file_formatter)
  36. logger.addHandler(file_handler)
  37. if log_config.console_output:
  38. console_handler = logging.StreamHandler()
  39. if THROTTLING_FILTER_ENABLED:
  40. console_handler.addFilter(ThrottlingFilter())
  41. console_formatter = logging.Formatter('%(levelname)s: %(message)s')
  42. console_handler.setFormatter(console_formatter)
  43. logger.addHandler(console_handler)
  44. return logger
  45. class ThrottlingFilter(logging.Filter):
  46. def __init__(self, name='', initial_throttle_interval=1.0, max_throttle_interval=120.0,
  47. throttle_multiplier=2.0, reset_after=120.0):
  48. """
  49. :param name: Filter name (can be left empty)
  50. :param initial_throttle_interval: Initial time interval (in seconds) before allowing duplicate messages
  51. :param max_throttle_interval: Maximum throttle interval (in seconds)
  52. :param throttle_multiplier: Factor by which to increase the interval for repeated messages
  53. :param reset_after: Time in seconds after which to reset throttle interval if message wasn't seen
  54. """
  55. super().__init__(name)
  56. self.initial_throttle_interval = initial_throttle_interval
  57. self.max_throttle_interval = max_throttle_interval
  58. self.throttle_multiplier = throttle_multiplier
  59. self.reset_after = reset_after
  60. self.message_states = {}
  61. def filter(self, record):
  62. current_time = time.time()
  63. message_key = record.getMessage()
  64. if message_key not in self.message_states:
  65. self.message_states[message_key] = {
  66. 'last_time': current_time,
  67. 'current_interval': self.initial_throttle_interval
  68. }
  69. return True
  70. state = self.message_states[message_key]
  71. time_since_last = current_time - state['last_time']
  72. # Reset throttle interval if message hasn't been seen for reset_after seconds
  73. if time_since_last >= self.reset_after:
  74. self.message_states[message_key] = {
  75. 'last_time': current_time,
  76. 'current_interval': self.initial_throttle_interval
  77. }
  78. return True
  79. if time_since_last >= state['current_interval']:
  80. # Message allowed - increase throttle interval for next occurrence
  81. next_interval = min(
  82. state['current_interval'] * self.throttle_multiplier,
  83. self.max_throttle_interval
  84. )
  85. self.message_states[message_key] = {
  86. 'last_time': current_time,
  87. 'current_interval': next_interval
  88. }
  89. return True
  90. return False