debugbundle-python 1.4.0__tar.gz → 1.4.1__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-1.4.0 → debugbundle_python-1.4.1}/PKG-INFO +5 -1
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/README.md +4 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/pyproject.toml +1 -1
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/src/debugbundle/event_support.py +1 -1
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/src/debugbundle/logger_integrations.py +9 -37
- debugbundle_python-1.4.1/src/debugbundle/structlog_integration.py +136 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/src/debugbundle_python.egg-info/PKG-INFO +5 -1
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/src/debugbundle_python.egg-info/SOURCES.txt +2 -0
- debugbundle_python-1.4.1/tests/test_structlog_filtering.py +171 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/LICENSE +0 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/setup.cfg +0 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/src/debugbundle/__init__.py +0 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/src/debugbundle/acknowledgement.py +0 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/src/debugbundle/before_send.py +0 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/src/debugbundle/config.py +0 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/src/debugbundle/core.py +0 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/src/debugbundle/integrations/__init__.py +0 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/src/debugbundle/integrations/common.py +0 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/src/debugbundle/integrations/django.py +0 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/src/debugbundle/integrations/fastapi.py +0 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/src/debugbundle/integrations/flask.py +0 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/src/debugbundle/integrations/relay_django.py +0 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/src/debugbundle/integrations/relay_fastapi.py +0 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/src/debugbundle/integrations/relay_flask.py +0 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/src/debugbundle/py.typed +0 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/src/debugbundle/redaction.py +0 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/src/debugbundle/relay.py +0 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/src/debugbundle/relay_delivery.py +0 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/src/debugbundle/suppression.py +0 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/src/debugbundle/transport.py +0 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/src/debugbundle/trigger_token.py +0 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/src/debugbundle_python.egg-info/dependency_links.txt +0 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/src/debugbundle_python.egg-info/requires.txt +0 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/src/debugbundle_python.egg-info/top_level.txt +0 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/tests/test_before_send.py +0 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/tests/test_contracts.py +0 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/tests/test_framework_integrations.py +0 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/tests/test_hooks.py +0 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/tests/test_http_integration.py +0 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/tests/test_optional_imports.py +0 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/tests/test_relay.py +0 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/tests/test_relay_delivery.py +0 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/tests/test_remote_config.py +0 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/tests/test_repository_metadata.py +0 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/tests/test_schema_validation.py +0 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/tests/test_sdk.py +0 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/tests/test_smoke_script.py +0 -0
- {debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/tests/test_trigger_token.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: debugbundle-python
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.1
|
|
4
4
|
Summary: DebugBundle SDK for Python
|
|
5
5
|
Author: DebugBundle
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -49,6 +49,10 @@ Use this package to capture Python backend exceptions, request metadata, structu
|
|
|
49
49
|
|
|
50
50
|
Requires Python 3.10 or newer.
|
|
51
51
|
|
|
52
|
+
## Automatic capture and application filtering
|
|
53
|
+
|
|
54
|
+
`capture_logging()` attaches the stdlib logging handler and detects installed structlog/loguru integrations. Obtain structlog loggers after attachment. Structlog level filters and `DropEvent` processors run before capture; stdlib-backed structlog loggers also honor logger filters, disabled state and `logging.disable()`. Bound context and async logging are supported. SDK disposal stops capture through cached proxies. DebugBundle has its own minimum capture level; destination-specific filters on other handlers or Loguru sinks do not configure the DebugBundle handler.
|
|
55
|
+
|
|
52
56
|
## Installation
|
|
53
57
|
|
|
54
58
|
```bash
|
|
@@ -10,6 +10,10 @@ Use this package to capture Python backend exceptions, request metadata, structu
|
|
|
10
10
|
|
|
11
11
|
Requires Python 3.10 or newer.
|
|
12
12
|
|
|
13
|
+
## Automatic capture and application filtering
|
|
14
|
+
|
|
15
|
+
`capture_logging()` attaches the stdlib logging handler and detects installed structlog/loguru integrations. Obtain structlog loggers after attachment. Structlog level filters and `DropEvent` processors run before capture; stdlib-backed structlog loggers also honor logger filters, disabled state and `logging.disable()`. Bound context and async logging are supported. SDK disposal stops capture through cached proxies. DebugBundle has its own minimum capture level; destination-specific filters on other handlers or Loguru sinks do not configure the DebugBundle handler.
|
|
16
|
+
|
|
13
17
|
## Installation
|
|
14
18
|
|
|
15
19
|
```bash
|
{debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/src/debugbundle/logger_integrations.py
RENAMED
|
@@ -3,6 +3,8 @@ from __future__ import annotations
|
|
|
3
3
|
from collections.abc import Callable
|
|
4
4
|
from typing import Any, Protocol
|
|
5
5
|
|
|
6
|
+
from .structlog_integration import StructlogLoggerProxy as _StructlogLoggerProxy
|
|
7
|
+
|
|
6
8
|
|
|
7
9
|
class LogCaptureApi(Protocol):
|
|
8
10
|
def capture_log(
|
|
@@ -44,14 +46,18 @@ def _attach_structlog(
|
|
|
44
46
|
if getattr(original_get_logger, "__debugbundle_structlog_wrapper__", False):
|
|
45
47
|
return None
|
|
46
48
|
|
|
49
|
+
active = [True]
|
|
50
|
+
|
|
47
51
|
def get_logger(*args: Any, **kwargs: Any) -> Any:
|
|
48
|
-
return _StructlogLoggerProxy(original_get_logger(*args, **kwargs), sdk)
|
|
52
|
+
return _StructlogLoggerProxy(original_get_logger(*args, **kwargs), sdk, active)
|
|
49
53
|
|
|
50
54
|
setattr(get_logger, "__debugbundle_structlog_wrapper__", True)
|
|
51
55
|
structlog.get_logger = get_logger
|
|
52
56
|
|
|
53
57
|
def restore() -> None:
|
|
54
|
-
|
|
58
|
+
active[0] = False
|
|
59
|
+
if structlog.get_logger is get_logger:
|
|
60
|
+
structlog.get_logger = original_get_logger
|
|
55
61
|
|
|
56
62
|
return restore
|
|
57
63
|
except Exception as error:
|
|
@@ -100,40 +106,6 @@ def _normalize_level(level: str) -> str:
|
|
|
100
106
|
return normalized
|
|
101
107
|
|
|
102
108
|
|
|
103
|
-
class _StructlogLoggerProxy:
|
|
104
|
-
def __init__(self, logger: Any, sdk: LogCaptureApi) -> None:
|
|
105
|
-
self._logger = logger
|
|
106
|
-
self._sdk = sdk
|
|
107
|
-
|
|
108
|
-
def bind(self, *args: Any, **kwargs: Any) -> _StructlogLoggerProxy:
|
|
109
|
-
return _StructlogLoggerProxy(self._logger.bind(*args, **kwargs), self._sdk)
|
|
110
|
-
|
|
111
|
-
def new(self, *args: Any, **kwargs: Any) -> _StructlogLoggerProxy:
|
|
112
|
-
return _StructlogLoggerProxy(self._logger.new(*args, **kwargs), self._sdk)
|
|
113
|
-
|
|
114
|
-
def __getattr__(self, name: str) -> Any:
|
|
115
|
-
attribute = getattr(self._logger, name)
|
|
116
|
-
if name not in {
|
|
117
|
-
"debug",
|
|
118
|
-
"info",
|
|
119
|
-
"warning",
|
|
120
|
-
"warn",
|
|
121
|
-
"error",
|
|
122
|
-
"critical",
|
|
123
|
-
"exception",
|
|
124
|
-
} or not callable(attribute):
|
|
125
|
-
return attribute
|
|
126
|
-
|
|
127
|
-
def wrapped(event: Any = None, *args: Any, **kwargs: Any) -> Any:
|
|
128
|
-
context = dict(kwargs)
|
|
129
|
-
for index, value in enumerate(args):
|
|
130
|
-
context[f"arg_{index}"] = value
|
|
131
|
-
self._sdk.capture_log(str(event or ""), level=_normalize_level(name), context=context or None)
|
|
132
|
-
return attribute(event, *args, **kwargs)
|
|
133
|
-
|
|
134
|
-
return wrapped
|
|
135
|
-
|
|
136
|
-
|
|
137
109
|
def _emit_diagnostic(
|
|
138
110
|
on_diagnostic: Callable[[dict[str, object]], None] | None,
|
|
139
111
|
error: Exception,
|
|
@@ -151,4 +123,4 @@ def _emit_diagnostic(
|
|
|
151
123
|
}
|
|
152
124
|
},
|
|
153
125
|
}
|
|
154
|
-
)
|
|
126
|
+
)
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import copy
|
|
4
|
+
import inspect
|
|
5
|
+
import logging
|
|
6
|
+
from contextvars import ContextVar
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
_CAPTURING: ContextVar[bool] = ContextVar("debugbundle_structlog_capture", default=False)
|
|
10
|
+
_LOG_METHODS = {"debug", "info", "warning", "warn", "error", "critical", "fatal", "exception", "log"}
|
|
11
|
+
_BIND_METHODS = {"bind", "new", "unbind", "try_unbind"}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _capture(sdk: Any, active: list[bool], name: str, record: dict[str, Any]) -> None:
|
|
15
|
+
if not active[0] or _CAPTURING.get():
|
|
16
|
+
return
|
|
17
|
+
token = _CAPTURING.set(True)
|
|
18
|
+
try:
|
|
19
|
+
context = dict(record)
|
|
20
|
+
message = str(context.pop("event", ""))
|
|
21
|
+
level = {"warn": "warning", "exception": "error", "fatal": "critical"}.get(name, name)
|
|
22
|
+
sdk.capture_log(message, level=level, context=context or None)
|
|
23
|
+
except Exception:
|
|
24
|
+
# A logger adapter must not throw into the application's logging path.
|
|
25
|
+
pass
|
|
26
|
+
finally:
|
|
27
|
+
_CAPTURING.reset(token)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class _SinkProxy:
|
|
31
|
+
def __init__(self, sink: Any, sdk: Any, active: list[bool], record: dict[str, Any]) -> None:
|
|
32
|
+
self._sink = sink
|
|
33
|
+
self._sdk = sdk
|
|
34
|
+
self._active = active
|
|
35
|
+
self._record = record
|
|
36
|
+
|
|
37
|
+
def __getattr__(self, name: str) -> Any:
|
|
38
|
+
method = getattr(self._sink, name)
|
|
39
|
+
if name not in _LOG_METHODS or not callable(method):
|
|
40
|
+
return method
|
|
41
|
+
|
|
42
|
+
def emit(*args: Any, **kwargs: Any) -> Any:
|
|
43
|
+
result = method(*args, **kwargs)
|
|
44
|
+
# stdlib records go through the existing SDK logging.Handler, after
|
|
45
|
+
# Logger.disabled, logging.disable(), levels and logger filters.
|
|
46
|
+
if not isinstance(self._sink, (logging.Logger, logging.LoggerAdapter)):
|
|
47
|
+
_capture(self._sdk, self._active, name, self._record)
|
|
48
|
+
return result
|
|
49
|
+
|
|
50
|
+
return emit
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class StructlogLoggerProxy:
|
|
54
|
+
def __init__(self, logger: Any, sdk: Any, active: list[bool] | None = None) -> None:
|
|
55
|
+
self._logger = logger
|
|
56
|
+
self._sdk = sdk
|
|
57
|
+
self._active = active if active is not None else [True]
|
|
58
|
+
|
|
59
|
+
def __getattr__(self, name: str) -> Any:
|
|
60
|
+
attribute = getattr(self._logger, name)
|
|
61
|
+
if not callable(attribute):
|
|
62
|
+
return attribute
|
|
63
|
+
if name in _BIND_METHODS:
|
|
64
|
+
def bind(*args: Any, **kwargs: Any) -> StructlogLoggerProxy:
|
|
65
|
+
return StructlogLoggerProxy(attribute(*args, **kwargs), self._sdk, self._active)
|
|
66
|
+
return bind
|
|
67
|
+
method_name = name[1:] if name.startswith("a") else name
|
|
68
|
+
if method_name not in _LOG_METHODS:
|
|
69
|
+
return attribute
|
|
70
|
+
|
|
71
|
+
def prepare(args: tuple[Any, ...], kwargs: dict[str, Any]) -> Any:
|
|
72
|
+
if not self._active[0] or _CAPTURING.get():
|
|
73
|
+
return attribute
|
|
74
|
+
try:
|
|
75
|
+
bound = self._logger.bind() if hasattr(self._logger, "bind") else self._logger
|
|
76
|
+
if not hasattr(bound, "_processors") or not hasattr(bound, "_logger"):
|
|
77
|
+
return self._legacy_method(attribute, method_name)
|
|
78
|
+
# Keep the application's bound logger and processor list intact.
|
|
79
|
+
# The per-call clone also isolates concurrent/async log records.
|
|
80
|
+
clone = copy.copy(bound)
|
|
81
|
+
self._ensure_stdlib_handler(bound._logger)
|
|
82
|
+
record = dict(bound._context)
|
|
83
|
+
record.update(kwargs)
|
|
84
|
+
if args:
|
|
85
|
+
record["event"] = args[1] if method_name == "log" and len(args) > 1 else args[0]
|
|
86
|
+
processors = list(bound._processors)
|
|
87
|
+
if processors:
|
|
88
|
+
last = processors[-1]
|
|
89
|
+
|
|
90
|
+
def observe(logger: Any, level: str, event: Any) -> Any:
|
|
91
|
+
result = last(logger, level, event)
|
|
92
|
+
snapshot = result if isinstance(result, dict) else event
|
|
93
|
+
if isinstance(snapshot, dict):
|
|
94
|
+
record.clear()
|
|
95
|
+
record.update(snapshot)
|
|
96
|
+
return result
|
|
97
|
+
|
|
98
|
+
processors[-1] = observe
|
|
99
|
+
clone._processors = processors
|
|
100
|
+
clone._logger = _SinkProxy(bound._logger, self._sdk, self._active, record)
|
|
101
|
+
return getattr(clone, name)
|
|
102
|
+
except Exception:
|
|
103
|
+
# Preparation can fail for a custom logger; call it exactly once.
|
|
104
|
+
return attribute
|
|
105
|
+
|
|
106
|
+
if inspect.iscoroutinefunction(attribute):
|
|
107
|
+
async def async_log(*args: Any, **kwargs: Any) -> Any:
|
|
108
|
+
return await prepare(args, kwargs)(*args, **kwargs)
|
|
109
|
+
return async_log
|
|
110
|
+
|
|
111
|
+
def log(*args: Any, **kwargs: Any) -> Any:
|
|
112
|
+
return prepare(args, kwargs)(*args, **kwargs)
|
|
113
|
+
return log
|
|
114
|
+
|
|
115
|
+
def _ensure_stdlib_handler(self, sink: Any) -> None:
|
|
116
|
+
logger = sink.logger if isinstance(sink, logging.LoggerAdapter) else sink
|
|
117
|
+
if not isinstance(logger, logging.Logger):
|
|
118
|
+
return
|
|
119
|
+
current: logging.Logger | None = logger
|
|
120
|
+
while current is not None:
|
|
121
|
+
if any(getattr(handler, "_sdk", None) is self._sdk for handler in current.handlers):
|
|
122
|
+
return
|
|
123
|
+
if not current.propagate:
|
|
124
|
+
break
|
|
125
|
+
current = current.parent
|
|
126
|
+
attach = getattr(self._sdk, "capture_logging", None)
|
|
127
|
+
if callable(attach):
|
|
128
|
+
attach(logger=logger)
|
|
129
|
+
|
|
130
|
+
def _legacy_method(self, attribute: Any, name: str) -> Any:
|
|
131
|
+
def log(event: Any = None, *args: Any, **kwargs: Any) -> Any:
|
|
132
|
+
result = attribute(event, *args, **kwargs)
|
|
133
|
+
record = {**kwargs, **{f"arg_{i}": value for i, value in enumerate(args)}, "event": event or ""}
|
|
134
|
+
_capture(self._sdk, self._active, name, record)
|
|
135
|
+
return result
|
|
136
|
+
return log
|
{debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/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: 1.4.
|
|
3
|
+
Version: 1.4.1
|
|
4
4
|
Summary: DebugBundle SDK for Python
|
|
5
5
|
Author: DebugBundle
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -49,6 +49,10 @@ Use this package to capture Python backend exceptions, request metadata, structu
|
|
|
49
49
|
|
|
50
50
|
Requires Python 3.10 or newer.
|
|
51
51
|
|
|
52
|
+
## Automatic capture and application filtering
|
|
53
|
+
|
|
54
|
+
`capture_logging()` attaches the stdlib logging handler and detects installed structlog/loguru integrations. Obtain structlog loggers after attachment. Structlog level filters and `DropEvent` processors run before capture; stdlib-backed structlog loggers also honor logger filters, disabled state and `logging.disable()`. Bound context and async logging are supported. SDK disposal stops capture through cached proxies. DebugBundle has its own minimum capture level; destination-specific filters on other handlers or Loguru sinks do not configure the DebugBundle handler.
|
|
55
|
+
|
|
52
56
|
## Installation
|
|
53
57
|
|
|
54
58
|
```bash
|
{debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/src/debugbundle_python.egg-info/SOURCES.txt
RENAMED
|
@@ -12,6 +12,7 @@ src/debugbundle/py.typed
|
|
|
12
12
|
src/debugbundle/redaction.py
|
|
13
13
|
src/debugbundle/relay.py
|
|
14
14
|
src/debugbundle/relay_delivery.py
|
|
15
|
+
src/debugbundle/structlog_integration.py
|
|
15
16
|
src/debugbundle/suppression.py
|
|
16
17
|
src/debugbundle/transport.py
|
|
17
18
|
src/debugbundle/trigger_token.py
|
|
@@ -41,4 +42,5 @@ tests/test_repository_metadata.py
|
|
|
41
42
|
tests/test_schema_validation.py
|
|
42
43
|
tests/test_sdk.py
|
|
43
44
|
tests/test_smoke_script.py
|
|
45
|
+
tests/test_structlog_filtering.py
|
|
44
46
|
tests/test_trigger_token.py
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import io
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
import structlog
|
|
10
|
+
|
|
11
|
+
from debugbundle.core import DebugBundleSdk
|
|
12
|
+
from debugbundle.logger_integrations import _attach_structlog
|
|
13
|
+
|
|
14
|
+
_ORIGINAL_GET_LOGGER = structlog.get_logger
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Capture:
|
|
18
|
+
def __init__(self):
|
|
19
|
+
self.events = []
|
|
20
|
+
|
|
21
|
+
def capture_log(self, message, level="warning", context=None):
|
|
22
|
+
self.events.append((message, level, context))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest.fixture(autouse=True)
|
|
26
|
+
def reset_structlog(monkeypatch):
|
|
27
|
+
previous = structlog.get_config()
|
|
28
|
+
monkeypatch.setattr(structlog, "get_logger", _ORIGINAL_GET_LOGGER)
|
|
29
|
+
yield
|
|
30
|
+
structlog.configure(**previous)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@pytest.mark.parametrize("mode", ["level", "processor"])
|
|
34
|
+
def test_native_structlog_filters_run_before_capture(mode):
|
|
35
|
+
output = io.StringIO()
|
|
36
|
+
sdk = Capture()
|
|
37
|
+
|
|
38
|
+
def drop(_logger, _name, event):
|
|
39
|
+
if event["event"] == "suppressed":
|
|
40
|
+
raise structlog.DropEvent
|
|
41
|
+
return event
|
|
42
|
+
|
|
43
|
+
structlog.configure(
|
|
44
|
+
wrapper_class=structlog.make_filtering_bound_logger(logging.ERROR if mode == "level" else logging.DEBUG),
|
|
45
|
+
logger_factory=structlog.PrintLoggerFactory(file=output),
|
|
46
|
+
processors=([drop] if mode == "processor" else []) + [structlog.processors.JSONRenderer()],
|
|
47
|
+
)
|
|
48
|
+
restore = _attach_structlog(sdk)
|
|
49
|
+
try:
|
|
50
|
+
logger = structlog.get_logger().bind(tenant="example")
|
|
51
|
+
logger.warning("suppressed")
|
|
52
|
+
logger.error("accepted", request_id="req-1")
|
|
53
|
+
assert [event[0] for event in sdk.events] == ["accepted"]
|
|
54
|
+
assert [json.loads(line)["event"] for line in output.getvalue().splitlines()] == ["accepted"]
|
|
55
|
+
assert sdk.events[0][2] == {"tenant": "example", "request_id": "req-1"}
|
|
56
|
+
restore()
|
|
57
|
+
logger.error("after detach")
|
|
58
|
+
assert len(sdk.events) == 1
|
|
59
|
+
finally:
|
|
60
|
+
restore()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_structlog_async_and_context_operations_preserve_filters():
|
|
64
|
+
sdk = Capture()
|
|
65
|
+
structlog.configure(
|
|
66
|
+
wrapper_class=structlog.make_filtering_bound_logger(logging.ERROR),
|
|
67
|
+
logger_factory=structlog.ReturnLoggerFactory(),
|
|
68
|
+
processors=[],
|
|
69
|
+
)
|
|
70
|
+
restore = _attach_structlog(sdk)
|
|
71
|
+
try:
|
|
72
|
+
logger = structlog.get_logger().bind(old="gone").unbind("old").bind(retained=True)
|
|
73
|
+
asyncio.run(logger.awarning("suppressed"))
|
|
74
|
+
asyncio.run(logger.aerror("accepted"))
|
|
75
|
+
assert sdk.events == [("accepted", "error", {"retained": True})]
|
|
76
|
+
finally:
|
|
77
|
+
restore()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_structlog_capture_failures_do_not_change_native_result_or_repeat_processors():
|
|
81
|
+
sdk = Capture()
|
|
82
|
+
calls = []
|
|
83
|
+
|
|
84
|
+
def processor(_logger, _name, event):
|
|
85
|
+
calls.append(event["event"])
|
|
86
|
+
return event
|
|
87
|
+
|
|
88
|
+
def fail(*_args, **_kwargs):
|
|
89
|
+
raise RuntimeError("SDK failed")
|
|
90
|
+
|
|
91
|
+
sdk.capture_log = fail
|
|
92
|
+
structlog.configure(logger_factory=structlog.ReturnLoggerFactory(), processors=[processor])
|
|
93
|
+
restore = _attach_structlog(sdk)
|
|
94
|
+
try:
|
|
95
|
+
result = structlog.get_logger().error("accepted", value=1)
|
|
96
|
+
assert result == ((), {"value": 1, "event": "accepted"})
|
|
97
|
+
assert calls == ["accepted"]
|
|
98
|
+
finally:
|
|
99
|
+
restore()
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def test_structlog_numeric_log_is_filtered():
|
|
103
|
+
sdk = Capture()
|
|
104
|
+
structlog.configure(
|
|
105
|
+
wrapper_class=structlog.make_filtering_bound_logger(logging.WARNING),
|
|
106
|
+
logger_factory=structlog.ReturnLoggerFactory(), processors=[],
|
|
107
|
+
)
|
|
108
|
+
restore = _attach_structlog(sdk)
|
|
109
|
+
try:
|
|
110
|
+
logger = structlog.get_logger()
|
|
111
|
+
logger.log(logging.DEBUG, "suppressed")
|
|
112
|
+
logger.log(logging.ERROR, "accepted")
|
|
113
|
+
assert [event[:2] for event in sdk.events] == [("accepted", "error")]
|
|
114
|
+
finally:
|
|
115
|
+
restore()
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_structlog_stdlib_filters_and_non_propagating_loggers_capture_once():
|
|
119
|
+
sdk = DebugBundleSdk(transport=lambda _request: {"status": 202})
|
|
120
|
+
native = logging.getLogger("debugbundle-stdlib-suppression-test")
|
|
121
|
+
previous = (native.level, native.propagate, native.disabled)
|
|
122
|
+
native.setLevel(logging.ERROR)
|
|
123
|
+
native.propagate = False
|
|
124
|
+
reject = logging.Filter()
|
|
125
|
+
reject.filter = lambda record: record.getMessage() != "drop"
|
|
126
|
+
native.addFilter(reject)
|
|
127
|
+
structlog.configure(
|
|
128
|
+
wrapper_class=structlog.stdlib.BoundLogger,
|
|
129
|
+
logger_factory=lambda: native,
|
|
130
|
+
processors=[structlog.processors.EventRenamer("msg"), lambda _log, _name, event: ((event.pop("msg"),), event)],
|
|
131
|
+
)
|
|
132
|
+
sdk.init(project_token="dbundle_proj_test", service="logger-test", environment="production")
|
|
133
|
+
try:
|
|
134
|
+
sdk.capture_logging()
|
|
135
|
+
logger = structlog.get_logger()
|
|
136
|
+
logger.warning("below level")
|
|
137
|
+
logger.error("drop")
|
|
138
|
+
native.disabled = True
|
|
139
|
+
logger.error("disabled")
|
|
140
|
+
native.disabled = False
|
|
141
|
+
logging.disable(logging.CRITICAL)
|
|
142
|
+
logger.error("globally disabled")
|
|
143
|
+
logging.disable(logging.NOTSET)
|
|
144
|
+
logger.error("accepted")
|
|
145
|
+
assert [event["payload"]["message"] for event in sdk._buffer] == ["accepted"]
|
|
146
|
+
finally:
|
|
147
|
+
logging.disable(logging.NOTSET)
|
|
148
|
+
sdk.dispose()
|
|
149
|
+
native.removeFilter(reject)
|
|
150
|
+
native.setLevel(previous[0])
|
|
151
|
+
native.propagate = previous[1]
|
|
152
|
+
native.disabled = previous[2]
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def test_structlog_native_exceptions_are_not_retried():
|
|
156
|
+
sdk = Capture()
|
|
157
|
+
calls = []
|
|
158
|
+
|
|
159
|
+
def fail_processor(_logger, _method, _event):
|
|
160
|
+
calls.append(_event)
|
|
161
|
+
raise ValueError("application processor failed")
|
|
162
|
+
|
|
163
|
+
structlog.configure(processors=[fail_processor])
|
|
164
|
+
restore = _attach_structlog(sdk)
|
|
165
|
+
try:
|
|
166
|
+
with pytest.raises(ValueError, match="application processor failed"):
|
|
167
|
+
structlog.get_logger().error("do not retry")
|
|
168
|
+
assert sdk.events == []
|
|
169
|
+
assert len(calls) == 1
|
|
170
|
+
finally:
|
|
171
|
+
restore()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/src/debugbundle/integrations/__init__.py
RENAMED
|
File without changes
|
{debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/src/debugbundle/integrations/common.py
RENAMED
|
File without changes
|
{debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/src/debugbundle/integrations/django.py
RENAMED
|
File without changes
|
{debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/src/debugbundle/integrations/fastapi.py
RENAMED
|
File without changes
|
|
File without changes
|
{debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/src/debugbundle/integrations/relay_django.py
RENAMED
|
File without changes
|
{debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/src/debugbundle/integrations/relay_fastapi.py
RENAMED
|
File without changes
|
{debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/src/debugbundle/integrations/relay_flask.py
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
|
{debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/src/debugbundle_python.egg-info/requires.txt
RENAMED
|
File without changes
|
{debugbundle_python-1.4.0 → debugbundle_python-1.4.1}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|