Skip to content

Commit 019f82a

Browse files
committed
feat: Code locations for metrics
1 parent 91676ec commit 019f82a

File tree

2 files changed

+70
-7
lines changed

2 files changed

+70
-7
lines changed

sentry_sdk/metrics.py

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import os
22
import io
33
import re
4+
import sys
45
import threading
56
import random
67
import time
@@ -57,6 +58,19 @@
5758
)
5859

5960

61+
def get_code_location(stacklevel):
62+
try:
63+
frm = sys._getframe(stacklevel + 3)
64+
except Exception:
65+
return None
66+
return {
67+
"line": frm.f_lineno,
68+
"module": frm.f_globals.get("__name__"),
69+
"filename": frm.f_code.co_filename,
70+
"function": frm.f_code.co_name,
71+
}
72+
73+
6074
@contextmanager
6175
def recursion_protection():
6276
# type: () -> Generator[bool, None, None]
@@ -314,6 +328,7 @@ def __init__(
314328
):
315329
# type: (...) -> None
316330
self.buckets = {} # type: Dict[int, Any]
331+
self.code_locations = {} # type: Dict[BucketKey, Any]
317332
self._buckets_total_weight = 0
318333
self._capture_func = capture_func
319334
self._lock = Lock()
@@ -409,6 +424,7 @@ def add(
409424
unit, # type: MeasurementUnit
410425
tags, # type: Optional[MetricTags]
411426
timestamp=None, # type: Optional[Union[float, datetime]]
427+
stacklevel=1, # type: int
412428
):
413429
# type: (...) -> None
414430
if not self._ensure_thread() or self._flusher is None:
@@ -441,6 +457,12 @@ def add(
441457

442458
self._buckets_total_weight += metric.weight - previous_weight
443459

460+
# Store code location once per bucket
461+
if bucket_key not in self.code_locations:
462+
loc = get_code_location(stacklevel)
463+
if loc is not None:
464+
self.code_locations[bucket_key] = loc
465+
444466
# Given the new weight we consider whether we want to force flush.
445467
self._consider_force_flush()
446468

@@ -536,6 +558,7 @@ def incr(
536558
unit="none", # type: MeasurementUnit
537559
tags=None, # type: Optional[MetricTags]
538560
timestamp=None, # type: Optional[Union[float, datetime]]
561+
stacklevel=1, # type: int
539562
):
540563
# type: (...) -> None
541564
"""Increments a counter."""
@@ -552,6 +575,7 @@ def __init__(
552575
timestamp, # type: Optional[Union[float, datetime]]
553576
value, # type: Optional[float]
554577
unit, # type: DurationUnit
578+
stacklevel # type: int
555579
):
556580
# type: (...) -> None
557581
self.key = key
@@ -560,6 +584,7 @@ def __init__(
560584
self.value = value
561585
self.unit = unit
562586
self.entered = None # type: Optional[float]
587+
self.stacklevel = stacklevel
563588

564589
def _validate_invocation(self, context):
565590
# type: (str) -> None
@@ -579,7 +604,7 @@ def __exit__(self, exc_type, exc_value, tb):
579604
aggregator, tags = _get_aggregator_and_update_tags(self.key, self.tags)
580605
if aggregator is not None:
581606
elapsed = TIMING_FUNCTIONS[self.unit]() - self.entered # type: ignore
582-
aggregator.add("d", self.key, elapsed, self.unit, tags, self.timestamp)
607+
aggregator.add("d", self.key, elapsed, self.unit, tags, self.timestamp, self.stacklevel)
583608

584609
def __call__(self, f):
585610
# type: (Any) -> Any
@@ -589,7 +614,8 @@ def __call__(self, f):
589614
def timed_func(*args, **kwargs):
590615
# type: (*Any, **Any) -> Any
591616
with timing(
592-
key=self.key, tags=self.tags, timestamp=self.timestamp, unit=self.unit
617+
key=self.key, tags=self.tags, timestamp=self.timestamp, unit=self.unit,
618+
stacklevel=self.stacklevel + 1
593619
):
594620
return f(*args, **kwargs)
595621

@@ -602,6 +628,7 @@ def timing(
602628
unit="second", # type: DurationUnit
603629
tags=None, # type: Optional[MetricTags]
604630
timestamp=None, # type: Optional[Union[float, datetime]]
631+
stacklevel=1, # type: int
605632
):
606633
# type: (...) -> _Timing
607634
"""Emits a distribution with the time it takes to run the given code block.
@@ -615,8 +642,8 @@ def timing(
615642
if value is not None:
616643
aggregator, tags = _get_aggregator_and_update_tags(key, tags)
617644
if aggregator is not None:
618-
aggregator.add("d", key, value, unit, tags, timestamp)
619-
return _Timing(key, tags, timestamp, value, unit)
645+
aggregator.add("d", key, value, unit, tags, timestamp, stacklevel)
646+
return _Timing(key, tags, timestamp, value, unit, stacklevel)
620647

621648

622649
def distribution(
@@ -625,12 +652,13 @@ def distribution(
625652
unit="none", # type: MeasurementUnit
626653
tags=None, # type: Optional[MetricTags]
627654
timestamp=None, # type: Optional[Union[float, datetime]]
655+
stacklevel=1, # type: int
628656
):
629657
# type: (...) -> None
630658
"""Emits a distribution."""
631659
aggregator, tags = _get_aggregator_and_update_tags(key, tags)
632660
if aggregator is not None:
633-
aggregator.add("d", key, value, unit, tags, timestamp)
661+
aggregator.add("d", key, value, unit, tags, timestamp, stacklevel)
634662

635663

636664
def set(
@@ -639,12 +667,13 @@ def set(
639667
unit="none", # type: MeasurementUnit
640668
tags=None, # type: Optional[MetricTags]
641669
timestamp=None, # type: Optional[Union[float, datetime]]
670+
stacklevel=1, # type: int
642671
):
643672
# type: (...) -> None
644673
"""Emits a set."""
645674
aggregator, tags = _get_aggregator_and_update_tags(key, tags)
646675
if aggregator is not None:
647-
aggregator.add("s", key, value, unit, tags, timestamp)
676+
aggregator.add("s", key, value, unit, tags, timestamp, stacklevel)
648677

649678

650679
def gauge(
@@ -653,9 +682,10 @@ def gauge(
653682
unit="none", # type: MetricValue
654683
tags=None, # type: Optional[MetricTags]
655684
timestamp=None, # type: Optional[Union[float, datetime]]
685+
stacklevel=1, # type: int
656686
):
657687
# type: (...) -> None
658688
"""Emits a gauge."""
659689
aggregator, tags = _get_aggregator_and_update_tags(key, tags)
660690
if aggregator is not None:
661-
aggregator.add("g", key, value, unit, tags, timestamp)
691+
aggregator.add("g", key, value, unit, tags, timestamp, stacklevel)

tests/test_metrics.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,13 @@ def test_timing(sentry_init, capture_envelopes):
9797
"environment": "not-fun-env",
9898
}
9999

100+
loc = Hub.current.client.metrics_aggregator.code_locations
101+
first_loc, = loc.values()
102+
assert first_loc["filename"] == __file__
103+
assert first_loc["line"] > 0
104+
assert first_loc["module"] == "tests.test_metrics"
105+
assert first_loc["function"] == "test_timing"
106+
100107

101108
def test_timing_decorator(sentry_init, capture_envelopes):
102109
sentry_init(
@@ -147,6 +154,18 @@ def amazing_nano():
147154
"environment": "not-fun-env",
148155
}
149156

157+
loc = Hub.current.client.metrics_aggregator.code_locations
158+
assert len(loc) == 2
159+
first_loc, second_loc = loc.values()
160+
assert first_loc["filename"] == __file__
161+
assert first_loc["line"] > 0
162+
assert first_loc["module"] == "tests.test_metrics"
163+
assert first_loc["function"] == "test_timing_decorator"
164+
assert second_loc["filename"] == __file__
165+
assert second_loc["line"] > 0
166+
assert second_loc["module"] == "tests.test_metrics"
167+
assert second_loc["function"] == "test_timing_decorator"
168+
150169

151170
def test_timing_basic(sentry_init, capture_envelopes):
152171
sentry_init(
@@ -180,6 +199,13 @@ def test_timing_basic(sentry_init, capture_envelopes):
180199
"environment": "not-fun-env",
181200
}
182201

202+
loc = Hub.current.client.metrics_aggregator.code_locations
203+
first_loc, = loc.values()
204+
assert first_loc["filename"] == __file__
205+
assert first_loc["line"] > 0
206+
assert first_loc["module"] == "tests.test_metrics"
207+
assert first_loc["function"] == "test_timing_basic"
208+
183209

184210
def test_distribution(sentry_init, capture_envelopes):
185211
sentry_init(
@@ -504,6 +530,13 @@ def test_tag_serialization(sentry_init, capture_envelopes):
504530
"environment": "not-fun-env",
505531
}
506532

533+
loc = Hub.current.client.metrics_aggregator.code_locations
534+
first_loc = next(iter(loc.values()))
535+
assert first_loc["filename"] == __file__
536+
assert first_loc["line"] > 0
537+
assert first_loc["module"] == "tests.test_metrics"
538+
assert first_loc["function"] == "test_tag_serialization"
539+
507540

508541
def test_flush_recursion_protection(sentry_init, capture_envelopes, monkeypatch):
509542
sentry_init(

0 commit comments

Comments
 (0)