mirror of
https://github.com/servo/servo
synced 2026-05-10 17:12:23 +02:00
181 lines
6.5 KiB
Python
181 lines
6.5 KiB
Python
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
from logging import getLogger as getSysLogger
|
|
from logging import *
|
|
# Some of the build slave environments don't see the following when doing
|
|
# 'from logging import *'
|
|
# see https://bugzilla.mozilla.org/show_bug.cgi?id=700415#c35
|
|
from logging import getLoggerClass, addLevelName, setLoggerClass, shutdown, debug, info, basicConfig
|
|
import json
|
|
|
|
_default_level = INFO
|
|
_LoggerClass = getLoggerClass()
|
|
|
|
# Define mozlog specific log levels
|
|
START = _default_level + 1
|
|
END = _default_level + 2
|
|
PASS = _default_level + 3
|
|
KNOWN_FAIL = _default_level + 4
|
|
FAIL = _default_level + 5
|
|
CRASH = _default_level + 6
|
|
# Define associated text of log levels
|
|
addLevelName(START, 'TEST-START')
|
|
addLevelName(END, 'TEST-END')
|
|
addLevelName(PASS, 'TEST-PASS')
|
|
addLevelName(KNOWN_FAIL, 'TEST-KNOWN-FAIL')
|
|
addLevelName(FAIL, 'TEST-UNEXPECTED-FAIL')
|
|
addLevelName(CRASH, 'PROCESS-CRASH')
|
|
|
|
class MozLogger(_LoggerClass):
|
|
"""
|
|
MozLogger class which adds some convenience log levels
|
|
related to automated testing in Mozilla and ability to
|
|
output structured log messages.
|
|
"""
|
|
def testStart(self, message, *args, **kwargs):
|
|
"""Logs a test start message"""
|
|
self.log(START, message, *args, **kwargs)
|
|
|
|
def testEnd(self, message, *args, **kwargs):
|
|
"""Logs a test end message"""
|
|
self.log(END, message, *args, **kwargs)
|
|
|
|
def testPass(self, message, *args, **kwargs):
|
|
"""Logs a test pass message"""
|
|
self.log(PASS, message, *args, **kwargs)
|
|
|
|
def testFail(self, message, *args, **kwargs):
|
|
"""Logs a test fail message"""
|
|
self.log(FAIL, message, *args, **kwargs)
|
|
|
|
def testKnownFail(self, message, *args, **kwargs):
|
|
"""Logs a test known fail message"""
|
|
self.log(KNOWN_FAIL, message, *args, **kwargs)
|
|
|
|
def processCrash(self, message, *args, **kwargs):
|
|
"""Logs a process crash message"""
|
|
self.log(CRASH, message, *args, **kwargs)
|
|
|
|
def log_structured(self, action, params=None):
|
|
"""Logs a structured message object."""
|
|
if params is None:
|
|
params = {}
|
|
|
|
level = params.get('_level', _default_level)
|
|
if isinstance(level, int):
|
|
params['_level'] = getLevelName(level)
|
|
else:
|
|
params['_level'] = level
|
|
level = getLevelName(level.upper())
|
|
|
|
# If the logger is fed a level number unknown to the logging
|
|
# module, getLevelName will return a string. Unfortunately,
|
|
# the logging module will raise a type error elsewhere if
|
|
# the level is not an integer.
|
|
if not isinstance(level, int):
|
|
level = _default_level
|
|
|
|
params['action'] = action
|
|
|
|
# The can message be None. This is expected, and shouldn't cause
|
|
# unstructured formatters to fail.
|
|
message = params.get('_message')
|
|
|
|
self.log(level, message, extra={'params': params})
|
|
|
|
class JSONFormatter(Formatter):
|
|
"""Log formatter for emitting structured JSON entries."""
|
|
|
|
def format(self, record):
|
|
# Default values determined by logger metadata
|
|
output = {
|
|
'_time': int(round(record.created * 1000, 0)),
|
|
'_namespace': record.name,
|
|
'_level': getLevelName(record.levelno),
|
|
}
|
|
|
|
# If this message was created by a call to log_structured,
|
|
# anything specified by the caller's params should act as
|
|
# an override.
|
|
output.update(getattr(record, 'params', {}))
|
|
|
|
if record.msg and output.get('_message') is None:
|
|
# For compatibility with callers using the printf like
|
|
# API exposed by python logging, call the default formatter.
|
|
output['_message'] = Formatter.format(self, record)
|
|
|
|
return json.dumps(output, indent=output.get('indent'))
|
|
|
|
class MozFormatter(Formatter):
|
|
"""
|
|
MozFormatter class used to standardize formatting
|
|
If a different format is desired, this can be explicitly
|
|
overriden with the log handler's setFormatter() method
|
|
"""
|
|
level_length = 0
|
|
max_level_length = len('TEST-START')
|
|
|
|
def __init__(self, include_timestamp=False):
|
|
"""
|
|
Formatter.__init__ has fmt and datefmt parameters that won't have
|
|
any affect on a MozFormatter instance.
|
|
|
|
:param include_timestamp: if True, include formatted time at the
|
|
beginning of the message
|
|
"""
|
|
self.include_timestamp = include_timestamp
|
|
Formatter.__init__(self)
|
|
|
|
def format(self, record):
|
|
# Handles padding so record levels align nicely
|
|
if len(record.levelname) > self.level_length:
|
|
pad = 0
|
|
if len(record.levelname) <= self.max_level_length:
|
|
self.level_length = len(record.levelname)
|
|
else:
|
|
pad = self.level_length - len(record.levelname) + 1
|
|
sep = '|'.rjust(pad)
|
|
fmt = '%(name)s %(levelname)s ' + sep + ' %(message)s'
|
|
if self.include_timestamp:
|
|
fmt = '%(asctime)s ' + fmt
|
|
# this protected member is used to define the format
|
|
# used by the base Formatter's method
|
|
self._fmt = fmt
|
|
return Formatter.format(self, record)
|
|
|
|
def getLogger(name, handler=None):
|
|
"""
|
|
Returns the logger with the specified name.
|
|
If the logger doesn't exist, it is created.
|
|
If handler is specified, adds it to the logger. Otherwise a default handler
|
|
that logs to standard output will be used.
|
|
|
|
:param name: The name of the logger to retrieve
|
|
:param handler: A handler to add to the logger. If the logger already exists,
|
|
and a handler is specified, an exception will be raised. To
|
|
add a handler to an existing logger, call that logger's
|
|
addHandler method.
|
|
"""
|
|
setLoggerClass(MozLogger)
|
|
|
|
if name in Logger.manager.loggerDict:
|
|
if handler:
|
|
raise ValueError('The handler parameter requires ' + \
|
|
'that a logger by this name does ' + \
|
|
'not already exist')
|
|
return Logger.manager.loggerDict[name]
|
|
|
|
logger = getSysLogger(name)
|
|
logger.setLevel(_default_level)
|
|
|
|
if handler is None:
|
|
handler = StreamHandler()
|
|
handler.setFormatter(MozFormatter())
|
|
|
|
logger.addHandler(handler)
|
|
logger.propagate = False
|
|
return logger
|
|
|