sentry-python
sentry-python copied to clipboard
Feature: HTTP Headers Overrides
Add http_headers options to the sentry_sdk.init() method to allow passing extra options to the HTTPTransport. This is for adding Authorization headers to be used when authenticating with a Single Sign-On service that lives in front of a self-hosted Sentry server.
Example:
import sentry_sdk
sentry_sdk.init(
"https://[email protected]/1",
debug=True,
http_headers={
'Authorization': f'Bearer xyz'
}
)
hey @harmon, can you elaborate on your usecase for this? thanks
Sure! We have a BeyondCorp setup that requires an extra "Authorization" header on each request to the Sentry server we are hosting. (I added this to the description above: "This is used for adding Authorization headers to be used when authenticating with a Single Sign-On service that lives in front of a self-hosted Sentry server.")
I first looked into subclassing the HTTPTransport and replacing the _send_request() method with one that accomplishes what's in the PR, but that seemed quite brittle to deploy to our projects, as the implementation might change if we upgraded the sentry-sdk. For example, just two months ago large swaths of the HTTPTransport class were re-arranged internally: https://github.com/getsentry/sentry-python/pull/627/files
I figured this would be a feature improvement that could help others in the future.
Let me know if there's any other questions/concerns you have. This might be a feature I might submit to (at least) the NodeJS SDK as well. I'm not up to speed on your patch submission process in terms of the approach you take to modifying your top level APIs, so let me know if I've overstepped my bounds by not getting in touch with your team first to vet the idea. It was a small PR, so I figured this would be a good discussion forum for it.
I'd be happy to update the documentation repo as well if this get's approved.
I'm loving the latest Sentry! It's come a long way since I last used the paid service back in 2015.
I think your way of implementing this feature is good and I don't really know of alternative ways. I initially thought that maybe we can make this a ctor argument on the transport class but that actually clashes with how transports receive the client options such as DSN, so I don't really know how to make this any better.
This decision will take a while... in the meantime, if you have concerns wrt the stability of the internals of HTTPTransport I suggest for the time being to vendor the entirety of that Transport implementation. The Transport "abstract" class is stable and we commit to that.
FYI we have a recurring meeting tomorrow to go over these kinds of decisions... I have not forgotten about this!
hey @harmon I just got word back. What do you think about:
- adding a new option called
transport_optionsto the SDK, a dict of kwargs that will be forwarded to the transport constructor - making this option a part of the transport's constructor?
init(
transport_options={"headers": ...}
)
...
class HTTPTransport:
def __init__(self, client_options, headers=None):
...
@untitaker I think that's a great idea. I'll see if I can refactor this PR towards that goal. Hopefully by early next week I can have this updated.
This pull request has gone three weeks without activity. In another week, I will close it.
But! If you comment or otherwise update it, I will reset the clock, and if you label it Status: Backlog or Status: In Progress, I will leave it alone ... forever!
"A weed is but an unloved flower." ― Ella Wheeler Wilcox 🥀
Hey @harmon ! Sorry that this PR of yours somehow has been neglected.
It seems the refactoring towards transport_options never happened. How have you solved this in the mean time?
Hi @antonpirker , yes, sorry, I never got around to doing a proper PR for this! Here's how I wrote an adapter for our code:
custom_sentry_sdk.py
"""
Example usage:
import custom_sentry_sdk
custome_sentry_sdk.init_sentry_error_logging(
dsn="",
debug=True,
custom_auth_token="ABC123"
)
"""
class CustomHttpTransport(sentry_sdk.transport.HttpTransport):
custom_auth_token = None
def _send_request(self, body, headers, endpoint_type="store"):
headers.update(
{
"Authorization": f"Bearer {self.custom_auth_token}",
}
)
super()._send_request(body, headers)
def init_sentry_error_logging(**kwargs):
"""
Loads Sentry and sets up logging hooks for errors and exceptions to be sent to
the host defined by the env var SENTRY_DSN.
"""
try:
LOG = logging.getLogger(__name__)
if os.getenv("SENTRY_DSN", None):
kwargs.update(
{
"dsn": os.getenv("SENTRY_DSN"),
"environment": os.getenv("SENTRY_ENVIRONMENT", None),
"server_name": os.getenv("SENTRY_SERVER_NAME", None),
}
)
if "SENTRY_DEBUG_LEVEL" in os.environ:
import http.client
http.client.HTTPConnection.debuglevel = int(os.getenv("SENTRY_DEBUG_LEVEL", "5").strip())
if "SENTRY_AUTH_TOKEN" in os.environ:
CustomHttpTransport.custom_auth_token = os.getenv("SENTRY_AUTH_TOKEN")
kwargs.update({"transport": CustomHttpTransport})
LOG.info(f"Sentry error logging setting up... {kwargs}")
return sentry_sdk.init(**kwargs)
else:
LOG.info("Sentry error logging not loaded, no SENTRY_DSN env var found")
except Exception:
LOG.warning("Sentry error logging package could not be loaded")
closing this since custom transports solve this and there haven't been more requests to mainline this