Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[docs] Update logging cookbook with recipe for using a logger like an output… (GH-97730) #97730

Merged
merged 1 commit into from
Oct 2, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions Doc/howto/logging-cookbook.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down