Skip to content

Commit

Permalink
Use local context when using async_handler ctx manager
Browse files Browse the repository at this point in the history
When reporting via concurrent coroutines, the async_handler ctx manager
stores a locally substituted async handler. This allows each coroutine
to use different handler.

This solution is based on `contextvars` module that was added in
Python 3.7+. In case of Python 3.6, it tries to backport the module
using `aiocontextvars` 3rd party package.
  • Loading branch information
bxsx committed May 21, 2021
1 parent 1f66fd9 commit 35da296
Show file tree
Hide file tree
Showing 4 changed files with 343 additions and 37 deletions.
7 changes: 6 additions & 1 deletion rollbar/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,12 @@ def send_payload(payload, access_token):
if payload is False:
return

handler = SETTINGS.get('handler')
if sys.version_info >= (3, 6):
from rollbar.lib._async import get_current_handler
handler = get_current_handler()
else:
handler = SETTINGS.get('handler')

if handler == 'twisted':
payload['data']['framework'] = 'twisted'

Expand Down
75 changes: 62 additions & 13 deletions rollbar/lib/_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,28 @@
)


if sys.version_info[:2] == (3, 6):
# Backport PEP 567
try:
import aiocontextvars
except ImportError:
log.warning(
'Python3.6 does not provide the `contextvars` module.'
' Some advanced features may not work as expected.'
' Please upgrade Python or install `aiocontextvars`.'
)

try:
from contextvars import ContextVar
except ImportError:
ContextVar = None

if ContextVar:
_ctx_handler = ContextVar('handler', default=None)
else:
_ctx_handler = None


class RollbarAsyncError(Exception):
...

Expand Down Expand Up @@ -142,21 +164,48 @@ async def try_report(
)


@contextlib.contextmanager
def async_handler():
original_handler = rollbar.SETTINGS.get('handler')
class async_handler:
def __init__(self):
self.global_handler = None
self.token = None

if original_handler not in ALLOWED_HANDLERS:
log.warning(
'Running coroutines requires async compatible handler. Switching to default async handler.'
)
rollbar.SETTINGS['handler'] = 'async'
def with_ctx_handler(self):
if self.global_handler in ALLOWED_HANDLERS:
self.token = _ctx_handler.set(self.global_handler)
else:
log.warning(
'Running coroutines requires async compatible handler. Switching to default async handler.'
)
self.token = _ctx_handler.set('async')

try:
yield rollbar.SETTINGS['handler']
finally:
if original_handler is not None:
rollbar.SETTINGS['handler'] = original_handler
return _ctx_handler.get()

def with_global_handler(self):
return self.global_handler

def __enter__(self):
self.global_handler = rollbar.SETTINGS.get('handler')

if _ctx_handler:
return self.with_ctx_handler()
else:
return self.with_global_handler()

def __exit__(self, exc_type, exc_value, traceback):
if _ctx_handler and self.token:
_ctx_handler.reset(self.token)


def get_current_handler():
if _ctx_handler is None:
return rollbar.SETTINGS.get('handler')

handler = _ctx_handler.get()

if handler is None:
return rollbar.SETTINGS.get('handler')

return handler


def call_later(coro):
Expand Down
Loading

0 comments on commit 35da296

Please sign in to comment.