From 011260bca044f0186c230e7a341577c2e6708001 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Sun, 2 Oct 2022 09:44:42 -0700 Subject: [PATCH] =?UTF-8?q?[3.11]=20[docs]=20Update=20logging=20cookbook?= =?UTF-8?q?=20with=20recipe=20for=20using=20a=20logger=20like=20an=20outpu?= =?UTF-8?q?t=E2=80=A6=20(GH-97730)=20(GH-97735)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Doc/howto/logging-cookbook.rst | 76 ++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/Doc/howto/logging-cookbook.rst b/Doc/howto/logging-cookbook.rst index 5b079744df12ed..ff7ba0789608ff 100644 --- a/Doc/howto/logging-cookbook.rst +++ b/Doc/howto/logging-cookbook.rst @@ -3428,6 +3428,82 @@ the above handler, you'd pass structured data using something like this:: i = 1 logger.debug('Message %d', i, extra=extra) +How to treat a logger like an output stream +------------------------------------------- + +Sometimes, you need to interface to a third-party API which expects a file-like +object to write to, but you want to direct the API's output to a logger. You +can do this using a class which wraps a logger with a file-like API. +Here's a short script illustrating such a class: + +.. code-block:: python + + import logging + + class LoggerWriter: + def __init__(self, logger, level): + self.logger = logger + self.level = level + + def write(self, message): + if message != '\n': # avoid printing bare newlines, if you like + self.logger.log(self.level, message) + + def flush(self): + # doesn't actually do anything, but might be expected of a file-like + # object - so optional depending on your situation + pass + + def close(self): + # doesn't actually do anything, but might be expected of a file-like + # object - so optional depending on your situation. You might want + # to set a flag so that later calls to write raise an exception + pass + + def main(): + logging.basicConfig(level=logging.DEBUG) + logger = logging.getLogger('demo') + info_fp = LoggerWriter(logger, logging.INFO) + debug_fp = LoggerWriter(logger, logging.DEBUG) + print('An INFO message', file=info_fp) + print('A DEBUG message', file=debug_fp) + + if __name__ == "__main__": + main() + +When this script is run, it prints + +.. code-block:: text + + INFO:demo:An INFO message + DEBUG:demo:A DEBUG message + +You could also use ``LoggerWriter`` to redirect ``sys.stdout`` and +``sys.stderr`` by doing something like this: + +.. code-block:: python + + import sys + + sys.stdout = LoggerWriter(logger, logging.INFO) + sys.stderr = LoggerWriter(logger, logging.WARNING) + +You should do this *after* configuring logging for your needs. In the above +example, the :func:`~logging.basicConfig` call does this (using the +``sys.stderr`` value *before* it is overwritten by a ``LoggerWriter`` +instance). Then, you'd get this kind of result: + +.. code-block:: pycon + + >>> print('Foo') + INFO:demo:Foo + >>> print('Bar', file=sys.stderr) + WARNING:demo:Bar + >>> + +Of course, these above examples show output according to the format used by +:func:`~logging.basicConfig`, but you can use a different formatter when you +configure logging. .. patterns-to-avoid: