Skip to content

Commit

Permalink
Add logged kwargs to the "extra" dict automatically (Delgan#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
Delgan committed May 17, 2020
1 parent 981edf0 commit 7a23404
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 9 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
=============

- Remove the possibility to modify the severity ``no`` of levels once they have been added in order to prevent surprising behavior (`#209 <https://github.com/Delgan/loguru/issues/209>`_).
- Add better support for "structured logging" by automatically adding ``**kwargs`` to the ``extra`` dict besides using these arguments to format the message. This behavior can be disabled by using the new ``opt(capture=False)`` parameter (`#2 <https://github.com/Delgan/loguru/issues/2>`_).
- Add a new ``onerror`` optional argument to ``logger.catch()``, it should be a function which will be called when an exception occurs in order to customize error handling (`#224 <https://github.com/Delgan/loguru/issues/224>`_).
- Add a new ``exclude`` optional argument to ``logger.catch()``, is should be a type of exception to be purposefully ignored and propagated to the caller without being logged (`#248 <https://github.com/Delgan/loguru/issues/248>`_).
- Modify ``complete()`` to make it callable from non-asynchronous functions, it can thus be used if ``enqueue=True`` to make sure all messages have been processed (`#231 <https://github.com/Delgan/loguru/issues/231>`_).
Expand Down
7 changes: 4 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -287,9 +287,10 @@ Using |bind|_ you can contextualize your logger messages by modifying the `extra
::

logger.add("file.log", format="{extra[ip]} {extra[user]} {message}")
logger_ctx = logger.bind(ip="192.168.0.1", user="someone")
logger_ctx.info("Contextualize your logger easily")
logger_ctx.bind(user="someoneelse").info("Inline binding of extra attribute")
context_logger = logger.bind(ip="192.168.0.1", user="someone")
context_logger.info("Contextualize your logger easily")
context_logger.bind(user="someone_else").info("Inline binding of extra attribute")
context_logger.info("Use kwargs to add context during formatting: {user}", user="anybody")

It is possible to modify a context-local state temporarily with |contextualize|_:

Expand Down
2 changes: 1 addition & 1 deletion loguru/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

__all__ = ["logger"]

logger = _Logger(_Core(), None, 0, False, False, False, False, None, {})
logger = _Logger(_Core(), None, 0, False, False, False, False, True, None, {})

if _defaults.LOGURU_AUTOINIT and _sys.stderr:
logger.add(_sys.stderr)
Expand Down
23 changes: 19 additions & 4 deletions loguru/_logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,9 +216,9 @@ class Logger:
You should not instantiate a |Logger| by yourself, use ``from loguru import logger`` instead.
"""

def __init__(self, core, exception, depth, record, lazy, colors, raw, patcher, extra):
def __init__(self, core, exception, depth, record, lazy, colors, raw, capture, patcher, extra):
self._core = core
self._options = (exception, depth, record, lazy, colors, raw, patcher, extra)
self._options = (exception, depth, record, lazy, colors, raw, capture, patcher, extra)

def __repr__(self):
return "<loguru.logger handlers=%r>" % list(self._core.handlers.values())
Expand Down Expand Up @@ -377,6 +377,10 @@ def add(
unconditionally. In order to set a default level, the ``""`` module name should be used as
it is the parent of all modules (it does not suppress global ``level`` threshold, though).
Note that while calling a logging method, the keyword arguments (if any) are automatically
added to the ``extra`` dict for convenient contextualization (in addition to being used for
formatting).
.. _levels:
.. rubric:: The severity levels
Expand Down Expand Up @@ -1218,6 +1222,7 @@ def opt(
lazy=False,
colors=False,
raw=False,
capture=True,
depth=0,
ansi=False
):
Expand Down Expand Up @@ -1245,6 +1250,9 @@ def opt(
raw : |bool|, optional
If ``True``, the formatting of each sink will be bypassed and the message will be sent
as is.
capture : |bool|, optional
If ``False``, the ``**kwargs`` of logged message will not automatically populate
the ``extra`` dict (although they are still used for formatting).
depth : |int|, optional
Specify which stacktrace should be used to contextualize the logged message. This is
useful while using the logger from inside a wrapped function to retrieve worthwhile
Expand Down Expand Up @@ -1283,6 +1291,9 @@ def opt(
>>> logger.opt(raw=True).debug("No formatting\n")
No formatting
>>> logger.opt(capture=False).info("Displayed but not captured: {value}", value=123)
[18:11:41] Displayed but not captured: 123
>>> def wrapped():
... logger.opt(depth=1).info("Get parent context")
...
Expand All @@ -1299,7 +1310,8 @@ def opt(
DeprecationWarning,
)

return Logger(self._core, exception, depth, record, lazy, colors, raw, *self._options[-2:])
args = self._options[-2:]
return Logger(self._core, exception, depth, record, lazy, colors, raw, capture, *args)

def bind(__self, **kwargs):
"""Bind attributes to the ``extra`` dict of each logged message record.
Expand Down Expand Up @@ -1821,7 +1833,7 @@ def _log(self, level_id, static_level_no, from_decorator, options, message, args
if not core.handlers:
return

(exception, depth, record, lazy, colors, raw, patcher, extra) = options
(exception, depth, record, lazy, colors, raw, capture, patcher, extra) = options

frame = get_frame(depth + 2)

Expand Down Expand Up @@ -1911,6 +1923,9 @@ def _log(self, level_id, static_level_no, from_decorator, options, message, args
)
kwargs.update(record=log_record)

if capture and kwargs:
log_record["extra"].update(kwargs)

if colors:
if args or kwargs:
colored_message = Colorizer.prepare_message(message, args, kwargs)
Expand Down
4 changes: 3 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ def check_env_variables():
def reset_logger():
def reset():
loguru.logger.remove()
loguru.logger.__init__(loguru._logger.Core(), None, 0, False, False, False, False, None, {})
loguru.logger.__init__(
loguru._logger.Core(), None, 0, False, False, False, False, True, None, {}
)
loguru._logger.context.set({})
logging.Logger.manager.loggerDict.clear()
logging.root = logging.RootLogger("WARNING")
Expand Down
41 changes: 41 additions & 0 deletions tests/test_formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,47 @@ def test_extra_formatting(writer):
assert writer.read() == "my_test -> {'a': 10} -> level: DEBUG\n"


def test_kwargs_in_extra_dict():
extra_dicts = []
messages = []

def sink(message):
extra_dicts.append(message.record["extra"])
messages.append(str(message))

logger.add(sink, format="{message}")
logger.info("A")
logger.info("B", foo=123)
logger.bind(merge=True).info("C", other=False)
logger.bind(override=False).info("D", override=True)
logger.info("Formatted kwargs: {foobar}", foobar=123)
logger.info("Ignored args: {}", 456)
logger.info("Both: {foobar} {}", 456, foobar=123)
logger.opt(lazy=True).info("Lazy: {lazy}", lazy=lambda: 789)

assert messages == [
"A\n",
"B\n",
"C\n",
"D\n",
"Formatted kwargs: 123\n",
"Ignored args: 456\n",
"Both: 123 456\n",
"Lazy: 789\n",
]

assert extra_dicts == [
{},
{"foo": 123},
{"merge": True, "other": False},
{"override": True},
{"foobar": 123},
{},
{"foobar": 123},
{"lazy": 789},
]


def test_invalid_color_markup(writer):
with pytest.raises(ValueError):
logger.add(writer, format="<red>Not closed tag", colorize=True)
8 changes: 8 additions & 0 deletions tests/test_opt.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,14 @@ def a():
assert writer.read() == "test_depth : Test 1\na : Test 2\ntest_depth : Test 3\n"


def test_capture(writer):
logger.add(writer, format="{message} {extra}")
logger.opt(capture=False).info("No {}", 123, no=False)
logger.opt(capture=False).info("Formatted: {fmt}", fmt=456)
logger.opt(capture=False).info("Formatted bis: {} {fmt}", 123, fmt=456)
assert writer.read() == "No 123 {}\nFormatted: 456 {}\nFormatted bis: 123 456 {}\n"


def test_colors(writer):
logger.add(writer, format="<red>a</red> {message}", colorize=True)
logger.opt(colors=True).debug("<blue>b</blue>")
Expand Down

0 comments on commit 7a23404

Please sign in to comment.