1# Copyright (C) 2010 Chris Jerdonek (cjerdonek@webkit.org) 2# 3# Redistribution and use in source and binary forms, with or without 4# modification, are permitted provided that the following conditions 5# are met: 6# 1. Redistributions of source code must retain the above copyright 7# notice, this list of conditions and the following disclaimer. 8# 2. Redistributions in binary form must reproduce the above copyright 9# notice, this list of conditions and the following disclaimer in the 10# documentation and/or other materials provided with the distribution. 11# 12# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND 13# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 14# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 15# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR 16# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 18# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 19# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 20# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 21# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 22 23"""Supports webkitpy logging.""" 24 25# FIXME: Move this file to webkitpy/python24 since logging needs to 26# be configured prior to running version-checking code. 27 28import logging 29import os 30import sys 31 32import webkitpy 33 34 35_log = logging.getLogger(__name__) 36 37# We set these directory paths lazily in get_logger() below. 38_scripts_dir = "" 39"""The normalized, absolute path to the ...Scripts directory.""" 40 41_webkitpy_dir = "" 42"""The normalized, absolute path to the ...Scripts/webkitpy directory.""" 43 44 45def _normalize_path(path): 46 """Return the given path normalized. 47 48 Converts a path to an absolute path, removes any trailing slashes, 49 removes any extension, and lower-cases it. 50 51 """ 52 path = os.path.abspath(path) 53 path = os.path.normpath(path) 54 path = os.path.splitext(path)[0] # Remove the extension, if any. 55 path = path.lower() 56 57 return path 58 59 60# Observe that the implementation of this function does not require 61# the use of any hard-coded strings like "webkitpy", etc. 62# 63# The main benefit this function has over using-- 64# 65# _log = logging.getLogger(__name__) 66# 67# is that get_logger() returns the same value even if __name__ is 68# "__main__" -- i.e. even if the module is the script being executed 69# from the command-line. 70def get_logger(path): 71 """Return a logging.logger for the given path. 72 73 Returns: 74 A logger whose name is the name of the module corresponding to 75 the given path. If the module is in webkitpy, the name is 76 the fully-qualified dotted module name beginning with webkitpy.... 77 Otherwise, the name is the base name of the module (i.e. without 78 any dotted module name prefix). 79 80 Args: 81 path: The path of the module. Normally, this parameter should be 82 the __file__ variable of the module. 83 84 Sample usage: 85 86 from webkitpy.common.system import logutils 87 88 _log = logutils.get_logger(__file__) 89 90 """ 91 # Since we assign to _scripts_dir and _webkitpy_dir in this function, 92 # we need to declare them global. 93 global _scripts_dir 94 global _webkitpy_dir 95 96 path = _normalize_path(path) 97 98 # Lazily evaluate _webkitpy_dir and _scripts_dir. 99 if not _scripts_dir: 100 # The normalized, absolute path to ...Scripts/webkitpy/__init__. 101 webkitpy_path = _normalize_path(webkitpy.__file__) 102 103 _webkitpy_dir = os.path.split(webkitpy_path)[0] 104 _scripts_dir = os.path.split(_webkitpy_dir)[0] 105 106 if path.startswith(_webkitpy_dir): 107 # Remove the initial Scripts directory portion, so the path 108 # starts with /webkitpy, for example "/webkitpy/init/logutils". 109 path = path[len(_scripts_dir):] 110 111 parts = [] 112 while True: 113 (path, tail) = os.path.split(path) 114 if not tail: 115 break 116 parts.insert(0, tail) 117 118 logger_name = ".".join(parts) # For example, webkitpy.common.system.logutils. 119 else: 120 # The path is outside of webkitpy. Default to the basename 121 # without the extension. 122 basename = os.path.basename(path) 123 logger_name = os.path.splitext(basename)[0] 124 125 return logging.getLogger(logger_name) 126 127 128def _default_handlers(stream): 129 """Return a list of the default logging handlers to use. 130 131 Args: 132 stream: See the configure_logging() docstring. 133 134 """ 135 # Create the filter. 136 def should_log(record): 137 """Return whether a logging.LogRecord should be logged.""" 138 # FIXME: Enable the logging of autoinstall messages once 139 # autoinstall is adjusted. Currently, autoinstall logs 140 # INFO messages when importing already-downloaded packages, 141 # which is too verbose. 142 if record.name.startswith("webkitpy.thirdparty.autoinstall"): 143 return False 144 return True 145 146 logging_filter = logging.Filter() 147 logging_filter.filter = should_log 148 149 # Create the handler. 150 handler = logging.StreamHandler(stream) 151 formatter = logging.Formatter("%(name)s: [%(levelname)s] %(message)s") 152 handler.setFormatter(formatter) 153 handler.addFilter(logging_filter) 154 155 return [handler] 156 157 158def configure_logging(logging_level=None, logger=None, stream=None, 159 handlers=None): 160 """Configure logging for standard purposes. 161 162 Returns: 163 A list of references to the logging handlers added to the root 164 logger. This allows the caller to later remove the handlers 165 using logger.removeHandler. This is useful primarily during unit 166 testing where the caller may want to configure logging temporarily 167 and then undo the configuring. 168 169 Args: 170 logging_level: The minimum logging level to log. Defaults to 171 logging.INFO. 172 logger: A logging.logger instance to configure. This parameter 173 should be used only in unit tests. Defaults to the 174 root logger. 175 stream: A file-like object to which to log used in creating the default 176 handlers. The stream must define an "encoding" data attribute, 177 or else logging raises an error. Defaults to sys.stderr. 178 handlers: A list of logging.Handler instances to add to the logger 179 being configured. If this parameter is provided, then the 180 stream parameter is not used. 181 182 """ 183 # If the stream does not define an "encoding" data attribute, the 184 # logging module can throw an error like the following: 185 # 186 # Traceback (most recent call last): 187 # File "/System/Library/Frameworks/Python.framework/Versions/2.6/... 188 # lib/python2.6/logging/__init__.py", line 761, in emit 189 # self.stream.write(fs % msg.encode(self.stream.encoding)) 190 # LookupError: unknown encoding: unknown 191 if logging_level is None: 192 logging_level = logging.INFO 193 if logger is None: 194 logger = logging.getLogger() 195 if stream is None: 196 stream = sys.stderr 197 if handlers is None: 198 handlers = _default_handlers(stream) 199 200 logger.setLevel(logging_level) 201 202 for handler in handlers: 203 logger.addHandler(handler) 204 205 _log.debug("Debug logging enabled.") 206 207 return handlers 208