-
Notifications
You must be signed in to change notification settings - Fork 541
Scoped sentry_sdk.init for per-package logging #610
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
Comments
Instead of writing _library_client = sentry_sdk.Hub(sentry_sdk.Client(...)) Then you can wrap each codeblock that should go to that different client with: with _library_client:
... All the integrations work too, just provide them as kwargs to This can also be used to disable the SDK on a per-codeblock level like this: with sentry_sdk.Hub():
... |
Thanks for the fast response @untitaker! That seems like it would work nicely. The only thing that seems a bit inconvenient is that we'd have to wrap every function using the with statement you proposed. Is there an easier way to wrap the entire package's functionality with its |
I mean, you could use a decorator instead, but yes, only this way exists. You will likely also have to write a lot of try-catch blocks and call I think you can build some automatic dispatching based on package name on top of that, but we haven't attempted building such a thing, and I see a few challenges in doing this in a way that is generally useful. |
All right, thank you. I think we'll need to investigate a bit more to see how we can make this practical. To make sure I understand:
This would be true even for a global
I'm thinking we might try to turn the with statement into a decorator, and perhaps even apply it automatically across the package's functions and classes. Do you see any challenges or road bumps with this approach? |
I think it will have to happen more often for library instrumentation. For example, an error that bubbles like this will end up being reported with the client of package A (the one that was initialized with # package A
def foo():
return bar()
# package B
def bar():
with _library_client:
1/0 Since the exception will just bubble past the context manager.
That should be fine. |
Closing this as the question has been answered. |
@untitaker See below for an implementation of the ideas discussed above. By applying As you had predicted, I found that it is indeed necessary to capture exceptions to prevent these from "bubbling past the context manager". While capturing the exception in the wrapper "solves" the logging of uncaught exceptions, I wonder if other Sentry integrations will work as intended without special treatment? Any further suggestions on how to improve scoped Sentry logging are very welcome! import ast
import inspect
from types import ModuleType
from typing import Any, Callable, Dict, List, Optional
import sentry_sdk
import wrapt
# https://docs.sentry.io/error-reporting/configuration/?platform=python#common-options
sentry_client = sentry_sdk.Hub(sentry_sdk.Client(dsn="..."))
@wrapt.decorator
def log_function_with_sentry(wrapped: Callable[..., Any], instance: Any, args: List[Any], kwargs: Dict[str, Any]) -> Any:
"""Attaches Sentry integrations to a function."""
with sentry_client:
try:
return wrapped(*args, **kwargs)
except Exception as e:
sentry_sdk.capture_exception(e)
raise
def log_module_with_sentry(module: Optional[ModuleType] = None) -> None:
"""Attaches Sentry integrations to a module."""
module = module or inspect.getmodule(inspect.stack()[1][0])
for node in ast.parse(inspect.getsource(module)).body: # type: ignore
if isinstance(node, ast.ClassDef):
cls = getattr(module, node.name)
for key, value in cls.__dict__.items():
if callable(value) or isinstance(value, (classmethod, staticmethod)):
setattr(cls, key, log_function_with_sentry(value))
elif isinstance(node, (ast.AsyncFunctionDef, ast.FunctionDef)):
setattr(module, node.name, log_function_with_sentry(getattr(module, node.name))) |
Another idea could be to only use the The only downside would be that a package's Hub/Client might log a parent package's exceptions if the parent package forgets to wrap it's functionality, but that's probably a better alternative than missing the exception outright. |
That seems like a fine approach, and I think it's as close to a generic impl as you can get. I still think that this makes too many assumptions about how the library API works (assumptions which might be common sense), so I think libraries should vendor this logic and adapt it to their own usecases. For example this somewhat falls apart with underscore-prefixed types which would normally not constitute an API boundary, or when passing callbacks into a library method/function, or when a library method/function returns another function. A bug that I would fix is that your wrapper for AsyncFunctionDef should likely await for the return value within the try-except. Otherwise you are only catching exceptions for the timeframe where the future/task/promise is created, not when it is being executed. |
Reviving this thread again, since we have a similar requirement! I have this python script as follows: import sentry_sdk
first_client = sentry_sdk.init(......)
second_client = sentry_sdk.Hub(sentry_sdk.Client(.........))
with second_client:
l = {}
try:
x = l['error']
except Exception as e:
sentry_sdk.capture_exception(e) As per @untitaker's comment this should emit to the second project right? But irrespective of whether I catch it or not, it always goes to the first client. Any ideas how to get around this? This was a nice way of wrapping code against a certain project, right now we have to import the first client from some other code and switch it at runtime, which can lead to erroneous code. |
@SanketDG I just tried out import sentry_sdk
DSN1 = "<dsn1>"
DSN2 = "<dsn2"
first_client = sentry_sdk.init(DSN1, debug=True)
second_client = sentry_sdk.Hub(sentry_sdk.Client(DSN2, debug=True))
try:
1 / 0
except Exception as e:
sentry_sdk.capture_exception(e)
with second_client:
l = {}
try:
x = l['error']
except Exception as e:
sentry_sdk.capture_exception(e) and I successfully get the errors in sentry in different projects. |
If I remove this part of your code: try:
1 / 0
except Exception as e:
sentry_sdk.capture_exception(e) It does not seem to work. Any idea why both projects need to generate errors? Debug output:
|
@SanketDG sorry for the late reply here, you might have to call (also did import sentry_sdk
DSN1 = "<dsn1>"
DSN2 = "<dsn2>"
first_hub = sentry_sdk.init(DSN1, debug=True)
second_hub = sentry_sdk.Hub(sentry_sdk.Client(DSN2, debug=True))
with second_hub:
l = {}
try:
x = l['adasdasdasdas']
except Exception as e:
sentry_sdk.capture_exception(e)
second_hub.flush() In general, some of the integrations logic might not work with such multi-hub setups, please report if something doesn't work and we'll try to figure it out together. These are sort of edge cases and the SDK is definitely not built with such scenarios in mind. |
It looks like
sentry_sdk.init
integrations are generally attached in a global way. For example, theLoggingIntegration
wrapslogging.Logger.callHandlers
[1], andExcepthookIntegration
wrapssys.excepthook
[2].Let's say you have multiple Sentry projects (Project A, Project B, Project C) that each correspond to one Python package (Package A, Package B, Package C). All packages could be used independently, so it makes sense to instrument all of them individually with
sentry_sdk.init
. However, if Package C depends on Package A and B, then it would seem that Project C will receive all error notifications even though some of the errors might actually be caused by Packages A and B. Ideally, I'd like to be able to do a scopedsentry_sdk.init
on the three packages so that errors from different packages are routed to their corresponding Sentry project, regardless of their interdependencies.Is such a use case supported, or are there any plans to support this use case in the future?
Potentially relevant issue: #198.
[1] https://github.com/getsentry/sentry-python/blob/master/sentry_sdk/integrations/logging.py#L72
[2] https://github.com/getsentry/sentry-python/blob/master/sentry_sdk/integrations/excepthook.py#L46
The text was updated successfully, but these errors were encountered: