Skip to content
Snippets Groups Projects
Commit a41f2cf0 authored by Renaud Boyer's avatar Renaud Boyer Committed by Renaud Boyer
Browse files

sentry: Implement performance tracing

parent c6b8290d
No related branches found
Tags v3.7.0
1 merge request!404sentry: Implement performance tracing
Pipeline #12649 passed
......@@ -11,6 +11,7 @@ and redefining functions and variables they want.
May be imported by gunicorn using
`--config 'python:swh.core.api.gunicorn_config'`."""
from typing import Optional
from ..sentry import init_sentry
......@@ -24,6 +25,7 @@ def post_fork(
sentry_integrations=None,
extra_sentry_kwargs={},
disable_logging_events=True,
traces_sample_rate: Optional[float] = None,
):
# Initializes sentry as soon as possible in gunicorn's worker processes.
......@@ -38,4 +40,5 @@ def post_fork(
integrations=sentry_integrations,
extra_kwargs=extra_sentry_kwargs,
disable_logging_events=disable_logging_events,
traces_sample_rate=traces_sample_rate,
)
......@@ -29,6 +29,7 @@ def test_post_fork_default(mocker):
debug=False,
release="0.0.0",
environment=None,
traces_sample_rate=None,
)
......@@ -53,6 +54,7 @@ def test_post_fork_with_dsn_env(mocker):
debug=False,
release=None,
environment=None,
traces_sample_rate=None,
)
......@@ -87,6 +89,7 @@ def test_post_fork_with_package_env(mocker):
debug=False,
release="swh.core@" + version,
environment="tests",
traces_sample_rate=None,
)
......@@ -114,6 +117,7 @@ def test_post_fork_debug(mocker):
debug=True,
release=None,
environment=None,
traces_sample_rate=None,
)
......@@ -134,6 +138,7 @@ def test_post_fork_no_flask(mocker):
debug=False,
release=None,
environment=None,
traces_sample_rate=None,
)
......@@ -152,6 +157,7 @@ def test_post_fork_override_logging_events_envvar(mocker):
debug=False,
release=None,
environment=None,
traces_sample_rate=None,
)
......@@ -178,4 +184,58 @@ def test_post_fork_extras(mocker):
bar="baz",
release=None,
environment=None,
traces_sample_rate=None,
)
def test_post_fork_traces_sample_rate(mocker):
flask_integration = object() # unique object to check for equality
logging_integration = object() # unique object to check for equality
mocker.patch(
"sentry_sdk.integrations.flask.FlaskIntegration", new=lambda: flask_integration
)
mocker.patch(
"sentry_sdk.integrations.logging.LoggingIntegration",
new=lambda event_level: logging_integration,
)
sentry_sdk_init = mocker.patch("sentry_sdk.init")
mocker.patch.dict(os.environ, {"SWH_SENTRY_DSN": "test_dsn"})
gunicorn_config.post_fork(None, None, traces_sample_rate=1.0)
sentry_sdk_init.assert_called_once_with(
dsn="test_dsn",
integrations=[flask_integration, logging_integration],
debug=False,
release=None,
environment=None,
traces_sample_rate=1.0,
)
def test_post_fork_override_traces_sample_rate_envvar(mocker):
flask_integration = object() # unique object to check for equality
logging_integration = object() # unique object to check for equality
mocker.patch(
"sentry_sdk.integrations.flask.FlaskIntegration", new=lambda: flask_integration
)
mocker.patch(
"sentry_sdk.integrations.logging.LoggingIntegration",
new=lambda event_level: logging_integration,
)
sentry_sdk_init = mocker.patch("sentry_sdk.init")
mocker.patch.dict(
os.environ,
{"SWH_SENTRY_DSN": "test_dsn", "SWH_SENTRY_TRACES_SAMPLE_RATE": "0.999"},
)
gunicorn_config.post_fork(None, None)
sentry_sdk_init.assert_called_once_with(
dsn="test_dsn",
integrations=[flask_integration, logging_integration],
debug=False,
release=None,
environment=None,
traces_sample_rate=0.999,
)
......@@ -46,6 +46,37 @@ def override_with_bool_envvar(envvar: str, default: bool) -> bool:
return default
def override_with_float_envvar(
envvar: str, default: Optional[float]
) -> Optional[float]:
"""Override `default` with the environment variable `envvar` casted as a float.
`default` is returned if the environment variable `envvar` is missing or if
we're not able to cast it to a float.
Args:
envvar: the name of the environment variable
default: default value
Returns:
A float or `default`
"""
envvalue = os.environ.get(envvar)
if envvalue is None:
return default
try:
return float(envvalue)
except ValueError:
logger.warning(
"Could not interpret environment variable %s=%r as float, "
"using default value %s",
envvar,
envvalue,
default,
)
return default
def init_sentry(
sentry_dsn: Optional[str] = None,
*,
......@@ -54,10 +85,11 @@ def init_sentry(
debug: bool = False,
disable_logging_events: bool = False,
integrations: Optional[List] = None,
traces_sample_rate: Optional[float] = None,
extra_kwargs: Optional[Dict] = None,
deferred_init: bool = False,
):
"""Configure the sentry integration
) -> None:
"""Configure the sentry integration.
Args:
sentry_dsn: Sentry DSN; where sentry report will be sent. Overridden by
......@@ -70,10 +102,12 @@ def init_sentry(
log entries as Sentry events. Overridden by
:envvar:`SWH_SENTRY_DISABLE_LOGGING_EVENTS`
integrations: list of dedicated Sentry integrations to include
traces_sample_rate: a number between 0 and 1, controlling the percentage chance a
given transaction will be sent to Sentry. Overridden by
:envvar:`SWH_SENTRY_TRACES_SAMPLE_RATE`
extra_kwargs: dict of additional parameters passed to :func:`sentry_sdk.init`
deferred_init: indicates that sentry will be properly initialized in subsequent
calls and that no warnings about missing DSN should be logged
"""
if integrations is None:
integrations = []
......@@ -101,10 +135,17 @@ def init_sentry(
integrations.append(LoggingIntegration(event_level=None))
# to completely disable tracing `traces_sample_rate` should be set to None instead
# of 0.0
traces_sample_rate = override_with_float_envvar(
"SWH_SENTRY_TRACES_SAMPLE_RATE", traces_sample_rate
)
sentry_sdk.init(
release=get_sentry_release(main_package, sentry_dsn),
environment=environment,
dsn=sentry_dsn,
traces_sample_rate=traces_sample_rate,
integrations=integrations,
debug=debug,
**extra_kwargs,
......
......@@ -124,6 +124,7 @@ def test_command(swhmain, mocker):
integrations=[],
release="0.0.0",
environment=None,
traces_sample_rate=None,
)
assert_result(result)
assert result.output.strip() == "Hello SWH!"
......@@ -185,6 +186,7 @@ def test_sentry(swhmain, mocker):
integrations=[],
release=None,
environment=None,
traces_sample_rate=None,
)
......@@ -207,6 +209,7 @@ def test_sentry_debug(swhmain, mocker):
integrations=[],
release=None,
environment=None,
traces_sample_rate=None,
)
......@@ -231,6 +234,7 @@ def test_sentry_env(swhmain, mocker):
integrations=[],
release=None,
environment=None,
traces_sample_rate=None,
)
......@@ -259,6 +263,7 @@ def test_sentry_env_main_package(swhmain, mocker):
integrations=[],
release="swh.core@" + version,
environment="tests",
traces_sample_rate=None,
)
......
......@@ -9,7 +9,11 @@ import pytest
import sentry_sdk
from sentry_sdk import capture_exception, capture_message, set_tag
from swh.core.sentry import init_sentry, override_with_bool_envvar
from swh.core.sentry import (
init_sentry,
override_with_bool_envvar,
override_with_float_envvar,
)
SENTRY_DSN = "https://user@example.org/1234"
......@@ -48,6 +52,31 @@ def test_override_with_bool_envvar_logging(monkeypatch, caplog):
assert caplog.records[0].levelname == "WARNING"
@pytest.mark.parametrize(
"envvalue,retval",
(("1.0", 1.0), ("0.0", 0.0), ("0", 0.0), ("a", None)),
)
def test_override_with_float_envvar(monkeypatch, envvalue: str, retval: bool):
envvar = "OVERRIDE_WITH_FLOAT_ENVVAR"
monkeypatch.setenv(envvar, envvalue)
assert override_with_float_envvar(envvar, None) == retval
def test_override_with_float_envvar_logging(monkeypatch, caplog):
envvar = "OVERRIDE_WITH_FLOAT_ENVVAR"
monkeypatch.setenv(envvar, "not a float env value")
for default in (None, 1.0):
caplog.clear()
assert override_with_float_envvar(envvar, default) == default
assert len(caplog.records) == 1
assert (
"OVERRIDE_WITH_FLOAT_ENVVAR='not a float env value'"
in caplog.records[0].getMessage()
)
assert f"using default value {default}" in caplog.records[0].getMessage()
assert caplog.records[0].levelname == "WARNING"
def test_sentry():
reports = []
init_sentry(SENTRY_DSN, extra_kwargs={"transport": reports.append})
......@@ -160,3 +189,14 @@ def test_sentry_deferred_init(caplog, deferred_init):
)
else:
assert not caplog.records
@pytest.mark.parametrize("traces_sample_rate", [1.0, 0.0, None])
def test_sentry_traces_sample_rate(caplog, traces_sample_rate):
init_sentry(None, traces_sample_rate=traces_sample_rate)
client = sentry_sdk.get_client()
if traces_sample_rate is not None:
assert client.options["traces_sample_rate"] == traces_sample_rate
else:
assert client.options["enable_tracing"] is None
assert client.options["traces_sample_rate"] is None
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment