elven-logs-interceptor-python 0.1.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.
- elven_logs_interceptor_python-0.1.2/.github/workflows/ci.yml +30 -0
- elven_logs_interceptor_python-0.1.2/.github/workflows/publish.yml +65 -0
- elven_logs_interceptor_python-0.1.2/.gitignore +15 -0
- elven_logs_interceptor_python-0.1.2/ARCHITECTURE.md +36 -0
- elven_logs_interceptor_python-0.1.2/Makefile +34 -0
- elven_logs_interceptor_python-0.1.2/PKG-INFO +262 -0
- elven_logs_interceptor_python-0.1.2/README.md +201 -0
- elven_logs_interceptor_python-0.1.2/examples/basic_app.py +18 -0
- elven_logs_interceptor_python-0.1.2/examples/fastapi_integration.py +26 -0
- elven_logs_interceptor_python-0.1.2/examples/full_config_reference.py +67 -0
- elven_logs_interceptor_python-0.1.2/examples/high_volume.py +30 -0
- elven_logs_interceptor_python-0.1.2/examples/tracking_usage.py +14 -0
- elven_logs_interceptor_python-0.1.2/pyproject.toml +141 -0
- elven_logs_interceptor_python-0.1.2/scripts/publish.sh +180 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/__init__.py +333 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/application/__init__.py +27 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/application/config_service.py +232 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/application/log_service.py +383 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/config.py +190 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/domain/__init__.py +25 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/domain/entities.py +41 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/domain/interfaces.py +149 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/domain/value_objects.py +40 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/infrastructure/__init__.py +48 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/infrastructure/buffer/__init__.py +3 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/infrastructure/buffer/memory_buffer.py +187 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/infrastructure/circuit_breaker/__init__.py +3 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/infrastructure/circuit_breaker/circuit_breaker.py +110 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/infrastructure/compression/__init__.py +14 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/infrastructure/compression/base.py +20 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/infrastructure/compression/brotli_compressor.py +27 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/infrastructure/compression/factory.py +18 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/infrastructure/compression/gzip_compressor.py +20 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/infrastructure/compression/noop_compressor.py +14 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/infrastructure/context/__init__.py +3 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/infrastructure/context/context_provider.py +44 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/infrastructure/dlq/__init__.py +4 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/infrastructure/dlq/file_dlq.py +170 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/infrastructure/dlq/memory_dlq.py +59 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/infrastructure/filter/__init__.py +3 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/infrastructure/filter/log_filter.py +55 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/infrastructure/interceptors/__init__.py +3 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/infrastructure/interceptors/runtime_interceptor.py +139 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/infrastructure/memory/__init__.py +3 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/infrastructure/memory/memory_tracker.py +95 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/infrastructure/metrics/__init__.py +3 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/infrastructure/metrics/metrics_collector.py +104 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/infrastructure/transport/__init__.py +12 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/infrastructure/transport/loki_json_transport.py +226 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/infrastructure/transport/loki_protobuf_transport.py +209 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/infrastructure/transport/resilient_transport.py +161 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/infrastructure/transport/transport_factory.py +39 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/infrastructure/workers/__init__.py +3 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/infrastructure/workers/worker_pool.py +57 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/integrations/__init__.py +17 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/integrations/celery.py +53 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/integrations/django.py +44 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/integrations/fastapi.py +53 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/integrations/flask.py +50 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/integrations/logging_handler.py +43 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/integrations/loguru.py +36 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/integrations/structlog.py +21 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/preload.py +61 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/presentation/__init__.py +3 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/presentation/factory.py +128 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/types.py +89 -0
- elven_logs_interceptor_python-0.1.2/src/logs_interceptor/utils.py +508 -0
- elven_logs_interceptor_python-0.1.2/test-apps/elven-observability-smoke/.env.example +167 -0
- elven_logs_interceptor_python-0.1.2/test-apps/elven-observability-smoke/README.md +27 -0
- elven_logs_interceptor_python-0.1.2/test-apps/elven-observability-smoke/app.py +90 -0
- elven_logs_interceptor_python-0.1.2/test-apps/elven-observability-smoke/run.sh +21 -0
- elven_logs_interceptor_python-0.1.2/tests/integration/test_api.py +102 -0
- elven_logs_interceptor_python-0.1.2/tests/unit/test_circuit_breaker_extra.py +48 -0
- elven_logs_interceptor_python-0.1.2/tests/unit/test_config_service.py +114 -0
- elven_logs_interceptor_python-0.1.2/tests/unit/test_core_components.py +77 -0
- elven_logs_interceptor_python-0.1.2/tests/unit/test_env_config.py +47 -0
- elven_logs_interceptor_python-0.1.2/tests/unit/test_log_filter_extra.py +68 -0
- elven_logs_interceptor_python-0.1.2/tests/unit/test_log_service_unit.py +377 -0
- elven_logs_interceptor_python-0.1.2/tests/unit/test_loki_json_transport.py +82 -0
- elven_logs_interceptor_python-0.1.2/tests/unit/test_memory_buffer_extra.py +118 -0
- elven_logs_interceptor_python-0.1.2/tests/unit/test_protobuf_transport_safety.py +93 -0
- elven_logs_interceptor_python-0.1.2/tests/unit/test_resilient_transport.py +211 -0
- elven_logs_interceptor_python-0.1.2/tests/unit/test_utils_extra.py +274 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["main", "master"]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
fail-fast: false
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.10", "3.11", "3.12"]
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
- uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: ${{ matrix.python-version }}
|
|
21
|
+
- name: Install
|
|
22
|
+
run: |
|
|
23
|
+
python -m pip install --upgrade pip
|
|
24
|
+
pip install -e ".[dev]"
|
|
25
|
+
- name: Lint
|
|
26
|
+
run: ruff check .
|
|
27
|
+
- name: Typecheck
|
|
28
|
+
run: mypy src
|
|
29
|
+
- name: Tests
|
|
30
|
+
run: pytest
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
name: publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
inputs:
|
|
6
|
+
repository:
|
|
7
|
+
description: Target repository
|
|
8
|
+
required: true
|
|
9
|
+
default: testpypi
|
|
10
|
+
type: choice
|
|
11
|
+
options:
|
|
12
|
+
- testpypi
|
|
13
|
+
- pypi
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
publish:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
|
|
22
|
+
- uses: actions/setup-python@v5
|
|
23
|
+
with:
|
|
24
|
+
python-version: "3.12"
|
|
25
|
+
|
|
26
|
+
- name: Install dependencies
|
|
27
|
+
run: |
|
|
28
|
+
python -m pip install --upgrade pip
|
|
29
|
+
pip install -e ".[dev]"
|
|
30
|
+
|
|
31
|
+
- name: Quality gates
|
|
32
|
+
run: |
|
|
33
|
+
python -m ruff check .
|
|
34
|
+
python -m mypy src
|
|
35
|
+
python -m pytest -q
|
|
36
|
+
|
|
37
|
+
- name: Build package
|
|
38
|
+
run: |
|
|
39
|
+
rm -rf build dist
|
|
40
|
+
python -m build
|
|
41
|
+
python -m twine check dist/*
|
|
42
|
+
|
|
43
|
+
- name: Publish to TestPyPI
|
|
44
|
+
if: ${{ inputs.repository == 'testpypi' }}
|
|
45
|
+
env:
|
|
46
|
+
TWINE_USERNAME: __token__
|
|
47
|
+
TWINE_PASSWORD: ${{ secrets.TEST_PYPI_API_TOKEN }}
|
|
48
|
+
run: |
|
|
49
|
+
if [ -z "${TWINE_PASSWORD}" ]; then
|
|
50
|
+
echo "Missing secret TEST_PYPI_API_TOKEN"
|
|
51
|
+
exit 1
|
|
52
|
+
fi
|
|
53
|
+
python -m twine upload --repository-url https://test.pypi.org/legacy/ --skip-existing dist/*
|
|
54
|
+
|
|
55
|
+
- name: Publish to PyPI
|
|
56
|
+
if: ${{ inputs.repository == 'pypi' }}
|
|
57
|
+
env:
|
|
58
|
+
TWINE_USERNAME: __token__
|
|
59
|
+
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
|
|
60
|
+
run: |
|
|
61
|
+
if [ -z "${TWINE_PASSWORD}" ]; then
|
|
62
|
+
echo "Missing secret PYPI_API_TOKEN"
|
|
63
|
+
exit 1
|
|
64
|
+
fi
|
|
65
|
+
python -m twine upload --repository-url https://upload.pypi.org/legacy/ --skip-existing dist/*
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Clean Architecture - elven-logs-interceptor-python
|
|
2
|
+
|
|
3
|
+
This project follows Clean Architecture and SOLID principles.
|
|
4
|
+
|
|
5
|
+
## Layers
|
|
6
|
+
|
|
7
|
+
- `domain/`: entities, value objects, interfaces/protocols.
|
|
8
|
+
- `application/`: orchestration use-cases (`ConfigService`, `LogService`).
|
|
9
|
+
- `infrastructure/`: concrete implementations (transport, buffer, filter, circuit breaker, DLQ, interceptors).
|
|
10
|
+
- `presentation/`: object graph factory and runtime wiring.
|
|
11
|
+
|
|
12
|
+
## Core Flow
|
|
13
|
+
|
|
14
|
+
1. User logs through API (`logger.info`, etc).
|
|
15
|
+
2. `LogService` builds `LogEntryEntity` with context and dynamic labels.
|
|
16
|
+
3. `LogFilter` applies level checks, sampling, sanitization.
|
|
17
|
+
4. `MemoryBuffer` stores entries with bounded-memory policies.
|
|
18
|
+
5. Flush pipeline pushes batches to `ResilientTransport`.
|
|
19
|
+
6. `ResilientTransport` applies retry + circuit breaker + DLQ fallback.
|
|
20
|
+
7. Loki receives logs through JSON or Snappy path.
|
|
21
|
+
|
|
22
|
+
## Resilience
|
|
23
|
+
|
|
24
|
+
- Retry with exponential backoff + jitter.
|
|
25
|
+
- Circuit breaker with closed/open/half-open states.
|
|
26
|
+
- DLQ with bounded memory/file storage and drop-oldest policy.
|
|
27
|
+
|
|
28
|
+
## Runtime Model
|
|
29
|
+
|
|
30
|
+
- Sync-first API for app safety.
|
|
31
|
+
- Async wrappers (`aflush`, `adestroy`) via thread offload.
|
|
32
|
+
- Background flush workers capped by `max_concurrent_flushes`.
|
|
33
|
+
|
|
34
|
+
## Extensibility
|
|
35
|
+
|
|
36
|
+
Add a new transport by implementing `ILogTransport` contract and wiring it in `TransportFactory`.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
PYTHON ?= python3
|
|
2
|
+
|
|
3
|
+
.PHONY: install-dev lint typecheck test qa build clean publish-dry-run publish-testpypi publish-pypi
|
|
4
|
+
|
|
5
|
+
install-dev:
|
|
6
|
+
$(PYTHON) -m pip install -e ".[dev]"
|
|
7
|
+
|
|
8
|
+
lint:
|
|
9
|
+
$(PYTHON) -m ruff check .
|
|
10
|
+
|
|
11
|
+
typecheck:
|
|
12
|
+
$(PYTHON) -m mypy src
|
|
13
|
+
|
|
14
|
+
test:
|
|
15
|
+
$(PYTHON) -m pytest -q
|
|
16
|
+
|
|
17
|
+
qa: lint typecheck test
|
|
18
|
+
|
|
19
|
+
clean:
|
|
20
|
+
rm -rf build dist
|
|
21
|
+
find . -maxdepth 1 -type d -name "*.egg-info" -exec rm -rf {} +
|
|
22
|
+
|
|
23
|
+
build: clean
|
|
24
|
+
$(PYTHON) -m build
|
|
25
|
+
$(PYTHON) -m twine check dist/*
|
|
26
|
+
|
|
27
|
+
publish-dry-run:
|
|
28
|
+
./scripts/publish.sh --dry-run
|
|
29
|
+
|
|
30
|
+
publish-testpypi:
|
|
31
|
+
./scripts/publish.sh --repository testpypi
|
|
32
|
+
|
|
33
|
+
publish-pypi:
|
|
34
|
+
./scripts/publish.sh --repository pypi
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: elven-logs-interceptor-python
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: Production-grade logs interceptor for Python with Loki transport, resilience, batching, and framework integrations.
|
|
5
|
+
Author: Elven Observability
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: clean-architecture,interceptor,logging,logs,loki,observability,resilience
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: System :: Logging
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Requires-Dist: httpx>=0.27.0
|
|
18
|
+
Requires-Dist: typing-extensions>=4.12.0
|
|
19
|
+
Provides-Extra: all
|
|
20
|
+
Requires-Dist: brotli>=1.1.0; extra == 'all'
|
|
21
|
+
Requires-Dist: celery>=5.4.0; extra == 'all'
|
|
22
|
+
Requires-Dist: django>=4.2; extra == 'all'
|
|
23
|
+
Requires-Dist: fastapi>=0.111.0; extra == 'all'
|
|
24
|
+
Requires-Dist: flask>=3.0.0; extra == 'all'
|
|
25
|
+
Requires-Dist: loguru>=0.7.2; extra == 'all'
|
|
26
|
+
Requires-Dist: opentelemetry-api>=1.24.0; extra == 'all'
|
|
27
|
+
Requires-Dist: orjson>=3.10.0; extra == 'all'
|
|
28
|
+
Requires-Dist: protobuf>=5.0.0; extra == 'all'
|
|
29
|
+
Requires-Dist: python-snappy>=0.7.1; extra == 'all'
|
|
30
|
+
Requires-Dist: starlette>=0.37.0; extra == 'all'
|
|
31
|
+
Requires-Dist: structlog>=24.0.0; extra == 'all'
|
|
32
|
+
Provides-Extra: celery
|
|
33
|
+
Requires-Dist: celery>=5.4.0; extra == 'celery'
|
|
34
|
+
Provides-Extra: dev
|
|
35
|
+
Requires-Dist: build>=1.2.2; extra == 'dev'
|
|
36
|
+
Requires-Dist: mypy>=1.11.0; extra == 'dev'
|
|
37
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
|
|
38
|
+
Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
|
|
39
|
+
Requires-Dist: pytest>=8.3.0; extra == 'dev'
|
|
40
|
+
Requires-Dist: ruff>=0.6.0; extra == 'dev'
|
|
41
|
+
Requires-Dist: twine>=5.1.1; extra == 'dev'
|
|
42
|
+
Provides-Extra: django
|
|
43
|
+
Requires-Dist: django>=4.2; extra == 'django'
|
|
44
|
+
Provides-Extra: fastapi
|
|
45
|
+
Requires-Dist: fastapi>=0.111.0; extra == 'fastapi'
|
|
46
|
+
Requires-Dist: starlette>=0.37.0; extra == 'fastapi'
|
|
47
|
+
Provides-Extra: flask
|
|
48
|
+
Requires-Dist: flask>=3.0.0; extra == 'flask'
|
|
49
|
+
Provides-Extra: loguru
|
|
50
|
+
Requires-Dist: loguru>=0.7.2; extra == 'loguru'
|
|
51
|
+
Provides-Extra: otel
|
|
52
|
+
Requires-Dist: opentelemetry-api>=1.24.0; extra == 'otel'
|
|
53
|
+
Provides-Extra: perf
|
|
54
|
+
Requires-Dist: brotli>=1.1.0; extra == 'perf'
|
|
55
|
+
Requires-Dist: orjson>=3.10.0; extra == 'perf'
|
|
56
|
+
Requires-Dist: protobuf>=5.0.0; extra == 'perf'
|
|
57
|
+
Requires-Dist: python-snappy>=0.7.1; extra == 'perf'
|
|
58
|
+
Provides-Extra: structlog
|
|
59
|
+
Requires-Dist: structlog>=24.0.0; extra == 'structlog'
|
|
60
|
+
Description-Content-Type: text/markdown
|
|
61
|
+
|
|
62
|
+
# elven-logs-interceptor-python
|
|
63
|
+
|
|
64
|
+
High-performance, production-ready log interceptor for Python with Loki transport, batching, compression, circuit breaker, DLQ, and framework integrations.
|
|
65
|
+
|
|
66
|
+
## Installation
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pip install elven-logs-interceptor-python
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
With all extras:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
pip install "elven-logs-interceptor-python[all]"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Quick Start
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from logs_interceptor import init, logger
|
|
82
|
+
|
|
83
|
+
init(
|
|
84
|
+
{
|
|
85
|
+
"appName": "billing-service",
|
|
86
|
+
"interceptConsole": True,
|
|
87
|
+
"transport": {
|
|
88
|
+
"url": "https://loki.example.com/loki/api/v1/push",
|
|
89
|
+
"tenantId": "tenant-a",
|
|
90
|
+
"authToken": "token",
|
|
91
|
+
"compression": "gzip",
|
|
92
|
+
},
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
logger.info("service started", {"port": 3000})
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Environment Variables
|
|
100
|
+
|
|
101
|
+
The package supports all `LOGS_*` variables from the JS v3 design.
|
|
102
|
+
|
|
103
|
+
Required:
|
|
104
|
+
|
|
105
|
+
- `LOGS_URL`
|
|
106
|
+
- `LOGS_TENANT`
|
|
107
|
+
- `LOGS_APP_NAME`
|
|
108
|
+
|
|
109
|
+
Core:
|
|
110
|
+
|
|
111
|
+
- `LOGS_TOKEN`
|
|
112
|
+
- `LOGS_APP_VERSION`
|
|
113
|
+
- `LOGS_ENVIRONMENT`
|
|
114
|
+
|
|
115
|
+
Transport:
|
|
116
|
+
|
|
117
|
+
- `LOGS_COMPRESSION` (`none|gzip|brotli|snappy`)
|
|
118
|
+
- `LOGS_COMPRESSION_LEVEL`
|
|
119
|
+
- `LOGS_COMPRESSION_THRESHOLD`
|
|
120
|
+
- `LOGS_USE_WORKERS`
|
|
121
|
+
- `LOGS_MAX_WORKERS`
|
|
122
|
+
- `LOGS_WORKER_TIMEOUT`
|
|
123
|
+
- `LOGS_CONNECTION_POOLING`
|
|
124
|
+
- `LOGS_MAX_SOCKETS`
|
|
125
|
+
- `LOGS_TIMEOUT`
|
|
126
|
+
- `LOGS_MAX_RETRIES`
|
|
127
|
+
- `LOGS_RETRY_DELAY`
|
|
128
|
+
|
|
129
|
+
Buffer:
|
|
130
|
+
|
|
131
|
+
- `LOGS_BUFFER_MAX_SIZE`
|
|
132
|
+
- `LOGS_BUFFER_FLUSH_INTERVAL`
|
|
133
|
+
- `LOGS_BUFFER_MAX_MEMORY_MB`
|
|
134
|
+
- `LOGS_BUFFER_MAX_AGE`
|
|
135
|
+
- `LOGS_BUFFER_AUTO_FLUSH`
|
|
136
|
+
|
|
137
|
+
Filter:
|
|
138
|
+
|
|
139
|
+
- `LOGS_FILTER_LEVELS`
|
|
140
|
+
- `LOGS_FILTER_SAMPLING_RATE`
|
|
141
|
+
- `LOGS_FILTER_SANITIZE`
|
|
142
|
+
- `LOGS_FILTER_MAX_MESSAGE_LENGTH`
|
|
143
|
+
|
|
144
|
+
Circuit Breaker:
|
|
145
|
+
|
|
146
|
+
- `LOGS_CIRCUIT_BREAKER_ENABLED`
|
|
147
|
+
- `LOGS_CIRCUIT_BREAKER_FAILURE_THRESHOLD`
|
|
148
|
+
- `LOGS_CIRCUIT_BREAKER_RESET_TIMEOUT`
|
|
149
|
+
- `LOGS_CIRCUIT_BREAKER_HALF_OPEN_REQUESTS`
|
|
150
|
+
|
|
151
|
+
DLQ:
|
|
152
|
+
|
|
153
|
+
- `LOGS_DLQ_ENABLED`
|
|
154
|
+
- `LOGS_DLQ_TYPE` (`memory|file`)
|
|
155
|
+
- `LOGS_DLQ_MAX_SIZE`
|
|
156
|
+
- `LOGS_DLQ_MAX_RETRIES`
|
|
157
|
+
- `LOGS_DLQ_BASE_PATH`
|
|
158
|
+
|
|
159
|
+
Runtime:
|
|
160
|
+
|
|
161
|
+
- `LOGS_MAX_CONCURRENT_FLUSHES`
|
|
162
|
+
- `LOGS_INTERCEPT_CONSOLE`
|
|
163
|
+
- `LOGS_PRESERVE_ORIGINAL_CONSOLE`
|
|
164
|
+
- `LOGS_ENABLE_METRICS`
|
|
165
|
+
- `LOGS_ENABLE_HEALTH_CHECK`
|
|
166
|
+
- `LOGS_DEBUG`
|
|
167
|
+
- `LOGS_SILENT_ERRORS`
|
|
168
|
+
- `LOGS_ENABLED`
|
|
169
|
+
- `LOGS_AUTO_INIT`
|
|
170
|
+
- `LOGS_ENABLE_EXPERIMENTAL_PROTOBUF` (optional, enables experimental snappy/protobuf transport path)
|
|
171
|
+
|
|
172
|
+
Labels:
|
|
173
|
+
|
|
174
|
+
- Prefix `LOGS_LABEL_*` (example: `LOGS_LABEL_SERVICE=billing`)
|
|
175
|
+
|
|
176
|
+
## Public API
|
|
177
|
+
|
|
178
|
+
- `init(config)`
|
|
179
|
+
- `get_logger()`
|
|
180
|
+
- `is_initialized()`
|
|
181
|
+
- `destroy()`
|
|
182
|
+
- `adestroy()`
|
|
183
|
+
- `logger` proxy with `debug/info/warn/error/fatal/log/track_event/flush/aflush/get_metrics/get_health/destroy/adestroy/with_context/with_context_async`
|
|
184
|
+
|
|
185
|
+
Python import remains:
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
import logs_interceptor
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Integrations
|
|
192
|
+
|
|
193
|
+
- Python `logging` (`LoggingHandler`)
|
|
194
|
+
- FastAPI / Starlette (`FastAPIMiddleware`)
|
|
195
|
+
- Django (`DjangoMiddleware`)
|
|
196
|
+
- Flask (`FlaskExtension`)
|
|
197
|
+
- Celery (`CelerySignals`)
|
|
198
|
+
- structlog (`StructlogProcessor`)
|
|
199
|
+
- loguru (`LoguruSink`)
|
|
200
|
+
|
|
201
|
+
## Auto Init
|
|
202
|
+
|
|
203
|
+
Set `LOGS_AUTO_INIT=true` and import the package.
|
|
204
|
+
|
|
205
|
+
Preload mode:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
python -m logs_interceptor.preload
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Development
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
pip install -e ".[dev]"
|
|
215
|
+
ruff check .
|
|
216
|
+
mypy src
|
|
217
|
+
pytest
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Deploy / Publish
|
|
221
|
+
|
|
222
|
+
Local publish script:
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
./scripts/publish.sh --repository testpypi --dry-run
|
|
226
|
+
./scripts/publish.sh --repository testpypi
|
|
227
|
+
./scripts/publish.sh --repository pypi
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Token environment variables used by default:
|
|
231
|
+
|
|
232
|
+
- `TEST_PYPI_API_TOKEN` for `--repository testpypi`
|
|
233
|
+
- `PYPI_API_TOKEN` for `--repository pypi`
|
|
234
|
+
|
|
235
|
+
Optional script flags:
|
|
236
|
+
|
|
237
|
+
- `--skip-checks` (skip lint/type/test)
|
|
238
|
+
- `--skip-build` (skip build/twine check)
|
|
239
|
+
- `--token-env CUSTOM_VAR` (custom token env var name)
|
|
240
|
+
- `--no-skip-existing` (upload fails if version already exists)
|
|
241
|
+
|
|
242
|
+
Makefile shortcuts:
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
make qa
|
|
246
|
+
make publish-dry-run
|
|
247
|
+
make publish-testpypi
|
|
248
|
+
make publish-pypi
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
GitHub Actions publish workflow:
|
|
252
|
+
|
|
253
|
+
- File: `.github/workflows/publish.yml`
|
|
254
|
+
- Trigger: manual (`workflow_dispatch`)
|
|
255
|
+
- Inputs: `repository = testpypi | pypi`
|
|
256
|
+
- Required secrets:
|
|
257
|
+
- `TEST_PYPI_API_TOKEN`
|
|
258
|
+
- `PYPI_API_TOKEN`
|
|
259
|
+
|
|
260
|
+
## License
|
|
261
|
+
|
|
262
|
+
MIT
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# elven-logs-interceptor-python
|
|
2
|
+
|
|
3
|
+
High-performance, production-ready log interceptor for Python with Loki transport, batching, compression, circuit breaker, DLQ, and framework integrations.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install elven-logs-interceptor-python
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
With all extras:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install "elven-logs-interceptor-python[all]"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from logs_interceptor import init, logger
|
|
21
|
+
|
|
22
|
+
init(
|
|
23
|
+
{
|
|
24
|
+
"appName": "billing-service",
|
|
25
|
+
"interceptConsole": True,
|
|
26
|
+
"transport": {
|
|
27
|
+
"url": "https://loki.example.com/loki/api/v1/push",
|
|
28
|
+
"tenantId": "tenant-a",
|
|
29
|
+
"authToken": "token",
|
|
30
|
+
"compression": "gzip",
|
|
31
|
+
},
|
|
32
|
+
}
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
logger.info("service started", {"port": 3000})
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Environment Variables
|
|
39
|
+
|
|
40
|
+
The package supports all `LOGS_*` variables from the JS v3 design.
|
|
41
|
+
|
|
42
|
+
Required:
|
|
43
|
+
|
|
44
|
+
- `LOGS_URL`
|
|
45
|
+
- `LOGS_TENANT`
|
|
46
|
+
- `LOGS_APP_NAME`
|
|
47
|
+
|
|
48
|
+
Core:
|
|
49
|
+
|
|
50
|
+
- `LOGS_TOKEN`
|
|
51
|
+
- `LOGS_APP_VERSION`
|
|
52
|
+
- `LOGS_ENVIRONMENT`
|
|
53
|
+
|
|
54
|
+
Transport:
|
|
55
|
+
|
|
56
|
+
- `LOGS_COMPRESSION` (`none|gzip|brotli|snappy`)
|
|
57
|
+
- `LOGS_COMPRESSION_LEVEL`
|
|
58
|
+
- `LOGS_COMPRESSION_THRESHOLD`
|
|
59
|
+
- `LOGS_USE_WORKERS`
|
|
60
|
+
- `LOGS_MAX_WORKERS`
|
|
61
|
+
- `LOGS_WORKER_TIMEOUT`
|
|
62
|
+
- `LOGS_CONNECTION_POOLING`
|
|
63
|
+
- `LOGS_MAX_SOCKETS`
|
|
64
|
+
- `LOGS_TIMEOUT`
|
|
65
|
+
- `LOGS_MAX_RETRIES`
|
|
66
|
+
- `LOGS_RETRY_DELAY`
|
|
67
|
+
|
|
68
|
+
Buffer:
|
|
69
|
+
|
|
70
|
+
- `LOGS_BUFFER_MAX_SIZE`
|
|
71
|
+
- `LOGS_BUFFER_FLUSH_INTERVAL`
|
|
72
|
+
- `LOGS_BUFFER_MAX_MEMORY_MB`
|
|
73
|
+
- `LOGS_BUFFER_MAX_AGE`
|
|
74
|
+
- `LOGS_BUFFER_AUTO_FLUSH`
|
|
75
|
+
|
|
76
|
+
Filter:
|
|
77
|
+
|
|
78
|
+
- `LOGS_FILTER_LEVELS`
|
|
79
|
+
- `LOGS_FILTER_SAMPLING_RATE`
|
|
80
|
+
- `LOGS_FILTER_SANITIZE`
|
|
81
|
+
- `LOGS_FILTER_MAX_MESSAGE_LENGTH`
|
|
82
|
+
|
|
83
|
+
Circuit Breaker:
|
|
84
|
+
|
|
85
|
+
- `LOGS_CIRCUIT_BREAKER_ENABLED`
|
|
86
|
+
- `LOGS_CIRCUIT_BREAKER_FAILURE_THRESHOLD`
|
|
87
|
+
- `LOGS_CIRCUIT_BREAKER_RESET_TIMEOUT`
|
|
88
|
+
- `LOGS_CIRCUIT_BREAKER_HALF_OPEN_REQUESTS`
|
|
89
|
+
|
|
90
|
+
DLQ:
|
|
91
|
+
|
|
92
|
+
- `LOGS_DLQ_ENABLED`
|
|
93
|
+
- `LOGS_DLQ_TYPE` (`memory|file`)
|
|
94
|
+
- `LOGS_DLQ_MAX_SIZE`
|
|
95
|
+
- `LOGS_DLQ_MAX_RETRIES`
|
|
96
|
+
- `LOGS_DLQ_BASE_PATH`
|
|
97
|
+
|
|
98
|
+
Runtime:
|
|
99
|
+
|
|
100
|
+
- `LOGS_MAX_CONCURRENT_FLUSHES`
|
|
101
|
+
- `LOGS_INTERCEPT_CONSOLE`
|
|
102
|
+
- `LOGS_PRESERVE_ORIGINAL_CONSOLE`
|
|
103
|
+
- `LOGS_ENABLE_METRICS`
|
|
104
|
+
- `LOGS_ENABLE_HEALTH_CHECK`
|
|
105
|
+
- `LOGS_DEBUG`
|
|
106
|
+
- `LOGS_SILENT_ERRORS`
|
|
107
|
+
- `LOGS_ENABLED`
|
|
108
|
+
- `LOGS_AUTO_INIT`
|
|
109
|
+
- `LOGS_ENABLE_EXPERIMENTAL_PROTOBUF` (optional, enables experimental snappy/protobuf transport path)
|
|
110
|
+
|
|
111
|
+
Labels:
|
|
112
|
+
|
|
113
|
+
- Prefix `LOGS_LABEL_*` (example: `LOGS_LABEL_SERVICE=billing`)
|
|
114
|
+
|
|
115
|
+
## Public API
|
|
116
|
+
|
|
117
|
+
- `init(config)`
|
|
118
|
+
- `get_logger()`
|
|
119
|
+
- `is_initialized()`
|
|
120
|
+
- `destroy()`
|
|
121
|
+
- `adestroy()`
|
|
122
|
+
- `logger` proxy with `debug/info/warn/error/fatal/log/track_event/flush/aflush/get_metrics/get_health/destroy/adestroy/with_context/with_context_async`
|
|
123
|
+
|
|
124
|
+
Python import remains:
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
import logs_interceptor
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Integrations
|
|
131
|
+
|
|
132
|
+
- Python `logging` (`LoggingHandler`)
|
|
133
|
+
- FastAPI / Starlette (`FastAPIMiddleware`)
|
|
134
|
+
- Django (`DjangoMiddleware`)
|
|
135
|
+
- Flask (`FlaskExtension`)
|
|
136
|
+
- Celery (`CelerySignals`)
|
|
137
|
+
- structlog (`StructlogProcessor`)
|
|
138
|
+
- loguru (`LoguruSink`)
|
|
139
|
+
|
|
140
|
+
## Auto Init
|
|
141
|
+
|
|
142
|
+
Set `LOGS_AUTO_INIT=true` and import the package.
|
|
143
|
+
|
|
144
|
+
Preload mode:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
python -m logs_interceptor.preload
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Development
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
pip install -e ".[dev]"
|
|
154
|
+
ruff check .
|
|
155
|
+
mypy src
|
|
156
|
+
pytest
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Deploy / Publish
|
|
160
|
+
|
|
161
|
+
Local publish script:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
./scripts/publish.sh --repository testpypi --dry-run
|
|
165
|
+
./scripts/publish.sh --repository testpypi
|
|
166
|
+
./scripts/publish.sh --repository pypi
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Token environment variables used by default:
|
|
170
|
+
|
|
171
|
+
- `TEST_PYPI_API_TOKEN` for `--repository testpypi`
|
|
172
|
+
- `PYPI_API_TOKEN` for `--repository pypi`
|
|
173
|
+
|
|
174
|
+
Optional script flags:
|
|
175
|
+
|
|
176
|
+
- `--skip-checks` (skip lint/type/test)
|
|
177
|
+
- `--skip-build` (skip build/twine check)
|
|
178
|
+
- `--token-env CUSTOM_VAR` (custom token env var name)
|
|
179
|
+
- `--no-skip-existing` (upload fails if version already exists)
|
|
180
|
+
|
|
181
|
+
Makefile shortcuts:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
make qa
|
|
185
|
+
make publish-dry-run
|
|
186
|
+
make publish-testpypi
|
|
187
|
+
make publish-pypi
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
GitHub Actions publish workflow:
|
|
191
|
+
|
|
192
|
+
- File: `.github/workflows/publish.yml`
|
|
193
|
+
- Trigger: manual (`workflow_dispatch`)
|
|
194
|
+
- Inputs: `repository = testpypi | pypi`
|
|
195
|
+
- Required secrets:
|
|
196
|
+
- `TEST_PYPI_API_TOKEN`
|
|
197
|
+
- `PYPI_API_TOKEN`
|
|
198
|
+
|
|
199
|
+
## License
|
|
200
|
+
|
|
201
|
+
MIT
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from logs_interceptor import init
|
|
2
|
+
|
|
3
|
+
init(
|
|
4
|
+
{
|
|
5
|
+
"appName": "my-service",
|
|
6
|
+
"interceptConsole": True,
|
|
7
|
+
"transport": {
|
|
8
|
+
"url": "http://localhost:3100/loki/api/v1/push",
|
|
9
|
+
"tenantId": "my-tenant",
|
|
10
|
+
"compression": "gzip",
|
|
11
|
+
"enableConnectionPooling": True,
|
|
12
|
+
},
|
|
13
|
+
"deadLetterQueue": {"enabled": True, "type": "file"},
|
|
14
|
+
"circuitBreaker": {"enabled": True},
|
|
15
|
+
}
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
print("service started")
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from fastapi import FastAPI
|
|
2
|
+
|
|
3
|
+
from logs_interceptor import init, logger
|
|
4
|
+
from logs_interceptor.integrations import FastAPIMiddleware
|
|
5
|
+
|
|
6
|
+
init(
|
|
7
|
+
{
|
|
8
|
+
"transport": {
|
|
9
|
+
"url": "http://localhost:3100/loki/api/v1/push",
|
|
10
|
+
"tenantId": "my-tenant",
|
|
11
|
+
"compression": "brotli",
|
|
12
|
+
},
|
|
13
|
+
"appName": "my-api",
|
|
14
|
+
"interceptConsole": True,
|
|
15
|
+
"deadLetterQueue": {"enabled": True, "type": "file"},
|
|
16
|
+
}
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
app = FastAPI()
|
|
20
|
+
app.add_middleware(FastAPIMiddleware, logger=logger)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@app.get("/ping")
|
|
24
|
+
def ping() -> dict[str, str]:
|
|
25
|
+
logger.info("ping called")
|
|
26
|
+
return {"message": "pong"}
|