Skip to content

Commit

Permalink
stdlib: add callsite collection support for async methods (#618)
Browse files Browse the repository at this point in the history
* stdlib: add callsite collection support for async methods

fixes #615

* Put change notices where people notice them
  • Loading branch information
hynek authored May 13, 2024
1 parent 0a93f82 commit 1475ed9
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 11 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ You can find our backwards-compatibility policy [here](https://github.com/hynek/
- The `structlog.processors.CallsiteParameterAdder` can now be pickled.
[#603](https://github.com/hynek/structlog/pull/603)

- `structlog.processors.CallsiteParameterAdder` now also works with `structlog.stdlib.BoundLogger`'s non-standard async methods (`ainfo()`, and so forth)
[#618](https://github.com/hynek/structlog/pull/618)


### Changed

Expand Down
20 changes: 14 additions & 6 deletions src/structlog/stdlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ class BoundLogger(BoundLoggerBase):
.. versionadded:: 23.1.0
Async variants `alog()`, `adebug()`, `ainfo()`, and so forth.
.. versionchanged:: 24.2.0
Callsite parameters are now also collected by
`structlog.processors.CallsiteParameterAdder` for async log methods.
"""

_logger: logging.Logger
Expand Down Expand Up @@ -393,12 +397,16 @@ async def _dispatch_to_sync(
"""
Merge contextvars and log using the sync logger in a thread pool.
"""
scs_token = _ASYNC_CALLING_STACK.set(sys._getframe().f_back.f_back) # type: ignore[union-attr, arg-type, unused-ignore]
ctx = contextvars.copy_context()

await asyncio.get_running_loop().run_in_executor(
None,
lambda: ctx.run(lambda: meth(event, *args, **kw)),
)
try:
await asyncio.get_running_loop().run_in_executor(
None,
lambda: ctx.run(lambda: meth(event, *args, **kw)),
)
finally:
_ASYNC_CALLING_STACK.reset(scs_token)

async def adebug(self, event: str, *args: Any, **kw: Any) -> None:
"""
Expand Down Expand Up @@ -499,6 +507,8 @@ class AsyncBoundLogger:
.. versionchanged:: 20.2.0 fix _dispatch_to_sync contextvars usage
.. deprecated:: 23.1.0
Use the regular `BoundLogger` with its a-prefixed methods instead.
.. versionchanged:: 23.3.0
Callsite parameters are now also collected for async log methods.
"""

__slots__ = ("sync_bl", "_loop")
Expand Down Expand Up @@ -594,8 +604,6 @@ async def _dispatch_to_sync(
) -> None:
"""
Merge contextvars and log using the sync logger in a thread pool.
.. versionchanged:: 23.3.0
Callsite parameters are now also collected under asyncio.
"""
scs_token = _ASYNC_CALLING_STACK.set(sys._getframe().f_back.f_back) # type: ignore[union-attr, arg-type, unused-ignore]
ctx = contextvars.copy_context()
Expand Down
17 changes: 12 additions & 5 deletions tests/processors/test_processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,28 +298,35 @@ def test_all_parameters(self) -> None:
assert self.parameter_strings == self.get_callsite_parameters().keys()

@pytest.mark.asyncio()
async def test_async(self) -> None:
@pytest.mark.parametrize(
("wrapper_class", "method_name"),
[
(structlog.stdlib.BoundLogger, "ainfo"),
(structlog.stdlib.AsyncBoundLogger, "info"),
],
)
async def test_async(self, wrapper_class, method_name) -> None:
"""
Callsite information for async invocations are correct.
"""
string_io = StringIO()

class StingIOLogger(structlog.PrintLogger):
class StringIOLogger(structlog.PrintLogger):
def __init__(self):
super().__init__(file=string_io)

processor = self.make_processor(None, ["concurrent", "threading"])
structlog.configure(
processors=[processor, JSONRenderer()],
logger_factory=StingIOLogger,
wrapper_class=structlog.stdlib.AsyncBoundLogger,
logger_factory=StringIOLogger,
wrapper_class=wrapper_class,
cache_logger_on_first_use=True,
)

logger = structlog.stdlib.get_logger()

callsite_params = self.get_callsite_parameters()
await logger.info("baz")
await getattr(logger, method_name)("baz")
logger_params = json.loads(string_io.getvalue())

# These are different when running under async
Expand Down

0 comments on commit 1475ed9

Please sign in to comment.