1# -*- coding: utf-8 -*- 2# Licensed under the Apache License, Version 2.0 (the "License"); you may 3# not use this file except in compliance with the License. You may obtain 4# a copy of the License at 5# 6# http://www.apache.org/licenses/LICENSE-2.0 7# 8# Unless required by applicable law or agreed to in writing, software 9# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 10# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 11# License for the specific language governing permissions and limitations 12# under the License. 13 14import inspect 15import logging 16import logging.config 17import logging.handlers 18import os 19 20try: 21 from systemd import journal 22except ImportError: 23 journal = None 24try: 25 import syslog 26except ImportError: 27 syslog = None 28 29 30NullHandler = logging.NullHandler 31 32 33def _get_binary_name(): 34 return os.path.basename(inspect.stack()[-1][1]) 35 36 37_AUDIT = logging.INFO + 1 38_TRACE = 5 39 40# This is a copy of the numerical constants from syslog.h. The 41# definition of these goes back at least 20 years, and is specifically 42# 3 bits in a packed field, so these aren't likely to ever need 43# changing. 44SYSLOG_MAP = { 45 "CRITICAL": 2, 46 "ERROR": 3, 47 "WARNING": 4, 48 "WARN": 4, 49 "INFO": 6, 50 "DEBUG": 7, 51} 52 53 54class OSSysLogHandler(logging.Handler): 55 """Syslog based handler. Only available on UNIX-like platforms.""" 56 57 def __init__(self, facility=None): 58 # Default values always get evaluated, for which reason we avoid 59 # using 'syslog' directly, which may not be available. 60 facility = facility if facility is not None else syslog.LOG_USER 61 # Do not use super() unless type(logging.Handler) is 'type' 62 # (i.e. >= Python 2.7). 63 if not syslog: 64 raise RuntimeError("Syslog not available on this platform") 65 logging.Handler.__init__(self) 66 binary_name = _get_binary_name() 67 syslog.openlog(binary_name, 0, facility) 68 69 def emit(self, record): 70 priority = SYSLOG_MAP.get(record.levelname, 7) 71 message = self.format(record) 72 syslog.syslog(priority, message) 73 74 75class OSJournalHandler(logging.Handler): 76 77 custom_fields = ( 78 'project_name', 79 'project_id', 80 'user_name', 81 'user_id', 82 'request_id', 83 ) 84 85 def __init__(self, facility=None): 86 if not journal: 87 raise RuntimeError("Systemd bindings do not exist") 88 89 if not facility: 90 if not syslog: 91 raise RuntimeError("syslog is not available on this platform") 92 facility = syslog.LOG_USER 93 94 # Do not use super() unless type(logging.Handler) is 'type' 95 # (i.e. >= Python 2.7). 96 logging.Handler.__init__(self) 97 self.binary_name = _get_binary_name() 98 self.facility = facility 99 100 def emit(self, record): 101 priority = SYSLOG_MAP.get(record.levelname, 7) 102 message = self.format(record) 103 104 extras = { 105 'CODE_FILE': record.pathname, 106 'CODE_LINE': record.lineno, 107 'CODE_FUNC': record.funcName, 108 'THREAD_NAME': record.threadName, 109 'PROCESS_NAME': record.processName, 110 'LOGGER_NAME': record.name, 111 'LOGGER_LEVEL': record.levelname, 112 'SYSLOG_IDENTIFIER': self.binary_name, 113 'PRIORITY': priority, 114 'SYSLOG_FACILITY': self.facility, 115 } 116 117 if record.exc_info: 118 # Cache the traceback text to avoid converting it multiple times 119 # (it's constant anyway) 120 if not record.exc_text: 121 record.exc_text = self.formatter.formatException( 122 record.exc_info) 123 if record.exc_text: 124 extras['EXCEPTION_INFO'] = record.exc_text 125 # Leave EXCEPTION_TEXT for backward compatibility 126 extras['EXCEPTION_TEXT'] = record.exc_text 127 128 for field in self.custom_fields: 129 value = record.__dict__.get(field) 130 if value: 131 extras[field.upper()] = value 132 133 journal.send(message, **extras) 134 135 136class ColorHandler(logging.StreamHandler): 137 """Log handler that sets the 'color' key based on the level 138 139 To use, include a '%(color)s' entry in the logging_context_format_string. 140 There is also a '%(reset_color)s' key that can be used to manually reset 141 the color within a log line. 142 """ 143 LEVEL_COLORS = { 144 _TRACE: '\033[00;35m', # MAGENTA 145 logging.DEBUG: '\033[00;32m', # GREEN 146 logging.INFO: '\033[00;36m', # CYAN 147 _AUDIT: '\033[01;36m', # BOLD CYAN 148 logging.WARN: '\033[01;33m', # BOLD YELLOW 149 logging.ERROR: '\033[01;31m', # BOLD RED 150 logging.CRITICAL: '\033[01;31m', # BOLD RED 151 } 152 153 def format(self, record): 154 record.color = self.LEVEL_COLORS[record.levelno] 155 record.reset_color = '\033[00m' 156 return logging.StreamHandler.format(self, record) + record.reset_color 157