omnixys-observability 2.0.4__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.
- omnixys_observability-2.0.4/.gitattributes +2 -0
- omnixys_observability-2.0.4/.github/workflows/ci.yaml +101 -0
- omnixys_observability-2.0.4/.gitignore +2 -0
- omnixys_observability-2.0.4/CHANGELOG.md +42 -0
- omnixys_observability-2.0.4/PKG-INFO +57 -0
- omnixys_observability-2.0.4/README.md +27 -0
- omnixys_observability-2.0.4/pyproject.toml +76 -0
- omnixys_observability-2.0.4/release.config.js +56 -0
- omnixys_observability-2.0.4/src/observability/__init__.py +61 -0
- omnixys_observability-2.0.4/src/observability/logging.py +150 -0
- omnixys_observability-2.0.4/src/observability/metrics.py +144 -0
- omnixys_observability-2.0.4/src/observability/py.typed +0 -0
- omnixys_observability-2.0.4/src/observability/request_context.py +33 -0
- omnixys_observability-2.0.4/src/observability/runtime.py +35 -0
- omnixys_observability-2.0.4/src/observability/tracing.py +64 -0
- omnixys_observability-2.0.4/tests/__init__.py +0 -0
- omnixys_observability-2.0.4/tests/conftest.py +18 -0
- omnixys_observability-2.0.4/tests/test_import.py +20 -0
- omnixys_observability-2.0.4/uv.lock +799 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
env:
|
|
10
|
+
PYTHON_VERSION: "3.14"
|
|
11
|
+
PACKAGE: ${{ vars.PACKAGE }}
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
test:
|
|
15
|
+
name: Test
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- name: Setup uv
|
|
21
|
+
uses: astral-sh/setup-uv@v4
|
|
22
|
+
with:
|
|
23
|
+
python-version: ${{ env.PYTHON_VERSION }}
|
|
24
|
+
|
|
25
|
+
- name: Install dependencies
|
|
26
|
+
run: uv sync
|
|
27
|
+
|
|
28
|
+
- name: Lint (ruff)
|
|
29
|
+
run: uv run ruff check src/
|
|
30
|
+
|
|
31
|
+
- name: Typecheck (mypy)
|
|
32
|
+
run: uv run mypy src/ || true
|
|
33
|
+
|
|
34
|
+
- name: Test (pytest)
|
|
35
|
+
run: uv run pytest
|
|
36
|
+
|
|
37
|
+
release:
|
|
38
|
+
needs: test
|
|
39
|
+
runs-on: ubuntu-latest
|
|
40
|
+
permissions:
|
|
41
|
+
contents: write
|
|
42
|
+
issues: write
|
|
43
|
+
pull-requests: write
|
|
44
|
+
outputs:
|
|
45
|
+
released: ${{ steps.semantic.outputs.released }}
|
|
46
|
+
version: ${{ steps.semantic.outputs.version }}
|
|
47
|
+
steps:
|
|
48
|
+
- uses: actions/checkout@v6
|
|
49
|
+
with:
|
|
50
|
+
fetch-depth: 0
|
|
51
|
+
|
|
52
|
+
- name: Setup Node.js
|
|
53
|
+
uses: actions/setup-node@v4
|
|
54
|
+
with:
|
|
55
|
+
node-version: "22"
|
|
56
|
+
|
|
57
|
+
- name: Install semantic-release
|
|
58
|
+
run: |
|
|
59
|
+
npm install -g semantic-release
|
|
60
|
+
npm install -g @semantic-release/changelog
|
|
61
|
+
npm install -g @semantic-release/exec
|
|
62
|
+
npm install -g @semantic-release/git
|
|
63
|
+
npm install -g @semantic-release/github
|
|
64
|
+
npm install -g conventional-changelog-conventionalcommits
|
|
65
|
+
|
|
66
|
+
- name: Semantic Release
|
|
67
|
+
id: semantic
|
|
68
|
+
run: |
|
|
69
|
+
OUTPUT=$(npx semantic-release --no-ci 2>&1 || true)
|
|
70
|
+
echo "$OUTPUT"
|
|
71
|
+
|
|
72
|
+
if echo "$OUTPUT" | grep -q "Published"; then
|
|
73
|
+
echo "released=true" >> $GITHUB_OUTPUT
|
|
74
|
+
else
|
|
75
|
+
echo "released=false" >> $GITHUB_OUTPUT
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
VERSION=$(grep -oP '__version__\s*=\s*"\K[^"]+' src/${PACKAGE}/__init__.py)
|
|
79
|
+
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
|
80
|
+
env:
|
|
81
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
82
|
+
|
|
83
|
+
publish:
|
|
84
|
+
needs: release
|
|
85
|
+
if: needs.release.outputs.released == 'true'
|
|
86
|
+
runs-on: ubuntu-latest
|
|
87
|
+
steps:
|
|
88
|
+
- uses: actions/checkout@v4
|
|
89
|
+
|
|
90
|
+
- name: Setup uv
|
|
91
|
+
uses: astral-sh/setup-uv@v4
|
|
92
|
+
with:
|
|
93
|
+
python-version: ${{ env.PYTHON_VERSION }}
|
|
94
|
+
|
|
95
|
+
- name: Build package
|
|
96
|
+
run: uv build
|
|
97
|
+
|
|
98
|
+
- name: Publish to PyPI
|
|
99
|
+
run: uv publish dist/*
|
|
100
|
+
env:
|
|
101
|
+
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
## [2.0.4](https://github.com/omnixys/observability-python/compare/v2.0.3...v2.0.4) (2026-07-22)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **publish:** add uv build before uv publish ([d5aa1fa](https://github.com/omnixys/observability-python/commit/d5aa1fa58a97432ebd70926451eb74a6916333ee))
|
|
7
|
+
|
|
8
|
+
## [2.0.3](https://github.com/omnixys/observability-python/compare/v2.0.2...v2.0.3) (2026-07-22)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **publish:** replace gh release upload with uv publish to PyPI ([420ef67](https://github.com/omnixys/observability-python/commit/420ef6724596d9fb311e7af3c1a0d17d7b151d2a))
|
|
14
|
+
|
|
15
|
+
## [2.0.2](https://github.com/omnixys/observability-python/compare/v2.0.1...v2.0.2) (2026-07-22)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
* **release:** add @semantic-release/exec to update __version__ in __init__.py ([969ed6f](https://github.com/omnixys/observability-python/commit/969ed6f6b6725cd611b6f73aad36242d24c6b97c))
|
|
21
|
+
|
|
22
|
+
## [2.0.1](https://github.com/omnixys/observability-python/compare/v2.0.0...v2.0.1) (2026-07-22)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
### Bug Fixes
|
|
26
|
+
|
|
27
|
+
* **cicd:** use version comparison for release detection ([eab84dd](https://github.com/omnixys/observability-python/commit/eab84ddbc0ff4a8563537204fdcba19058b8b7af))
|
|
28
|
+
|
|
29
|
+
# [2.0.0](https://github.com/omnixys/observability-python/compare/v1.1.1...v2.0.0) (2026-07-22)
|
|
30
|
+
|
|
31
|
+
# Changelog
|
|
32
|
+
|
|
33
|
+
All notable changes in this project will be documented in this file.
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
## [1.1.1](https://github.com/omnixys/observability-python/compare/v1.1.0...v1.1.1) (2026-07-22)
|
|
37
|
+
|
|
38
|
+
## [1.0.2](https://github.com/omnixys/observability-python/compare/v1.0.1...v1.0.2) (2026-07-22)
|
|
39
|
+
|
|
40
|
+
## [1.0.1](https://github.com/omnixys/observability-python/compare/v1.0.0...v1.0.1) (2026-07-15)
|
|
41
|
+
|
|
42
|
+
## 1.0.0 (2026-07-15)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: omnixys-observability
|
|
3
|
+
Version: 2.0.4
|
|
4
|
+
Summary: Omnixys shared observability package (OTel tracing, structured logging, Prometheus metrics)
|
|
5
|
+
Project-URL: Homepage, https://github.com/omnixys/omnixys
|
|
6
|
+
Project-URL: Repository, https://github.com/omnixys/omnixys
|
|
7
|
+
Author-email: Omnixys Team <team@omnixys.com>
|
|
8
|
+
License: GPL-3.0-or-later
|
|
9
|
+
Keywords: logging,metrics,observability,omnixys,opentelemetry,tracing
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
+
Classifier: Typing :: Typed
|
|
15
|
+
Requires-Python: >=3.14
|
|
16
|
+
Requires-Dist: fastapi>=0.115.0
|
|
17
|
+
Requires-Dist: httpx>=0.28.0
|
|
18
|
+
Requires-Dist: opentelemetry-api>=1.42.1
|
|
19
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.42.1
|
|
20
|
+
Requires-Dist: opentelemetry-instrumentation-asgi>=0.63b1
|
|
21
|
+
Requires-Dist: opentelemetry-instrumentation-fastapi>=0.63b1
|
|
22
|
+
Requires-Dist: opentelemetry-instrumentation-httpx>=0.63b1
|
|
23
|
+
Requires-Dist: opentelemetry-instrumentation-logging>=0.63b1
|
|
24
|
+
Requires-Dist: opentelemetry-instrumentation-sqlalchemy>=0.63b1
|
|
25
|
+
Requires-Dist: opentelemetry-instrumentation>=0.63b1
|
|
26
|
+
Requires-Dist: opentelemetry-sdk>=1.42.1
|
|
27
|
+
Requires-Dist: prometheus-client>=0.25.0
|
|
28
|
+
Requires-Dist: structlog>=26.1.0
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# omnixys-observability
|
|
32
|
+
|
|
33
|
+
Omnixys shared observability package with OpenTelemetry tracing, structured logging, and Prometheus metrics.
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install omnixys-observability
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Features
|
|
42
|
+
|
|
43
|
+
- OpenTelemetry distributed tracing
|
|
44
|
+
- Structured logging with structlog
|
|
45
|
+
- Prometheus metrics collection
|
|
46
|
+
- FastAPI instrumentation
|
|
47
|
+
- Request context propagation
|
|
48
|
+
|
|
49
|
+
## Usage
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from observability import configure_logging, configure_tracing, instrument_fastapi
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## License
|
|
56
|
+
|
|
57
|
+
GPL-3.0-or-later
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# omnixys-observability
|
|
2
|
+
|
|
3
|
+
Omnixys shared observability package with OpenTelemetry tracing, structured logging, and Prometheus metrics.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install omnixys-observability
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- OpenTelemetry distributed tracing
|
|
14
|
+
- Structured logging with structlog
|
|
15
|
+
- Prometheus metrics collection
|
|
16
|
+
- FastAPI instrumentation
|
|
17
|
+
- Request context propagation
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from observability import configure_logging, configure_tracing, instrument_fastapi
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## License
|
|
26
|
+
|
|
27
|
+
GPL-3.0-or-later
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "omnixys-observability"
|
|
3
|
+
dynamic = ["version"]
|
|
4
|
+
description = "Omnixys shared observability package (OTel tracing, structured logging, Prometheus metrics)"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = { text = "GPL-3.0-or-later" }
|
|
7
|
+
requires-python = ">=3.14"
|
|
8
|
+
authors = [{ name = "Omnixys Team", email = "team@omnixys.com" }]
|
|
9
|
+
keywords = ["omnixys", "observability", "opentelemetry", "tracing", "metrics", "logging"]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 4 - Beta",
|
|
12
|
+
"Intended Audience :: Developers",
|
|
13
|
+
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
|
|
14
|
+
"Programming Language :: Python :: 3.14",
|
|
15
|
+
"Typing :: Typed",
|
|
16
|
+
]
|
|
17
|
+
dependencies = [
|
|
18
|
+
"fastapi>=0.115.0",
|
|
19
|
+
"httpx>=0.28.0",
|
|
20
|
+
"structlog>=26.1.0",
|
|
21
|
+
"prometheus-client>=0.25.0",
|
|
22
|
+
"opentelemetry-api>=1.42.1",
|
|
23
|
+
"opentelemetry-sdk>=1.42.1",
|
|
24
|
+
"opentelemetry-exporter-otlp-proto-http>=1.42.1",
|
|
25
|
+
"opentelemetry-instrumentation>=0.63b1",
|
|
26
|
+
"opentelemetry-instrumentation-fastapi>=0.63b1",
|
|
27
|
+
"opentelemetry-instrumentation-sqlalchemy>=0.63b1",
|
|
28
|
+
"opentelemetry-instrumentation-logging>=0.63b1",
|
|
29
|
+
"opentelemetry-instrumentation-httpx>=0.63b1",
|
|
30
|
+
"opentelemetry-instrumentation-asgi>=0.63b1",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
Homepage = "https://github.com/omnixys/omnixys"
|
|
35
|
+
Repository = "https://github.com/omnixys/omnixys"
|
|
36
|
+
|
|
37
|
+
[build-system]
|
|
38
|
+
requires = ["hatchling"]
|
|
39
|
+
build-backend = "hatchling.build"
|
|
40
|
+
|
|
41
|
+
[tool.hatch.version]
|
|
42
|
+
path = "src/observability/__init__.py"
|
|
43
|
+
|
|
44
|
+
[tool.hatch.build.targets.wheel]
|
|
45
|
+
packages = ["src/observability"]
|
|
46
|
+
|
|
47
|
+
[dependency-groups]
|
|
48
|
+
dev = [
|
|
49
|
+
"mypy>=2.3.0",
|
|
50
|
+
"ruff>=0.15.21",
|
|
51
|
+
"pytest>=9.1.0",
|
|
52
|
+
"pytest-asyncio>=0.26.0",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
[tool.ruff]
|
|
56
|
+
line-length = 120
|
|
57
|
+
target-version = "py314"
|
|
58
|
+
|
|
59
|
+
[tool.ruff.lint]
|
|
60
|
+
select = ["ALL"]
|
|
61
|
+
|
|
62
|
+
[tool.ruff.lint.per-file-ignores]
|
|
63
|
+
"tests/**" = [
|
|
64
|
+
"ANN101", "ANN401", "S101",
|
|
65
|
+
"T20", "FBT", "D", "INP001",
|
|
66
|
+
]
|
|
67
|
+
"src/**" = [
|
|
68
|
+
"S104", "D", "ANN401", "ARG002",
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
[tool.mypy]
|
|
72
|
+
strict = true
|
|
73
|
+
|
|
74
|
+
[tool.pytest.ini_options]
|
|
75
|
+
testpaths = ["tests"]
|
|
76
|
+
pythonpath = ["src"]
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license GPL-3.0-or-later
|
|
3
|
+
* Copyright (C) 2025 Caleb Gyamfi
|
|
4
|
+
* Omnixys Technologies
|
|
5
|
+
*
|
|
6
|
+
* This program is free software: you can redistribute it and/or modify
|
|
7
|
+
* it under the terms of the GNU General Public License as published by
|
|
8
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
9
|
+
* (at your option) any later version.
|
|
10
|
+
*
|
|
11
|
+
* This program is distributed in the hope that it will be useful,
|
|
12
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
14
|
+
* See the GNU General Public License for more details.
|
|
15
|
+
*
|
|
16
|
+
* For more information, visit <https://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export default {
|
|
20
|
+
branches: ['main'],
|
|
21
|
+
tagFormat: 'v${version}',
|
|
22
|
+
plugins: [
|
|
23
|
+
['@semantic-release/commit-analyzer', {
|
|
24
|
+
preset: 'conventionalcommits',
|
|
25
|
+
releaseRules: [
|
|
26
|
+
{ type: 'breaking', release: 'major' },
|
|
27
|
+
{ type: 'feat', release: 'minor' },
|
|
28
|
+
{ type: 'fix', release: 'patch' },
|
|
29
|
+
{ type: 'perf', release: 'patch' },
|
|
30
|
+
{ type: 'refactor', release: 'patch' },
|
|
31
|
+
{ type: 'revert', release: 'patch' },
|
|
32
|
+
{ type: 'docs', release: false },
|
|
33
|
+
{ type: 'style', release: false },
|
|
34
|
+
{ type: 'test', release: false },
|
|
35
|
+
{ type: 'chore', release: false },
|
|
36
|
+
{ type: 'ci', release: false },
|
|
37
|
+
{ type: 'build', release: false },
|
|
38
|
+
],
|
|
39
|
+
}],
|
|
40
|
+
'@semantic-release/release-notes-generator',
|
|
41
|
+
['@semantic-release/changelog', { changelogFile: 'CHANGELOG.md' }],
|
|
42
|
+
['@semantic-release/exec', {
|
|
43
|
+
prepareCmd: 'sed -i "s/__version__ = \\".*\\"/__version__ = \\"${nextRelease.version}\\"/" src/observability/__init__.py',
|
|
44
|
+
}],
|
|
45
|
+
['@semantic-release/git', {
|
|
46
|
+
assets: ['pyproject.toml', 'CHANGELOG.md', 'src/observability/__init__.py'],
|
|
47
|
+
message: 'chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}',
|
|
48
|
+
}],
|
|
49
|
+
['@semantic-release/github', {
|
|
50
|
+
assets: [
|
|
51
|
+
{ path: 'CHANGELOG.md', label: 'Changelog' },
|
|
52
|
+
{ path: 'dist/**', label: 'Build Artifacts' },
|
|
53
|
+
],
|
|
54
|
+
}],
|
|
55
|
+
],
|
|
56
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from observability.logging import configure_logging, get_logger, shutdown_logging
|
|
2
|
+
from observability.metrics import (
|
|
3
|
+
ObservabilityMetrics,
|
|
4
|
+
ObservabilityMiddleware,
|
|
5
|
+
auth_failures,
|
|
6
|
+
cache_hits,
|
|
7
|
+
cache_misses,
|
|
8
|
+
db_query_duration,
|
|
9
|
+
graphql_operations,
|
|
10
|
+
http_request_duration,
|
|
11
|
+
http_requests_total,
|
|
12
|
+
kafka_publish_failures,
|
|
13
|
+
kafka_publish_total,
|
|
14
|
+
outbox_failed,
|
|
15
|
+
outbox_pending,
|
|
16
|
+
outbox_processed,
|
|
17
|
+
outbox_processing_duration,
|
|
18
|
+
rate_limit_blocked,
|
|
19
|
+
)
|
|
20
|
+
from observability.request_context import (
|
|
21
|
+
RequestContext,
|
|
22
|
+
current_request_context,
|
|
23
|
+
reset_request_context,
|
|
24
|
+
set_request_context,
|
|
25
|
+
)
|
|
26
|
+
from observability.runtime import configure_observability, shutdown_observability
|
|
27
|
+
from observability.tracing import configure_tracing, instrument_fastapi, shutdown_tracing, uninstrument_fastapi
|
|
28
|
+
|
|
29
|
+
__version__ = "2.0.4"
|
|
30
|
+
|
|
31
|
+
__all__ = [
|
|
32
|
+
"ObservabilityMetrics",
|
|
33
|
+
"ObservabilityMiddleware",
|
|
34
|
+
"RequestContext",
|
|
35
|
+
"auth_failures",
|
|
36
|
+
"cache_hits",
|
|
37
|
+
"cache_misses",
|
|
38
|
+
"configure_logging",
|
|
39
|
+
"configure_observability",
|
|
40
|
+
"configure_tracing",
|
|
41
|
+
"current_request_context",
|
|
42
|
+
"db_query_duration",
|
|
43
|
+
"get_logger",
|
|
44
|
+
"graphql_operations",
|
|
45
|
+
"http_request_duration",
|
|
46
|
+
"http_requests_total",
|
|
47
|
+
"instrument_fastapi",
|
|
48
|
+
"kafka_publish_failures",
|
|
49
|
+
"kafka_publish_total",
|
|
50
|
+
"outbox_failed",
|
|
51
|
+
"outbox_pending",
|
|
52
|
+
"outbox_processed",
|
|
53
|
+
"outbox_processing_duration",
|
|
54
|
+
"rate_limit_blocked",
|
|
55
|
+
"reset_request_context",
|
|
56
|
+
"set_request_context",
|
|
57
|
+
"shutdown_logging",
|
|
58
|
+
"shutdown_observability",
|
|
59
|
+
"shutdown_tracing",
|
|
60
|
+
"uninstrument_fastapi",
|
|
61
|
+
]
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
from typing import TYPE_CHECKING, Any
|
|
7
|
+
|
|
8
|
+
import structlog
|
|
9
|
+
from opentelemetry import trace
|
|
10
|
+
from opentelemetry._logs import set_logger_provider
|
|
11
|
+
from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter
|
|
12
|
+
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
|
|
13
|
+
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
|
|
14
|
+
from opentelemetry.sdk.resources import Resource
|
|
15
|
+
|
|
16
|
+
from observability.request_context import current_request_context
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from collections.abc import MutableMapping
|
|
20
|
+
|
|
21
|
+
_SENSITIVE_KEY = re.compile(r"authorization|cookie|password|secret|token|api[-_]?key", re.IGNORECASE)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _add_context(_logger: Any, _method_name: str, event_dict: MutableMapping[str, Any]) -> MutableMapping[str, Any]: # noqa: C901
|
|
25
|
+
if _service_name:
|
|
26
|
+
event_dict["service"] = _service_name
|
|
27
|
+
ctx = current_request_context()
|
|
28
|
+
if ctx:
|
|
29
|
+
if ctx.request_id:
|
|
30
|
+
event_dict["request_id"] = ctx.request_id
|
|
31
|
+
if ctx.correlation_id:
|
|
32
|
+
event_dict["correlation_id"] = ctx.correlation_id
|
|
33
|
+
if ctx.user_id:
|
|
34
|
+
event_dict["user_id"] = ctx.user_id
|
|
35
|
+
event_dict["actor_id"] = ctx.user_id
|
|
36
|
+
if ctx.organization_id:
|
|
37
|
+
event_dict["organization_id"] = ctx.organization_id
|
|
38
|
+
if ctx.tenant_id:
|
|
39
|
+
event_dict["tenant_id"] = ctx.tenant_id
|
|
40
|
+
|
|
41
|
+
span = trace.get_current_span()
|
|
42
|
+
span_context = span.get_span_context()
|
|
43
|
+
if span_context.is_valid:
|
|
44
|
+
event_dict["traceId"] = format(span_context.trace_id, "032x")
|
|
45
|
+
event_dict["spanId"] = format(span_context.span_id, "016x")
|
|
46
|
+
elif ctx:
|
|
47
|
+
if ctx.trace_id:
|
|
48
|
+
event_dict["traceId"] = ctx.trace_id
|
|
49
|
+
if ctx.span_id:
|
|
50
|
+
event_dict["spanId"] = ctx.span_id
|
|
51
|
+
|
|
52
|
+
return event_dict
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _redact_sensitive(
|
|
56
|
+
_logger: Any, _method_name: str, event_dict: MutableMapping[str, Any],
|
|
57
|
+
) -> MutableMapping[str, Any]:
|
|
58
|
+
return _redact_mapping(event_dict)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _redact_mapping(value: MutableMapping[str, Any]) -> MutableMapping[str, Any]:
|
|
62
|
+
for key, nested in tuple(value.items()):
|
|
63
|
+
if _SENSITIVE_KEY.search(key):
|
|
64
|
+
value[key] = "[REDACTED]"
|
|
65
|
+
elif isinstance(nested, dict):
|
|
66
|
+
value[key] = _redact_mapping(nested)
|
|
67
|
+
elif isinstance(nested, list):
|
|
68
|
+
value[key] = [
|
|
69
|
+
_redact_mapping(item) if isinstance(item, dict) else item for item in nested
|
|
70
|
+
]
|
|
71
|
+
return value
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
_logger_provider: LoggerProvider | None = None
|
|
75
|
+
_service_name: str | None = None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _signal_endpoint(endpoint: str, signal: str) -> str:
|
|
79
|
+
base = endpoint.rstrip("/")
|
|
80
|
+
for suffix in ("/v1/traces", "/v1/logs"):
|
|
81
|
+
if base.endswith(suffix):
|
|
82
|
+
base = base[: -len(suffix)]
|
|
83
|
+
break
|
|
84
|
+
return f"{base}/v1/{signal}"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _setup_otel_logging(service_name: str, endpoint: str, environment: str) -> LoggerProvider:
|
|
88
|
+
global _logger_provider, _service_name # noqa: PLW0603
|
|
89
|
+
resource = Resource.create(
|
|
90
|
+
{
|
|
91
|
+
"service.name": service_name,
|
|
92
|
+
"service.namespace": "omnixys",
|
|
93
|
+
"service.version": os.environ.get("OTEL_SERVICE_VERSION", "unknown"),
|
|
94
|
+
"deployment.environment.name": environment,
|
|
95
|
+
},
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
logger_provider = LoggerProvider(resource=resource)
|
|
99
|
+
logger_provider.add_log_record_processor(
|
|
100
|
+
BatchLogRecordProcessor(OTLPLogExporter(endpoint=_signal_endpoint(endpoint, "logs"))),
|
|
101
|
+
)
|
|
102
|
+
set_logger_provider(logger_provider)
|
|
103
|
+
|
|
104
|
+
handler = LoggingHandler(logger_provider=logger_provider)
|
|
105
|
+
handler.setLevel(logging.DEBUG)
|
|
106
|
+
logging.getLogger().addHandler(handler)
|
|
107
|
+
_logger_provider = logger_provider
|
|
108
|
+
_service_name = service_name
|
|
109
|
+
return logger_provider
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def configure_logging(
|
|
113
|
+
log_level: str = "INFO",
|
|
114
|
+
service_name: str | None = None,
|
|
115
|
+
*,
|
|
116
|
+
otlp_endpoint: str | None = None,
|
|
117
|
+
environment: str = "local",
|
|
118
|
+
) -> None:
|
|
119
|
+
if service_name:
|
|
120
|
+
endpoint = otlp_endpoint or os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4318")
|
|
121
|
+
_setup_otel_logging(service_name, endpoint, environment)
|
|
122
|
+
|
|
123
|
+
structlog.configure(
|
|
124
|
+
processors=[
|
|
125
|
+
structlog.contextvars.merge_contextvars,
|
|
126
|
+
structlog.stdlib.add_log_level,
|
|
127
|
+
_add_context,
|
|
128
|
+
_redact_sensitive,
|
|
129
|
+
structlog.processors.TimeStamper(fmt="iso", utc=True),
|
|
130
|
+
structlog.processors.JSONRenderer(),
|
|
131
|
+
],
|
|
132
|
+
wrapper_class=structlog.stdlib.BoundLogger,
|
|
133
|
+
logger_factory=structlog.stdlib.LoggerFactory(),
|
|
134
|
+
cache_logger_on_first_use=True,
|
|
135
|
+
)
|
|
136
|
+
logging.basicConfig(
|
|
137
|
+
level=getattr(logging, log_level.upper(), logging.INFO),
|
|
138
|
+
format="%(message)s",
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def get_logger(name: str | None = None) -> Any:
|
|
143
|
+
return structlog.get_logger(name)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def shutdown_logging() -> None:
|
|
147
|
+
global _logger_provider # noqa: PLW0603
|
|
148
|
+
if _logger_provider is not None:
|
|
149
|
+
_logger_provider.shutdown()
|
|
150
|
+
_logger_provider = None
|