policyengine-observability 1.3.0__tar.gz → 1.3.2__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.
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/CHANGELOG.md +14 -0
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/PKG-INFO +7 -7
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/policyengine_observability/destinations/manager.py +37 -0
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/pyproject.toml +7 -7
- policyengine_observability-1.3.2/tests/test_destinations.py +210 -0
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/uv.lock +43 -43
- policyengine_observability-1.3.0/tests/test_destinations.py +0 -102
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/.github/bump_version.py +0 -0
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/.github/check-changelog.sh +0 -0
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/.github/copilot-instructions.md +0 -0
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/.github/fetch_version.py +0 -0
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/.github/get-changelog-diff.sh +0 -0
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/.github/publish-git-tag.sh +0 -0
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/.github/workflows/pr.yml +0 -0
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/.github/workflows/push.yml +0 -0
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/.gitignore +0 -0
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/AGENTS.md +0 -0
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/CLAUDE.md +0 -0
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/README.md +0 -0
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/docs/engineering/skills/README.md +0 -0
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/docs/engineering/skills/github-prs.md +0 -0
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/docs/engineering/skills/repository-guidance.md +0 -0
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/docs/operations/google-cloud-stage3-runbook.md +0 -0
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/policyengine_observability/__init__.py +0 -0
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/policyengine_observability/adapters/__init__.py +0 -0
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/policyengine_observability/adapters/fastapi.py +0 -0
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/policyengine_observability/adapters/flask.py +0 -0
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/policyengine_observability/config.py +0 -0
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/policyengine_observability/context.py +0 -0
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/policyengine_observability/destinations/__init__.py +0 -0
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/policyengine_observability/destinations/base.py +0 -0
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/policyengine_observability/destinations/google_cloud_logging.py +0 -0
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/policyengine_observability/destinations/stdout.py +0 -0
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/policyengine_observability/google_credentials.py +0 -0
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/policyengine_observability/integrations/__init__.py +0 -0
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/policyengine_observability/integrations/httpx.py +0 -0
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/policyengine_observability/logging.py +0 -0
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/policyengine_observability/runtime.py +0 -0
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/policyengine_observability/segments.py +0 -0
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/tests/test_fastapi_adapter.py +0 -0
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/tests/test_flask_adapter.py +0 -0
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/tests/test_google_credentials.py +0 -0
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/tests/test_public_api.py +0 -0
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/tests/test_release_scripts.py +0 -0
- {policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/tests/test_runtime.py +0 -0
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [1.3.2] - 2026-07-03
|
|
2
|
+
|
|
3
|
+
### Fixed
|
|
4
|
+
|
|
5
|
+
- Disable a log destination after repeated consecutive emit failures, restoring stdout if none remain — an observability sink can no longer degrade its host service's request path.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
## [1.3.1] - 2026-07-02
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Set coherent minimum versions across the OpenTelemetry dependency stack: `opentelemetry-api`, `-sdk`, `-exporter-otlp-proto-grpc`, and `-exporter-otlp-proto-http` to `>=1.43.0`, and `opentelemetry-instrumentation-fastapi` and `-httpx` to `>=0.64b0`. These forward-pin the whole stack to the coordinated OpenTelemetry release that fixes `AttributeError: '_IncludedRouter' object has no attribute 'path'` — which earlier `opentelemetry-instrumentation-fastapi` raised on FastAPI >= 0.137 `include_router` routing, turning every CORS preflight `OPTIONS` request into a 500 for consumers that enable FastAPI instrumentation.
|
|
13
|
+
|
|
14
|
+
|
|
1
15
|
## [1.3.0] - 2026-07-01
|
|
2
16
|
|
|
3
17
|
### Added
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: policyengine-observability
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.2
|
|
4
4
|
Summary: Shared PolicyEngine observability runtime for logs, timings, metrics, and OpenTelemetry.
|
|
5
5
|
Author-email: PolicyEngine <hello@policyengine.org>
|
|
6
6
|
Requires-Python: >=3.12
|
|
7
|
-
Requires-Dist: opentelemetry-api
|
|
8
|
-
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc
|
|
9
|
-
Requires-Dist: opentelemetry-exporter-otlp-proto-http
|
|
10
|
-
Requires-Dist: opentelemetry-instrumentation-fastapi
|
|
11
|
-
Requires-Dist: opentelemetry-instrumentation-httpx
|
|
12
|
-
Requires-Dist: opentelemetry-sdk
|
|
7
|
+
Requires-Dist: opentelemetry-api>=1.43.0
|
|
8
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc>=1.43.0
|
|
9
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.43.0
|
|
10
|
+
Requires-Dist: opentelemetry-instrumentation-fastapi>=0.64b0
|
|
11
|
+
Requires-Dist: opentelemetry-instrumentation-httpx>=0.64b0
|
|
12
|
+
Requires-Dist: opentelemetry-sdk>=1.43.0
|
|
13
13
|
Provides-Extra: all
|
|
14
14
|
Requires-Dist: fastapi; extra == 'all'
|
|
15
15
|
Requires-Dist: flask>=2.2; extra == 'all'
|
|
@@ -9,6 +9,13 @@ from .base import LogDestination
|
|
|
9
9
|
from .google_cloud_logging import GoogleCloudLoggingDestination
|
|
10
10
|
from .stdout import StdoutJsonDestination
|
|
11
11
|
|
|
12
|
+
# A destination that fails this many consecutive emits is disabled for the
|
|
13
|
+
# rest of the process. Emission is synchronous on the caller's (request) path,
|
|
14
|
+
# so a persistently failing destination — e.g. a credential exchange that
|
|
15
|
+
# errors after long internal retries — must not keep charging every request;
|
|
16
|
+
# an observability sink can never be allowed to degrade the host service.
|
|
17
|
+
DESTINATION_FAILURE_LIMIT = 3
|
|
18
|
+
|
|
12
19
|
|
|
13
20
|
class LogDestinationManager:
|
|
14
21
|
def __init__(
|
|
@@ -25,6 +32,7 @@ class LogDestinationManager:
|
|
|
25
32
|
self.on_failure = on_failure
|
|
26
33
|
self.destinations: list[LogDestination] = []
|
|
27
34
|
self.configured = False
|
|
35
|
+
self._consecutive_failures: dict[int, int] = {}
|
|
28
36
|
|
|
29
37
|
def configure(self) -> None:
|
|
30
38
|
failures: list[tuple[str, BaseException]] = []
|
|
@@ -62,6 +70,7 @@ class LogDestinationManager:
|
|
|
62
70
|
severity: str,
|
|
63
71
|
) -> None:
|
|
64
72
|
emitted_payload = {**payload, "severity": severity}
|
|
73
|
+
tripped: list[LogDestination] = []
|
|
65
74
|
for destination in self._ensure_destinations():
|
|
66
75
|
try:
|
|
67
76
|
destination.emit(
|
|
@@ -69,13 +78,23 @@ class LogDestinationManager:
|
|
|
69
78
|
log_type=log_type,
|
|
70
79
|
severity=severity,
|
|
71
80
|
)
|
|
81
|
+
self._consecutive_failures.pop(id(destination), None)
|
|
72
82
|
except BaseException as exc:
|
|
83
|
+
failures = (
|
|
84
|
+
self._consecutive_failures.get(id(destination), 0) + 1
|
|
85
|
+
)
|
|
86
|
+
self._consecutive_failures[id(destination)] = failures
|
|
87
|
+
if failures >= DESTINATION_FAILURE_LIMIT:
|
|
88
|
+
tripped.append(destination)
|
|
73
89
|
self.on_failure(
|
|
74
90
|
"logging.destination_emit",
|
|
75
91
|
exc,
|
|
76
92
|
destination=getattr(destination, "name", None),
|
|
77
93
|
log_type=log_type,
|
|
94
|
+
consecutive_failures=failures,
|
|
78
95
|
)
|
|
96
|
+
for destination in tripped:
|
|
97
|
+
self._disable_destination(destination)
|
|
79
98
|
|
|
80
99
|
def _ensure_destinations(self) -> list[LogDestination]:
|
|
81
100
|
if not self.configured:
|
|
@@ -87,6 +106,24 @@ class LogDestinationManager:
|
|
|
87
106
|
self.on_failure("logging.destination_config", exc)
|
|
88
107
|
return self.destinations
|
|
89
108
|
|
|
109
|
+
def _disable_destination(self, destination: LogDestination) -> None:
|
|
110
|
+
self.destinations = [
|
|
111
|
+
existing
|
|
112
|
+
for existing in self.destinations
|
|
113
|
+
if existing is not destination
|
|
114
|
+
]
|
|
115
|
+
self._consecutive_failures.pop(id(destination), None)
|
|
116
|
+
self.on_failure(
|
|
117
|
+
"logging.destination_disabled",
|
|
118
|
+
RuntimeError(
|
|
119
|
+
"Disabling observability log destination after "
|
|
120
|
+
f"{DESTINATION_FAILURE_LIMIT} consecutive emit failures."
|
|
121
|
+
),
|
|
122
|
+
destination=getattr(destination, "name", None),
|
|
123
|
+
)
|
|
124
|
+
if not self.destinations:
|
|
125
|
+
self.destinations.append(self._stdout_destination())
|
|
126
|
+
|
|
90
127
|
def _build_destination(self, destination_name: str) -> LogDestination:
|
|
91
128
|
normalized = destination_name.strip().lower().replace("-", "_")
|
|
92
129
|
if normalized == "stdout":
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "policyengine-observability"
|
|
3
|
-
version = "1.3.
|
|
3
|
+
version = "1.3.2"
|
|
4
4
|
description = "Shared PolicyEngine observability runtime for logs, timings, metrics, and OpenTelemetry."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [{ name = "PolicyEngine", email = "hello@policyengine.org" }]
|
|
7
7
|
requires-python = ">=3.12"
|
|
8
8
|
dependencies = [
|
|
9
|
-
"opentelemetry-api",
|
|
10
|
-
"opentelemetry-sdk",
|
|
11
|
-
"opentelemetry-exporter-otlp-proto-grpc",
|
|
12
|
-
"opentelemetry-exporter-otlp-proto-http",
|
|
13
|
-
"opentelemetry-instrumentation-fastapi",
|
|
14
|
-
"opentelemetry-instrumentation-httpx",
|
|
9
|
+
"opentelemetry-api>=1.43.0",
|
|
10
|
+
"opentelemetry-sdk>=1.43.0",
|
|
11
|
+
"opentelemetry-exporter-otlp-proto-grpc>=1.43.0",
|
|
12
|
+
"opentelemetry-exporter-otlp-proto-http>=1.43.0",
|
|
13
|
+
"opentelemetry-instrumentation-fastapi>=0.64b0",
|
|
14
|
+
"opentelemetry-instrumentation-httpx>=0.64b0",
|
|
15
15
|
]
|
|
16
16
|
|
|
17
17
|
[project.optional-dependencies]
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from policyengine_observability.destinations import (
|
|
4
|
+
GoogleCloudLoggingDestination,
|
|
5
|
+
google_cloud_logging,
|
|
6
|
+
normalize_payload,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Unprintable:
|
|
11
|
+
def __str__(self) -> str:
|
|
12
|
+
raise RuntimeError("cannot stringify")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class FakeLogger:
|
|
16
|
+
def __init__(self) -> None:
|
|
17
|
+
self.calls = []
|
|
18
|
+
|
|
19
|
+
def log_struct(self, payload, **kwargs) -> None:
|
|
20
|
+
self.calls.append((payload, kwargs))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class FakeClient:
|
|
24
|
+
def __init__(self) -> None:
|
|
25
|
+
self.project = "resolved-project"
|
|
26
|
+
self.fake_logger = FakeLogger()
|
|
27
|
+
self.log_names = []
|
|
28
|
+
|
|
29
|
+
def logger(self, log_name: str) -> FakeLogger:
|
|
30
|
+
self.log_names.append(log_name)
|
|
31
|
+
return self.fake_logger
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_normalize_payload_recursively_stringifies_unsafe_values() -> None:
|
|
35
|
+
normalized = normalize_payload(
|
|
36
|
+
{
|
|
37
|
+
"keep": "value",
|
|
38
|
+
"drop_none": None,
|
|
39
|
+
"bytes": b"value",
|
|
40
|
+
"list": [1, Unprintable()],
|
|
41
|
+
"nested": {"object": object()},
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
assert normalized["keep"] == "value"
|
|
46
|
+
assert normalized["drop_none"] is None
|
|
47
|
+
assert normalized["bytes"] == "value"
|
|
48
|
+
assert normalized["list"] == [1, "<unprintable Unprintable>"]
|
|
49
|
+
assert normalized["nested"]["object"].startswith("<object object at ")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_google_destination_writes_structured_log_with_bounded_labels(
|
|
53
|
+
monkeypatch,
|
|
54
|
+
) -> None:
|
|
55
|
+
monkeypatch.setattr(
|
|
56
|
+
google_cloud_logging,
|
|
57
|
+
"load_google_credentials",
|
|
58
|
+
lambda *, prefer_workload_identity: None,
|
|
59
|
+
)
|
|
60
|
+
monkeypatch.setattr(
|
|
61
|
+
google_cloud_logging,
|
|
62
|
+
"configure_google_application_credentials",
|
|
63
|
+
lambda: None,
|
|
64
|
+
)
|
|
65
|
+
client = FakeClient()
|
|
66
|
+
destination = GoogleCloudLoggingDestination(
|
|
67
|
+
project=None,
|
|
68
|
+
log_name="policyengine-observability",
|
|
69
|
+
client_factory=lambda _project, _credentials: client,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
destination.emit(
|
|
73
|
+
{
|
|
74
|
+
"schema_version": "policyengine.observability.request.v1",
|
|
75
|
+
"service_name": "svc",
|
|
76
|
+
"service_role": "api",
|
|
77
|
+
"environment": "production",
|
|
78
|
+
"request_id": "request-1",
|
|
79
|
+
"trace_id": "abc123",
|
|
80
|
+
"span_id": "def456",
|
|
81
|
+
"path": "/calculate",
|
|
82
|
+
"object": object(),
|
|
83
|
+
},
|
|
84
|
+
log_type="request",
|
|
85
|
+
severity="ERROR",
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
payload, kwargs = client.fake_logger.calls[0]
|
|
89
|
+
assert client.log_names == ["policyengine-observability"]
|
|
90
|
+
assert payload["object"].startswith("<object object at ")
|
|
91
|
+
assert kwargs["severity"] == "ERROR"
|
|
92
|
+
assert kwargs["trace"] == "projects/resolved-project/traces/abc123"
|
|
93
|
+
assert kwargs["span_id"] == "def456"
|
|
94
|
+
assert kwargs["labels"] == {
|
|
95
|
+
"log_type": "request",
|
|
96
|
+
"service_name": "svc",
|
|
97
|
+
"service_role": "api",
|
|
98
|
+
"environment": "production",
|
|
99
|
+
"schema_version": "policyengine.observability.request.v1",
|
|
100
|
+
}
|
|
101
|
+
assert "request_id" not in kwargs["labels"]
|
|
102
|
+
assert "path" not in kwargs["labels"]
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# ── Destination circuit breaker ──────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class FlakyDestination:
|
|
109
|
+
name = "flaky"
|
|
110
|
+
|
|
111
|
+
def __init__(self, fail_first: int | None = None) -> None:
|
|
112
|
+
self.calls = 0
|
|
113
|
+
self.fail_first = fail_first
|
|
114
|
+
|
|
115
|
+
def emit(self, payload, *, log_type, severity) -> None:
|
|
116
|
+
self.calls += 1
|
|
117
|
+
if self.fail_first is None or self.calls <= self.fail_first:
|
|
118
|
+
raise RuntimeError("emit failed")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class RecordingDestination:
|
|
122
|
+
name = "recording"
|
|
123
|
+
|
|
124
|
+
def __init__(self) -> None:
|
|
125
|
+
self.payloads = []
|
|
126
|
+
|
|
127
|
+
def emit(self, payload, *, log_type, severity) -> None:
|
|
128
|
+
self.payloads.append(payload)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _manager(destinations):
|
|
132
|
+
import json
|
|
133
|
+
import logging
|
|
134
|
+
|
|
135
|
+
from policyengine_observability.config import ObservabilityConfig
|
|
136
|
+
from policyengine_observability.destinations.manager import (
|
|
137
|
+
LogDestinationManager,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
failures = []
|
|
141
|
+
manager = LogDestinationManager(
|
|
142
|
+
config=ObservabilityConfig(),
|
|
143
|
+
loggers={"event": logging.getLogger("test-destinations")},
|
|
144
|
+
serializer=json.dumps,
|
|
145
|
+
on_failure=lambda operation, exc, **fields: failures.append(
|
|
146
|
+
(operation, fields)
|
|
147
|
+
),
|
|
148
|
+
)
|
|
149
|
+
manager.destinations = list(destinations)
|
|
150
|
+
manager.configured = True
|
|
151
|
+
return manager, failures
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def test_destination_disabled_after_consecutive_emit_failures() -> None:
|
|
155
|
+
from policyengine_observability.destinations.manager import (
|
|
156
|
+
DESTINATION_FAILURE_LIMIT,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
flaky = FlakyDestination()
|
|
160
|
+
healthy = RecordingDestination()
|
|
161
|
+
manager, failures = _manager([flaky, healthy])
|
|
162
|
+
|
|
163
|
+
for _ in range(DESTINATION_FAILURE_LIMIT + 2):
|
|
164
|
+
manager.emit({"event": "x"}, log_type="event", severity="INFO")
|
|
165
|
+
|
|
166
|
+
assert flaky.calls == DESTINATION_FAILURE_LIMIT
|
|
167
|
+
assert flaky not in manager.destinations
|
|
168
|
+
assert len(healthy.payloads) == DESTINATION_FAILURE_LIMIT + 2
|
|
169
|
+
assert any(op == "logging.destination_disabled" for op, _ in failures)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def test_emit_success_resets_the_failure_counter() -> None:
|
|
173
|
+
from policyengine_observability.destinations.manager import (
|
|
174
|
+
DESTINATION_FAILURE_LIMIT,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
flaky = FlakyDestination(fail_first=DESTINATION_FAILURE_LIMIT - 1)
|
|
178
|
+
manager, failures = _manager([flaky])
|
|
179
|
+
|
|
180
|
+
for _ in range(DESTINATION_FAILURE_LIMIT + 2):
|
|
181
|
+
manager.emit({"event": "x"}, log_type="event", severity="INFO")
|
|
182
|
+
|
|
183
|
+
assert flaky in manager.destinations
|
|
184
|
+
counts = [
|
|
185
|
+
fields["consecutive_failures"]
|
|
186
|
+
for op, fields in failures
|
|
187
|
+
if op == "logging.destination_emit"
|
|
188
|
+
]
|
|
189
|
+
assert max(counts) == DESTINATION_FAILURE_LIMIT - 1
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def test_sole_disabled_destination_falls_back_to_stdout() -> None:
|
|
193
|
+
from policyengine_observability.destinations.manager import (
|
|
194
|
+
DESTINATION_FAILURE_LIMIT,
|
|
195
|
+
)
|
|
196
|
+
from policyengine_observability.destinations.stdout import (
|
|
197
|
+
StdoutJsonDestination,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
flaky = FlakyDestination()
|
|
201
|
+
manager, failures = _manager([flaky])
|
|
202
|
+
|
|
203
|
+
for _ in range(DESTINATION_FAILURE_LIMIT + 1):
|
|
204
|
+
manager.emit({"event": "x"}, log_type="event", severity="INFO")
|
|
205
|
+
|
|
206
|
+
assert flaky not in manager.destinations
|
|
207
|
+
assert any(
|
|
208
|
+
isinstance(destination, StdoutJsonDestination)
|
|
209
|
+
for destination in manager.destinations
|
|
210
|
+
)
|
|
@@ -707,31 +707,31 @@ wheels = [
|
|
|
707
707
|
|
|
708
708
|
[[package]]
|
|
709
709
|
name = "opentelemetry-api"
|
|
710
|
-
version = "1.
|
|
710
|
+
version = "1.43.0"
|
|
711
711
|
source = { registry = "https://pypi.org/simple" }
|
|
712
712
|
dependencies = [
|
|
713
713
|
{ name = "typing-extensions" },
|
|
714
714
|
]
|
|
715
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
715
|
+
sdist = { url = "https://files.pythonhosted.org/packages/ae/cc/e4c9584181f86494df0f6bdec1a4f3280c50db44704dc2a407e994fc87bb/opentelemetry_api-1.43.0.tar.gz", hash = "sha256:107d0d03857ea8fc7c5fcbbbd83f800c281f0d560553d61c1d675fccfd1761c1", size = 73476, upload-time = "2026-06-24T15:19:55.323Z" }
|
|
716
716
|
wheels = [
|
|
717
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
717
|
+
{ url = "https://files.pythonhosted.org/packages/17/83/6dba32b85f31868400440dc7ad2ca1eab94cbbf3a7b0459ed39f8311a9e2/opentelemetry_api-1.43.0-py3-none-any.whl", hash = "sha256:20acf45e9b21851926835292e4045d290acade1edd2ff3de86d2f069687ba1fd", size = 61912, upload-time = "2026-06-24T15:19:35.434Z" },
|
|
718
718
|
]
|
|
719
719
|
|
|
720
720
|
[[package]]
|
|
721
721
|
name = "opentelemetry-exporter-otlp-proto-common"
|
|
722
|
-
version = "1.
|
|
722
|
+
version = "1.43.0"
|
|
723
723
|
source = { registry = "https://pypi.org/simple" }
|
|
724
724
|
dependencies = [
|
|
725
725
|
{ name = "opentelemetry-proto" },
|
|
726
726
|
]
|
|
727
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
727
|
+
sdist = { url = "https://files.pythonhosted.org/packages/55/c1/e8098490ab15abf116dcaf9fa89ededcb35547c7d08d4b5a62f573dc1e63/opentelemetry_exporter_otlp_proto_common-1.43.0.tar.gz", hash = "sha256:c4e32ba6d6b13bdb2b8f6764c4fd28d00192826561aa04f6d14eedfce7ac076f", size = 20197, upload-time = "2026-06-24T15:20:00.247Z" }
|
|
728
728
|
wheels = [
|
|
729
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
729
|
+
{ url = "https://files.pythonhosted.org/packages/d0/b2/41ebc74ae1d5859901f1b69305de58724bf043381103d6ef413521cbc35a/opentelemetry_exporter_otlp_proto_common-1.43.0-py3-none-any.whl", hash = "sha256:123c3f9cc87218562490c63b36f497bf3a722faf174a515d1443f31ababa6264", size = 17048, upload-time = "2026-06-24T15:19:41.264Z" },
|
|
730
730
|
]
|
|
731
731
|
|
|
732
732
|
[[package]]
|
|
733
733
|
name = "opentelemetry-exporter-otlp-proto-grpc"
|
|
734
|
-
version = "1.
|
|
734
|
+
version = "1.43.0"
|
|
735
735
|
source = { registry = "https://pypi.org/simple" }
|
|
736
736
|
dependencies = [
|
|
737
737
|
{ name = "googleapis-common-protos" },
|
|
@@ -742,14 +742,14 @@ dependencies = [
|
|
|
742
742
|
{ name = "opentelemetry-sdk" },
|
|
743
743
|
{ name = "typing-extensions" },
|
|
744
744
|
]
|
|
745
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
745
|
+
sdist = { url = "https://files.pythonhosted.org/packages/e2/1d/6336453716ca0a240d4417d19e6d5b77a5e7163e5670ec4f7ec4d3ede7bf/opentelemetry_exporter_otlp_proto_grpc-1.43.0.tar.gz", hash = "sha256:1b3e0627daa9bc21884d4a13946807c255eb558bfe5bdd543dffb6f4c9faee0d", size = 27213, upload-time = "2026-06-24T15:20:00.907Z" }
|
|
746
746
|
wheels = [
|
|
747
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
747
|
+
{ url = "https://files.pythonhosted.org/packages/6d/74/2700b5d5c946bf2dba87073fce3dfc198c46bc92ea3d5693f54bc51c90b1/opentelemetry_exporter_otlp_proto_grpc-1.43.0-py3-none-any.whl", hash = "sha256:6a10d1feacffffda19acacbf277b736094b1e2f4dbb98c90ccb2c6e1962e2ec6", size = 19626, upload-time = "2026-06-24T15:19:42.233Z" },
|
|
748
748
|
]
|
|
749
749
|
|
|
750
750
|
[[package]]
|
|
751
751
|
name = "opentelemetry-exporter-otlp-proto-http"
|
|
752
|
-
version = "1.
|
|
752
|
+
version = "1.43.0"
|
|
753
753
|
source = { registry = "https://pypi.org/simple" }
|
|
754
754
|
dependencies = [
|
|
755
755
|
{ name = "googleapis-common-protos" },
|
|
@@ -760,14 +760,14 @@ dependencies = [
|
|
|
760
760
|
{ name = "requests" },
|
|
761
761
|
{ name = "typing-extensions" },
|
|
762
762
|
]
|
|
763
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
763
|
+
sdist = { url = "https://files.pythonhosted.org/packages/fc/92/0b9f56412483a8891d4843890294796c9df8ab42417bd9bad8035d840cb3/opentelemetry_exporter_otlp_proto_http-1.43.0.tar.gz", hash = "sha256:fa8a42bb7d00ee5391f4c0b04d8e6a46c03caa437903296ab73a81dc11ba118f", size = 25406, upload-time = "2026-06-24T15:20:01.515Z" }
|
|
764
764
|
wheels = [
|
|
765
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
765
|
+
{ url = "https://files.pythonhosted.org/packages/b3/20/b685ed7af2e17c29ffc8af56f1fa8bc2033258fc30fb0d2b722f49d13ba0/opentelemetry_exporter_otlp_proto_http-1.43.0-py3-none-any.whl", hash = "sha256:647f603aa8efdbdb4dbff842e0729d0406a6fff26b295a72d3d60e7d963b2610", size = 21795, upload-time = "2026-06-24T15:19:43.164Z" },
|
|
766
766
|
]
|
|
767
767
|
|
|
768
768
|
[[package]]
|
|
769
769
|
name = "opentelemetry-instrumentation"
|
|
770
|
-
version = "0.
|
|
770
|
+
version = "0.64b0"
|
|
771
771
|
source = { registry = "https://pypi.org/simple" }
|
|
772
772
|
dependencies = [
|
|
773
773
|
{ name = "opentelemetry-api" },
|
|
@@ -775,14 +775,14 @@ dependencies = [
|
|
|
775
775
|
{ name = "packaging" },
|
|
776
776
|
{ name = "wrapt" },
|
|
777
777
|
]
|
|
778
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
778
|
+
sdist = { url = "https://files.pythonhosted.org/packages/7e/97/02fe6e1c8b1ffac42d0b429c18080edb24e0e0d18c86612edf72b5752382/opentelemetry_instrumentation-0.64b0.tar.gz", hash = "sha256:b47d528dead6271d7743114417eb67fc915bd9258111c48dbf9a4951d2efa88d", size = 41935, upload-time = "2026-06-24T15:19:12.951Z" }
|
|
779
779
|
wheels = [
|
|
780
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
780
|
+
{ url = "https://files.pythonhosted.org/packages/d2/0c/cb9fe342de5299c7af24582eb7d788661cc53a1c4b904da92309caaa9417/opentelemetry_instrumentation-0.64b0-py3-none-any.whl", hash = "sha256:133ab7ffca796557aec059bf6be3190a34b6dea987f25be3d9409e230cbdad8b", size = 35880, upload-time = "2026-06-24T15:18:17.277Z" },
|
|
781
781
|
]
|
|
782
782
|
|
|
783
783
|
[[package]]
|
|
784
784
|
name = "opentelemetry-instrumentation-asgi"
|
|
785
|
-
version = "0.
|
|
785
|
+
version = "0.64b0"
|
|
786
786
|
source = { registry = "https://pypi.org/simple" }
|
|
787
787
|
dependencies = [
|
|
788
788
|
{ name = "asgiref" },
|
|
@@ -791,14 +791,14 @@ dependencies = [
|
|
|
791
791
|
{ name = "opentelemetry-semantic-conventions" },
|
|
792
792
|
{ name = "opentelemetry-util-http" },
|
|
793
793
|
]
|
|
794
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
794
|
+
sdist = { url = "https://files.pythonhosted.org/packages/85/0c/71c696fccb86d37af383ea1604af4729fabad0af2fabaf203e4c79c1e859/opentelemetry_instrumentation_asgi-0.64b0.tar.gz", hash = "sha256:4dd3eee566a4303f8e6b9b84f2a0a7abc57a6640df768926c68a3868bf5b2090", size = 26136, upload-time = "2026-06-24T15:19:17.003Z" }
|
|
795
795
|
wheels = [
|
|
796
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
796
|
+
{ url = "https://files.pythonhosted.org/packages/51/20/218b65a63d847a7ed28d1bea84c39234689160b74480b8702272e37f4240/opentelemetry_instrumentation_asgi-0.64b0-py3-none-any.whl", hash = "sha256:e0840b66e15303a9254b0540946010bd008aa0504f4d89b8e1b7fb63490a36f0", size = 15906, upload-time = "2026-06-24T15:18:23.107Z" },
|
|
797
797
|
]
|
|
798
798
|
|
|
799
799
|
[[package]]
|
|
800
800
|
name = "opentelemetry-instrumentation-fastapi"
|
|
801
|
-
version = "0.
|
|
801
|
+
version = "0.64b0"
|
|
802
802
|
source = { registry = "https://pypi.org/simple" }
|
|
803
803
|
dependencies = [
|
|
804
804
|
{ name = "opentelemetry-api" },
|
|
@@ -807,14 +807,14 @@ dependencies = [
|
|
|
807
807
|
{ name = "opentelemetry-semantic-conventions" },
|
|
808
808
|
{ name = "opentelemetry-util-http" },
|
|
809
809
|
]
|
|
810
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
810
|
+
sdist = { url = "https://files.pythonhosted.org/packages/db/a1/52282e2cc5c08f4df13b087896d9907258fe2ff4f34035d3b21aa92684c3/opentelemetry_instrumentation_fastapi-0.64b0.tar.gz", hash = "sha256:05f75149929e433c1630de381688e650bf651c1e1cce7f9a7b649a807dac8a98", size = 26236, upload-time = "2026-06-24T15:19:27.219Z" }
|
|
811
811
|
wheels = [
|
|
812
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
812
|
+
{ url = "https://files.pythonhosted.org/packages/72/2d/4f48dc2d6f289f2febc5b871940dbea9f3b04ab9186d23342581b49cb984/opentelemetry_instrumentation_fastapi-0.64b0-py3-none-any.whl", hash = "sha256:43cbbfb2d3079dc81104478a2950ae93ac6d0e90a5020fa3987a236f8f2bdef1", size = 13263, upload-time = "2026-06-24T15:18:38.553Z" },
|
|
813
813
|
]
|
|
814
814
|
|
|
815
815
|
[[package]]
|
|
816
816
|
name = "opentelemetry-instrumentation-httpx"
|
|
817
|
-
version = "0.
|
|
817
|
+
version = "0.64b0"
|
|
818
818
|
source = { registry = "https://pypi.org/simple" }
|
|
819
819
|
dependencies = [
|
|
820
820
|
{ name = "opentelemetry-api" },
|
|
@@ -823,57 +823,57 @@ dependencies = [
|
|
|
823
823
|
{ name = "opentelemetry-util-http" },
|
|
824
824
|
{ name = "wrapt" },
|
|
825
825
|
]
|
|
826
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
826
|
+
sdist = { url = "https://files.pythonhosted.org/packages/0d/2a/2893a8781b93894f1e8014904c0342da7e4302de6597c2d5c0cb6c1a552e/opentelemetry_instrumentation_httpx-0.64b0.tar.gz", hash = "sha256:c2cfcd03d3665762860ebd0c28038c6e47fbb48d7942dec31dd75fc634d25c92", size = 23555, upload-time = "2026-06-24T15:19:29.107Z" }
|
|
827
827
|
wheels = [
|
|
828
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
828
|
+
{ url = "https://files.pythonhosted.org/packages/06/29/a20309bd3f5a8051b61ca475e78623410c3b077e3deccae19aa4f8b5b9a2/opentelemetry_instrumentation_httpx-0.64b0-py3-none-any.whl", hash = "sha256:04829e5723941b5ceb0c88b44d63983e226b5c75b2b2e34a57739fdd0e060608", size = 16336, upload-time = "2026-06-24T15:18:41.412Z" },
|
|
829
829
|
]
|
|
830
830
|
|
|
831
831
|
[[package]]
|
|
832
832
|
name = "opentelemetry-proto"
|
|
833
|
-
version = "1.
|
|
833
|
+
version = "1.43.0"
|
|
834
834
|
source = { registry = "https://pypi.org/simple" }
|
|
835
835
|
dependencies = [
|
|
836
836
|
{ name = "protobuf" },
|
|
837
837
|
]
|
|
838
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
838
|
+
sdist = { url = "https://files.pythonhosted.org/packages/e0/b9/d357faefb40bda1d4799913e6af611171ff22a2dedcb93576bc92242d056/opentelemetry_proto-1.43.0.tar.gz", hash = "sha256:224778df17e1f3fafeaaa21d874236ca5f6ffc2f86e0899298ec7351aac27924", size = 46481, upload-time = "2026-06-24T15:20:07.625Z" }
|
|
839
839
|
wheels = [
|
|
840
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
840
|
+
{ url = "https://files.pythonhosted.org/packages/ed/a7/3e5308cf548b8f72529c7db1afdb3a404211982376a12927fd7759f77bf3/opentelemetry_proto-1.43.0-py3-none-any.whl", hash = "sha256:c58f1f7ef84bc7dc2834016c0c37fe0081dde7ca9f6339be1970fbf9cdaaa90d", size = 72489, upload-time = "2026-06-24T15:19:51.164Z" },
|
|
841
841
|
]
|
|
842
842
|
|
|
843
843
|
[[package]]
|
|
844
844
|
name = "opentelemetry-sdk"
|
|
845
|
-
version = "1.
|
|
845
|
+
version = "1.43.0"
|
|
846
846
|
source = { registry = "https://pypi.org/simple" }
|
|
847
847
|
dependencies = [
|
|
848
848
|
{ name = "opentelemetry-api" },
|
|
849
849
|
{ name = "opentelemetry-semantic-conventions" },
|
|
850
850
|
{ name = "typing-extensions" },
|
|
851
851
|
]
|
|
852
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
852
|
+
sdist = { url = "https://files.pythonhosted.org/packages/3e/eb/5041074274ac0956b03637cc039d434569112468e875eddfcc9a0674ce06/opentelemetry_sdk-1.43.0.tar.gz", hash = "sha256:d8187c81c162df9913e4003dd6485f7390d9a24fc17026ec7387b8b8218b08e9", size = 254744, upload-time = "2026-06-24T15:20:08.467Z" }
|
|
853
853
|
wheels = [
|
|
854
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
854
|
+
{ url = "https://files.pythonhosted.org/packages/49/e3/b17be23af124201c9f52eececd4cc8ddfed1597d37b4ee771895d325805c/opentelemetry_sdk-1.43.0-py3-none-any.whl", hash = "sha256:d1323a547c1ce69d6a069a17a44b7da82bb8b332051ecb074041f87642c86823", size = 178852, upload-time = "2026-06-24T15:19:52.169Z" },
|
|
855
855
|
]
|
|
856
856
|
|
|
857
857
|
[[package]]
|
|
858
858
|
name = "opentelemetry-semantic-conventions"
|
|
859
|
-
version = "0.
|
|
859
|
+
version = "0.64b0"
|
|
860
860
|
source = { registry = "https://pypi.org/simple" }
|
|
861
861
|
dependencies = [
|
|
862
862
|
{ name = "opentelemetry-api" },
|
|
863
863
|
{ name = "typing-extensions" },
|
|
864
864
|
]
|
|
865
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
865
|
+
sdist = { url = "https://files.pythonhosted.org/packages/5a/30/5f26df29509eccd86b99b481ac9ffa39da49ba9577cc69071c552ae30447/opentelemetry_semantic_conventions-0.64b0.tar.gz", hash = "sha256:72f76fb2d1582d9d033dd1fcd84532e961e6ff3d90d24ba6fabc72975a83864c", size = 148340, upload-time = "2026-06-24T15:20:09.267Z" }
|
|
866
866
|
wheels = [
|
|
867
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
867
|
+
{ url = "https://files.pythonhosted.org/packages/f2/ca/23ba87a221b574a7c5a99d48849d80bfe8b047624681357e2b002e566187/opentelemetry_semantic_conventions-0.64b0-py3-none-any.whl", hash = "sha256:ea77e85e354b8f604ddbe5f3d9135216f982fa4d77e5859ac30f6d8a50505aa6", size = 203713, upload-time = "2026-06-24T15:19:53.339Z" },
|
|
868
868
|
]
|
|
869
869
|
|
|
870
870
|
[[package]]
|
|
871
871
|
name = "opentelemetry-util-http"
|
|
872
|
-
version = "0.
|
|
872
|
+
version = "0.64b0"
|
|
873
873
|
source = { registry = "https://pypi.org/simple" }
|
|
874
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
874
|
+
sdist = { url = "https://files.pythonhosted.org/packages/c3/1b/1029a805fd7242f7dfce91633b244c3b14a94d703232878f71e01ce862b1/opentelemetry_util_http-0.64b0.tar.gz", hash = "sha256:8a86a220dbfc56d736f47f1e5c4e7932a21fcf69052312e1bcf166444dc79322", size = 11102, upload-time = "2026-06-24T15:19:48.974Z" }
|
|
875
875
|
wheels = [
|
|
876
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
876
|
+
{ url = "https://files.pythonhosted.org/packages/1c/c7/5f8ec5b30546f2dc22cd5fc5759bce2ab5be6e89a2e710a405ac9ef64ed3/opentelemetry_util_http-0.64b0-py3-none-any.whl", hash = "sha256:c1e5350d25507c1afcd6076cf9ac062485a0a4f79cd9971366996fd3056bacdb", size = 8204, upload-time = "2026-06-24T15:19:09.02Z" },
|
|
877
877
|
]
|
|
878
878
|
|
|
879
879
|
[[package]]
|
|
@@ -896,7 +896,7 @@ wheels = [
|
|
|
896
896
|
|
|
897
897
|
[[package]]
|
|
898
898
|
name = "policyengine-observability"
|
|
899
|
-
version = "1.3.
|
|
899
|
+
version = "1.3.2"
|
|
900
900
|
source = { editable = "." }
|
|
901
901
|
dependencies = [
|
|
902
902
|
{ name = "opentelemetry-api" },
|
|
@@ -946,12 +946,12 @@ requires-dist = [
|
|
|
946
946
|
{ name = "google-cloud-logging", marker = "extra == 'google'", specifier = ">=3.15.0" },
|
|
947
947
|
{ name = "httpx", marker = "extra == 'all'" },
|
|
948
948
|
{ name = "httpx", marker = "extra == 'httpx'" },
|
|
949
|
-
{ name = "opentelemetry-api" },
|
|
950
|
-
{ name = "opentelemetry-exporter-otlp-proto-grpc" },
|
|
951
|
-
{ name = "opentelemetry-exporter-otlp-proto-http" },
|
|
952
|
-
{ name = "opentelemetry-instrumentation-fastapi" },
|
|
953
|
-
{ name = "opentelemetry-instrumentation-httpx" },
|
|
954
|
-
{ name = "opentelemetry-sdk" },
|
|
949
|
+
{ name = "opentelemetry-api", specifier = ">=1.43.0" },
|
|
950
|
+
{ name = "opentelemetry-exporter-otlp-proto-grpc", specifier = ">=1.43.0" },
|
|
951
|
+
{ name = "opentelemetry-exporter-otlp-proto-http", specifier = ">=1.43.0" },
|
|
952
|
+
{ name = "opentelemetry-instrumentation-fastapi", specifier = ">=0.64b0" },
|
|
953
|
+
{ name = "opentelemetry-instrumentation-httpx", specifier = ">=0.64b0" },
|
|
954
|
+
{ name = "opentelemetry-sdk", specifier = ">=1.43.0" },
|
|
955
955
|
{ name = "pytest", marker = "extra == 'dev'" },
|
|
956
956
|
{ name = "ruff", marker = "extra == 'dev'", specifier = ">=0.9.0" },
|
|
957
957
|
{ name = "towncrier", marker = "extra == 'dev'", specifier = ">=24.8.0" },
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from policyengine_observability.destinations import (
|
|
4
|
-
GoogleCloudLoggingDestination,
|
|
5
|
-
google_cloud_logging,
|
|
6
|
-
normalize_payload,
|
|
7
|
-
)
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class Unprintable:
|
|
11
|
-
def __str__(self) -> str:
|
|
12
|
-
raise RuntimeError("cannot stringify")
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class FakeLogger:
|
|
16
|
-
def __init__(self) -> None:
|
|
17
|
-
self.calls = []
|
|
18
|
-
|
|
19
|
-
def log_struct(self, payload, **kwargs) -> None:
|
|
20
|
-
self.calls.append((payload, kwargs))
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class FakeClient:
|
|
24
|
-
def __init__(self) -> None:
|
|
25
|
-
self.project = "resolved-project"
|
|
26
|
-
self.fake_logger = FakeLogger()
|
|
27
|
-
self.log_names = []
|
|
28
|
-
|
|
29
|
-
def logger(self, log_name: str) -> FakeLogger:
|
|
30
|
-
self.log_names.append(log_name)
|
|
31
|
-
return self.fake_logger
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def test_normalize_payload_recursively_stringifies_unsafe_values() -> None:
|
|
35
|
-
normalized = normalize_payload(
|
|
36
|
-
{
|
|
37
|
-
"keep": "value",
|
|
38
|
-
"drop_none": None,
|
|
39
|
-
"bytes": b"value",
|
|
40
|
-
"list": [1, Unprintable()],
|
|
41
|
-
"nested": {"object": object()},
|
|
42
|
-
}
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
assert normalized["keep"] == "value"
|
|
46
|
-
assert normalized["drop_none"] is None
|
|
47
|
-
assert normalized["bytes"] == "value"
|
|
48
|
-
assert normalized["list"] == [1, "<unprintable Unprintable>"]
|
|
49
|
-
assert normalized["nested"]["object"].startswith("<object object at ")
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def test_google_destination_writes_structured_log_with_bounded_labels(
|
|
53
|
-
monkeypatch,
|
|
54
|
-
) -> None:
|
|
55
|
-
monkeypatch.setattr(
|
|
56
|
-
google_cloud_logging,
|
|
57
|
-
"load_google_credentials",
|
|
58
|
-
lambda *, prefer_workload_identity: None,
|
|
59
|
-
)
|
|
60
|
-
monkeypatch.setattr(
|
|
61
|
-
google_cloud_logging,
|
|
62
|
-
"configure_google_application_credentials",
|
|
63
|
-
lambda: None,
|
|
64
|
-
)
|
|
65
|
-
client = FakeClient()
|
|
66
|
-
destination = GoogleCloudLoggingDestination(
|
|
67
|
-
project=None,
|
|
68
|
-
log_name="policyengine-observability",
|
|
69
|
-
client_factory=lambda _project, _credentials: client,
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
destination.emit(
|
|
73
|
-
{
|
|
74
|
-
"schema_version": "policyengine.observability.request.v1",
|
|
75
|
-
"service_name": "svc",
|
|
76
|
-
"service_role": "api",
|
|
77
|
-
"environment": "production",
|
|
78
|
-
"request_id": "request-1",
|
|
79
|
-
"trace_id": "abc123",
|
|
80
|
-
"span_id": "def456",
|
|
81
|
-
"path": "/calculate",
|
|
82
|
-
"object": object(),
|
|
83
|
-
},
|
|
84
|
-
log_type="request",
|
|
85
|
-
severity="ERROR",
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
payload, kwargs = client.fake_logger.calls[0]
|
|
89
|
-
assert client.log_names == ["policyengine-observability"]
|
|
90
|
-
assert payload["object"].startswith("<object object at ")
|
|
91
|
-
assert kwargs["severity"] == "ERROR"
|
|
92
|
-
assert kwargs["trace"] == "projects/resolved-project/traces/abc123"
|
|
93
|
-
assert kwargs["span_id"] == "def456"
|
|
94
|
-
assert kwargs["labels"] == {
|
|
95
|
-
"log_type": "request",
|
|
96
|
-
"service_name": "svc",
|
|
97
|
-
"service_role": "api",
|
|
98
|
-
"environment": "production",
|
|
99
|
-
"schema_version": "policyengine.observability.request.v1",
|
|
100
|
-
}
|
|
101
|
-
assert "request_id" not in kwargs["labels"]
|
|
102
|
-
assert "path" not in kwargs["labels"]
|
{policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/.github/bump_version.py
RENAMED
|
File without changes
|
{policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/.github/check-changelog.sh
RENAMED
|
File without changes
|
|
File without changes
|
{policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/.github/fetch_version.py
RENAMED
|
File without changes
|
{policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/.github/get-changelog-diff.sh
RENAMED
|
File without changes
|
{policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/.github/publish-git-tag.sh
RENAMED
|
File without changes
|
{policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/.github/workflows/pr.yml
RENAMED
|
File without changes
|
{policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/.github/workflows/push.yml
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
|
|
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
|
{policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/tests/test_fastapi_adapter.py
RENAMED
|
File without changes
|
{policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/tests/test_flask_adapter.py
RENAMED
|
File without changes
|
|
File without changes
|
{policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/tests/test_public_api.py
RENAMED
|
File without changes
|
{policyengine_observability-1.3.0 → policyengine_observability-1.3.2}/tests/test_release_scripts.py
RENAMED
|
File without changes
|
|
File without changes
|