summaryrefslogtreecommitdiffstats
path: root/Tools/Scripts/webkitpy/common/system/logutils.py
blob: cd4e60f6532291bab125c0b51989b1c552e626eb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# Copyright (C) 2010 Chris Jerdonek (cjerdonek@webkit.org)
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1.  Redistributions of source code must retain the above copyright
#     notice, this list of conditions and the following disclaimer.
# 2.  Redistributions in binary form must reproduce the above copyright
#     notice, this list of conditions and the following disclaimer in the
#     documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

"""Supports webkitpy logging."""

# FIXME: Move this file to webkitpy/python24 since logging needs to
#        be configured prior to running version-checking code.

import logging
import os
import sys

import webkitpy


_log = logging.getLogger(__name__)

# We set these directory paths lazily in get_logger() below.
_scripts_dir = ""
"""The normalized, absolute path to the ...Scripts directory."""

_webkitpy_dir = ""
"""The normalized, absolute path to the ...Scripts/webkitpy directory."""


def _normalize_path(path):
    """Return the given path normalized.

    Converts a path to an absolute path, removes any trailing slashes,
    removes any extension, and lower-cases it.

    """
    path = os.path.abspath(path)
    path = os.path.normpath(path)
    path = os.path.splitext(path)[0]  # Remove the extension, if any.
    path = path.lower()

    return path


# Observe that the implementation of this function does not require
# the use of any hard-coded strings like "webkitpy", etc.
#
# The main benefit this function has over using--
#
# _log = logging.getLogger(__name__)
#
# is that get_logger() returns the same value even if __name__ is
# "__main__" -- i.e. even if the module is the script being executed
# from the command-line.
def get_logger(path):
    """Return a logging.logger for the given path.

    Returns:
      A logger whose name is the name of the module corresponding to
      the given path.  If the module is in webkitpy, the name is
      the fully-qualified dotted module name beginning with webkitpy....
      Otherwise, the name is the base name of the module (i.e. without
      any dotted module name prefix).

    Args:
      path: The path of the module.  Normally, this parameter should be
            the __file__ variable of the module.

    Sample usage:

      import webkitpy.common.system.logutils as logutils

      _log = logutils.get_logger(__file__)

    """
    # Since we assign to _scripts_dir and _webkitpy_dir in this function,
    # we need to declare them global.
    global _scripts_dir
    global _webkitpy_dir

    path = _normalize_path(path)

    # Lazily evaluate _webkitpy_dir and _scripts_dir.
    if not _scripts_dir:
        # The normalized, absolute path to ...Scripts/webkitpy/__init__.
        webkitpy_path = _normalize_path(webkitpy.__file__)

        _webkitpy_dir = os.path.split(webkitpy_path)[0]
        _scripts_dir = os.path.split(_webkitpy_dir)[0]

    if path.startswith(_webkitpy_dir):
        # Remove the initial Scripts directory portion, so the path
        # starts with /webkitpy, for example "/webkitpy/init/logutils".
        path = path[len(_scripts_dir):]

        parts = []
        while True:
            (path, tail) = os.path.split(path)
            if not tail:
                break
            parts.insert(0, tail)

        logger_name = ".".join(parts)  # For example, webkitpy.common.system.logutils.
    else:
        # The path is outside of webkitpy.  Default to the basename
        # without the extension.
        basename = os.path.basename(path)
        logger_name = os.path.splitext(basename)[0]

    return logging.getLogger(logger_name)


def _default_handlers(stream):
    """Return a list of the default logging handlers to use.

    Args:
      stream: See the configure_logging() docstring.

    """
    # Create the filter.
    def should_log(record):
        """Return whether a logging.LogRecord should be logged."""
        # FIXME: Enable the logging of autoinstall messages once
        #        autoinstall is adjusted.  Currently, autoinstall logs
        #        INFO messages when importing already-downloaded packages,
        #        which is too verbose.
        if record.name.startswith("webkitpy.thirdparty.autoinstall"):
            return False
        return True

    logging_filter = logging.Filter()
    logging_filter.filter = should_log

    # Create the handler.
    handler = logging.StreamHandler(stream)
    formatter = logging.Formatter("%(name)s: [%(levelname)s] %(message)s")
    handler.setFormatter(formatter)
    handler.addFilter(logging_filter)

    return [handler]


def configure_logging(logging_level=None, logger=None, stream=None,
                      handlers=None):
    """Configure logging for standard purposes.

    Returns:
      A list of references to the logging handlers added to the root
      logger.  This allows the caller to later remove the handlers
      using logger.removeHandler.  This is useful primarily during unit
      testing where the caller may want to configure logging temporarily
      and then undo the configuring.

    Args:
      logging_level: The minimum logging level to log.  Defaults to
                     logging.INFO.
      logger: A logging.logger instance to configure.  This parameter
              should be used only in unit tests.  Defaults to the
              root logger.
      stream: A file-like object to which to log used in creating the default
              handlers.  The stream must define an "encoding" data attribute,
              or else logging raises an error.  Defaults to sys.stderr.
      handlers: A list of logging.Handler instances to add to the logger
                being configured.  If this parameter is provided, then the
                stream parameter is not used.

    """
    # If the stream does not define an "encoding" data attribute, the
    # logging module can throw an error like the following:
    #
    # Traceback (most recent call last):
    #   File "/System/Library/Frameworks/Python.framework/Versions/2.6/...
    #         lib/python2.6/logging/__init__.py", line 761, in emit
    #     self.stream.write(fs % msg.encode(self.stream.encoding))
    # LookupError: unknown encoding: unknown
    if logging_level is None:
        logging_level = logging.INFO
    if logger is None:
        logger = logging.getLogger()
    if stream is None:
        stream = sys.stderr
    if handlers is None:
        handlers = _default_handlers(stream)

    logger.setLevel(logging_level)

    for handler in handlers:
        logger.addHandler(handler)

    _log.debug("Debug logging enabled.")

    return handlers