• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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