blob: 67ac537ded29824e7a66b5c3b06f49692a25cf1d [file] [log] [blame]
# 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
from logging import FileHandler
_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:
from webkitpy.common.system import 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, logging_level):
"""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)
if logging_level == logging.DEBUG:
formatter = logging.Formatter("%(name)s: [%(levelname)s] %(message)s")
else:
formatter = logging.Formatter("%(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, logging_level)
logger.setLevel(logging_level)
for handler in handlers:
logger.addHandler(handler)
_log.debug("Debug logging enabled.")
return handlers
def configure_logger_to_log_to_file(logger, log_path, filesystem):
log_directory = filesystem.dirname(log_path)
if log_directory and not filesystem.exists(log_directory):
filesystem.maybe_make_directory(log_directory)
handler = FileSystemHandler(log_path, filesystem)
formatter = logging.Formatter('%(asctime)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
return handler
class FileSystemHandler(FileHandler):
def __init__(self, filename, filesystem):
self.filename = filename
self.filesystem = filesystem
FileHandler.__init__(self, filename)
def _open(self):
return self.filesystem.open_text_file_for_writing(self.filename, should_append=True)