Skip to content

Commit 77959d8

Browse files
committed
feat: add the ignore user_agent ability
1 parent ce900be commit 77959d8

File tree

12 files changed

+297
-11
lines changed

12 files changed

+297
-11
lines changed

controller/sentry/admin.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ class AppAdmin(
222222
"fields": (
223223
"wsgi_collect_metrics",
224224
"wsgi_ignore_path",
225+
"wsgi_ignore_user_agent",
225226
"wsgi_metrics",
226227
),
227228
},

controller/sentry/metrics/celery.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,35 @@
11
"""Celery Metrics."""
22
from collections import Counter
3-
from typing import TYPE_CHECKING
3+
from typing import TYPE_CHECKING, Union
4+
5+
from controller.sentry.utils import depth
46

57
if TYPE_CHECKING: # pragma: no cover
68
from controller.sentry.models import App
79

810

9-
def celery_merger(app: "App", new: dict[str, int]) -> None:
11+
def celery_merger(app: "App", new: Union[dict[str, int], dict[str, dict[str, int]]]) -> None:
1012
"""celery_merger function is used to merge :attr:`Celery <controller.sentry.choices.MetricType.CELERY>` metrics.
1113
1214
Args:
1315
app (App): The app associated to the metric
1416
new (dict[str, int]): The new metric dict
1517
"""
16-
app.celery_metrics = dict(Counter(app.celery_metrics) + Counter(new))
18+
# Code for Migration
19+
if depth(app.celery_metrics) == 1:
20+
app.celery_metrics = {"task": app.celery_metrics}
21+
22+
if depth(new) == 1:
23+
new = {"task": new}
24+
# End of migration code
25+
26+
if app.celery_metrics is None:
27+
app.celery_metrics = {}
28+
29+
tmp = {}
30+
for key in set(list(app.celery_metrics.keys()) + list(new.keys())):
31+
old_value = app.celery_metrics.get(key)
32+
new_value = new.get(key)
33+
tmp[key] = dict(Counter(old_value) + Counter(new_value))
34+
35+
app.celery_metrics = tmp

controller/sentry/metrics/wsgi.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,35 @@
11
"""Wsgi Metrics."""
22
from collections import Counter
3-
from typing import TYPE_CHECKING
3+
from typing import TYPE_CHECKING, Union
4+
5+
from controller.sentry.utils import depth
46

57
if TYPE_CHECKING: # pragma: no cover
68
from controller.sentry.models import App
79

810

9-
def wsgi_merger(app: "App", new: dict[str, int]) -> None:
11+
def wsgi_merger(app: "App", new: Union[dict[str, int], dict[str, dict[str, int]]]) -> None:
1012
"""wsgi_merger function is used to merge :attr:`WSGI <controller.sentry.choices.MetricType.WSGI>` metrics.
1113
1214
Args:
1315
app (App): The app associated to the metric
1416
new (dict[str, int]): The new metric dict
1517
"""
16-
app.wsgi_metrics = dict(Counter(app.wsgi_metrics) + Counter(new))
18+
# Code for Migration
19+
if depth(app.wsgi_metrics) == 1:
20+
app.wsgi_metrics = {"path": app.wsgi_metrics}
21+
22+
if depth(new) == 1:
23+
new = {"path": new}
24+
# End of migration code
25+
26+
if app.wsgi_metrics is None:
27+
app.wsgi_metrics = {}
28+
29+
tmp = {}
30+
for key in set(list(app.wsgi_metrics.keys()) + list(new.keys())):
31+
old_value = app.wsgi_metrics.get(key)
32+
new_value = new.get(key)
33+
tmp[key] = dict(Counter(old_value) + Counter(new_value))
34+
35+
app.wsgi_metrics = tmp
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Generated by Django 4.2 on 2023-05-28 14:20
2+
3+
import functools
4+
5+
import django_better_admin_arrayfield.models.fields
6+
from django.db import migrations, models
7+
8+
import controller.sentry.models
9+
10+
11+
class Migration(migrations.Migration):
12+
dependencies = [
13+
("sentry", "0014_alter_app_celery_collect_metrics_and_more"),
14+
]
15+
16+
operations = [
17+
migrations.AddField(
18+
model_name="app",
19+
name="wsgi_ignore_user_agent",
20+
field=django_better_admin_arrayfield.models.fields.ArrayField(
21+
base_field=models.CharField(blank=True, max_length=50),
22+
blank=True,
23+
default=functools.partial(
24+
controller.sentry.models.settings_default_value, *("DEFAULT_WSGI_IGNORE_USER_AGENT",), **{}
25+
),
26+
help_text="A list of user agent to ignore, matched with startswith",
27+
size=None,
28+
),
29+
),
30+
migrations.AlterField(
31+
model_name="app",
32+
name="wsgi_ignore_path",
33+
field=django_better_admin_arrayfield.models.fields.ArrayField(
34+
base_field=models.CharField(blank=True, max_length=50),
35+
blank=True,
36+
default=functools.partial(
37+
controller.sentry.models.settings_default_value, *("DEFAULT_WSGI_IGNORE_PATHS",), **{}
38+
),
39+
help_text="A list of path to ignore, matched using fill match ==",
40+
size=None,
41+
),
42+
),
43+
]

