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

Add a custom stdlib logging.Formatter for MultiErrors #344

Closed
Closed
Show file tree
Hide file tree
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 docs/source/reference-core.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1046,6 +1046,82 @@ you return a new exception object, then the new object's
``__context__`` attribute will automatically be set to the original
exception.

.. autoclass:: Formatter

Example:

To setup a logger that will properly format :exc:`MultiError`
tracebacks::

import logging

# Create logger
logger = logging.getLogger('simple_example')

# Create console handler
ch = logging.StreamHandler()

# Create formatter
formatter = trio.Formatter()

# add formatter to ch
ch.setFormatter(formatter)

# add ch to logger
logger.addHandler(ch)

If you use a ``logging.conf`` file with :func:`logging.config.fileConfig`

.. code-block:: ini

[loggers]
keys=root,trioExample

[handlers]
keys=consoleHandler

[formatters]
keys=trioFormatter

[logger_root]
handlers=consoleHandler

[logger_trioExample]
handlers=consoleHandler
qualname=trioExample
propagate=0

[handler_consoleHandler]
class=StreamHandler
formatter=trioFormatter
args=(sys.stdout,)

[formatter_trioFormatter]
class=trio.Formatter

If you use a YAML file with :func:`logging.config.dictConfig`

.. code-block:: yaml

version: 1
formatters:
trio:
class: trio.Formatter
handlers:
console:
class: logging.StreamHandler
formatter: trio
stream: ext://sys.stdout
loggers:
trioExample:
handlers: [console]
propagate: no
root:
handlers: [console]

For more details see the :ref:`Standard Library Logging Configuration
functions<logging-config-api>`.


Task-local storage
------------------
Expand Down
25 changes: 24 additions & 1 deletion trio/_core/_multierror.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
from contextlib import contextmanager

import attr
import logging

__all__ = ["MultiError", "format_exception"]
__all__ = ["MultiError", "format_exception", "Formatter"]

################################################################
# MultiError
Expand Down Expand Up @@ -407,6 +408,28 @@ def _format_exception_multi(seen, etype, value, tb, limit, chain):
return chunks


################################################################
# MultiError logging Formatter
################################################################


class Formatter(logging.Formatter):
"""
A :class:`logging.Formatter` but with special support for printing tracebacks for :class:`trio.MultiError`.
"""

def formatException(self, ei):
"""
Format and return the specified exception information as a string.

This implementation uses :func:`trio.format_exception`.
"""
s = "".join(format_exception(ei[0], ei[1], ei[2]))
if s[-1:] == "\n":
s = s[:-1]
return s


def trio_excepthook(etype, value, tb):
for chunk in format_exception(etype, value, tb):
sys.stderr.write(chunk)
Expand Down
29 changes: 24 additions & 5 deletions trio/_core/tests/test_multierror.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import logging
import pytest

from traceback import extract_tb, print_exception
Expand All @@ -9,11 +10,7 @@

from .tutil import slow

from .._multierror import (
MultiError,
format_exception,
concat_tb,
)
from .._multierror import (MultiError, format_exception, concat_tb, Formatter)


def raiser1():
Expand Down Expand Up @@ -424,6 +421,28 @@ def einfo(exc):
)


def test_trio_formatter(caplog):
formatter = Formatter()
caplog.handler.setFormatter(formatter)

exc1 = get_exc(raiser1)
exc2 = get_exc(raiser2)

m = MultiError([exc1, exc2])

message = "test test test"
try:
raise m
except MultiError as exc:
logging.getLogger().exception(message)
# Join lines together
formatted = "".join(
format_exception(type(exc), exc, exc.__traceback__)
)
assert message in caplog.text
assert formatted in caplog.text


def run_script(name, use_ipython=False):
import trio
trio_path = Path(trio.__file__).parent.parent
Expand Down
3 changes: 2 additions & 1 deletion trio/_toplevel_core_reexports.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"TrioInternalError", "RunFinishedError", "WouldBlock", "Cancelled",
"ResourceBusyError", "MultiError", "format_exception", "run",
"open_nursery", "open_cancel_scope", "current_effective_deadline",
"STATUS_IGNORED", "current_time", "current_instruments", "TaskLocal"
"STATUS_IGNORED", "current_time", "current_instruments", "TaskLocal",
"Formatter"
]

from . import _core
Expand Down