debugbundle-python 0.1.1__tar.gz → 0.1.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/PKG-INFO +22 -2
- {debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/README.md +21 -1
- {debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/pyproject.toml +1 -1
- {debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/src/debugbundle/core.py +84 -3
- {debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/src/debugbundle_python.egg-info/PKG-INFO +22 -2
- {debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/tests/test_remote_config.py +85 -1
- {debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/tests/test_sdk.py +13 -1
- {debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/LICENSE +0 -0
- {debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/setup.cfg +0 -0
- {debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/src/debugbundle/__init__.py +0 -0
- {debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/src/debugbundle/config.py +0 -0
- {debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/src/debugbundle/integrations/__init__.py +0 -0
- {debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/src/debugbundle/integrations/common.py +0 -0
- {debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/src/debugbundle/integrations/django.py +0 -0
- {debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/src/debugbundle/integrations/fastapi.py +0 -0
- {debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/src/debugbundle/integrations/flask.py +0 -0
- {debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/src/debugbundle/integrations/relay_django.py +0 -0
- {debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/src/debugbundle/integrations/relay_fastapi.py +0 -0
- {debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/src/debugbundle/integrations/relay_flask.py +0 -0
- {debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/src/debugbundle/logger_integrations.py +0 -0
- {debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/src/debugbundle/py.typed +0 -0
- {debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/src/debugbundle/redaction.py +0 -0
- {debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/src/debugbundle/relay.py +0 -0
- {debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/src/debugbundle/suppression.py +0 -0
- {debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/src/debugbundle/transport.py +0 -0
- {debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/src/debugbundle/trigger_token.py +0 -0
- {debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/src/debugbundle_python.egg-info/SOURCES.txt +0 -0
- {debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/src/debugbundle_python.egg-info/dependency_links.txt +0 -0
- {debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/src/debugbundle_python.egg-info/requires.txt +0 -0
- {debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/src/debugbundle_python.egg-info/top_level.txt +0 -0
- {debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/tests/test_contracts.py +0 -0
- {debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/tests/test_framework_integrations.py +0 -0
- {debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/tests/test_hooks.py +0 -0
- {debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/tests/test_http_integration.py +0 -0
- {debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/tests/test_relay.py +0 -0
- {debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/tests/test_repository_metadata.py +0 -0
- {debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/tests/test_schema_validation.py +0 -0
- {debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/tests/test_trigger_token.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: debugbundle-python
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: DebugBundle SDK for Python
|
|
5
5
|
Author: DebugBundle
|
|
6
6
|
License-Expression: AGPL-3.0-only
|
|
@@ -59,8 +59,28 @@ debugbundle.flush()
|
|
|
59
59
|
|
|
60
60
|
## Status
|
|
61
61
|
|
|
62
|
-
This repository currently contains the full Phase 18 Python SDK scope in eleven implementation slices: core SDK surface, buffering, redaction, duplicate suppression, probe buffering, vanilla runtime hooks, framework integrations for Django, Flask, and FastAPI, remote config polling and capture-policy enforcement, optional `structlog` and `loguru` auto-detection when `capture_logging()` is enabled, contract-aligned `EventEnvelope` emission for log, request, exception, suppression, and probe payloads, explicit public wrapper signatures and a validated buildable typed package artifact, real HTTP integration coverage against a lightweight mock ingestion server, vendored machine-readable schema validation for all event types the Python SDK currently emits, a standalone CI workflow that validates Ruff, mypy, pytest, and package builds for the Python 3.10+ support floor actually used by the package, an enforced per-file coverage gate that keeps every shipped Python SDK module at or above the required 80% minimum,
|
|
62
|
+
This repository currently contains the full Phase 18 Python SDK scope in eleven implementation slices: core SDK surface, buffering, redaction, duplicate suppression, probe buffering, vanilla runtime hooks, framework integrations for Django, Flask, and FastAPI, remote config polling and capture-policy enforcement, optional `structlog` and `loguru` auto-detection when `capture_logging()` is enabled, contract-aligned `EventEnvelope` emission for log, request, exception, suppression, and probe payloads, explicit public wrapper signatures and a validated buildable typed package artifact, real HTTP integration coverage against a lightweight mock ingestion server, vendored machine-readable schema validation for all event types the Python SDK currently emits, a standalone CI workflow that validates Ruff, mypy, pytest, and package builds for the Python 3.10+ support floor actually used by the package, an enforced per-file coverage gate that keeps every shipped Python SDK module at or above the required 80% minimum, request-local framework correlation binding so `X-DebugBundle-Trace-Id` flows through Django, Flask, and FastAPI into the emitted event correlation metadata for cross-context linking, and safe backend runtime process facts on exception payloads without reading environment variables.
|
|
63
|
+
|
|
64
|
+
## Runtime Context
|
|
65
|
+
|
|
66
|
+
Backend exception events now include safe runtime process facts when the host exposes them, including:
|
|
67
|
+
|
|
68
|
+
- Python version
|
|
69
|
+
- platform
|
|
70
|
+
- architecture
|
|
71
|
+
- pid
|
|
72
|
+
- cwd
|
|
73
|
+
- uptime
|
|
74
|
+
- hostname
|
|
75
|
+
- thread id
|
|
76
|
+
- best-effort memory metadata
|
|
77
|
+
|
|
78
|
+
The SDK does not read or emit environment variables in this runtime block.
|
|
63
79
|
|
|
64
80
|
## Docs
|
|
65
81
|
|
|
66
82
|
https://debugbundle.com/docs/sdk-python
|
|
83
|
+
|
|
84
|
+
## License
|
|
85
|
+
|
|
86
|
+
AGPL-3.0-only
|
|
@@ -20,8 +20,28 @@ debugbundle.flush()
|
|
|
20
20
|
|
|
21
21
|
## Status
|
|
22
22
|
|
|
23
|
-
This repository currently contains the full Phase 18 Python SDK scope in eleven implementation slices: core SDK surface, buffering, redaction, duplicate suppression, probe buffering, vanilla runtime hooks, framework integrations for Django, Flask, and FastAPI, remote config polling and capture-policy enforcement, optional `structlog` and `loguru` auto-detection when `capture_logging()` is enabled, contract-aligned `EventEnvelope` emission for log, request, exception, suppression, and probe payloads, explicit public wrapper signatures and a validated buildable typed package artifact, real HTTP integration coverage against a lightweight mock ingestion server, vendored machine-readable schema validation for all event types the Python SDK currently emits, a standalone CI workflow that validates Ruff, mypy, pytest, and package builds for the Python 3.10+ support floor actually used by the package, an enforced per-file coverage gate that keeps every shipped Python SDK module at or above the required 80% minimum,
|
|
23
|
+
This repository currently contains the full Phase 18 Python SDK scope in eleven implementation slices: core SDK surface, buffering, redaction, duplicate suppression, probe buffering, vanilla runtime hooks, framework integrations for Django, Flask, and FastAPI, remote config polling and capture-policy enforcement, optional `structlog` and `loguru` auto-detection when `capture_logging()` is enabled, contract-aligned `EventEnvelope` emission for log, request, exception, suppression, and probe payloads, explicit public wrapper signatures and a validated buildable typed package artifact, real HTTP integration coverage against a lightweight mock ingestion server, vendored machine-readable schema validation for all event types the Python SDK currently emits, a standalone CI workflow that validates Ruff, mypy, pytest, and package builds for the Python 3.10+ support floor actually used by the package, an enforced per-file coverage gate that keeps every shipped Python SDK module at or above the required 80% minimum, request-local framework correlation binding so `X-DebugBundle-Trace-Id` flows through Django, Flask, and FastAPI into the emitted event correlation metadata for cross-context linking, and safe backend runtime process facts on exception payloads without reading environment variables.
|
|
24
|
+
|
|
25
|
+
## Runtime Context
|
|
26
|
+
|
|
27
|
+
Backend exception events now include safe runtime process facts when the host exposes them, including:
|
|
28
|
+
|
|
29
|
+
- Python version
|
|
30
|
+
- platform
|
|
31
|
+
- architecture
|
|
32
|
+
- pid
|
|
33
|
+
- cwd
|
|
34
|
+
- uptime
|
|
35
|
+
- hostname
|
|
36
|
+
- thread id
|
|
37
|
+
- best-effort memory metadata
|
|
38
|
+
|
|
39
|
+
The SDK does not read or emit environment variables in this runtime block.
|
|
24
40
|
|
|
25
41
|
## Docs
|
|
26
42
|
|
|
27
43
|
https://debugbundle.com/docs/sdk-python
|
|
44
|
+
|
|
45
|
+
## License
|
|
46
|
+
|
|
47
|
+
AGPL-3.0-only
|
|
@@ -2,9 +2,12 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import logging
|
|
5
|
+
import os
|
|
5
6
|
import platform
|
|
7
|
+
import socket
|
|
6
8
|
import sys
|
|
7
9
|
import threading
|
|
10
|
+
import time
|
|
8
11
|
import traceback
|
|
9
12
|
import uuid
|
|
10
13
|
from collections import deque
|
|
@@ -32,10 +35,16 @@ from .suppression import EventSuppressionTracker
|
|
|
32
35
|
from .transport import HttpTransport, Transport, coerce_transport_response
|
|
33
36
|
from .trigger_token import resolve_request_trigger_directives
|
|
34
37
|
|
|
38
|
+
try:
|
|
39
|
+
import resource
|
|
40
|
+
except ImportError: # pragma: no cover - resource is unavailable on some platforms.
|
|
41
|
+
resource = None # type: ignore[assignment]
|
|
42
|
+
|
|
35
43
|
DEFAULT_BATCH_SIZE = 25
|
|
36
44
|
DEFAULT_FLUSH_INTERVAL = 5.0
|
|
37
45
|
DEFAULT_ENDPOINT = "https://api.debugbundle.com/v1/events"
|
|
38
46
|
DEFAULT_LOG_LEVEL = "warning"
|
|
47
|
+
PROCESS_START_MONOTONIC = time.monotonic()
|
|
39
48
|
SCHEMA_VERSION = "2026-03-01"
|
|
40
49
|
LEVEL_RANKS = {
|
|
41
50
|
"debug": 10,
|
|
@@ -44,6 +53,11 @@ LEVEL_RANKS = {
|
|
|
44
53
|
"error": 40,
|
|
45
54
|
"critical": 50,
|
|
46
55
|
}
|
|
56
|
+
BALANCED_IMMEDIATE_REQUEST_STATUSES = {408, 423, 424, 425, 429}
|
|
57
|
+
INVESTIGATIVE_IMMEDIATE_REQUEST_STATUSES = BALANCED_IMMEDIATE_REQUEST_STATUSES | {409}
|
|
58
|
+
BALANCED_STANDARD_ANOMALY_STATUSES = {401, 403, 404, 409, 422}
|
|
59
|
+
BALANCED_HIGH_VOLUME_ANOMALY_STATUSES = {400, 410}
|
|
60
|
+
INVESTIGATIVE_ANOMALY_STATUSES = BALANCED_STANDARD_ANOMALY_STATUSES | BALANCED_HIGH_VOLUME_ANOMALY_STATUSES
|
|
47
61
|
|
|
48
62
|
|
|
49
63
|
@dataclass
|
|
@@ -225,7 +239,7 @@ class DebugBundleSdk:
|
|
|
225
239
|
"handled": handled,
|
|
226
240
|
"request": request_payload,
|
|
227
241
|
"response": response_payload,
|
|
228
|
-
"runtime":
|
|
242
|
+
"runtime": _runtime_process_facts(),
|
|
229
243
|
}
|
|
230
244
|
if self._probe_flush_on_error:
|
|
231
245
|
probe_data = self._build_probe_data()
|
|
@@ -595,7 +609,7 @@ class DebugBundleSdk:
|
|
|
595
609
|
candidate = response.get("status_code") or response.get("response_status")
|
|
596
610
|
if isinstance(candidate, int):
|
|
597
611
|
status_code = candidate
|
|
598
|
-
if status_code
|
|
612
|
+
if _is_immediate_request_incident_status(status_code, self._capture_policy.preset):
|
|
599
613
|
return True
|
|
600
614
|
if policy == "off":
|
|
601
615
|
return False
|
|
@@ -606,7 +620,7 @@ class DebugBundleSdk:
|
|
|
606
620
|
if status_code is None:
|
|
607
621
|
return policy == "filtered"
|
|
608
622
|
if policy == "failures_only":
|
|
609
|
-
return
|
|
623
|
+
return _is_request_anomaly_candidate_status(status_code, self._capture_policy.preset)
|
|
610
624
|
if policy == "filtered":
|
|
611
625
|
return False
|
|
612
626
|
return True
|
|
@@ -699,6 +713,51 @@ def _redact_mapping(value: object, redact_fields: set[str]) -> Any:
|
|
|
699
713
|
return value
|
|
700
714
|
|
|
701
715
|
|
|
716
|
+
def _runtime_process_facts() -> dict[str, object]:
|
|
717
|
+
return {
|
|
718
|
+
"version": platform.python_version(),
|
|
719
|
+
"platform": sys.platform,
|
|
720
|
+
"arch": platform.machine() or None,
|
|
721
|
+
"pid": os.getpid(),
|
|
722
|
+
"cwd": _safe_cwd(),
|
|
723
|
+
"uptime_sec": round(max(0.0, time.monotonic() - PROCESS_START_MONOTONIC), 3),
|
|
724
|
+
"hostname": _safe_hostname(),
|
|
725
|
+
"thread_id": threading.get_ident(),
|
|
726
|
+
"memory": _memory_facts(),
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
def _safe_cwd() -> str | None:
|
|
731
|
+
try:
|
|
732
|
+
return os.getcwd()
|
|
733
|
+
except OSError:
|
|
734
|
+
return None
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
def _safe_hostname() -> str | None:
|
|
738
|
+
try:
|
|
739
|
+
return socket.gethostname()
|
|
740
|
+
except OSError:
|
|
741
|
+
return None
|
|
742
|
+
|
|
743
|
+
|
|
744
|
+
def _memory_facts() -> dict[str, object]:
|
|
745
|
+
memory: dict[str, object] = {
|
|
746
|
+
"rss": None,
|
|
747
|
+
"heap_total": None,
|
|
748
|
+
"heap_used": None,
|
|
749
|
+
"external": None,
|
|
750
|
+
"peak": None,
|
|
751
|
+
}
|
|
752
|
+
if resource is None:
|
|
753
|
+
return memory
|
|
754
|
+
|
|
755
|
+
usage = resource.getrusage(resource.RUSAGE_SELF)
|
|
756
|
+
# ru_maxrss is KiB on Linux and bytes on macOS/BSD.
|
|
757
|
+
memory["peak"] = usage.ru_maxrss if sys.platform == "darwin" else usage.ru_maxrss * 1024
|
|
758
|
+
return memory
|
|
759
|
+
|
|
760
|
+
|
|
702
761
|
def _backend_exception_request_payload(candidate: object | None) -> dict[str, object]:
|
|
703
762
|
mapping = _dict_from_object(candidate)
|
|
704
763
|
payload: dict[str, object] = {
|
|
@@ -792,6 +851,28 @@ def _iso_now(time_provider: Callable[[], float]) -> str:
|
|
|
792
851
|
return datetime.fromtimestamp(time_provider(), tz=timezone.utc).isoformat().replace("+00:00", "Z")
|
|
793
852
|
|
|
794
853
|
|
|
854
|
+
def _is_immediate_request_incident_status(status_code: int | None, preset: str) -> bool:
|
|
855
|
+
if status_code is None:
|
|
856
|
+
return False
|
|
857
|
+
if status_code >= 500:
|
|
858
|
+
return True
|
|
859
|
+
if preset == "investigative":
|
|
860
|
+
return status_code in INVESTIGATIVE_IMMEDIATE_REQUEST_STATUSES
|
|
861
|
+
if preset == "balanced":
|
|
862
|
+
return status_code in BALANCED_IMMEDIATE_REQUEST_STATUSES
|
|
863
|
+
return False
|
|
864
|
+
|
|
865
|
+
|
|
866
|
+
def _is_request_anomaly_candidate_status(status_code: int | None, preset: str) -> bool:
|
|
867
|
+
if status_code is None or status_code < 400 or status_code >= 500:
|
|
868
|
+
return False
|
|
869
|
+
if preset == "investigative":
|
|
870
|
+
return status_code in INVESTIGATIVE_ANOMALY_STATUSES
|
|
871
|
+
if preset == "balanced":
|
|
872
|
+
return status_code in BALANCED_STANDARD_ANOMALY_STATUSES or status_code in BALANCED_HIGH_VOLUME_ANOMALY_STATUSES
|
|
873
|
+
return False
|
|
874
|
+
|
|
875
|
+
|
|
795
876
|
def _time_now() -> float:
|
|
796
877
|
return datetime.now(tz=timezone.utc).timestamp()
|
|
797
878
|
|
{debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/src/debugbundle_python.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: debugbundle-python
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: DebugBundle SDK for Python
|
|
5
5
|
Author: DebugBundle
|
|
6
6
|
License-Expression: AGPL-3.0-only
|
|
@@ -59,8 +59,28 @@ debugbundle.flush()
|
|
|
59
59
|
|
|
60
60
|
## Status
|
|
61
61
|
|
|
62
|
-
This repository currently contains the full Phase 18 Python SDK scope in eleven implementation slices: core SDK surface, buffering, redaction, duplicate suppression, probe buffering, vanilla runtime hooks, framework integrations for Django, Flask, and FastAPI, remote config polling and capture-policy enforcement, optional `structlog` and `loguru` auto-detection when `capture_logging()` is enabled, contract-aligned `EventEnvelope` emission for log, request, exception, suppression, and probe payloads, explicit public wrapper signatures and a validated buildable typed package artifact, real HTTP integration coverage against a lightweight mock ingestion server, vendored machine-readable schema validation for all event types the Python SDK currently emits, a standalone CI workflow that validates Ruff, mypy, pytest, and package builds for the Python 3.10+ support floor actually used by the package, an enforced per-file coverage gate that keeps every shipped Python SDK module at or above the required 80% minimum,
|
|
62
|
+
This repository currently contains the full Phase 18 Python SDK scope in eleven implementation slices: core SDK surface, buffering, redaction, duplicate suppression, probe buffering, vanilla runtime hooks, framework integrations for Django, Flask, and FastAPI, remote config polling and capture-policy enforcement, optional `structlog` and `loguru` auto-detection when `capture_logging()` is enabled, contract-aligned `EventEnvelope` emission for log, request, exception, suppression, and probe payloads, explicit public wrapper signatures and a validated buildable typed package artifact, real HTTP integration coverage against a lightweight mock ingestion server, vendored machine-readable schema validation for all event types the Python SDK currently emits, a standalone CI workflow that validates Ruff, mypy, pytest, and package builds for the Python 3.10+ support floor actually used by the package, an enforced per-file coverage gate that keeps every shipped Python SDK module at or above the required 80% minimum, request-local framework correlation binding so `X-DebugBundle-Trace-Id` flows through Django, Flask, and FastAPI into the emitted event correlation metadata for cross-context linking, and safe backend runtime process facts on exception payloads without reading environment variables.
|
|
63
|
+
|
|
64
|
+
## Runtime Context
|
|
65
|
+
|
|
66
|
+
Backend exception events now include safe runtime process facts when the host exposes them, including:
|
|
67
|
+
|
|
68
|
+
- Python version
|
|
69
|
+
- platform
|
|
70
|
+
- architecture
|
|
71
|
+
- pid
|
|
72
|
+
- cwd
|
|
73
|
+
- uptime
|
|
74
|
+
- hostname
|
|
75
|
+
- thread id
|
|
76
|
+
- best-effort memory metadata
|
|
77
|
+
|
|
78
|
+
The SDK does not read or emit environment variables in this runtime block.
|
|
63
79
|
|
|
64
80
|
## Docs
|
|
65
81
|
|
|
66
82
|
https://debugbundle.com/docs/sdk-python
|
|
83
|
+
|
|
84
|
+
## License
|
|
85
|
+
|
|
86
|
+
AGPL-3.0-only
|
|
@@ -226,4 +226,88 @@ def test_capture_policy_filters_logs_and_request_events_from_remote_config() ->
|
|
|
226
226
|
events = _events(transport)
|
|
227
227
|
assert [event["event_type"] for event in events] == ["log_event", "request_event"]
|
|
228
228
|
assert events[0]["payload"]["message"] == "error kept"
|
|
229
|
-
assert events[1]["payload"]["response_status"] == 503
|
|
229
|
+
assert events[1]["payload"]["response_status"] == 503
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def test_balanced_capture_policy_keeps_request_failure_anomaly_candidates() -> None:
|
|
233
|
+
clock = ManualClock()
|
|
234
|
+
fetch = FakeFetch(
|
|
235
|
+
responses=[
|
|
236
|
+
FakeConfigResponse(
|
|
237
|
+
200,
|
|
238
|
+
{
|
|
239
|
+
"probes_enabled": True,
|
|
240
|
+
"remote_probes_enabled": True,
|
|
241
|
+
"active_probes": [],
|
|
242
|
+
"poll_interval_ms": 15000,
|
|
243
|
+
"capture_policy": {
|
|
244
|
+
"preset": "balanced",
|
|
245
|
+
"capture_logs": "warning",
|
|
246
|
+
"capture_request_events": "failures_only",
|
|
247
|
+
"capture_breadcrumbs": "exception_only",
|
|
248
|
+
"capture_probe_events": "buffer_only",
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
)
|
|
252
|
+
]
|
|
253
|
+
)
|
|
254
|
+
transport = FakeTransport()
|
|
255
|
+
sdk = DebugBundleSdk(transport=transport, time_provider=clock.time)
|
|
256
|
+
sdk.init(
|
|
257
|
+
project_token="dbundle_proj_test",
|
|
258
|
+
service="checkout-api",
|
|
259
|
+
environment="production",
|
|
260
|
+
fetch_impl=fetch,
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
sdk.capture_request({"method": "POST", "path": "/checkout", "headers": {}}, {"status_code": 429})
|
|
264
|
+
sdk.capture_request({"method": "POST", "path": "/checkout", "headers": {}}, {"status_code": 404})
|
|
265
|
+
sdk.capture_request({"method": "POST", "path": "/checkout", "headers": {}}, {"status_code": 409})
|
|
266
|
+
sdk.flush()
|
|
267
|
+
|
|
268
|
+
events = _events(transport)
|
|
269
|
+
request_statuses = [
|
|
270
|
+
event["payload"]["response_status"]
|
|
271
|
+
for event in events
|
|
272
|
+
if event["event_type"] == "request_event"
|
|
273
|
+
]
|
|
274
|
+
assert request_statuses == [429, 404, 409]
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def test_investigative_capture_policy_promotes_409_even_when_request_capture_is_off() -> None:
|
|
278
|
+
clock = ManualClock()
|
|
279
|
+
fetch = FakeFetch(
|
|
280
|
+
responses=[
|
|
281
|
+
FakeConfigResponse(
|
|
282
|
+
200,
|
|
283
|
+
{
|
|
284
|
+
"probes_enabled": True,
|
|
285
|
+
"remote_probes_enabled": True,
|
|
286
|
+
"active_probes": [],
|
|
287
|
+
"poll_interval_ms": 15000,
|
|
288
|
+
"capture_policy": {
|
|
289
|
+
"preset": "investigative",
|
|
290
|
+
"capture_logs": "info",
|
|
291
|
+
"capture_request_events": "off",
|
|
292
|
+
"capture_breadcrumbs": "standalone",
|
|
293
|
+
"capture_probe_events": "standalone_when_activated",
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
)
|
|
297
|
+
]
|
|
298
|
+
)
|
|
299
|
+
transport = FakeTransport()
|
|
300
|
+
sdk = DebugBundleSdk(transport=transport, time_provider=clock.time)
|
|
301
|
+
sdk.init(
|
|
302
|
+
project_token="dbundle_proj_test",
|
|
303
|
+
service="checkout-api",
|
|
304
|
+
environment="production",
|
|
305
|
+
fetch_impl=fetch,
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
sdk.capture_request({"method": "POST", "path": "/checkout", "headers": {}}, {"status_code": 409})
|
|
309
|
+
sdk.capture_request({"method": "POST", "path": "/checkout", "headers": {}}, {"status_code": 404})
|
|
310
|
+
sdk.flush()
|
|
311
|
+
|
|
312
|
+
events = _events(transport)
|
|
313
|
+
assert [event["payload"]["response_status"] for event in events if event["event_type"] == "request_event"] == [409]
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import platform
|
|
4
|
+
import sys
|
|
4
5
|
import uuid
|
|
5
6
|
from dataclasses import dataclass
|
|
6
7
|
|
|
@@ -234,7 +235,18 @@ def test_emits_contract_compliant_event_envelopes() -> None:
|
|
|
234
235
|
assert exception_event["payload"]["handled"] is True
|
|
235
236
|
assert exception_event["payload"]["request"]["path"] == "/checkout"
|
|
236
237
|
assert exception_event["payload"]["response"]["status_code"] == 500
|
|
237
|
-
|
|
238
|
+
runtime = exception_event["payload"]["runtime"]
|
|
239
|
+
assert runtime["version"] == platform.python_version()
|
|
240
|
+
assert runtime["platform"] == sys.platform
|
|
241
|
+
assert isinstance(runtime["arch"], str)
|
|
242
|
+
assert isinstance(runtime["pid"], int)
|
|
243
|
+
assert isinstance(runtime["cwd"], str)
|
|
244
|
+
assert isinstance(runtime["uptime_sec"], (int, float))
|
|
245
|
+
assert runtime["uptime_sec"] >= 0
|
|
246
|
+
assert isinstance(runtime["hostname"], str)
|
|
247
|
+
assert isinstance(runtime["thread_id"], int)
|
|
248
|
+
assert isinstance(runtime["memory"], dict)
|
|
249
|
+
assert "environment" not in runtime
|
|
238
250
|
|
|
239
251
|
|
|
240
252
|
def test_suppresses_duplicate_exceptions_after_the_first_three() -> None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/src/debugbundle/integrations/__init__.py
RENAMED
|
File without changes
|
{debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/src/debugbundle/integrations/common.py
RENAMED
|
File without changes
|
{debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/src/debugbundle/integrations/django.py
RENAMED
|
File without changes
|
{debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/src/debugbundle/integrations/fastapi.py
RENAMED
|
File without changes
|
|
File without changes
|
{debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/src/debugbundle/integrations/relay_django.py
RENAMED
|
File without changes
|
{debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/src/debugbundle/integrations/relay_fastapi.py
RENAMED
|
File without changes
|
{debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/src/debugbundle/integrations/relay_flask.py
RENAMED
|
File without changes
|
{debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/src/debugbundle/logger_integrations.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/src/debugbundle_python.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/src/debugbundle_python.egg-info/requires.txt
RENAMED
|
File without changes
|
{debugbundle_python-0.1.1 → debugbundle_python-0.1.3}/src/debugbundle_python.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|