controller/sentry/models.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,16 @@ class App(models.Model):
7777
models.CharField(max_length=50, blank=True),
7878
blank=True,
7979
default=partial(settings_default_value, "DEFAULT_WSGI_IGNORE_PATHS"),
80+
help_text="A list of path to ignore, matched using fill match ==",
81+
)
82+
wsgi_ignore_user_agent = ArrayField(
83+
models.CharField(max_length=50, blank=True),
84+
blank=True,
85+
default=partial(
86+
settings_default_value,
87+
"DEFAULT_WSGI_IGNORE_USER_AGENT",
88+
),
89+
help_text="A list of user agent to ignore, matched with startswith",
8090
)
8191
wsgi_collect_metrics = models.BooleanField(default=False, verbose_name="wsgi metrics")
8292
wsgi_metrics = models.JSONField(null=True, blank=True)

controller/sentry/serializers.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class Meta:
1717
"active_sample_rate",
1818
"active_window_end",
1919
"wsgi_ignore_path",
20+
"wsgi_ignore_user_agent",
2021
"wsgi_collect_metrics",
2122
"celery_ignore_task",
2223
"celery_collect_metrics",

controller/sentry/tests/test_models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@ def test_app_model_merge():
1313
app.merge({"type": MetricType.CELERY, "data": {"test": 1}})
1414
collect, metrics = app.get_metric(MetricType.CELERY)
1515
assert not collect
16-
assert metrics == {"test1": 5, "test": 6}
16+
assert metrics == {"task": {"test1": 5, "test": 6}}

controller/sentry/tests/test_views.py

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from collections import Counter
12
from unittest.mock import Mock, patch
23

34
import pytest
@@ -34,19 +35,52 @@ def test_app_view_retrieve_panic(cache: Mock, client):
3435

3536

3637
@pytest.mark.django_db
37-
@pytest.mark.parametrize("metric", MetricType)
38-
def test_app_view_metrics(client, metric: MetricType):
38+
@pytest.mark.parametrize("metric,default_name", [(MetricType.WSGI, "path"), (MetricType.CELERY, "task")])
39+
def test_app_view_metrics(client, metric: MetricType, default_name: str):
3940
reference = "test"
4041
data = {"type": metric.value, "data": {"test": 1, "test1": 5}}
4142
url = reverse("sentry:apps-metrics", kwargs={"pk": reference, "metric_name": metric.value})
4243

44+
response = client.post(url, data, content_type="application/json")
45+
assert response.status_code == 200, response.data
46+
app = App.objects.get(reference=reference)
47+
_, metric_data = app.get_metric(metric)
48+
assert metric_data == {default_name: data["data"]}
49+
50+
51+
@pytest.mark.django_db
52+
@pytest.mark.parametrize("metric,default_name", [(MetricType.WSGI, "path"), (MetricType.CELERY, "task")])
53+
def test_app_view_metrics_new(client, metric: MetricType, default_name: str):
54+
reference = "test"
55+
data = {"type": metric.value, "data": {default_name: {"test": 1, "test1": 5}}}
56+
url = reverse("sentry:apps-metrics", kwargs={"pk": reference, "metric_name": metric.value})
57+
4358
response = client.post(url, data, content_type="application/json")
4459
assert response.status_code == 200, response.data
4560
app = App.objects.get(reference=reference)
4661
_, metric_data = app.get_metric(metric)
4762
assert metric_data == data["data"]
4863

4964

65+
@pytest.mark.django_db
66+
@pytest.mark.parametrize("metric,default_name", [(MetricType.WSGI, "path"), (MetricType.CELERY, "task")])
67+
def test_app_view_metrics_with_old_value(client, metric: MetricType, default_name: str):
68+
reference = "test"
69+
metrics = {"test1": 5, "test": 5}
70+
app = App(reference=reference)
71+
app.celery_metrics = metrics
72+
app.wsgi_metrics = metrics
73+
app.save()
74+
data = {"type": metric.value, "data": {default_name: metrics}}
75+
url = reverse("sentry:apps-metrics", kwargs={"pk": reference, "metric_name": metric.value})
76+
77+
response = client.post(url, data, content_type="application/json")
78+
assert response.status_code == 200, response.data
79+
app = App.objects.get(reference=reference)
80+
_, metric_data = app.get_metric(metric)
81+
assert metric_data == {default_name: Counter(metrics) + Counter(metrics)}
82+
83+
5084
@pytest.mark.django_db
5185
def test_app_view_list(client):
5286
response = client.get("/sentry/apps/")

controller/sentry/utils.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,10 @@ def __call__(cls, *args, **kwargs):
5656
if cls not in cls._instances:
5757
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
5858
return cls._instances[cls]
59+
60+
61+
def depth(_dict: dict) -> int:
62+
"""Compute the depth of a dict."""
63+
if isinstance(_dict, dict):
64+
return 1 + (max(map(depth, _dict.values())) if _dict else 0)
65+
return 0

controller/settings.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,8 @@
248248

249249
DEFAULT_WSGI_IGNORE_PATHS = os.getenv("DEFAULT_WSGI_IGNORE_PATHS", "/health,/healthz,/health/,/healthz/").split(",")
250250

251+
DEFAULT_WSGI_IGNORE_USER_AGENT = os.getenv("DEFAULT_WSGI_IGNORE_USER_AGENT", "kube-probe/").split(",")
252+
251253

252254
DEFAULT_CELERY_IGNORE_TASKS = []
253255

@@ -338,7 +340,7 @@
338340

339341
ENVIRONMENT = os.getenv("ENV", "production")
340342
sentry_sdk.init(
341-
dsn="https://[email protected]/4504617124888576",
343+
dsn=SENTRY_DSN,
342344
environment=ENVIRONMENT,
343345
)
344346

0 commit comments

Comments
 (0)