policyengine-observability 0.2.0__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-0.2.0/.github/bump_version.py +134 -0
- policyengine_observability-0.2.0/.github/check-changelog.sh +6 -0
- policyengine_observability-0.2.0/.github/fetch_version.py +23 -0
- policyengine_observability-0.2.0/.github/get-changelog-diff.sh +9 -0
- policyengine_observability-0.2.0/.github/publish-git-tag.sh +12 -0
- policyengine_observability-0.2.0/.github/workflows/pr.yml +69 -0
- policyengine_observability-0.2.0/.github/workflows/push.yml +158 -0
- policyengine_observability-0.2.0/.gitignore +8 -0
- policyengine_observability-0.2.0/CHANGELOG.md +9 -0
- policyengine_observability-0.2.0/PKG-INFO +52 -0
- policyengine_observability-0.2.0/README.md +13 -0
- policyengine_observability-0.2.0/policyengine_observability/__init__.py +166 -0
- policyengine_observability-0.2.0/policyengine_observability/adapters/__init__.py +1 -0
- policyengine_observability-0.2.0/policyengine_observability/adapters/fastapi.py +286 -0
- policyengine_observability-0.2.0/policyengine_observability/adapters/flask.py +132 -0
- policyengine_observability-0.2.0/policyengine_observability/config.py +164 -0
- policyengine_observability-0.2.0/policyengine_observability/context.py +238 -0
- policyengine_observability-0.2.0/policyengine_observability/integrations/__init__.py +1 -0
- policyengine_observability-0.2.0/policyengine_observability/integrations/httpx.py +8 -0
- policyengine_observability-0.2.0/policyengine_observability/logging.py +17 -0
- policyengine_observability-0.2.0/policyengine_observability/runtime.py +1784 -0
- policyengine_observability-0.2.0/policyengine_observability/segments.py +35 -0
- policyengine_observability-0.2.0/pyproject.toml +122 -0
- policyengine_observability-0.2.0/tests/test_fastapi_adapter.py +334 -0
- policyengine_observability-0.2.0/tests/test_flask_adapter.py +170 -0
- policyengine_observability-0.2.0/tests/test_public_api.py +101 -0
- policyengine_observability-0.2.0/tests/test_release_scripts.py +76 -0
- policyengine_observability-0.2.0/tests/test_runtime.py +1415 -0
- policyengine_observability-0.2.0/uv.lock +1042 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Infer semver bump from Towncrier fragment types and update version."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
SEMVER_PATTERN = re.compile(r"^(\d+)\.(\d+)\.(\d+)$")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def parse_version(version: str) -> tuple[int, int, int]:
|
|
14
|
+
match = SEMVER_PATTERN.match(version)
|
|
15
|
+
if not match:
|
|
16
|
+
raise ValueError(f"Invalid semver: {version}")
|
|
17
|
+
return tuple(int(part) for part in match.groups())
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_pyproject_version(pyproject_path: Path) -> str:
|
|
21
|
+
text = pyproject_path.read_text()
|
|
22
|
+
match = re.search(r'^version\s*=\s*"(\d+\.\d+\.\d+)"', text, re.MULTILINE)
|
|
23
|
+
if not match:
|
|
24
|
+
print("Could not find version in pyproject.toml", file=sys.stderr)
|
|
25
|
+
sys.exit(1)
|
|
26
|
+
return match.group(1)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_changelog_versions(changelog_path: Path) -> list[str]:
|
|
30
|
+
if not changelog_path.exists():
|
|
31
|
+
return []
|
|
32
|
+
return re.findall(
|
|
33
|
+
r"^## \[(\d+\.\d+\.\d+)\]",
|
|
34
|
+
changelog_path.read_text(),
|
|
35
|
+
re.MULTILINE,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def get_git_tag_versions(repo_root: Path) -> list[str]:
|
|
40
|
+
try:
|
|
41
|
+
result = subprocess.run(
|
|
42
|
+
["git", "tag"],
|
|
43
|
+
cwd=repo_root,
|
|
44
|
+
capture_output=True,
|
|
45
|
+
text=True,
|
|
46
|
+
check=True,
|
|
47
|
+
)
|
|
48
|
+
except (FileNotFoundError, subprocess.CalledProcessError):
|
|
49
|
+
return []
|
|
50
|
+
|
|
51
|
+
versions = []
|
|
52
|
+
for tag in result.stdout.splitlines():
|
|
53
|
+
normalized = tag.removeprefix("v").strip()
|
|
54
|
+
if SEMVER_PATTERN.match(normalized):
|
|
55
|
+
versions.append(normalized)
|
|
56
|
+
return versions
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def get_current_version(
|
|
60
|
+
pyproject_path: Path,
|
|
61
|
+
changelog_path: Path,
|
|
62
|
+
repo_root: Path,
|
|
63
|
+
) -> str:
|
|
64
|
+
candidates = [get_pyproject_version(pyproject_path)]
|
|
65
|
+
candidates.extend(get_changelog_versions(changelog_path))
|
|
66
|
+
candidates.extend(get_git_tag_versions(repo_root))
|
|
67
|
+
return max(candidates, key=parse_version)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def infer_bump(changelog_dir: Path) -> str:
|
|
71
|
+
fragments = [
|
|
72
|
+
path
|
|
73
|
+
for path in changelog_dir.iterdir()
|
|
74
|
+
if path.is_file() and path.name != ".gitkeep"
|
|
75
|
+
]
|
|
76
|
+
if not fragments:
|
|
77
|
+
print("No changelog fragments found", file=sys.stderr)
|
|
78
|
+
sys.exit(1)
|
|
79
|
+
|
|
80
|
+
categories = {path.suffix.lstrip(".") for path in fragments}
|
|
81
|
+
for path in fragments:
|
|
82
|
+
parts = path.stem.split(".")
|
|
83
|
+
if len(parts) >= 2:
|
|
84
|
+
categories.add(parts[-1])
|
|
85
|
+
|
|
86
|
+
if "breaking" in categories:
|
|
87
|
+
return "major"
|
|
88
|
+
if "added" in categories or "removed" in categories:
|
|
89
|
+
return "minor"
|
|
90
|
+
return "patch"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def bump_version(version: str, bump: str) -> str:
|
|
94
|
+
major, minor, patch = (int(part) for part in version.split("."))
|
|
95
|
+
if bump == "major":
|
|
96
|
+
return f"{major + 1}.0.0"
|
|
97
|
+
if bump == "minor":
|
|
98
|
+
return f"{major}.{minor + 1}.0"
|
|
99
|
+
return f"{major}.{minor}.{patch + 1}"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def update_file(path: Path, new_version: str) -> None:
|
|
103
|
+
text = path.read_text()
|
|
104
|
+
updated, replacements = re.subn(
|
|
105
|
+
r'(^version\s*=\s*")(\d+\.\d+\.\d+)(")',
|
|
106
|
+
rf"\g<1>{new_version}\g<3>",
|
|
107
|
+
text,
|
|
108
|
+
count=1,
|
|
109
|
+
flags=re.MULTILINE,
|
|
110
|
+
)
|
|
111
|
+
if replacements == 0:
|
|
112
|
+
print(f"Could not update version in {path}", file=sys.stderr)
|
|
113
|
+
sys.exit(1)
|
|
114
|
+
if updated != text:
|
|
115
|
+
path.write_text(updated)
|
|
116
|
+
print(f" Updated {path}")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def main() -> None:
|
|
120
|
+
root = Path(__file__).resolve().parent.parent
|
|
121
|
+
pyproject = root / "pyproject.toml"
|
|
122
|
+
changelog = root / "CHANGELOG.md"
|
|
123
|
+
changelog_dir = root / "changelog.d"
|
|
124
|
+
|
|
125
|
+
current = get_current_version(pyproject, changelog, root)
|
|
126
|
+
bump = infer_bump(changelog_dir)
|
|
127
|
+
new = bump_version(current, bump)
|
|
128
|
+
|
|
129
|
+
print(f"Version: {current} -> {new} ({bump})")
|
|
130
|
+
update_file(pyproject, new)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
if __name__ == "__main__":
|
|
134
|
+
main()
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def fetch_version(pyproject_path: Path) -> str:
|
|
9
|
+
text = pyproject_path.read_text()
|
|
10
|
+
match = re.search(r'^version\s*=\s*"(\d+\.\d+\.\d+)"', text, re.MULTILINE)
|
|
11
|
+
if not match:
|
|
12
|
+
print("Could not find version in pyproject.toml", file=sys.stderr)
|
|
13
|
+
sys.exit(1)
|
|
14
|
+
return match.group(1)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def main() -> None:
|
|
18
|
+
root = Path(__file__).resolve().parent.parent
|
|
19
|
+
print(fetch_version(root / "pyproject.toml"))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
if __name__ == "__main__":
|
|
23
|
+
main()
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
if git describe --tags --abbrev=0 --first-parent >/dev/null 2>&1; then
|
|
5
|
+
LAST_TAGGED_COMMIT="$(git describe --tags --abbrev=0 --first-parent)"
|
|
6
|
+
git --no-pager diff "$LAST_TAGGED_COMMIT" -- CHANGELOG.md
|
|
7
|
+
else
|
|
8
|
+
git --no-pager diff HEAD -- CHANGELOG.md
|
|
9
|
+
fi
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
VERSION="$(python .github/fetch_version.py)"
|
|
5
|
+
|
|
6
|
+
if git rev-parse --verify --quiet "refs/tags/${VERSION}" >/dev/null; then
|
|
7
|
+
echo "Tag ${VERSION} already exists locally."
|
|
8
|
+
exit 0
|
|
9
|
+
fi
|
|
10
|
+
|
|
11
|
+
git tag "${VERSION}"
|
|
12
|
+
git push origin "${VERSION}"
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
name: Pull request
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
paths:
|
|
6
|
+
- "policyengine_observability/**"
|
|
7
|
+
- "tests/**"
|
|
8
|
+
- ".github/**"
|
|
9
|
+
- "changelog.d/**"
|
|
10
|
+
- "pyproject.toml"
|
|
11
|
+
- "uv.lock"
|
|
12
|
+
workflow_dispatch:
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
check-changelog:
|
|
19
|
+
name: Check changelog fragment
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
steps:
|
|
22
|
+
- name: Checkout repo
|
|
23
|
+
uses: actions/checkout@v6
|
|
24
|
+
with:
|
|
25
|
+
fetch-depth: 0
|
|
26
|
+
- name: Install uv
|
|
27
|
+
uses: astral-sh/setup-uv@v8.1.0
|
|
28
|
+
- name: Set up Python
|
|
29
|
+
uses: actions/setup-python@v6
|
|
30
|
+
with:
|
|
31
|
+
python-version: "3.12"
|
|
32
|
+
- name: Check for changelog fragment
|
|
33
|
+
run: uv run --extra dev bash .github/check-changelog.sh "origin/${{ github.base_ref }}"
|
|
34
|
+
|
|
35
|
+
lint:
|
|
36
|
+
name: Lint
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
steps:
|
|
39
|
+
- name: Checkout repo
|
|
40
|
+
uses: actions/checkout@v6
|
|
41
|
+
- name: Install uv
|
|
42
|
+
uses: astral-sh/setup-uv@v8.1.0
|
|
43
|
+
- name: Set up Python
|
|
44
|
+
uses: actions/setup-python@v6
|
|
45
|
+
with:
|
|
46
|
+
python-version: "3.12"
|
|
47
|
+
- name: Run ruff format check
|
|
48
|
+
run: uv run --extra dev ruff format --check .
|
|
49
|
+
- name: Run ruff check
|
|
50
|
+
run: uv run --extra dev ruff check .
|
|
51
|
+
|
|
52
|
+
test:
|
|
53
|
+
name: Test
|
|
54
|
+
runs-on: ubuntu-latest
|
|
55
|
+
steps:
|
|
56
|
+
- name: Checkout repo
|
|
57
|
+
uses: actions/checkout@v6
|
|
58
|
+
- name: Install uv
|
|
59
|
+
uses: astral-sh/setup-uv@v8.1.0
|
|
60
|
+
- name: Set up Python
|
|
61
|
+
uses: actions/setup-python@v6
|
|
62
|
+
with:
|
|
63
|
+
python-version: "3.12"
|
|
64
|
+
- name: Run tests with coverage
|
|
65
|
+
run: uv run --extra dev --extra all coverage run -m pytest
|
|
66
|
+
- name: Enforce coverage
|
|
67
|
+
run: uv run --extra dev --extra all coverage report
|
|
68
|
+
- name: Write coverage XML
|
|
69
|
+
run: uv run --extra dev --extra all coverage xml
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
name: Push
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
concurrency:
|
|
13
|
+
group: observability-release
|
|
14
|
+
|
|
15
|
+
env:
|
|
16
|
+
VERSION_COMMIT_MESSAGE: Update observability package version
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
check-changelog:
|
|
20
|
+
name: Check changelog fragment
|
|
21
|
+
if: github.event.head_commit.message != 'Update observability package version'
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
steps:
|
|
24
|
+
- name: Checkout repo
|
|
25
|
+
uses: actions/checkout@v6
|
|
26
|
+
with:
|
|
27
|
+
fetch-depth: 0
|
|
28
|
+
- name: Install uv
|
|
29
|
+
uses: astral-sh/setup-uv@v8.1.0
|
|
30
|
+
- name: Set up Python
|
|
31
|
+
uses: actions/setup-python@v6
|
|
32
|
+
with:
|
|
33
|
+
python-version: "3.12"
|
|
34
|
+
- name: Check for changelog fragment
|
|
35
|
+
env:
|
|
36
|
+
BASE_REF: ${{ github.event.before }}
|
|
37
|
+
run: |
|
|
38
|
+
BASE_REF="${BASE_REF:-HEAD^}"
|
|
39
|
+
uv run --extra dev bash .github/check-changelog.sh "$BASE_REF"
|
|
40
|
+
|
|
41
|
+
lint:
|
|
42
|
+
name: Lint
|
|
43
|
+
if: github.event.head_commit.message != 'Update observability package version'
|
|
44
|
+
runs-on: ubuntu-latest
|
|
45
|
+
steps:
|
|
46
|
+
- name: Checkout repo
|
|
47
|
+
uses: actions/checkout@v6
|
|
48
|
+
- name: Install uv
|
|
49
|
+
uses: astral-sh/setup-uv@v8.1.0
|
|
50
|
+
- name: Set up Python
|
|
51
|
+
uses: actions/setup-python@v6
|
|
52
|
+
with:
|
|
53
|
+
python-version: "3.12"
|
|
54
|
+
- name: Run ruff format check
|
|
55
|
+
run: uv run --extra dev ruff format --check .
|
|
56
|
+
- name: Run ruff check
|
|
57
|
+
run: uv run --extra dev ruff check .
|
|
58
|
+
|
|
59
|
+
test:
|
|
60
|
+
name: Test
|
|
61
|
+
if: github.event.head_commit.message != 'Update observability package version'
|
|
62
|
+
runs-on: ubuntu-latest
|
|
63
|
+
steps:
|
|
64
|
+
- name: Checkout repo
|
|
65
|
+
uses: actions/checkout@v6
|
|
66
|
+
- name: Install uv
|
|
67
|
+
uses: astral-sh/setup-uv@v8.1.0
|
|
68
|
+
- name: Set up Python
|
|
69
|
+
uses: actions/setup-python@v6
|
|
70
|
+
with:
|
|
71
|
+
python-version: "3.12"
|
|
72
|
+
- name: Run tests with coverage
|
|
73
|
+
run: uv run --extra dev --extra all coverage run -m pytest
|
|
74
|
+
- name: Enforce coverage
|
|
75
|
+
run: uv run --extra dev --extra all coverage report
|
|
76
|
+
- name: Write coverage XML
|
|
77
|
+
run: uv run --extra dev --extra all coverage xml
|
|
78
|
+
|
|
79
|
+
versioning:
|
|
80
|
+
name: Update versioning
|
|
81
|
+
needs:
|
|
82
|
+
- check-changelog
|
|
83
|
+
- lint
|
|
84
|
+
- test
|
|
85
|
+
if: github.event.head_commit.message != 'Update observability package version'
|
|
86
|
+
runs-on: ubuntu-latest
|
|
87
|
+
permissions:
|
|
88
|
+
contents: write
|
|
89
|
+
steps:
|
|
90
|
+
- name: Generate GitHub App token
|
|
91
|
+
id: app-token
|
|
92
|
+
uses: actions/create-github-app-token@v3
|
|
93
|
+
with:
|
|
94
|
+
app-id: ${{ secrets.APP_ID }}
|
|
95
|
+
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
|
96
|
+
- name: Checkout repo
|
|
97
|
+
uses: actions/checkout@v6
|
|
98
|
+
with:
|
|
99
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
100
|
+
fetch-depth: 0
|
|
101
|
+
- name: Fetch tags
|
|
102
|
+
run: git fetch --tags --force
|
|
103
|
+
- name: Install uv
|
|
104
|
+
uses: astral-sh/setup-uv@v8.1.0
|
|
105
|
+
- name: Set up Python
|
|
106
|
+
uses: actions/setup-python@v6
|
|
107
|
+
with:
|
|
108
|
+
python-version: "3.12"
|
|
109
|
+
- name: Build changelog
|
|
110
|
+
run: |
|
|
111
|
+
uv run --extra dev python .github/bump_version.py
|
|
112
|
+
uv run --extra dev towncrier build --yes --version "$(uv run python .github/fetch_version.py)"
|
|
113
|
+
- name: Preview changelog update
|
|
114
|
+
run: bash .github/get-changelog-diff.sh
|
|
115
|
+
- name: Update changelog and package version
|
|
116
|
+
uses: EndBug/add-and-commit@v10
|
|
117
|
+
with:
|
|
118
|
+
add: "."
|
|
119
|
+
message: ${{ env.VERSION_COMMIT_MESSAGE }}
|
|
120
|
+
github_token: ${{ steps.app-token.outputs.token }}
|
|
121
|
+
fetch: false
|
|
122
|
+
|
|
123
|
+
publish:
|
|
124
|
+
name: Publish
|
|
125
|
+
if: github.event.head_commit.message == 'Update observability package version'
|
|
126
|
+
runs-on: ubuntu-latest
|
|
127
|
+
environment: pypi
|
|
128
|
+
permissions:
|
|
129
|
+
contents: write
|
|
130
|
+
id-token: write
|
|
131
|
+
env:
|
|
132
|
+
GH_TOKEN: ${{ github.token }}
|
|
133
|
+
steps:
|
|
134
|
+
- name: Checkout repo
|
|
135
|
+
uses: actions/checkout@v6
|
|
136
|
+
with:
|
|
137
|
+
fetch-depth: 0
|
|
138
|
+
- name: Install uv
|
|
139
|
+
uses: astral-sh/setup-uv@v8.1.0
|
|
140
|
+
- name: Set up Python
|
|
141
|
+
uses: actions/setup-python@v6
|
|
142
|
+
with:
|
|
143
|
+
python-version: "3.12"
|
|
144
|
+
- name: Publish git tag
|
|
145
|
+
run: bash .github/publish-git-tag.sh
|
|
146
|
+
- name: Build package
|
|
147
|
+
run: uv build
|
|
148
|
+
- name: Publish package to PyPI
|
|
149
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
150
|
+
with:
|
|
151
|
+
skip-existing: true
|
|
152
|
+
- name: Create GitHub release
|
|
153
|
+
run: |
|
|
154
|
+
VERSION="$(uv run python .github/fetch_version.py)"
|
|
155
|
+
gh release create "$VERSION" \
|
|
156
|
+
--title "v$VERSION" \
|
|
157
|
+
--notes "See CHANGELOG.md for details." \
|
|
158
|
+
--latest
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: policyengine-observability
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Shared PolicyEngine observability runtime for logs, timings, metrics, and OpenTelemetry.
|
|
5
|
+
Author-email: PolicyEngine <hello@policyengine.org>
|
|
6
|
+
Requires-Python: >=3.12
|
|
7
|
+
Provides-Extra: all
|
|
8
|
+
Requires-Dist: fastapi; extra == 'all'
|
|
9
|
+
Requires-Dist: flask>=2.2; extra == 'all'
|
|
10
|
+
Requires-Dist: httpx; extra == 'all'
|
|
11
|
+
Requires-Dist: opentelemetry-api; extra == 'all'
|
|
12
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc; extra == 'all'
|
|
13
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http; extra == 'all'
|
|
14
|
+
Requires-Dist: opentelemetry-instrumentation-fastapi; extra == 'all'
|
|
15
|
+
Requires-Dist: opentelemetry-instrumentation-httpx; extra == 'all'
|
|
16
|
+
Requires-Dist: opentelemetry-sdk; extra == 'all'
|
|
17
|
+
Provides-Extra: dev
|
|
18
|
+
Requires-Dist: build; extra == 'dev'
|
|
19
|
+
Requires-Dist: coverage; extra == 'dev'
|
|
20
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
21
|
+
Requires-Dist: ruff>=0.9.0; extra == 'dev'
|
|
22
|
+
Requires-Dist: towncrier>=24.8.0; extra == 'dev'
|
|
23
|
+
Provides-Extra: fastapi
|
|
24
|
+
Requires-Dist: fastapi; extra == 'fastapi'
|
|
25
|
+
Requires-Dist: opentelemetry-instrumentation-fastapi; extra == 'fastapi'
|
|
26
|
+
Provides-Extra: flask
|
|
27
|
+
Requires-Dist: flask>=2.2; extra == 'flask'
|
|
28
|
+
Provides-Extra: httpx
|
|
29
|
+
Requires-Dist: httpx; extra == 'httpx'
|
|
30
|
+
Requires-Dist: opentelemetry-instrumentation-httpx; extra == 'httpx'
|
|
31
|
+
Provides-Extra: otel
|
|
32
|
+
Requires-Dist: opentelemetry-api; extra == 'otel'
|
|
33
|
+
Requires-Dist: opentelemetry-sdk; extra == 'otel'
|
|
34
|
+
Provides-Extra: otlp-grpc
|
|
35
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc; extra == 'otlp-grpc'
|
|
36
|
+
Provides-Extra: otlp-http
|
|
37
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http; extra == 'otlp-http'
|
|
38
|
+
Description-Content-Type: text/markdown
|
|
39
|
+
|
|
40
|
+
# policyengine-observability
|
|
41
|
+
|
|
42
|
+
Shared PolicyEngine observability runtime for fail-open local timings,
|
|
43
|
+
structured logs, OpenTelemetry traces, and OpenTelemetry metrics.
|
|
44
|
+
|
|
45
|
+
The package intentionally keeps framework support in adapters:
|
|
46
|
+
|
|
47
|
+
- `policyengine_observability.adapters.flask`
|
|
48
|
+
- `policyengine_observability.adapters.fastapi`
|
|
49
|
+
- `policyengine_observability.integrations.httpx`
|
|
50
|
+
|
|
51
|
+
OpenTelemetry imports are lazy. Timing and structured logging can run without
|
|
52
|
+
an OTel backend; exporting traces/metrics is opt-in through configuration.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# policyengine-observability
|
|
2
|
+
|
|
3
|
+
Shared PolicyEngine observability runtime for fail-open local timings,
|
|
4
|
+
structured logs, OpenTelemetry traces, and OpenTelemetry metrics.
|
|
5
|
+
|
|
6
|
+
The package intentionally keeps framework support in adapters:
|
|
7
|
+
|
|
8
|
+
- `policyengine_observability.adapters.flask`
|
|
9
|
+
- `policyengine_observability.adapters.fastapi`
|
|
10
|
+
- `policyengine_observability.integrations.httpx`
|
|
11
|
+
|
|
12
|
+
OpenTelemetry imports are lazy. Timing and structured logging can run without
|
|
13
|
+
an OTel backend; exporting traces/metrics is opt-in through configuration.
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from .config import ObservabilityConfig
|
|
6
|
+
from .context import OperationObservabilityContext, RequestObservabilityContext
|
|
7
|
+
from .runtime import (
|
|
8
|
+
OBSERVABILITY_INTERNAL_DISPATCH_HEADER,
|
|
9
|
+
REQUEST_ID_HEADER,
|
|
10
|
+
TRACEPARENT_HEADER,
|
|
11
|
+
ObservabilityRuntime,
|
|
12
|
+
observability_runtime,
|
|
13
|
+
set_observability_runtime,
|
|
14
|
+
)
|
|
15
|
+
from .segments import UNKNOWN_SEGMENT, coerce_segment_name
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def current_context() -> RequestObservabilityContext | None:
|
|
19
|
+
return observability_runtime().current_context()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def current_operation() -> OperationObservabilityContext | None:
|
|
23
|
+
return observability_runtime().current_operation()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def set_attribute(key: str, value: Any) -> None:
|
|
27
|
+
observability_runtime().set_attribute(key, value)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def record_error(
|
|
31
|
+
exc: BaseException,
|
|
32
|
+
*,
|
|
33
|
+
handled: bool,
|
|
34
|
+
status_code: int | None = None,
|
|
35
|
+
include_stack: bool = True,
|
|
36
|
+
) -> None:
|
|
37
|
+
observability_runtime().record_error(
|
|
38
|
+
exc,
|
|
39
|
+
handled=handled,
|
|
40
|
+
status_code=status_code,
|
|
41
|
+
include_stack=include_stack,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def record_event(event: str, **fields: Any) -> None:
|
|
46
|
+
observability_runtime().record_event(event, **fields)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def traceparent_header() -> str | None:
|
|
50
|
+
return observability_runtime().traceparent_header()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def capture_context():
|
|
54
|
+
return observability_runtime().capture_context()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def mark(key: str, ms: float) -> None:
|
|
58
|
+
observability_runtime().mark(key, ms)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def mark_ttft(key: str = "ttft_ms") -> None:
|
|
62
|
+
observability_runtime().mark_ttft(key)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def start_scope(
|
|
66
|
+
timings: dict[str, float],
|
|
67
|
+
*,
|
|
68
|
+
name: str = "operation",
|
|
69
|
+
parent_context: Any = None,
|
|
70
|
+
**attrs: Any,
|
|
71
|
+
):
|
|
72
|
+
return observability_runtime().start_scope(
|
|
73
|
+
timings,
|
|
74
|
+
name=name,
|
|
75
|
+
parent_context=parent_context,
|
|
76
|
+
**attrs,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def annotate(handle=None, **attrs: Any) -> None:
|
|
81
|
+
observability_runtime().annotate(handle, **attrs)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def end_scope(handle, error: BaseException | None = None) -> None:
|
|
85
|
+
observability_runtime().end_scope(handle, error)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def instrument_fastapi(app: Any) -> None:
|
|
89
|
+
observability_runtime().instrument_fastapi(app)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def instrument_httpx() -> None:
|
|
93
|
+
observability_runtime().instrument_httpx()
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def shutdown_observability() -> None:
|
|
97
|
+
observability_runtime().shutdown()
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def shutdown_tracing() -> None:
|
|
101
|
+
shutdown_observability()
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def operation(name: str, *, flavor: str | None = None, **attrs: Any):
|
|
105
|
+
return observability_runtime().operation(name, flavor=flavor, **attrs)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def entrypoint(
|
|
109
|
+
name: str | None = None,
|
|
110
|
+
*,
|
|
111
|
+
flavor: str | None = None,
|
|
112
|
+
**attrs: Any,
|
|
113
|
+
):
|
|
114
|
+
return observability_runtime().entrypoint(
|
|
115
|
+
name,
|
|
116
|
+
flavor=flavor,
|
|
117
|
+
**attrs,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def segment(name: Any, **attrs: Any):
|
|
122
|
+
return observability_runtime().segment(name, **attrs)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def asegment(name: Any, **attrs: Any):
|
|
126
|
+
return observability_runtime().asegment(name, **attrs)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def collect_timings(name: str = "operation", **attrs: Any):
|
|
130
|
+
return observability_runtime().collect_timings(name, **attrs)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
__all__ = [
|
|
134
|
+
"OBSERVABILITY_INTERNAL_DISPATCH_HEADER",
|
|
135
|
+
"REQUEST_ID_HEADER",
|
|
136
|
+
"TRACEPARENT_HEADER",
|
|
137
|
+
"UNKNOWN_SEGMENT",
|
|
138
|
+
"OperationObservabilityContext",
|
|
139
|
+
"ObservabilityConfig",
|
|
140
|
+
"ObservabilityRuntime",
|
|
141
|
+
"RequestObservabilityContext",
|
|
142
|
+
"annotate",
|
|
143
|
+
"asegment",
|
|
144
|
+
"capture_context",
|
|
145
|
+
"coerce_segment_name",
|
|
146
|
+
"collect_timings",
|
|
147
|
+
"current_context",
|
|
148
|
+
"current_operation",
|
|
149
|
+
"end_scope",
|
|
150
|
+
"entrypoint",
|
|
151
|
+
"instrument_fastapi",
|
|
152
|
+
"instrument_httpx",
|
|
153
|
+
"mark",
|
|
154
|
+
"mark_ttft",
|
|
155
|
+
"observability_runtime",
|
|
156
|
+
"operation",
|
|
157
|
+
"record_error",
|
|
158
|
+
"record_event",
|
|
159
|
+
"segment",
|
|
160
|
+
"set_attribute",
|
|
161
|
+
"set_observability_runtime",
|
|
162
|
+
"shutdown_observability",
|
|
163
|
+
"shutdown_tracing",
|
|
164
|
+
"start_scope",
|
|
165
|
+
"traceparent_header",
|
|
166
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Framework adapters for policyengine-observability."""
|