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