Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/1018.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Records the gateway and the entrypoint write while an endpoint runs, such as exception records and events, now carry the endpoint's route template as `http.route` (for example `/jobs/{job_id}`) instead of `<unmatched>`, so the daily telemetry error report groups them by route. Every service now requires policyengine-observability 3.0.6, which reads the matched route each time it writes a record.
17 changes: 14 additions & 3 deletions docs/engineering/skills/observability.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,22 @@ appropriate `StagePlan` and call `plan.name(Stage.VALUE)`. Do not add span name
string literals in services or workers.

Open a span with `runtime.span(plan.name(Stage.VALUE))`, and an operation with
`runtime.operation(...)`. policyengine-observability 3.0.4 types both scopes
correctly: their `__exit__` and `__aexit__` return `Literal[False]`, so Pyright
knows an exception raised in the block propagates; see
`runtime.operation(...)`. policyengine-observability 3.0.4 and later type both
scopes correctly: their `__exit__` and `__aexit__` return `Literal[False]`, so
Pyright knows an exception raised in the block propagates; see
`docs/engineering/skills/type-checking.md`.

In every app instrumented with `init_simulation_observability` (the gateway,
the entrypoint and the executor's local FastAPI app), every record written
while a FastAPI endpoint runs, from `record_exception`, `event` or `log`,
carries the endpoint's route template as `http.route`, such as
`/jobs/{job_id}`, as the request's `request.completed` record does.
policyengine-observability 3.0.5 and later read the route when each record is
written; 3.0.4 wrote `<unmatched>` on every record written before the request
ended, because its middleware begins the request before FastAPI matches the
route. A record written before routing, such as one from a middleware, and a
request that matches no route still carry `<unmatched>`.

When adding a run configuration or runtime stage:

1. add it to `RunConfiguration` or `Stage`;
Expand Down
6 changes: 3 additions & 3 deletions docs/engineering/skills/type-checking.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,11 +174,11 @@ section is still in the registry fails the guard on every pull request.
- If an error shows a real bug, first write a test that demonstrates it, then
fix it.
- Open observability spans and operations with `runtime.span(...)` and
`runtime.operation(...)`. policyengine-observability 3.0.4 types them
correctly: their `__exit__` and `__aexit__` return `Literal[False]`, so
`runtime.operation(...)`. policyengine-observability 3.0.4 and later type
them correctly: their `__exit__` and `__aexit__` return `Literal[False]`, so
Pyright knows they never suppress an exception and doesn't report a variable
the block assigns as possibly unbound after it. Every package that depends
on policyengine-observability requires 3.0.4 or later, and
on policyengine-observability requires 3.0.6 or later, and
`libs/policyengine-simulation-observability/tests/test_scope_exit_typing.py`
fails if an older release is installed.
- Before reading a simulation's output frames, narrow
Expand Down
8 changes: 4 additions & 4 deletions libs/policyengine-simulation-contract/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion libs/policyengine-simulation-observability/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ requires-python = ">=3.13"
dependencies = [
"fastapi>=0.115.0",
"pydantic>=2.0",
"policyengine-observability[fastapi,google,otlp-grpc]>=3.0.4,<4",
"policyengine-observability[fastapi,google,otlp-grpc]>=3.0.6,<4",
]

[project.optional-dependencies]
Expand Down
8 changes: 4 additions & 4 deletions libs/policyengine-simulation-observability/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions libs/policyengine-stage12-persistence/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion projects/policyengine-simulation-entry/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ dependencies = [
"fastapi>=0.115.0,<1",
"httpx>=0.28.0,<1",
"modal>=1.4,<2",
"policyengine-observability[fastapi,google,httpx,otlp-grpc]>=3.0.4,<4",
"policyengine-observability[fastapi,google,httpx,otlp-grpc]>=3.0.6,<4",
"policyengine-simulation-contract",
"policyengine-simulation-observability",
"policyengine-stage12-persistence",
Expand Down
34 changes: 34 additions & 0 deletions projects/policyengine-simulation-entry/tests/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -789,6 +789,40 @@ def test_job_status_records_structured_backend_telemetry(
assert attributes["job_id"] == "fc-123"


def test_event_recorded_while_a_route_runs_carries_the_matched_route(
capsys: pytest.CaptureFixture[str],
):
backend = FakeBackend()
backend.responses[("GET", "/jobs/fc-123")] = response(202, {"status": "running"})
# The runtime keeps the standard output it finds when it is built, so the
# app is built after capsys replaces it.
app = create_app(
settings=make_settings(), backend=backend, auth_dependency=lambda: None
)

from fastapi.testclient import TestClient

with TestClient(app) as test_client:
capsys.readouterr()
job_response = test_client.get("/jobs/fc-123")
captured_output = capsys.readouterr().out

assert job_response.status_code == 202
routes_by_event_name: dict[object, list[object]] = {}
for output_line in captured_output.splitlines():
try:
record = json.loads(output_line)
except json.JSONDecodeError:
continue
routes_by_event_name.setdefault(record.get("event.name"), []).append(
record.get("http.route")
)
assert routes_by_event_name["simulation_entry_backend_response"] == [
"/jobs/{job_id}"
]
assert routes_by_event_name["request.completed"] == ["/jobs/{job_id}"]


def test_budget_window_routes_use_original_batch_id(client, backend):
backend.responses[("POST", "/simulate/economy/budget-window")] = response(
202,
Expand Down
10 changes: 5 additions & 5 deletions projects/policyengine-simulation-entry/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions projects/policyengine-simulation-executor/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ dependencies = [
"policyengine[models]==6.12.1",
"tables>=3.10.2",
"modal>=0.73.0",
"policyengine-observability[fastapi,google,otlp-grpc]>=3.0.4,<4",
"policyengine-observability[fastapi,google,otlp-grpc]>=3.0.6,<4",
# Imported directly by the artifact store client; do not rely on the
# transitive pin via policyengine.
"google-cloud-storage>=2",
Expand All @@ -49,7 +49,7 @@ modal-simulation-image = [
"policyengine[models]==6.12.1",
"fastapi>=0.115.0",
"tables>=3.10.2",
"policyengine-observability[fastapi,google,otlp-grpc]>=3.0.4,<4",
"policyengine-observability[fastapi,google,otlp-grpc]>=3.0.6,<4",
# The artifact fetch layer and store client read GCS. The lib is also a
# transitive dependency of policyengine, but the image must not depend
# on an upstream package keeping it.
Expand Down
14 changes: 7 additions & 7 deletions projects/policyengine-simulation-executor/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion projects/policyengine-simulation-gateway/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ dependencies = [
"pyjwt>=2.10.1,<3.0.0",
# JWTDecoder (policyengine-fastapi lib) needs cryptography at runtime.
"cryptography>=41.0.0",
"policyengine-observability[fastapi,google,otlp-grpc]>=3.0.4,<4",
"policyengine-observability[fastapi,google,otlp-grpc]>=3.0.6,<4",
"modal>=1.4,<2",
]

Expand Down
Loading
Loading