Lines Matching +full:simulation +full:- +full:local +full:- +full:host
1 .. _logging-cookbook:
7 :Author: Vinay Sajip <vinay_sajip at red-dove dot com>
11 :ref:`cookbook-ref-links`.
16 ---------------------------------
39 formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
78 .. code-block:: none
80 2005-03-23 23:47:11,663 - spam_application - INFO -
82 2005-03-23 23:47:11,665 - spam_application.auxiliary.Auxiliary - INFO -
84 2005-03-23 23:47:11,665 - spam_application - INFO -
86 2005-03-23 23:47:11,668 - spam_application - INFO -
88 2005-03-23 23:47:11,668 - spam_application.auxiliary.Auxiliary - INFO -
90 2005-03-23 23:47:11,669 - spam_application.auxiliary.Auxiliary - INFO -
92 2005-03-23 23:47:11,670 - spam_application - INFO -
94 2005-03-23 23:47:11,671 - spam_application - INFO -
96 2005-03-23 23:47:11,672 - spam_application.auxiliary - INFO -
98 2005-03-23 23:47:11,673 - spam_application - INFO -
102 -----------------------------
135 .. code-block:: none
137 0 Thread-1 Hi from myfunc
139 505 Thread-1 Hi from myfunc
141 1007 Thread-1 Hi from myfunc
143 1508 Thread-1 Hi from myfunc
144 2010 Thread-1 Hi from myfunc
146 2512 Thread-1 Hi from myfunc
148 3013 Thread-1 Hi from myfunc
149 3515 Thread-1 Hi from myfunc
151 4017 Thread-1 Hi from myfunc
153 4518 Thread-1 Hi from myfunc
159 --------------------------------
167 previous simple module-based configuration example::
180 formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
197 The ability to create new handlers with higher- or lower-severity filters can be
205 .. _multiple-destinations:
208 --------------------------------
218 # set up logging to file - see previous section for more details
220 format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s',
221 datefmt='%m-%d %H:%M',
228 formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')
250 .. code-block:: none
259 .. code-block:: none
261 10-22 22:19 root INFO Jackdaws love my big sphinx of quartz.
262 10-22 22:19 myapp.area1 DEBUG Quick zephyrs blow, vexing daft Jim.
263 10-22 22:19 myapp.area1 INFO How quickly daft jumping zebras vex.
264 10-22 22:19 myapp.area2 WARNING Jail zesty vixen who grabbed pay from quack.
265 10-22 22:19 myapp.area2 ERROR The five boxing wizards jump quickly.
275 choose a different directory name for the log - just ensure that the directory exists
279 .. _custom-level-handling:
282 -------------------------
295 .. code-block:: json
302 "format": "%(levelname)-8s - %(message)s"
341 .. code-block:: json
354 .. code-block:: json
369 .. code-block:: python
383 so its module will be ``__main__`` - hence the ``__main__.filter_maker`` in the
389 .. code-block:: python
401 "format": "%(levelname)-8s - %(message)s"
459 .. code-block:: shell
465 .. code-block:: shell
471 DEBUG - A DEBUG message
472 INFO - An INFO message
473 WARNING - A WARNING message
474 ERROR - An ERROR message
475 CRITICAL - A CRITICAL message
479 ERROR - An ERROR message
480 CRITICAL - A CRITICAL message
484 INFO - An INFO message
485 WARNING - A WARNING message
489 ----------------------------
523 properly preceded with the binary-encoded length, as the new logging
532 HOST = 'localhost'
536 s.connect((HOST, PORT))
544 .. _blocking-handlers:
547 --------------------------------
558 performing mail or network infrastructure). But almost any network-based
563 One solution is to use a two-part approach. For the first part, attach only a
565 performance-critical threads. They simply write to their queue, which can be
569 in your code. If you are a library developer who has performance-critical
584 resource-friendly than, say, having threaded versions of the existing handler
589 que = queue.Queue(-1) # no limit on size
607 .. code-block:: none
629 .. _network-logging:
632 -----------------------------------------------------
681 Handle multiple requests - each expected to be a 4-byte length,
692 chunk = chunk + self.connection.recv(slen - len(chunk))
709 # is normally called AFTER logger-level filtering. If you want
716 Simple TCP socket-based logging receiver suitable for testing.
721 def __init__(self, host='localhost',
724 socketserver.ThreadingTCPServer.__init__(self, (host, port), handler)
742 format='%(relativeCreated)5d %(name)-15s %(levelname)-8s %(message)s')
753 .. code-block:: none
772 .. _socket-listener-gist: https://gist.github.com/vsajip/4b227eeec43817465ca835ca66f75e2b
775 process-management tool such as `Supervisor <http://supervisord.org/>`_.
776 `Here is a Gist <socket-listener-gist_>`__
777 which provides the bare-bones files to run the above functionality using
780 +-------------------------+----------------------------------------------------+
785 +-------------------------+----------------------------------------------------+
787 | | entries for the listener and a multi-process web |
789 +-------------------------+----------------------------------------------------+
792 +-------------------------+----------------------------------------------------+
795 +-------------------------+----------------------------------------------------+
798 +-------------------------+----------------------------------------------------+
800 +-------------------------+----------------------------------------------------+
802 +-------------------------+----------------------------------------------------+
807 without conflicting with one another --- they all go through the socket listener.
811 #. Download `the Gist <socket-listener-gist_>`__
817 This creates a :file:`run` subdirectory to contain Supervisor-related and
830 worker processes in a non-deterministic way.
833 ``venv/bin/supervisorctl -c supervisor.conf shutdown``.
840 .. _context-info:
843 ----------------------------------------------------
847 networked application, it may be desirable to log client-specific information
851 :class:`Logger` instances on a per-connection basis, this is not a good idea
871 :class:`Logger` instance and a dict-like object which contains your contextual
891 an 'extra' key in the keyword argument whose value is the dict-like object
895 The advantage of using 'extra' is that the values in the dict-like object are
898 the keys of the dict-like object. If you need a different method, e.g. if you
905 This example adapter expects the passed in dict-like object to have a
922 You don't need to pass an actual dict to a :class:`LoggerAdapter` - you could
928 .. _filters-contextual:
933 You can also add contextual information to log output using a user-defined
940 (:class:`threading.local`) variable, and then accessed from a ``Filter`` to
941 add, say, information from the request - say, the remote IP address and remote
942 user's username - to the ``LogRecord``, using the attribute names 'ip' and
970 … format='%(asctime)-15s %(name)-5s %(levelname)-8s IP: %(ip)-15s User: %(user)-8s %(message)s')
986 .. code-block:: none
988 2010-09-06 22:38:15,292 a.b.c DEBUG IP: 123.231.231.123 User: fred A debug message
989 …2010-09-06 22:38:15,300 a.b.c INFO IP: 192.168.0.1 User: sheila An info message with som…
990 …2010-09-06 22:38:15,300 d.e.f CRITICAL IP: 127.0.0.1 User: sheila A message at CRITICAL le…
991 …2010-09-06 22:38:15,300 d.e.f ERROR IP: 127.0.0.1 User: jim A message at ERROR level…
992 …2010-09-06 22:38:15,300 d.e.f DEBUG IP: 127.0.0.1 User: sheila A message at DEBUG level…
993 …2010-09-06 22:38:15,300 d.e.f ERROR IP: 123.231.231.123 User: fred A message at ERROR level…
994 …2010-09-06 22:38:15,300 d.e.f CRITICAL IP: 192.168.0.1 User: jim A message at CRITICAL le…
995 …2010-09-06 22:38:15,300 d.e.f CRITICAL IP: 127.0.0.1 User: sheila A message at CRITICAL le…
996 …2010-09-06 22:38:15,300 d.e.f DEBUG IP: 192.168.0.1 User: jim A message at DEBUG level…
997 …2010-09-06 22:38:15,301 d.e.f ERROR IP: 127.0.0.1 User: sheila A message at ERROR level…
998 …2010-09-06 22:38:15,301 d.e.f DEBUG IP: 123.231.231.123 User: fred A message at DEBUG level…
999 …2010-09-06 22:38:15,301 d.e.f INFO IP: 123.231.231.123 User: fred A message at INFO level …
1002 ----------------------
1004 Since Python 3.7, the :mod:`contextvars` module has provided context-local storage
1006 of storage may thus be generally preferable to thread-locals. The following example
1007 shows how, in a multi-threaded environment, logs can populated with contextual
1019 .. code-block:: python
1034 ``Request`` and ``WebApp``. These simulate how real threaded web applications work -
1037 .. code-block:: python
1062 # A dummy set of requests which will be used in the simulation - we'll just pick
1079 …formatter = logging.Formatter('%(threadName)-11s %(appName)s %(name)-9s %(user)-6s %(ip)s %(method…
1089 A filter which injects context-specific information into logs and ensures
1106 webapp-specific log.
1140 aa('--count', '-c', type=int, default=100, help='How many requests to simulate')
1179 logged to :file:`app.log`. Each webapp-specific log will contain only log entries for
1184 .. code-block:: shell
1186 ~/logging-contextual-webapp$ python main.py
1189 ~/logging-contextual-webapp$ wc -l *.log
1194 ~/logging-contextual-webapp$ head -3 app1.log
1195 Thread-3 (process_request) app1 __main__ jim 192.168.3.21 POST Request processing started
1196 Thread-3 (process_request) app1 webapplib jim 192.168.3.21 POST Hello from webapplib!
1197 Thread-5 (process_request) app1 __main__ jim 192.168.3.21 POST Request processing started
1198 ~/logging-contextual-webapp$ head -3 app2.log
1199 Thread-1 (process_request) app2 __main__ sheila 192.168.2.21 GET Request processing started
1200 Thread-1 (process_request) app2 webapplib sheila 192.168.2.21 GET Hello from webapplib!
1201 Thread-2 (process_request) app2 __main__ jim 192.168.2.20 GET Request processing started
1202 ~/logging-contextual-webapp$ head app.log
1203 Thread-1 (process_request) app2 __main__ sheila 192.168.2.21 GET Request processing started
1204 Thread-1 (process_request) app2 webapplib sheila 192.168.2.21 GET Hello from webapplib!
1205 Thread-2 (process_request) app2 __main__ jim 192.168.2.20 GET Request processing started
1206 Thread-3 (process_request) app1 __main__ jim 192.168.3.21 POST Request processing started
1207 Thread-2 (process_request) app2 webapplib jim 192.168.2.20 GET Hello from webapplib!
1208 Thread-3 (process_request) app1 webapplib jim 192.168.3.21 POST Hello from webapplib!
1209 Thread-4 (process_request) app2 __main__ fred 192.168.2.22 GET Request processing started
1210 Thread-5 (process_request) app1 __main__ jim 192.168.3.21 POST Request processing started
1211 Thread-4 (process_request) app2 webapplib fred 192.168.2.22 GET Hello from webapplib!
1212 Thread-6 (process_request) app1 __main__ jim 192.168.3.21 POST Request processing started
1213 ~/logging-contextual-webapp$ grep app1 app1.log | wc -l
1215 ~/logging-contextual-webapp$ grep app2 app2.log | wc -l
1217 ~/logging-contextual-webapp$ grep app1 app.log | wc -l
1219 ~/logging-contextual-webapp$ grep app2 app.log | wc -l
1224 --------------------------------------------
1229 a new :class:`~LogRecord` instead of modifying it in-place, as shown in the following script::
1243 formatter = logging.Formatter('%(message)s from %(user)-8s')
1250 .. _multiple-processes:
1253 ------------------------------------------------
1255 Although logging is thread-safe, and logging to a single file from multiple
1264 :ref:`This section <network-logging>` documents this approach in more detail and
1276 all logging events to one of the processes in your multi-process application.
1281 thread rather than a separate listener process -- the implementation would be
1310 f = logging.Formatter('%(asctime)s %(processName)-10s %(name)s %(levelname)-8s %(message)s')
1314 # This is the listener process top-level loop: wait for logging events
1325 logger.handle(record) # No level or filter logic applied - just do it!
1354 # This is the worker process top-level loop, which just logs ten events with
1373 queue = multiprocessing.Queue(-1)
1432 … 'format': '%(asctime)s %(name)-15s %(levelname)-8s %(processName)-10s %(message)s'
1448 'filename': 'mplog-foo.log',
1454 'filename': 'mplog-errors.log',
1487 - e.g. the ``foo`` logger has a special handler which stores all events in the
1488 ``foo`` subsystem in a file ``mplog-foo.log``. This will be used by the logging
1499 .. code-block:: python
1501 queue = multiprocessing.Queue(-1)
1505 .. code-block:: python
1507 queue = multiprocessing.Manager().Queue(-1) # also works with the examples above
1530 <https://uwsgi-docs.readthedocs.io/en/latest/>`_ (or similar), multiple worker
1532 file-based handlers directly in your web application. Instead, use a
1534 process. This can be set up using a process management tool such as Supervisor - see
1539 -------------------
1579 .. code-block:: none
1598 .. _format-styles:
1601 ------------------------------------
1604 formatting messages with variable content was to use the %-formatting
1619 .. code-block:: pycon
1631 2010-10-28 15:11:55,341 foo.bar DEBUG This is a DEBUG message
1633 2010-10-28 15:12:11,526 foo.bar CRITICAL This is a CRITICAL message
1638 2010-10-28 15:13:06,924 foo.bar DEBUG This is a DEBUG message
1640 2010-10-28 15:13:11,494 foo.bar CRITICAL This is a CRITICAL message
1645 That can still use %-formatting, as shown here::
1648 2010-10-28 15:19:29,833 foo.bar ERROR This is another, ERROR, message
1659 uses %-formatting to merge the format string and the variable arguments.
1661 all logging calls which are out there in existing code will be using %-format
1664 There is, however, a way that you can use {}- and $- formatting to construct
1688 Either of these can be used in place of a format string, to allow {}- or
1689 $-formatting to be used to build the actual "message" part which appears in the
1693 underscore --- not to be confused with _, the single underscore used as a
1700 .. code-block:: pycon
1765 .. _custom-logrecord:
1768 -------------------------
1811 top-level logger, but this would not be invoked if an application developer
1812 attached a handler to a lower-level library logger --- so output from that
1838 However, it should be borne in mind that each link in the chain adds run-time
1844 .. _zeromq-handlers:
1846 Subclassing QueueHandler and QueueListener- a ZeroMQ example
1847 ------------------------------------------------------------
1906 .. _pynng-handlers:
1908 Subclassing QueueHandler and QueueListener- a ``pynng`` example
1909 ---------------------------------------------------------------
1914 The following snippets illustrate -- you can test them in an environment which has
1921 .. code-block:: python
1956 except pynng.Closed: # sometimes happens when you hit Ctrl-C
1961 event = json.loads(data.decode('utf-8'))
1972 print('Press Ctrl-C to stop.')
1987 .. code-block:: python
2007 # Send the record as UTF-8 encoded JSON
2010 self.queue.send(data.encode('utf-8'))
2020 format='%(levelname)-8s %(name)10s %(process)6s %(message)s')
2038 .. code-block:: console
2053 .. code-block:: console
2068 .. code-block:: console
2071 Press Ctrl-C to stop.
2094 An example dictionary-based configuration
2095 -----------------------------------------
2097 Below is an example of a logging configuration dictionary - it's taken from
2098 …he Django project <https://docs.djangoproject.com/en/stable/topics/logging/#configuring-logging>`_.
2151 section <https://docs.djangoproject.com/en/stable/topics/logging/#configuring-logging>`_
2154 .. _cookbook-rotator-namer:
2157 --------------------------------------------------------------
2192 .. code-block:: shell-session
2198 2023-01-20 02:28:17,767 Message no. 996
2199 2023-01-20 02:28:17,767 Message no. 997
2200 2023-01-20 02:28:17,767 Message no. 998
2203 ----------------------------------------
2220 Here's the script - the docstrings and the comments hopefully explain how it
2269 # would appear - hence the "if posix" clause.
2300 # would appear - hence the "if posix" clause.
2356 … 'format': '%(asctime)s %(name)-15s %(levelname)-8s %(processName)-10s %(message)s'
2360 'format': '%(name)-15s %(levelname)-8s %(processName)-10s %(message)s'
2377 'filename': 'mplog-foo.log',
2383 'filename': 'mplog-errors.log',
2432 -----------------------------------------------------
2436 following structure: an optional pure-ASCII component, followed by a UTF-8 Byte
2437 Order Mark (BOM), followed by Unicode encoded using UTF-8. (See the
2438 :rfc:`relevant section of the specification <5424#section-6>`.)
2443 beginning of the message and hence not allowing any pure-ASCII component to
2448 want to produce :rfc:`5424`-compliant messages which include a BOM, an optional
2449 pure-ASCII sequence before it and arbitrary Unicode after it, encoded using
2450 UTF-8, then you need to do the following:
2458 The Unicode code point U+FEFF, when encoded using UTF-8, will be
2459 encoded as a UTF-8 BOM -- the byte-string ``b'\xef\xbb\xbf'``.
2463 way, it will remain unchanged after UTF-8 encoding).
2467 range, that's fine -- it will be encoded using UTF-8.
2469 The formatted message *will* be encoded using UTF-8 encoding by
2471 :rfc:`5424`-compliant messages. If you don't, logging may not complain, but your
2472 messages will not be RFC 5424-compliant, and your syslog daemon may complain.
2476 -------------------------------
2479 readily machine-parseable, there might be circumstances where you want to output
2484 which uses JSON to serialise the event in a machine-parseable manner::
2504 .. code-block:: none
2546 .. code-block:: none
2554 .. _custom-handlers:
2559 --------------------------------------------
2565 handlers in the stdlib don't offer built-in support. You can customize handler
2599 'encoding': 'utf-8',
2642 'encoding': 'utf-8',
2657 .. code-block:: shell-session
2661 2013-11-05 09:34:51,128 DEBUG mylogger A debug message
2662 $ ls -l chowntest.log
2663 -rw-r--r-- 1 pulse pulse 55 2013-11-05 09:34 chowntest.log
2667 supports :func:`dictConfig` - namely, Python 2.7, 3.2 or later. With pre-3.3
2671 In practice, the handler-creating function may be in a utility module somewhere
2686 types of file change - e.g. setting specific POSIX permission bits - in the
2690 :class:`~logging.FileHandler` - for example, one of the rotating file handlers,
2696 .. _formatting-styles:
2699 --------------------------------------------------------------
2715 because internally the logging package uses %-formatting to merge the format
2718 code will be using %-format strings.
2722 existing code could be using a given logger name and using %-formatting.
2724 For logging to work interoperably between any third-party libraries and your
2740 should be careful to support all formatting styles and allow %-formatting as
2751 There is another, perhaps simpler way that you can use {}- and $- formatting to
2753 :ref:`arbitrary-object-messages`) that when logging you can use an arbitrary
2776 Either of these can be used in place of a format string, to allow {}- or
2777 $-formatting to be used to build the actual "message" part which appears in the
2814 .. _filters-dictconfig:
2819 -------------------------------------------
2872 logging.debug('hello - noshow')
2878 .. code-block:: none
2889 in :ref:`logging-config-dict-externalobj`. For example, you could have used
2894 handlers and formatters. See :ref:`logging-config-dict-userdef` for more
2895 information on how logging supports using user-defined objects in its
2896 configuration, and see the other cookbook recipe :ref:`custom-handlers` above.
2899 .. _custom-format-exception:
2902 -------------------------------
2904 There might be times when you want to do customized exception formatting - for
2947 .. code-block:: none
2956 .. _spoken-messages:
2959 -------------------------
2963 text-to-speech (TTS) functionality available in your system, even if it doesn't have
2982 cmd = ['espeak', '-s150', '-ven+f3', msg]
3010 .. _buffered-logging:
3013 ------------------------------------------------------------
3026 - passed to another handler (the ``target`` handler) for processing. By default,
3035 parameter to ``foo`` which, if true, will log at ERROR and CRITICAL levels -
3111 .. code-block:: none
3150 .. _buffered-smtp:
3153 -------------------------------------------------
3160 send things via SMTP. (Run the downloaded script with the ``-h`` argument to see the
3163 .. code-block:: python
3182 self.setFormatter(logging.Formatter("%(asctime)s %(levelname)-5s %(message)s"))
3206 aa('host', metavar='HOST', help='SMTP server')
3207 aa('--port', '-p', type=int, default=587, help='SMTP port')
3212 aa('--subject', '-s',
3218 h = BufferingSMTPHandler(options.host, options.port, options.user,
3232 .. _utc-formatting:
3235 --------------------------------------------------
3266 'local': {
3277 'formatter': 'local',
3287 logging.warning('The local time is %s', time.asctime())
3291 .. code-block:: none
3293 2015-10-17 12:53:29,501 The local time is Sat Oct 17 13:53:29 2015
3294 2015-10-17 13:53:29,501 The local time is Sat Oct 17 13:53:29 2015
3296 showing how the time is formatted both as local time and UTC, one for each
3300 .. _context-manager:
3303 ---------------------------------------------
3342 block exit - you could do this if you don't need the handler any more.
3358 logger.debug('5. This should appear twice - once on stderr and once on stdout.')
3374 .. code-block:: shell-session
3379 5. This should appear twice - once on stderr and once on stdout.
3380 5. This should appear twice - once on stderr and once on stdout.
3386 .. code-block:: shell-session
3389 5. This should appear twice - once on stderr and once on stdout.
3393 .. code-block:: shell-session
3398 5. This should appear twice - once on stderr and once on stdout.
3408 .. _starter-template:
3411 ----------------------------------
3415 * Use a logging level based on command-line arguments
3420 Suppose we have a command-line application whose job is to stop, start or
3425 command-line argument, defaulting to ``logging.INFO``. Here's one way that
3438 parser.add_argument('--log-level', default='INFO', choices=levels)
3526 .. code-block:: shell-session
3543 .. code-block:: shell-session
3545 $ python app.py --log-level DEBUG start foo
3549 $ python app.py --log-level DEBUG stop foo bar
3553 $ python app.py --log-level DEBUG restart foo bar baz
3559 .. code-block:: shell-session
3561 $ python app.py --log-level WARNING start foo
3562 $ python app.py --log-level WARNING stop foo bar
3563 $ python app.py --log-level WARNING restart foo bar baz
3568 .. _qt-gui:
3571 --------------------
3575 cross-platform UI framework with Python bindings using :pypi:`PySide2`
3594 .. code-block:: python3
3637 # string is just a convenience - you could format a string for output any way
3655 # are named something like "Dummy-1". The function below gets the Qt name of the
3705 # * A read-only text edit window which holds formatted log messages
3738 fs = '%(asctime)s %(qThreadName)-12s %(levelname)-8s %(message)s'
3753 # Connect the non-worker slots and signals
3826 --------------------------------------
3847 tz_offset = re.compile(r'([+-]\d{2})(\d{2})$')
3871 hostname = '-'
3872 appname = self.appname or '-'
3874 msgid = '-'
3876 sdata = '-'
3879 # This should be a dict where the keys are SD-ID and the value is a
3880 # dict mapping PARAM-NAME to PARAM-VALUE (refer to the RFC for what these
3882 # There's no error checking here - it's purely for illustration, and you
3915 -------------------------------------------
3917 Sometimes, you need to interface to a third-party API which expects a file-like
3919 can do this using a class which wraps a logger with a file-like API.
3922 .. code-block:: python
3936 # doesn't actually do anything, but might be expected of a file-like
3937 # object - so optional depending on your situation
3941 # doesn't actually do anything, but might be expected of a file-like
3942 # object - so optional depending on your situation. You might want
3959 .. code-block:: text
3967 .. code-block:: python
3979 .. code-block:: pycon
3995 .. code-block:: python
4002 .. code-block:: text
4006 WARNING:demo: File "/home/runner/cookbook-loggerwriter/test.py", line 53, in <module>
4010 WARNING:demo: File "/home/runner/cookbook-loggerwriter/test.py", line 49, in main
4024 .. code-block:: python
4046 .. code-block:: text
4049 WARNING:demo: File "/home/runner/cookbook-loggerwriter/main.py", line 55, in <module>
4051 WARNING:demo: File "/home/runner/cookbook-loggerwriter/main.py", line 52, in main
4056 .. patterns-to-avoid:
4059 -----------------
4075 a copy/paste/forget-to-change error).
4095 and wasted debugging time - log entries end up in unexpected places, or are
4097 and grows in size unexpectedly despite size-based rotation being supposedly
4100 Use the techniques outlined in :ref:`multiple-processes` to circumvent such
4128 the :ref:`existing mechanisms <context-info>` for passing contextual
4131 more fine-grained than that).
4133 .. _cookbook-ref-links:
4136 ---------------
4149 :ref:`Basic Tutorial <logging-basic-tutorial>`
4151 :ref:`Advanced Tutorial <logging-advanced-tutorial>`