1# -*- coding: utf-8 -*- 2# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. 3# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr 4# 5# This file is part of logilab-common. 6# 7# logilab-common is free software: you can redistribute it and/or modify it under 8# the terms of the GNU Lesser General Public License as published by the Free 9# Software Foundation, either version 2.1 of the License, or (at your option) any 10# later version. 11# 12# logilab-common is distributed in the hope that it will be useful, but WITHOUT 13# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 14# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 15# details. 16# 17# You should have received a copy of the GNU Lesser General Public License along 18# with logilab-common. If not, see <http://www.gnu.org/licenses/>. 19"""Extends the logging module from the standard library.""" 20 21__docformat__ = "restructuredtext en" 22 23import os 24import sys 25import logging 26 27from six import string_types 28 29from logilab.common.textutils import colorize_ansi 30 31 32def set_log_methods(cls, logger): 33 """bind standard logger's methods as methods on the class""" 34 cls.__logger = logger 35 for attr in ('debug', 'info', 'warning', 'error', 'critical', 'exception'): 36 setattr(cls, attr, getattr(logger, attr)) 37 38 39def xxx_cyan(record): 40 if 'XXX' in record.message: 41 return 'cyan' 42 43class ColorFormatter(logging.Formatter): 44 """ 45 A color Formatter for the logging standard module. 46 47 By default, colorize CRITICAL and ERROR in red, WARNING in orange, INFO in 48 green and DEBUG in yellow. 49 50 self.colors is customizable via the 'color' constructor argument (dictionary). 51 52 self.colorfilters is a list of functions that get the LogRecord 53 and return a color name or None. 54 """ 55 56 def __init__(self, fmt=None, datefmt=None, colors=None): 57 logging.Formatter.__init__(self, fmt, datefmt) 58 self.colorfilters = [] 59 self.colors = {'CRITICAL': 'red', 60 'ERROR': 'red', 61 'WARNING': 'magenta', 62 'INFO': 'green', 63 'DEBUG': 'yellow', 64 } 65 if colors is not None: 66 assert isinstance(colors, dict) 67 self.colors.update(colors) 68 69 def format(self, record): 70 msg = logging.Formatter.format(self, record) 71 if record.levelname in self.colors: 72 color = self.colors[record.levelname] 73 return colorize_ansi(msg, color) 74 else: 75 for cf in self.colorfilters: 76 color = cf(record) 77 if color: 78 return colorize_ansi(msg, color) 79 return msg 80 81def set_color_formatter(logger=None, **kw): 82 """ 83 Install a color formatter on the 'logger'. If not given, it will 84 defaults to the default logger. 85 86 Any additional keyword will be passed as-is to the ColorFormatter 87 constructor. 88 """ 89 if logger is None: 90 logger = logging.getLogger() 91 if not logger.handlers: 92 logging.basicConfig() 93 format_msg = logger.handlers[0].formatter._fmt 94 fmt = ColorFormatter(format_msg, **kw) 95 fmt.colorfilters.append(xxx_cyan) 96 logger.handlers[0].setFormatter(fmt) 97 98 99LOG_FORMAT = '%(asctime)s - (%(name)s) %(levelname)s: %(message)s' 100LOG_DATE_FORMAT = '%Y-%m-%d %H:%M:%S' 101 102def get_handler(debug=False, syslog=False, logfile=None, rotation_parameters=None): 103 """get an apropriate handler according to given parameters""" 104 if os.environ.get('APYCOT_ROOT'): 105 handler = logging.StreamHandler(sys.stdout) 106 if debug: 107 handler = logging.StreamHandler() 108 elif logfile is None: 109 if syslog: 110 from logging import handlers 111 handler = handlers.SysLogHandler() 112 else: 113 handler = logging.StreamHandler() 114 else: 115 try: 116 if rotation_parameters is None: 117 if os.name == 'posix' and sys.version_info >= (2, 6): 118 from logging.handlers import WatchedFileHandler 119 handler = WatchedFileHandler(logfile) 120 else: 121 handler = logging.FileHandler(logfile) 122 else: 123 from logging.handlers import TimedRotatingFileHandler 124 handler = TimedRotatingFileHandler( 125 logfile, **rotation_parameters) 126 except IOError: 127 handler = logging.StreamHandler() 128 return handler 129 130def get_threshold(debug=False, logthreshold=None): 131 if logthreshold is None: 132 if debug: 133 logthreshold = logging.DEBUG 134 else: 135 logthreshold = logging.ERROR 136 elif isinstance(logthreshold, string_types): 137 logthreshold = getattr(logging, THRESHOLD_MAP.get(logthreshold, 138 logthreshold)) 139 return logthreshold 140 141def _colorable_terminal(): 142 isatty = hasattr(sys.__stdout__, 'isatty') and sys.__stdout__.isatty() 143 if not isatty: 144 return False 145 if os.name == 'nt': 146 try: 147 from colorama import init as init_win32_colors 148 except ImportError: 149 return False 150 init_win32_colors() 151 return True 152 153def get_formatter(logformat=LOG_FORMAT, logdateformat=LOG_DATE_FORMAT): 154 if _colorable_terminal(): 155 fmt = ColorFormatter(logformat, logdateformat) 156 def col_fact(record): 157 if 'XXX' in record.message: 158 return 'cyan' 159 if 'kick' in record.message: 160 return 'red' 161 fmt.colorfilters.append(col_fact) 162 else: 163 fmt = logging.Formatter(logformat, logdateformat) 164 return fmt 165 166def init_log(debug=False, syslog=False, logthreshold=None, logfile=None, 167 logformat=LOG_FORMAT, logdateformat=LOG_DATE_FORMAT, fmt=None, 168 rotation_parameters=None, handler=None): 169 """init the log service""" 170 logger = logging.getLogger() 171 if handler is None: 172 handler = get_handler(debug, syslog, logfile, rotation_parameters) 173 # only addHandler and removeHandler method while I would like a setHandler 174 # method, so do it this way :$ 175 logger.handlers = [handler] 176 logthreshold = get_threshold(debug, logthreshold) 177 logger.setLevel(logthreshold) 178 if fmt is None: 179 if debug: 180 fmt = get_formatter(logformat=logformat, logdateformat=logdateformat) 181 else: 182 fmt = logging.Formatter(logformat, logdateformat) 183 handler.setFormatter(fmt) 184 return handler 185 186# map logilab.common.logger thresholds to logging thresholds 187THRESHOLD_MAP = {'LOG_DEBUG': 'DEBUG', 188 'LOG_INFO': 'INFO', 189 'LOG_NOTICE': 'INFO', 190 'LOG_WARN': 'WARNING', 191 'LOG_WARNING': 'WARNING', 192 'LOG_ERR': 'ERROR', 193 'LOG_ERROR': 'ERROR', 194 'LOG_CRIT': 'CRITICAL', 195 } 196