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