Skip to content

Commit b5b7f3a

Browse files
authored
Strip leading slashes for sampling config (#809)
* strip leading slashes for sampling config * stripe whitespace * stripe leading slash in web requests and celery tests
1 parent 226d564 commit b5b7f3a

File tree

5 files changed

+26
-14
lines changed

5 files changed

+26
-14
lines changed

src/scout_apm/core/config.py

+17-5
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,10 @@ def value(self, key: str) -> None:
287287
return None
288288

289289

290+
def _strip_leading_slash(path: str) -> str:
291+
return path.lstrip(" /").strip()
292+
293+
290294
def convert_to_bool(value: Any) -> bool:
291295
if isinstance(value, bool):
292296
return value
@@ -339,14 +343,22 @@ def convert_to_list(value: Any) -> List[Any]:
339343
return []
340344

341345

346+
def convert_ignore_paths(value: Any) -> List[str]:
347+
"""
348+
Removes leading slashes from paths and returns a list of strings.
349+
"""
350+
raw_paths = convert_to_list(value)
351+
return [_strip_leading_slash(path) for path in raw_paths]
352+
353+
342354
def convert_endpoint_sampling(value: Union[str, Dict[str, Any]]) -> Dict[str, int]:
343355
"""
344356
Converts endpoint sampling configuration from string or dict format
345357
to a normalized dict.
346358
Example: '/endpoint:40,/test:0' -> {'/endpoint': 40, '/test': 0}
347359
"""
348360
if isinstance(value, dict):
349-
return {k: int(v) for k, v in value.items()}
361+
return {_strip_leading_slash(k): int(v) for k, v in value.items()}
350362
if isinstance(value, str):
351363
if not value.strip():
352364
return {}
@@ -362,7 +374,7 @@ def convert_endpoint_sampling(value: Union[str, Dict[str, Any]]) -> Dict[str, in
362374
"Must be between 0 and 100."
363375
)
364376
continue
365-
result[endpoint.strip()] = rate_int
377+
result[_strip_leading_slash(endpoint)] = rate_int
366378
except ValueError:
367379
logger.warning(f"Invalid sampling configuration: {pair}")
368380
continue
@@ -375,9 +387,9 @@ def convert_endpoint_sampling(value: Union[str, Dict[str, Any]]) -> Dict[str, in
375387
"core_agent_download": convert_to_bool,
376388
"core_agent_launch": convert_to_bool,
377389
"disabled_instruments": convert_to_list,
378-
"ignore": convert_to_list,
379-
"ignore_endpoints": convert_to_list,
380-
"ignore_jobs": convert_to_list,
390+
"ignore": convert_ignore_paths,
391+
"ignore_endpoints": convert_ignore_paths,
392+
"ignore_jobs": convert_ignore_paths,
381393
"monitor": convert_to_bool,
382394
"sample_rate": convert_sample_rate,
383395
"sample_endpoints": convert_endpoint_sampling,

src/scout_apm/core/web_requests.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ def filter_element(key, value):
9696
def ignore_path(path):
9797
ignored_paths = scout_config.value("ignore")
9898
for ignored in ignored_paths:
99-
if path.startswith(ignored):
99+
if path.lstrip(" /").startswith(ignored):
100100
return True
101101
return False
102102

tests/integration/test_celery.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def crash(foo, spam=None):
7171
def test_configuration_copied():
7272
celery_config = SimpleNamespace(SCOUT_IGNORE=["/foobar/"])
7373
with app_with_scout(celery_config=celery_config):
74-
assert scout_config.value("ignore") == ["/foobar/"]
74+
assert scout_config.value("ignore") == ["foobar/"]
7575

7676

7777
def test_hello_eager(tracked_requests):

tests/unit/core/test_config.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -266,17 +266,17 @@ def test_sample_rate_conversion_from_python(original, converted):
266266
def test_endpoint_sampling_conversion_from_env():
267267
config = ScoutConfig()
268268
with mock.patch.dict(
269-
os.environ, {"SCOUT_SAMPLE_ENDPOINTS": "/endpoint:40,/test:0"}
269+
os.environ, {"SCOUT_SAMPLE_ENDPOINTS": " /endpoint:40,/test:0"}
270270
):
271271
value = config.value("sample_endpoints")
272272
assert isinstance(value, dict)
273-
assert value == {"/endpoint": 40, "/test": 0}
273+
assert value == {"endpoint": 40, "test": 0}
274274

275275

276276
@pytest.mark.parametrize(
277277
"original, converted",
278278
[
279-
("/endpoint:40,/test:0", {"/endpoint": 40, "/test": 0}),
279+
("/endpoint:40,/test:0", {"endpoint": 40, "test": 0}),
280280
({"endpoint": 40, "test": 0}, {"endpoint": 40, "test": 0}),
281281
("", {}),
282282
(object(), {}),

tests/unit/core/test_sampler.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@ def config():
1515
sample_rate=50, # 50% global sampling
1616
sample_endpoints={
1717
"users/test": 0, # Never sample specific endpoint
18-
"users": 100, # Always sample
18+
"/users": 100, # Always sample
1919
"test": 20, # 20% sampling for test endpoints
20-
"health": 0, # Never sample health checks
20+
"/health": 0, # Never sample health checks
2121
},
2222
sample_jobs={
2323
"critical-job": 100, # Always sample
24-
"batch": 30, # 30% sampling for batch jobs
24+
"/batch": 30, # 30% sampling for batch jobs
2525
},
26-
ignore_endpoints=["metrics", "ping"],
26+
ignore_endpoints=["/metrics", "ping"],
2727
ignore_jobs=["test-job"],
2828
endpoint_sample_rate=70, # 70% sampling for unspecified endpoints
2929
job_sample_rate=40, # 40% sampling for unspecified jobs

0 commit comments

Comments
 (0)