tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

mozlog.rst (20379B)


      1 :mod:`mozlog` --- Structured logging for test output
      2 ===============================================================
      3 
      4 :py:mod:`mozlog` is a library designed for logging the
      5 execution and results of test harnesses. The internal data model is a
      6 stream of JSON-compatible objects, with one object per log entry. The
      7 default output format is line-based, with one JSON object serialized
      8 per line.
      9 
     10 :py:mod:`mozlog` is *not* based on the stdlib logging
     11 module, although it shares several concepts with it.
     12 
     13 One notable difference between this module and the standard logging
     14 module is the way that loggers are created. The structured logging
     15 module does not require that loggers with a specific name are
     16 singleton objects accessed through a factory function. Instead the
     17 ``StructuredLogger`` constructor may be used directly. However all
     18 loggers with the same name share the same internal state (the "Borg"
     19 pattern). In particular the list of handler functions is the same for
     20 all loggers with the same name.
     21 
     22 Typically, you would only instantiate one logger object per
     23 program. Two convenience methods are provided to set and get the
     24 default logger in the program.
     25 
     26 Logging is threadsafe, with access to handlers protected by a
     27 ``threading.Lock``. However it is `not` process-safe. This means that
     28 applications using multiple processes, e.g. via the
     29 ``multiprocessing`` module, should arrange for all logging to happen in
     30 a single process.
     31 
     32 Data Format
     33 -----------
     34 
     35 Structured loggers produce messages in a simple format designed to be
     36 compatible with the JSON data model. Each message is a single object,
     37 with the type of message indicated by the ``action`` key. It is
     38 intended that the set of ``action`` values be closed; where there are
     39 use cases for additional values they should be integrated into this
     40 module rather than extended in an ad-hoc way. The set of keys present
     41 on on all messages is:
     42 
     43 ``action``
     44  The type of the message (string).
     45 
     46 ``time``
     47  The timestamp of the message in ms since the epoch (int).
     48 
     49 ``thread``
     50  The name of the thread emitting the message (string).
     51 
     52 ``pid``
     53  The pid of the process creating the message (int).
     54 
     55 ``source``
     56  Name of the logger creating the message (string).
     57 
     58 For each ``action`` there are is a further set of specific fields
     59 describing the details of the event that caused the message to be
     60 emitted:
     61 
     62 ``suite_start``
     63  Emitted when the testsuite starts running.
     64 
     65  ``tests``
     66    A dict of test ids keyed by group. Groups are any logical grouping
     67    of tests, for example a manifest, directory or tag. For convenience,
     68    a list of test ids can be used instead. In this case all tests will
     69    automatically be placed in the 'default' group name. Test ids can
     70    either be strings or lists of strings (an example of the latter is
     71    reftests where the id has the form [test_url, ref_type, ref_url]).
     72    Test ids are assumed to be unique within a given testsuite. In cases
     73    where the test list is not known upfront an empty dict or list may
     74    be passed (dict).
     75 
     76  ``name``
     77    An optional string to identify the suite by.
     78 
     79  ``run_info``
     80    An optional dictionary describing the properties of the
     81    build and test environment. This contains the information provided
     82    by :doc:`mozinfo <mozinfo>`, plus a boolean ``debug`` field indicating
     83    whether the build under test is a debug build.
     84 
     85 ``suite_end``
     86  Emitted when the testsuite is finished and no more results will be produced.
     87 
     88 ``test_start``
     89  Emitted when a test is being started.
     90 
     91  ``test``
     92    A unique id for the test (string or list of strings).
     93 
     94  ``path``
     95    Optional path to the test relative to some base (typically the root of the
     96    source tree). Mainly used when ``test`` id is not a path (string).
     97 
     98 ``test_status``
     99  Emitted for a test which has subtests to record the result of a
    100  single subtest.
    101 
    102  ``test``
    103    The same unique id for the test as in the ``test_start`` message.
    104 
    105  ``subtest``
    106    Name of the subtest (string).
    107 
    108  ``status``
    109    Result of the test (string enum; ``PASS``, ``FAIL``,
    110    ``PRECONDITION_FAILED``, ``TIMEOUT``, ``NOTRUN``)
    111 
    112  ``expected``
    113    Expected result of the test. Omitted if the expected result is the
    114    same as the actual result (string enum, same as ``status``).
    115 
    116  ``known_intermittent``
    117    A list of known intermittent statuses for that test. Omitted if there are
    118    no intermittent statuses expected. (items in the list are string enum, same as ``status``)
    119 
    120 ``test_end``
    121  Emitted to give the result of a test with no subtests, or the status
    122  of the overall file when there are subtests.
    123 
    124  ``test``
    125    The same unique id for the test as in the ``test_start`` message.
    126 
    127  ``status``
    128    Either result of the test (if there are no subtests) in which case
    129    (string enum ``PASS``, ``FAIL``, ``PRECONDITION_FAILED``,
    130    ``TIMEOUT``, ``CRASH``, ``ASSERT``, , ``SKIP``) or the status of
    131    the overall file where there are subtests (string enum ``OK``,
    132    ``PRECONDITION_FAILED``, ``ERROR``, ``TIMEOUT``, ``CRASH``,
    133    ``ASSERT``, ``SKIP``).
    134 
    135  ``expected``
    136    The expected status, or omitted if the expected status matches the
    137    actual status (string enum, same as ``status``).
    138 
    139  ``known_intermittent``
    140    A list of known intermittent statuses for that test. Omitted if there are
    141    no intermittent statuses expected. (items in the list are string enum, same as ``status``)
    142 
    143 ``process_output``
    144  Output from a managed subprocess.
    145 
    146  ``process``
    147  pid of the subprocess.
    148 
    149  ``command``
    150  Command used to launch the subprocess.
    151 
    152  ``data``
    153  Data output by the subprocess.
    154 
    155 ``log``
    156  General human-readable logging message, used to debug the harnesses
    157  themselves rather than to provide input to other tools.
    158 
    159  ``level``
    160    Level of the log message (string enum ``CRITICAL``, ``ERROR``,
    161    ``WARNING``, ``INFO``, ``DEBUG``).
    162 
    163  ``message``
    164    Text of the log message.
    165 
    166 ``shutdown``
    167  This is a special action that can only be logged once per logger state.
    168  It is sent when calling :meth:`StructuredLogger.shutdown` or implicitly
    169  when exiting the context manager.
    170 
    171 Testsuite Protocol
    172 ------------------
    173 
    174 When used for testsuites, the following structured logging messages must be emitted:
    175 
    176 * One ``suite_start`` message before any ``test_*`` messages
    177 
    178 * One ``test_start`` message per test that is run
    179 
    180 * One ``test_status`` message per subtest that is run. This might be
    181   zero if the test type doesn't have the notion of subtests.
    182 
    183 * One ``test_end`` message per test that is run, after the
    184   ``test_start`` and any ``test_status`` messages for that same test.
    185 
    186 * One ``suite_end`` message after all ``test_*`` messages have been
    187   emitted.
    188 
    189 The above mandatory events may be interspersed with ``process_output``
    190 and ``log`` events, as required.
    191 
    192 Subtests
    193 ~~~~~~~~
    194 
    195 The purpose of subtests is to deal with situations where a single test
    196 produces more than one result, and the exact details of the number of
    197 results is not known ahead of time. For example consider a test
    198 harness that loads JavaScript-based tests in a browser. Each url
    199 loaded would be a single test, with corresponding ``test_start`` and
    200 ``test_end`` messages. If there can be more than one JS-defined test
    201 on a page, however, it it useful to track the results of those tests
    202 separately. Therefore each of those tests is a subtest, and one
    203 ``test_status`` message must be generated for each subtest result.
    204 
    205 Subtests must have a name that is unique within their parent test.
    206 
    207 Whether or not a test has subtests changes the meaning of the
    208 ``status`` property on the test itself. When the test does not have
    209 any subtests, this property is the actual test result such as ``PASS``
    210 or ``FAIL`` . When a test does have subtests, the test itself does not
    211 have a result as-such; it isn't meaningful to describe it as having a
    212 ``PASS`` result, especially if the subtests did not all pass. Instead
    213 this property is used to hold information about whether the test ran
    214 without error. If no errors were detected the test must be given the
    215 status ``OK``. Otherwise the test may get the status ``ERROR`` (for
    216 e.g. uncaught JS exceptions), ``TIMEOUT`` (if no results were reported
    217 in the allowed time) or ``CRASH`` (if the test caused the process
    218 under test to crash).
    219 
    220 StructuredLogger Objects
    221 ------------------------
    222 
    223 .. automodule:: mozlog.structuredlog
    224  :members: set_default_logger, get_default_logger, LoggerShutdownError
    225 
    226 .. autoclass:: StructuredLogger
    227   :members: add_handler, remove_handler, handlers, suite_start,
    228             suite_end, test_start, test_status, test_end,
    229             process_output, critical, error, warning, info, debug,
    230             shutdown
    231 
    232 .. autoclass:: StructuredLogFileLike
    233  :members:
    234 
    235 ProxyLogger Objects
    236 -------------------
    237 
    238 Since :func:`mozlog.structuredlog.get_default_logger` return None when
    239 the default logger is not initialized, it is not possible to directly
    240 use it at the module level.
    241 
    242 With ProxyLogger, it is possible to write the following code: ::
    243 
    244  from mozlog import get_proxy_logger
    245 
    246  LOG = get_proxy_logger('component_name')
    247 
    248 
    249  def my_function():
    250      LOG.info('logging with a module level object')
    251 
    252 
    253 .. note::
    254 
    255   mozlog still needs to be initialized before the first call occurs
    256   to a ProxyLogger instance, for example with
    257   :func:`mozlog.commandline.setup_logging`.
    258 
    259 .. automodule:: mozlog.proxy
    260  :members: get_proxy_logger, ProxyLogger
    261 
    262 Handlers
    263 --------
    264 
    265 A handler is a callable that is called for each log message produced
    266 and is responsible for handling the processing of that
    267 message. The typical example of this is a ``StreamHandler`` which takes
    268 a log message, invokes a formatter which converts the log to a string,
    269 and writes it to a file.
    270 
    271 .. automodule:: mozlog.handlers
    272 
    273 .. autoclass:: BaseHandler
    274  :members:
    275 
    276 .. autoclass:: StreamHandler
    277  :members:
    278 
    279 .. autoclass:: LogLevelFilter
    280  :members:
    281 
    282 .. autoclass:: BufferHandler
    283  :members:
    284 
    285 Formatters
    286 ----------
    287 
    288 Formatters are callables that take a log message, and return either a
    289 string representation of that message, or ``None`` if that message
    290 should not appear in the output. This allows formatters to both
    291 exclude certain items and create internal buffers of the output so
    292 that, for example, a single string might be returned for a
    293 ``test_end`` message indicating the overall result of the test,
    294 including data provided in the ``test_status`` messages.
    295 
    296 Formatter modules are written so that they can take raw input on stdin
    297 and write formatted output on stdout. This allows the formatters to be
    298 invoked as part of a command line for post-processing raw log files.
    299 
    300 .. automodule:: mozlog.formatters.base
    301 
    302 .. autoclass:: BaseFormatter
    303  :members:
    304 
    305 .. automodule:: mozlog.formatters.unittest
    306 
    307 .. autoclass:: UnittestFormatter
    308  :members:
    309 
    310 .. automodule:: mozlog.formatters.xunit
    311 
    312 .. autoclass:: XUnitFormatter
    313  :members:
    314 
    315 .. automodule:: mozlog.formatters.html
    316 
    317 .. autoclass:: HTMLFormatter
    318  :members:
    319 
    320 .. automodule:: mozlog.formatters.machformatter
    321 
    322 .. autoclass:: MachFormatter
    323  :members:
    324 
    325 .. automodule:: mozlog.formatters.tbplformatter
    326 
    327 .. autoclass:: TbplFormatter
    328  :members:
    329 
    330 Processing Log Files
    331 --------------------
    332 
    333 The ``mozlog.reader`` module provides utilities for working
    334 with structured log files.
    335 
    336 .. automodule:: mozlog.reader
    337  :members:
    338 
    339 Integration with argparse
    340 -------------------------
    341 
    342 The `mozlog.commandline` module provides integration with the `argparse`
    343 module to provide uniform logging-related command line arguments to programs
    344 using `mozlog`. Each known formatter gets a command line argument of the form
    345 ``--log-{name}``, which takes the name of a file to log to with that format,
    346 or ``-`` to indicate stdout.
    347 
    348 .. automodule:: mozlog.commandline
    349  :members:
    350 
    351 Simple Examples
    352 ---------------
    353 
    354 Log to stdout::
    355 
    356    from mozlog import structuredlog
    357    from mozlog import handlers, formatters
    358    logger = structuredlog.StructuredLogger("my-test-suite")
    359    logger.add_handler(handlers.StreamHandler(sys.stdout,
    360                                              formatters.JSONFormatter()))
    361    logger.suite_start(["test-id-1"])
    362    logger.test_start("test-id-1")
    363    logger.info("This is a message with action='LOG' and level='INFO'")
    364    logger.test_status("test-id-1", "subtest-1", "PASS")
    365    logger.test_end("test-id-1", "OK")
    366    logger.suite_end()
    367 
    368 Log with a context manager::
    369 
    370    from mozlog.structuredlog import StructuredLogger
    371    from mozlog.handlers import StreamHandler
    372    from mozlog.formatters import JSONFormatter
    373 
    374    with StructuredLogger("my-test-suite") as logger:
    375        logger.add_handler(StreamHandler(sys.stdout,
    376                                         JSONFormatter()))
    377        logger.info("This is an info message")
    378 
    379 Populate an ``argparse.ArgumentParser`` with logging options, and
    380 create a logger based on the value of those options, defaulting to
    381 JSON output on stdout if nothing else is supplied::
    382 
    383   import argparse
    384   from mozlog import commandline
    385 
    386   parser = argparse.ArgumentParser()
    387   # Here one would populate the parser with other options
    388   commandline.add_logging_group(parser)
    389 
    390   args = parser.parse_args()
    391   logger = commandline.setup_logging("testsuite-name", args, {"raw": sys.stdout})
    392 
    393 Count the number of tests that timed out in a testsuite::
    394 
    395   from mozlog import reader
    396 
    397   count = 0
    398 
    399   def handle_test_end(data):
    400       global count
    401       if data["status"] == "TIMEOUT":
    402           count += 1
    403 
    404   reader.each_log(reader.read("my_test_run.log"),
    405                   {"test_end": handle_test_end})
    406 
    407   print count
    408 
    409 More Complete Example
    410 ---------------------
    411 
    412 This example shows a complete toy testharness set up to used
    413 structured logging. It is available as `structured_example.py <_static/structured_example.py>`_:
    414 
    415 .. literalinclude:: _static/structured_example.py
    416 
    417 Each global function with a name starting
    418 ``test_`` represents a test. A passing test returns without
    419 throwing. A failing test throws a :py:class:`TestAssertion` exception
    420 via the :py:func:`assert_equals` function. Throwing anything else is
    421 considered an error in the test. There is also a :py:func:`expected`
    422 decorator that is used to annotate tests that are expected to do
    423 something other than pass.
    424 
    425 The main entry point to the test runner is via that :py:func:`main`
    426 function. This is responsible for parsing command line
    427 arguments, and initiating the test run. Although the test harness
    428 itself does not provide any command line arguments, the
    429 :py:class:`ArgumentParser` object is populated by
    430 :py:meth:`commandline.add_logging_group`, which provides a generic
    431 set of structured logging arguments appropriate to all tools producing
    432 structured logging.
    433 
    434 The values of these command line arguments are used to create a
    435 :py:class:`mozlog.StructuredLogger` object populated with the
    436 specified handlers and formatters in
    437 :py:func:`commandline.setup_logging`. The third argument to this
    438 function is the default arguments to use. In this case the default
    439 is to output raw (i.e. JSON-formatted) logs to stdout.
    440 
    441 The main test harness is provided by the :py:class:`TestRunner`
    442 class. This class is responsible for scheduling all the tests and
    443 logging all the results. It is passed the :py:obj:`logger` object
    444 created from the command line arguments. The :py:meth:`run` method
    445 starts the test run. Before the run is started it logs a
    446 ``suite_start`` message containing the id of each test that will run,
    447 and after the testrun is done it logs a ``suite_end`` message.
    448 
    449 Individual tests are run in the :py:meth:`run_test` method. For each
    450 test this logs a ``test_start`` message. It then runs the test and
    451 logs a ``test_end`` message containing the test name, status, expected
    452 status, and any informational message about the reason for the
    453 result. In this test harness there are no subtests, so the
    454 ``test_end`` message has the status of the test and there are no
    455 ``test_status`` messages.
    456 
    457 Example Output
    458 ~~~~~~~~~~~~~~
    459 
    460 When run without providing any command line options, the raw
    461 structured log messages are sent to stdout::
    462 
    463  $ python structured_example.py
    464 
    465  {"source": "structured-example", "tests": ["test_that_has_an_error", "test_that_fails", "test_expected_fail", "test_that_passes"], "thread": "MainThread", "time": 1401446682787, "action": "suite_start", "pid": 18456}
    466  {"source": "structured-example", "thread": "MainThread", "time": 1401446682787, "action": "log", "message": "Running tests", "level": "INFO", "pid": 18456}
    467  {"source": "structured-example", "test": "test_that_has_an_error", "thread": "MainThread", "time": 1401446682787, "action": "test_start", "pid": 18456}
    468  {"status": "ERROR", "thread": "MainThread", "pid": 18456, "source": "structured-example", "test": "test_that_has_an_error", "time": 1401446682788, "action": "test_end", "message": "Traceback (most recent call last):\n  File \"structured_example.py\", line 61, in run_test\n    func()\n  File \"structured_example.py\", line 31, in test_that_has_an_error\n    assert_equals(2, 1 + \"1\")\nTypeError: unsupported operand type(s) for +: 'int' and 'str'\n", "expected": "PASS"}
    469  {"source": "structured-example", "test": "test_that_fails", "thread": "MainThread", "time": 1401446682788, "action": "test_start", "pid": 18456}
    470  {"status": "FAIL", "thread": "MainThread", "pid": 18456, "source": "structured-example", "test": "test_that_fails", "time": 1401446682788, "action": "test_end", "message": "1 not equal to 2", "expected": "PASS"}
    471  {"source": "structured-example", "test": "test_expected_fail", "thread": "MainThread", "time": 1401446682788, "action": "test_start", "pid": 18456}
    472  {"status": "FAIL", "thread": "MainThread", "pid": 18456, "source": "structured-example", "test": "test_expected_fail", "time": 1401446682788, "action": "test_end", "message": "4 not equal to 5"}
    473  {"source": "structured-example", "test": "test_that_passes", "thread": "MainThread", "time": 1401446682788, "action": "test_start", "pid": 18456}
    474  {"status": "PASS", "source": "structured-example", "test": "test_that_passes", "thread": "MainThread", "time": 1401446682789, "action": "test_end", "pid": 18456}
    475  {"source": "structured-example", "test": "test_with_known_intermittent", "thread": "MainThread", "time": 1401446682789, "action": "test_start", "pid": 18456}
    476  {"status": "FAIL", thread": "MainThread", "pid": 18456, "source": "structured-example", "test": "test_with_known_intermittent", "time": 1401446682790, "action": "test_end", "expected": "PASS", "known_intermittent": ["FAIL", "TIMEOUT"]}
    477  {"action": "suite_end", "source": "structured-example", "pid": 18456, "thread": "MainThread", "time": 1401446682790}
    478 
    479 The structured logging module provides a number of command line
    480 options::
    481 
    482  $ python structured_example.py --help
    483 
    484  usage: structured_example.py [-h] [--log-unittest LOG_UNITTEST]
    485                               [--log-raw LOG_RAW] [--log-html LOG_HTML]
    486                               [--log-xunit LOG_XUNIT]
    487                               [--log-mach LOG_MACH]
    488 
    489  optional arguments:
    490    -h, --help            show this help message and exit
    491 
    492  Output Logging:
    493    Options for logging output. Each option represents a possible logging
    494    format and takes a filename to write that format to, or '-' to write to
    495    stdout.
    496 
    497    --log-unittest LOG_UNITTEST
    498                          Unittest style output
    499    --log-raw LOG_RAW     Raw structured log messages
    500    --log-html LOG_HTML   HTML report
    501    --log-xunit LOG_XUNIT
    502                          xUnit compatible XML
    503    --log-mach LOG_MACH   Human-readable output
    504 
    505 In order to get human-readable output on stdout and the structured log
    506 data to go to the file ``structured.log``, we would run::
    507 
    508  $ python structured_example.py --log-mach=- --log-raw=structured.log
    509 
    510  0:00.00 SUITE_START: MainThread 4
    511  0:01.00 LOG: MainThread INFO Running tests
    512  0:01.00 TEST_START: MainThread test_that_has_an_error
    513  0:01.00 TEST_END: MainThread Harness status ERROR, expected PASS. Subtests passed 0/0. Unexpected 1
    514  0:01.00 TEST_START: MainThread test_that_fails
    515  0:01.00 TEST_END: MainThread Harness status FAIL, expected PASS. Subtests passed 0/0. Unexpected 1
    516  0:01.00 TEST_START: MainThread test_expected_fail
    517  0:02.00 TEST_END: MainThread Harness status FAIL. Subtests passed 0/0. Unexpected 0
    518  0:02.00 TEST_START: MainThread test_that_passes
    519  0:02.00 TEST_END: MainThread Harness status PASS. Subtests passed 0/0. Unexpected 0
    520  0:02.00 SUITE_END: MainThread