Skip to content

Commit

Permalink
reorganize docs
Browse files Browse the repository at this point in the history
  • Loading branch information
fajpunk committed Jan 21, 2025
1 parent 0874d62 commit cbd2812
Showing 1 changed file with 71 additions and 63 deletions.
134 changes: 71 additions & 63 deletions docs/user-guide/sentry.rst
Original file line number Diff line number Diff line change
@@ -1,94 +1,95 @@
##################
Helpers for Sentry
Integrating Sentry
##################

`Sentry`_ is an exception reporting and tracing observability service.
It has great out-of-the-box integrations with many of the Python libaries that we use, including `FastAPI`_, `SqlAlchemy`_, and `arq`_.
It has great out-of-the-box integrations with many of the Python libaries that we use, including `FastAPI`_, `SQLAlchemy`_, and `arq`_.
Most apps can get a lot of value out of Sentry by doing nothing other than calling the `init function <https://docs.sentry.io/platforms/python/#configure>`_ early in their app and using some of the helpers described here.

``before_send`` handlers
========================

In the init function, you can specify a function to run before an event is sent to Sentry.
Safir has some useful functions you can use as ``before_send`` handlers.

before_send_handler
-------------------

.. note::
Instrumenting your application
==============================

You should probably use this handler in all of your safir apps.
The simplest instrumentation involves calling ``sentry_sdk.init`` as early as possible in your app's ``main.py`` file.
You will need to provide, at a minimum, a Sentry DSN associated with your app's Sentry project, and an environment with which to tag Sentry events.
You probably also want to be able to configure the `traces_sample_rate <https://docs.sentry.io/platforms/python/configuration/options/#traces-sample-rate>`_ so you can easily enable or disable tracing from Phalanx without changing your app's code.
You can also provide other `configuration options <https://docs.sentry.io/platforms/python/configuration/options/>`_.
The ``sentry_sdk`` will automatically get the DSN and environment from the ``SENTRY_DSN`` and ``SENTRY_ENVIRONMENT`` environment vars, but you can also provide them explicitly via your app's config.
Unless you want to explicitly instrument app config initialization, you should probably provide these values with the rest of your app's config to keep all config in the same place.
You also almost surely want to use the `~safir.sentry.before_send_handler` handler, which adds the environment to the Sentry fingerprint, and handles :ref:`sentry-exception-types` appropriately.

This adds the environment to the Sentry fingerprint, and handles :ref:`sentry-exception-types` appropriately.
It is a combination of the :ref:`fingerprint-env-handler` and :ref:`sentry-exception-handler` functionality.
Your config file may look something like this:

.. code-block:: python
from safir.sentry import before_send_handler, SentryException
sentry_sdk.init(before_send=before_send_handler)
class SomeError(SentryException):
def __init__(
self, message: str, some_tag: str, some_context: dict[str, Any]
) -> None:
super.__init__(message)
self.tags["some_tag"] = some_tag
self.contexts["some_context"] = some_context
raise SomeError(
"Some error!", some_tag="some_value", some_context={"foo": "bar"}
)
This will send an event to sentry that will properly trigger an event for every environment, and will have ``some_tag`` and ``some_context`` attached.

.. _fingerprint-env-handler:

fingerprint_env_handler
-----------------------

Alerts that are set to fire when an issue is first created, or regressed, will do so once an event that matches that issue occurs in `ANY environment <https://github.com/getsentry/sentry/issues/21612>`_, not every environment.
This means that if an the first event from an issue comes from a development environment, the alert will be triggered, and will not trigger again when the first event comes from a production environment.
To have such an alert trigger per-environment, we need to `change the fingerprint of the event <https://github.com/getsentry/sentry/issues/64354#issuecomment-1927839632>`_ to include the environment, which is what the `~safir.sentry.fingerprint_env_handler` does.

.. _sentry-exception-handler:

sentry_exception_handler
------------------------

In order to get the tags and contexts from the :ref:`sentry-exception-types` on events, you have to use this handler.

:caption: src/myapp/config.py
class Configuration(BaseSettings):
environment_name: Annotated[
str,
Field(
alias="MYAPP_ENVIRONMENT_NAME",
description=(
"The Phalanx name of the Rubin Science Platform environment."
),
),
]
sentry_dsn: Annotated[
str | None,
Field(
alias="MYAPP_SENTRY_DSN",
description="DSN for sending events to Sentry.",
),
] = None
sentry_traces_sample_rate: Annotated[
float,
Field(
alias="MYAPP_SENTRY_TRACES_SAMPLE_RATE",
description=(
"The percentage of transactions to send to Sentry, expressed "
"as a float between 0 and 1. 0 means send no traces, 1 means "
"send every trace."
),
ge=0,
le=1,
),
] = 0
config = Configuration()
And your ``main.py`` might look like this:

.. code-block:: python
:caption: src/myapp/main.py
from safir.sentry import sentry_exception_handler, SentryException
import sentry_sdk
from safir.sentry import before_send_handler
from .config import config
sentry_sdk.init(before_send=sentry_exception_handler)
class SomeError(SentryException): ...
sentry_sdk.init(
dsn=config.sentry_dsn,
environment=config.sentry_environment,
traces_sample_rate=config.sentry_traces_sample_rate,
before_send=before_send_handler,
)
exc = SomeError("Some error!")
exc.tags["some tag"] = "some value"
exc.contexts["some context"] = {"foo": "bar"}
@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncIterator: ...
raise exc
Without the `~safir.sentry.sentry_exception_handler` passed to ``before_send``, ``some tag`` and ``some context`` would not show up the error event sent to sentry from ``raise exc``.
app = FastAPI(title="My App", lifespan=lifespan, ...)
.. _sentry-exception-types:

Special Sentry exception types
==============================

Similar to :ref:`slack-exceptions`, you can use `~safir.sentry.SentryException` to create custom exceptions that will send specific Sentry tags and contexts with any events that arise from them.
You need to use the :ref:`sentry-exception-handler` ``before_send`` hook for this to work.
You need to use the `~safir.sentry.before_send_handler` handler for this to work.

SentryException
---------------
Expand All @@ -97,6 +98,12 @@ You can define custom exceptions that inherit from `~safir.sentry.SentryExceptio
These exceptions will have ``tags`` and ``contexts`` attributes.
If Sentry sends an event that arises from reporting one of these exceptions, the event will have those tags and contexts attached to it.

.. note::

`Tags <https://docs.sentry.io/platforms/python/enriching-events/tags/>`_ are short key-value pairs that are indexed by Sentry. Use tags for small values that you would like to search by and aggregate over when analyzing multiple Sentry events in the Sentry UI.
`Contexts <https://docs.sentry.io/platforms/python/enriching-events/context/>`_ are for more detailed information related to single events. You can not search by context values, but you can store more data in them.
You should use a tag for something like ``"query_type": "sync"`` and a context for something like ``"query_info": {"query_text": text}``

.. code-block:: python
from safir.sentry import sentry_exception_handler, SentryException
Expand Down Expand Up @@ -143,6 +150,7 @@ Similar to :ref:`slack-web-exceptions`, you can use `~safir.sentry.SentryWebExce
raise FooServiceError.from_exception(e) from e
This will set an ``httpx_request_info`` context with the body, and these tags if the info is available:

* ``httpx_request_method``
* ``gafaelfaw_user``
* ``httpx_request_url``
Expand Down

0 comments on commit cbd2812

Please sign in to comment.