Skip to content

Commit

Permalink
Add types
Browse files Browse the repository at this point in the history
  • Loading branch information
hynek committed Aug 5, 2020
1 parent 301cab9 commit 7864046
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 24 deletions.
11 changes: 11 additions & 0 deletions docs/standard-library.rst
Expand Up @@ -41,6 +41,17 @@ It behaves exactly like the generic `structlog.BoundLogger` except:
- hence causing less cryptic error messages if you get method names wrong.


``asyncio``
^^^^^^^^^^^

For ``asyncio`` applications, you may not want your whole application to block while your processor chain is formatting your log entries.
For that use case ``structlog`` comes with `structlog.stdlib.AsyncBoundLogger` that will do all processing in a thread pool executor.

This means an increased computational cost per log entry but your application will never block because of logging.

To use it, simply replace all instances of `structlog.stdlib.BoundLogger` with `AsyncBoundLogger` in all calls to `structlog.configure`.


Processors
----------

Expand Down
64 changes: 40 additions & 24 deletions src/structlog/stdlib.py
Expand Up @@ -11,8 +11,10 @@

import asyncio
import logging
import sys

from functools import partial
from typing import Any, Callable, List, Mapping, Sequence

from structlog._base import BoundLoggerBase
from structlog._frames import _find_first_app_frame_and_name, _format_stack
Expand Down Expand Up @@ -242,12 +244,12 @@ def getChild(self, suffix):
class AsyncBoundLogger:
"""
Wrap a `BoundLogger` and run its logging methods asynchronously in a thread
executor.
pool executor.
.. note::
This means more computational overhead per log call. But it also means
that the processor chain (e.g. JSON serialization) and I/O won't block
your whole application.
This means more computational overhead per log call. But it also means
that the processor chain (e.g. JSON serialization) and I/O won't block
your whole application.
Only available for Python 3.7 and later.
Expand All @@ -259,58 +261,72 @@ class AsyncBoundLogger:
_executor = None
_bound_logger_factory = BoundLogger

def __init__(self, *args, **kw):
self.sync_bl = self._bound_logger_factory(*args, **kw)
def __init__(
self,
logger: logging.Logger,
processors: List[Callable],
context: Mapping,
):
self.sync_bl = self._bound_logger_factory(
logger=logger, processors=processors, context=context
)
self._loop = asyncio.get_running_loop()

def bind(self, **new_values) -> "AsyncBoundLogger":
def bind(self, **new_values: Any) -> "AsyncBoundLogger":
self.sync_bl = self.sync_bl.bind(**new_values)
return self

def new(self, **new_values) -> "AsyncBoundLogger":
def new(self, **new_values: Any) -> "AsyncBoundLogger":
self.sync_bl = self.sync_bl.new(**new_values)
return self

def unbind(self, *keys) -> "AsyncBoundLogger":
def unbind(self, *keys: Sequence[str]) -> "AsyncBoundLogger":
self.sync_bl = self.sync_bl.try_unbind(*keys)
return self

async def debug(self, event, *args, **kw):
return await self._loop.run_in_executor(
async def debug(self, event: str, *args: Any, **kw: Any) -> None:
await self._loop.run_in_executor(
self._executor, partial(self.sync_bl.debug, event, *args, **kw)
)

async def info(self, event, *args, **kw):
return await self._loop.run_in_executor(
async def info(self, event: str, *args: Any, **kw: Any) -> None:
await self._loop.run_in_executor(
self._executor, partial(self.sync_bl.info, event, *args, **kw)
)

async def warning(self, event, *args, **kw):
return await self._loop.run_in_executor(
async def warning(self, event: str, *args: Any, **kw: Any) -> None:
await self._loop.run_in_executor(
self._executor, partial(self.sync_bl.warning, event, *args, **kw)
)

warn = warning

async def error(self, event, *args, **kw):
return await self._loop.run_in_executor(
async def error(self, event: str, *args: Any, **kw: Any) -> None:
await self._loop.run_in_executor(
self._executor, partial(self.sync_bl.error, event, *args, **kw)
)

async def critical(self, event, *args, **kw):
return await self._loop.run_in_executor(
async def critical(self, event: str, *args: Any, **kw: Any) -> None:
await self._loop.run_in_executor(
self._executor, partial(self.sync_bl.critical, event, *args, **kw)
)

fatal = critical

async def exception(self, event, *args, **kw):
return await self._loop.run_in_executor(
self._executor, partial(self.sync_bl.exception, event, *args, **kw)
async def exception(self, event: str, *args: Any, **kw: Any) -> None:
# To make `log.exception("foo") work, we have to check if the user
# passed an explicit exc_info and if not, supply our own.
ei = kw.pop("exc_info", None)
if ei is None and kw.get("exception") is None:
ei = sys.exc_info()

await self._loop.run_in_executor(
self._executor,
partial(self.sync_bl.exception, event, *args, exc_info=ei, **kw),
)

async def log(self, level, event, *args, **kw):
return await self._loop.run_in_executor(
async def log(self, level: Any, event: str, *args: Any, **kw: Any) -> None:
await self._loop.run_in_executor(
self._executor,
partial(self.sync_bl.log, level, event, *args, **kw),
)
Expand Down

0 comments on commit 7864046

Please sign in to comment.