loggers.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. # -*- coding: utf-8 -*-
  2. # Description:
  3. # Author: Ilya Mashchenko (ilyam8)
  4. # SPDX-License-Identifier: GPL-3.0-or-later
  5. import logging
  6. import os
  7. import stat
  8. import traceback
  9. from sys import exc_info
  10. try:
  11. from time import monotonic as time
  12. except ImportError:
  13. from time import time
  14. from bases.collection import on_try_except_finally, unicode_str
  15. LOGGING_LEVELS = {
  16. 'CRITICAL': 50,
  17. 'ERROR': 40,
  18. 'WARNING': 30,
  19. 'INFO': 20,
  20. 'DEBUG': 10,
  21. 'NOTSET': 0,
  22. }
  23. def is_stderr_connected_to_journal():
  24. journal_stream = os.environ.get("JOURNAL_STREAM")
  25. if not journal_stream:
  26. return False
  27. colon_index = journal_stream.find(":")
  28. if colon_index <= 0:
  29. return False
  30. device, inode = journal_stream[:colon_index], journal_stream[colon_index + 1:]
  31. try:
  32. device_number, inode_number = os.fstat(2)[stat.ST_DEV], os.fstat(2)[stat.ST_INO]
  33. except OSError:
  34. return False
  35. return str(device_number) == device and str(inode_number) == inode
  36. is_journal = is_stderr_connected_to_journal()
  37. DEFAULT_LOG_LINE_FORMAT = '%(asctime)s: %(name)s %(levelname)s : %(message)s'
  38. PYTHON_D_LOG_LINE_FORMAT = '%(asctime)s: %(name)s %(levelname)s: %(module_name)s[%(job_name)s] : %(message)s'
  39. if is_journal:
  40. DEFAULT_LOG_LINE_FORMAT = '%(name)s %(levelname)s : %(message)s'
  41. PYTHON_D_LOG_LINE_FORMAT = '%(name)s %(levelname)s: %(module_name)s[%(job_name)s] : %(message)s '
  42. DEFAULT_LOG_TIME_FORMAT = '%Y-%m-%d %H:%M:%S'
  43. PYTHON_D_LOG_NAME = 'python.d'
  44. def add_traceback(func):
  45. def on_call(*args):
  46. self = args[0]
  47. if not self.log_traceback:
  48. func(*args)
  49. else:
  50. if exc_info()[0]:
  51. func(*args)
  52. func(self, traceback.format_exc())
  53. else:
  54. func(*args)
  55. return on_call
  56. class BaseLogger(object):
  57. def __init__(
  58. self,
  59. logger_name,
  60. log_fmt=DEFAULT_LOG_LINE_FORMAT,
  61. date_fmt=DEFAULT_LOG_TIME_FORMAT,
  62. handler=logging.StreamHandler,
  63. ):
  64. self.logger = logging.getLogger(logger_name)
  65. self._muted = False
  66. if not self.has_handlers():
  67. self.severity = 'INFO'
  68. self.logger.addHandler(handler())
  69. self.set_formatter(fmt=log_fmt, date_fmt=date_fmt)
  70. def __repr__(self):
  71. return '<Logger: {name})>'.format(name=self.logger.name)
  72. def set_formatter(self, fmt, date_fmt=DEFAULT_LOG_TIME_FORMAT):
  73. if self.has_handlers():
  74. self.logger.handlers[0].setFormatter(logging.Formatter(fmt=fmt, datefmt=date_fmt))
  75. def has_handlers(self):
  76. return self.logger.handlers
  77. @property
  78. def severity(self):
  79. return self.logger.getEffectiveLevel()
  80. @severity.setter
  81. def severity(self, level):
  82. if level in LOGGING_LEVELS:
  83. self.logger.setLevel(LOGGING_LEVELS[level])
  84. def _log(self, level, *msg, **kwargs):
  85. if not self._muted:
  86. self.logger.log(level, ' '.join(map(unicode_str, msg)), **kwargs)
  87. def debug(self, *msg, **kwargs):
  88. self._log(logging.DEBUG, *msg, **kwargs)
  89. def info(self, *msg, **kwargs):
  90. self._log(logging.INFO, *msg, **kwargs)
  91. def warning(self, *msg, **kwargs):
  92. self._log(logging.WARN, *msg, **kwargs)
  93. def error(self, *msg, **kwargs):
  94. self._log(logging.ERROR, *msg, **kwargs)
  95. def alert(self, *msg, **kwargs):
  96. self._log(logging.CRITICAL, *msg, **kwargs)
  97. @on_try_except_finally(on_finally=(exit, 1))
  98. def fatal(self, *msg, **kwargs):
  99. self._log(logging.CRITICAL, *msg, **kwargs)
  100. def mute(self):
  101. self._muted = True
  102. def unmute(self):
  103. self._muted = False
  104. class PythonDLogger(object):
  105. def __init__(
  106. self,
  107. logger_name=PYTHON_D_LOG_NAME,
  108. log_fmt=PYTHON_D_LOG_LINE_FORMAT,
  109. ):
  110. self.logger = BaseLogger(logger_name, log_fmt=log_fmt)
  111. self.module_name = 'plugin'
  112. self.job_name = 'main'
  113. _LOG_TRACEBACK = False
  114. @property
  115. def log_traceback(self):
  116. return PythonDLogger._LOG_TRACEBACK
  117. @log_traceback.setter
  118. def log_traceback(self, value):
  119. PythonDLogger._LOG_TRACEBACK = value
  120. def debug(self, *msg):
  121. self.logger.debug(*msg, extra={
  122. 'module_name': self.module_name,
  123. 'job_name': self.job_name or self.module_name,
  124. })
  125. def info(self, *msg):
  126. self.logger.info(*msg, extra={
  127. 'module_name': self.module_name,
  128. 'job_name': self.job_name or self.module_name,
  129. })
  130. def warning(self, *msg):
  131. self.logger.warning(*msg, extra={
  132. 'module_name': self.module_name,
  133. 'job_name': self.job_name or self.module_name,
  134. })
  135. @add_traceback
  136. def error(self, *msg):
  137. self.logger.error(*msg, extra={
  138. 'module_name': self.module_name,
  139. 'job_name': self.job_name or self.module_name,
  140. })
  141. @add_traceback
  142. def alert(self, *msg):
  143. self.logger.alert(*msg, extra={
  144. 'module_name': self.module_name,
  145. 'job_name': self.job_name or self.module_name,
  146. })
  147. def fatal(self, *msg):
  148. self.logger.fatal(*msg, extra={
  149. 'module_name': self.module_name,
  150. 'job_name': self.job_name or self.module_name,
  151. })