elven-logs-interceptor-python 0.1.2__py3-none-any.whl

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.
Files changed (56) hide show
  1. elven_logs_interceptor_python-0.1.2.dist-info/METADATA +262 -0
  2. elven_logs_interceptor_python-0.1.2.dist-info/RECORD +56 -0
  3. elven_logs_interceptor_python-0.1.2.dist-info/WHEEL +4 -0
  4. logs_interceptor/__init__.py +333 -0
  5. logs_interceptor/application/__init__.py +27 -0
  6. logs_interceptor/application/config_service.py +232 -0
  7. logs_interceptor/application/log_service.py +383 -0
  8. logs_interceptor/config.py +190 -0
  9. logs_interceptor/domain/__init__.py +25 -0
  10. logs_interceptor/domain/entities.py +41 -0
  11. logs_interceptor/domain/interfaces.py +149 -0
  12. logs_interceptor/domain/value_objects.py +40 -0
  13. logs_interceptor/infrastructure/__init__.py +48 -0
  14. logs_interceptor/infrastructure/buffer/__init__.py +3 -0
  15. logs_interceptor/infrastructure/buffer/memory_buffer.py +187 -0
  16. logs_interceptor/infrastructure/circuit_breaker/__init__.py +3 -0
  17. logs_interceptor/infrastructure/circuit_breaker/circuit_breaker.py +110 -0
  18. logs_interceptor/infrastructure/compression/__init__.py +14 -0
  19. logs_interceptor/infrastructure/compression/base.py +20 -0
  20. logs_interceptor/infrastructure/compression/brotli_compressor.py +27 -0
  21. logs_interceptor/infrastructure/compression/factory.py +18 -0
  22. logs_interceptor/infrastructure/compression/gzip_compressor.py +20 -0
  23. logs_interceptor/infrastructure/compression/noop_compressor.py +14 -0
  24. logs_interceptor/infrastructure/context/__init__.py +3 -0
  25. logs_interceptor/infrastructure/context/context_provider.py +44 -0
  26. logs_interceptor/infrastructure/dlq/__init__.py +4 -0
  27. logs_interceptor/infrastructure/dlq/file_dlq.py +170 -0
  28. logs_interceptor/infrastructure/dlq/memory_dlq.py +59 -0
  29. logs_interceptor/infrastructure/filter/__init__.py +3 -0
  30. logs_interceptor/infrastructure/filter/log_filter.py +55 -0
  31. logs_interceptor/infrastructure/interceptors/__init__.py +3 -0
  32. logs_interceptor/infrastructure/interceptors/runtime_interceptor.py +139 -0
  33. logs_interceptor/infrastructure/memory/__init__.py +3 -0
  34. logs_interceptor/infrastructure/memory/memory_tracker.py +95 -0
  35. logs_interceptor/infrastructure/metrics/__init__.py +3 -0
  36. logs_interceptor/infrastructure/metrics/metrics_collector.py +104 -0
  37. logs_interceptor/infrastructure/transport/__init__.py +12 -0
  38. logs_interceptor/infrastructure/transport/loki_json_transport.py +226 -0
  39. logs_interceptor/infrastructure/transport/loki_protobuf_transport.py +209 -0
  40. logs_interceptor/infrastructure/transport/resilient_transport.py +161 -0
  41. logs_interceptor/infrastructure/transport/transport_factory.py +39 -0
  42. logs_interceptor/infrastructure/workers/__init__.py +3 -0
  43. logs_interceptor/infrastructure/workers/worker_pool.py +57 -0
  44. logs_interceptor/integrations/__init__.py +17 -0
  45. logs_interceptor/integrations/celery.py +53 -0
  46. logs_interceptor/integrations/django.py +44 -0
  47. logs_interceptor/integrations/fastapi.py +53 -0
  48. logs_interceptor/integrations/flask.py +50 -0
  49. logs_interceptor/integrations/logging_handler.py +43 -0
  50. logs_interceptor/integrations/loguru.py +36 -0
  51. logs_interceptor/integrations/structlog.py +21 -0
  52. logs_interceptor/preload.py +61 -0
  53. logs_interceptor/presentation/__init__.py +3 -0
  54. logs_interceptor/presentation/factory.py +128 -0
  55. logs_interceptor/types.py +89 -0
  56. logs_interceptor/utils.py +508 -0
@@ -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,56 @@
1
+ logs_interceptor/__init__.py,sha256=ABMJGimpNjQMn9BtSCkmvTpjlAKEKiw0r_gqj4uO3qk,12143
2
+ logs_interceptor/config.py,sha256=AdRGtoZ_XG8CgC-yYC9O0a1h16bU5I0tL62NNoTgKcU,5057
3
+ logs_interceptor/preload.py,sha256=8BQEpLibDCQJpm0PwSdvikEEbfGxZSms0ov2v9eQbiY,1735
4
+ logs_interceptor/types.py,sha256=USRT-1t8K12nlyqFES38jOtvzi-ZU3wPgeAhIGssjcQ,1971
5
+ logs_interceptor/utils.py,sha256=Mnnigx3WfC55ILDxJ872KB59T9hymHlO-Fv-FkM59dk,18232
6
+ logs_interceptor/application/__init__.py,sha256=UeK-yoi200-4FryM-v2cCruTHe0Yjzbzyiiti4ZIwes,613
7
+ logs_interceptor/application/config_service.py,sha256=zDvNsx3L5bNNCLavgquVdELwudO5kPqxc_AZRmoHMNM,10265
8
+ logs_interceptor/application/log_service.py,sha256=Rorlfq_L1C0fs8661MP61toq7fTfVezR1NGUU8RXEXE,13699
9
+ logs_interceptor/domain/__init__.py,sha256=01vyK7FVaGTaMbgjoBYD8SyQJ0oWMRpCqMNwNu_4pIE,474
10
+ logs_interceptor/domain/entities.py,sha256=RleSeQmU5rK3Vv8onNYHFkDMXHdfjpE-6R0heLvdjTw,1158
11
+ logs_interceptor/domain/interfaces.py,sha256=trFLzgauQdGRxMCdeVgli-fzvg_X8RKHtSjHSSyfMPQ,3787
12
+ logs_interceptor/domain/value_objects.py,sha256=Z9yIMF6-kJLC9juO2rLCo5MRaUlqFG1deFtwvexrlmc,1130
13
+ logs_interceptor/infrastructure/__init__.py,sha256=HSB0RlVGL_14SrwFOvC-Fnfqg35MPJlDBoFW0H0MLMc,1177
14
+ logs_interceptor/infrastructure/buffer/__init__.py,sha256=paRwx_7lvJ8quGeOMdJdZeDEHRUBGEkg6923LH-DQmY,68
15
+ logs_interceptor/infrastructure/buffer/memory_buffer.py,sha256=ZVRRJwROImAc3xVBR1NsovYq6vX5H8pIdO89esPx6L8,6437
16
+ logs_interceptor/infrastructure/circuit_breaker/__init__.py,sha256=KKKX5AHbvkPoI6gO6o3e4xqS0ch9F0uk0PoZJGLEoWs,74
17
+ logs_interceptor/infrastructure/circuit_breaker/circuit_breaker.py,sha256=8BZHFY1AjzWRnoGL1l47yrP1BEE-WEKLRGK1xanB_oM,3858
18
+ logs_interceptor/infrastructure/compression/__init__.py,sha256=063D7o9SuB_42T6XxIaxXgfyvs3yu0svPBJ7Th-_K8I,372
19
+ logs_interceptor/infrastructure/compression/base.py,sha256=DNgUfyPaONj9FkKnEeE4JOP0OspMZZRlbkkQMIq5t64,425
20
+ logs_interceptor/infrastructure/compression/brotli_compressor.py,sha256=lYSReVzY2SsUvNJK8gHxfGbhTpgFgf3jH54pbIzDCVs,813
21
+ logs_interceptor/infrastructure/compression/factory.py,sha256=BoRA-3Am8jqrYZOqrLGMzKdieJGRYKoh00Syb3znZxM,645
22
+ logs_interceptor/infrastructure/compression/gzip_compressor.py,sha256=1cOO_GEQCaTQYjKmvpv-LfbJ9WN88ecjT_DkdIqGnmI,563
23
+ logs_interceptor/infrastructure/compression/noop_compressor.py,sha256=GCxjF3NFMQvQokoMjCZS7vkS97bH5NjKo-SmNgdWgZE,283
24
+ logs_interceptor/infrastructure/context/__init__.py,sha256=0jj06F58P3d0cK0xzW-TQUwQolmk-VM7-MmgPISo-zE,83
25
+ logs_interceptor/infrastructure/context/context_provider.py,sha256=ntTK5QygprGgJPmBfXodJ57OlfDG_2uov8-w3exjAhw,1367
26
+ logs_interceptor/infrastructure/dlq/__init__.py,sha256=BHmiKjxD2RfToQoYwwwpnQrZq7BjTEiOF3dYHsP6ouc,148
27
+ logs_interceptor/infrastructure/dlq/file_dlq.py,sha256=YgI7800Gf8z6SvMDHplSmmAo_2Ea_fRcaYLEA3GCLOY,6184
28
+ logs_interceptor/infrastructure/dlq/memory_dlq.py,sha256=2ekk4-IRLA5zt6nfE4LtwNenYqTU7KvIcy70z4dSVws,1714
29
+ logs_interceptor/infrastructure/filter/__init__.py,sha256=j5x8PzlxkLQRuzQBV2TvR9MOgKbSY7s687j2XJEzPRc,59
30
+ logs_interceptor/infrastructure/filter/log_filter.py,sha256=_xWK9OQS8avntzKk4COBtH8IWVlweaFX7-0RCZBwtPo,1839
31
+ logs_interceptor/infrastructure/interceptors/__init__.py,sha256=vJ6b8mM_7xHgw0aw8IBJjy5d3Bml-DZRIK7zWTDvThc,86
32
+ logs_interceptor/infrastructure/interceptors/runtime_interceptor.py,sha256=9LyqbnbACO0YSb8GRSa6fw6PDpLRJaQ0HpiyRdSUDCY,4482
33
+ logs_interceptor/infrastructure/memory/__init__.py,sha256=hzGBIBZ7uWdEW5wY8NYITonz5BMXjn5msbJ7pgbccVs,99
34
+ logs_interceptor/infrastructure/memory/memory_tracker.py,sha256=1LnkxeCPp8fpl2Bem6nT4O0hcbtiGTXak_kGj2bnsTc,2601
35
+ logs_interceptor/infrastructure/metrics/__init__.py,sha256=2eCe6cjBoqNTB4AmatfLogzxLiZH3rUsgA1TOPUJu5w,80
36
+ logs_interceptor/infrastructure/metrics/metrics_collector.py,sha256=aU8X7npzc0LTtExVpbCsT84dWjy5lk9IsHPNdB2JBI0,3862
37
+ logs_interceptor/infrastructure/transport/__init__.py,sha256=VOjWlaP7swEYL0Vp2RJMPXIbEU5dS5C76Haz9Ail6ig,387
38
+ logs_interceptor/infrastructure/transport/loki_json_transport.py,sha256=7_ZX7Tkg_FqXk545T1o8bHj8G69Uo4h9TbchCGce5Yw,8806
39
+ logs_interceptor/infrastructure/transport/loki_protobuf_transport.py,sha256=D10_IdUOdHsSoPr2zT0jLTFHimRwB28-Ykda5QO0FhE,8158
40
+ logs_interceptor/infrastructure/transport/resilient_transport.py,sha256=sFGvUCAGLhw2pbIRYVZxj2m2pHCcHEiGVQqZKAQ9avM,5728
41
+ logs_interceptor/infrastructure/transport/transport_factory.py,sha256=wgRnwhMOmM6zgQmZiL3HehkYX8cf0lAxdybQMpOx5W0,1530
42
+ logs_interceptor/infrastructure/workers/__init__.py,sha256=bsSn5S7sl5Mq0hh5QjD3y-ThEqWjZDcTGrVPX44Mxpk,94
43
+ logs_interceptor/infrastructure/workers/worker_pool.py,sha256=zVStQELQVmjUOnj41K-U1XZmziGkX-b9a2KAzFS4tas,1738
44
+ logs_interceptor/integrations/__init__.py,sha256=ahZMYjrtL954TR9Hg-bD8sQCafkoLVsyz66tmlRhweU,434
45
+ logs_interceptor/integrations/celery.py,sha256=RmvBgXQbaami459ixcVs0rxhPx3yc7-2NbSbGpN49K0,1853
46
+ logs_interceptor/integrations/django.py,sha256=lPj_rvxOXoz62udqjP2NlAqaCxUFQ_ZYWjP5eRrNNV8,1291
47
+ logs_interceptor/integrations/fastapi.py,sha256=UvzCt7o3OU7o6cqjsC4aj1p6ZN12tGzm8V9Hi78o51o,1806
48
+ logs_interceptor/integrations/flask.py,sha256=8XG_Wtnqc-XD8H27fZttxbONUvCFHn3ls7IqKQ_iBZs,1670
49
+ logs_interceptor/integrations/logging_handler.py,sha256=CxN0kRUuITC4c3aKPQfq7bL6r_VdX3CPhhegkaWz1QM,1340
50
+ logs_interceptor/integrations/loguru.py,sha256=Tz7ivaVdQr7H5f0jwaPewU9cPEodCTNMCxNQexqL5u4,1074
51
+ logs_interceptor/integrations/structlog.py,sha256=7TFT-4s7D8ER5_Tg9F1r8XhJGRCuKZcwMMIRc8RhSkU,700
52
+ logs_interceptor/presentation/__init__.py,sha256=_U16fFB-s0ub0Tl1rfAK4zvzANQRD3KO81ni0qynjRA,114
53
+ logs_interceptor/presentation/factory.py,sha256=rvPU1kaQcgQlCZ8iPrwZQG7U70FukNjwaIe9eHzE2WY,4473
54
+ elven_logs_interceptor_python-0.1.2.dist-info/METADATA,sha256=D5RLzXt3_hF7m9c_tswYkxCyzznGZMccYJS8CyO1alM,6498
55
+ elven_logs_interceptor_python-0.1.2.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
56
+ elven_logs_interceptor_python-0.1.2.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,333 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import os
5
+ from dataclasses import dataclass
6
+ from typing import Any
7
+
8
+ from .application import ConfigService
9
+ from .config import (
10
+ BufferConfig,
11
+ CircuitBreakerConfig,
12
+ DeadLetterQueueConfig,
13
+ FilterConfig,
14
+ IntegrationsConfig,
15
+ LogsInterceptorConfig,
16
+ PerformanceConfig,
17
+ TransportConfig,
18
+ )
19
+ from .domain.interfaces import ILogger
20
+ from .integrations import (
21
+ CelerySignals,
22
+ DjangoMiddleware,
23
+ FastAPIMiddleware,
24
+ FlaskExtension,
25
+ LoggingHandler,
26
+ LoguruSink,
27
+ StructlogProcessor,
28
+ )
29
+ from .presentation import LogsInterceptorFactory
30
+ from .types import HealthStatus, LoggerMetrics, LogLevel
31
+ from .utils import internal_debug, internal_error, load_config_from_env, merge_configs, parse_bool
32
+
33
+ __all__ = [
34
+ "init",
35
+ "get_logger",
36
+ "is_initialized",
37
+ "destroy",
38
+ "adestroy",
39
+ "logger",
40
+ "LogsInterceptorConfig",
41
+ "TransportConfig",
42
+ "BufferConfig",
43
+ "FilterConfig",
44
+ "CircuitBreakerConfig",
45
+ "DeadLetterQueueConfig",
46
+ "PerformanceConfig",
47
+ "IntegrationsConfig",
48
+ "LoggingHandler",
49
+ "FastAPIMiddleware",
50
+ "DjangoMiddleware",
51
+ "FlaskExtension",
52
+ "CelerySignals",
53
+ "StructlogProcessor",
54
+ "LoguruSink",
55
+ ]
56
+
57
+
58
+ @dataclass(slots=True)
59
+ class _RuntimeState:
60
+ logger: ILogger
61
+ runtime_interceptor: Any | None = None
62
+
63
+
64
+ _global_runtime: _RuntimeState | None = None
65
+
66
+
67
+ def _pick(config: dict[str, Any], snake: str, camel: str, default: Any = None) -> Any:
68
+ if snake in config:
69
+ return config[snake]
70
+ if camel in config:
71
+ return config[camel]
72
+ return default
73
+
74
+
75
+ def _coerce_config(user_config: LogsInterceptorConfig | dict[str, Any] | None) -> LogsInterceptorConfig:
76
+ if user_config is None:
77
+ return LogsInterceptorConfig()
78
+ if isinstance(user_config, LogsInterceptorConfig):
79
+ return user_config
80
+
81
+ transport_raw = _pick(user_config, "transport", "transport", {}) or {}
82
+ buffer_raw = _pick(user_config, "buffer", "buffer")
83
+ filter_raw = _pick(user_config, "filter", "filter")
84
+ cb_raw = _pick(user_config, "circuit_breaker", "circuitBreaker")
85
+ perf_raw = _pick(user_config, "performance", "performance")
86
+ dlq_raw = _pick(user_config, "dead_letter_queue", "deadLetterQueue")
87
+ integrations_raw = _pick(user_config, "integrations", "integrations")
88
+
89
+ return LogsInterceptorConfig(
90
+ transport=TransportConfig(
91
+ url=_pick(transport_raw, "url", "url", ""),
92
+ tenant_id=_pick(transport_raw, "tenant_id", "tenantId", ""),
93
+ auth_token=_pick(transport_raw, "auth_token", "authToken"),
94
+ timeout=_pick(transport_raw, "timeout", "timeout"),
95
+ max_retries=_pick(transport_raw, "max_retries", "maxRetries"),
96
+ retry_delay=_pick(transport_raw, "retry_delay", "retryDelay"),
97
+ compression=_pick(transport_raw, "compression", "compression"),
98
+ compression_level=_pick(transport_raw, "compression_level", "compressionLevel"),
99
+ compression_threshold=_pick(transport_raw, "compression_threshold", "compressionThreshold"),
100
+ use_workers=_pick(transport_raw, "use_workers", "useWorkers"),
101
+ max_workers=_pick(transport_raw, "max_workers", "maxWorkers"),
102
+ worker_timeout=_pick(transport_raw, "worker_timeout", "workerTimeout"),
103
+ enable_connection_pooling=_pick(
104
+ transport_raw, "enable_connection_pooling", "enableConnectionPooling"
105
+ ),
106
+ max_sockets=_pick(transport_raw, "max_sockets", "maxSockets"),
107
+ ),
108
+ app_name=_pick(user_config, "app_name", "appName", ""),
109
+ version=_pick(user_config, "version", "version"),
110
+ environment=_pick(user_config, "environment", "environment"),
111
+ labels=_pick(user_config, "labels", "labels"),
112
+ dynamic_labels=_pick(user_config, "dynamic_labels", "dynamicLabels"),
113
+ buffer=(
114
+ BufferConfig(
115
+ max_size=_pick(buffer_raw, "max_size", "maxSize") if buffer_raw else None,
116
+ flush_interval=_pick(buffer_raw, "flush_interval", "flushInterval")
117
+ if buffer_raw
118
+ else None,
119
+ max_age=_pick(buffer_raw, "max_age", "maxAge") if buffer_raw else None,
120
+ auto_flush=_pick(buffer_raw, "auto_flush", "autoFlush") if buffer_raw else None,
121
+ max_memory_mb=_pick(buffer_raw, "max_memory_mb", "maxMemoryMB")
122
+ if buffer_raw
123
+ else None,
124
+ )
125
+ if buffer_raw
126
+ else None
127
+ ),
128
+ filter=(
129
+ FilterConfig(
130
+ levels=_pick(filter_raw, "levels", "levels"),
131
+ patterns=_pick(filter_raw, "patterns", "patterns"),
132
+ sampling_rate=_pick(filter_raw, "sampling_rate", "samplingRate"),
133
+ max_message_length=_pick(filter_raw, "max_message_length", "maxMessageLength"),
134
+ sanitize=_pick(filter_raw, "sanitize", "sanitize"),
135
+ sensitive_patterns=_pick(filter_raw, "sensitive_patterns", "sensitivePatterns"),
136
+ )
137
+ if filter_raw
138
+ else None
139
+ ),
140
+ circuit_breaker=(
141
+ CircuitBreakerConfig(
142
+ enabled=_pick(cb_raw, "enabled", "enabled"),
143
+ failure_threshold=_pick(cb_raw, "failure_threshold", "failureThreshold"),
144
+ reset_timeout=_pick(cb_raw, "reset_timeout", "resetTimeout"),
145
+ half_open_requests=_pick(cb_raw, "half_open_requests", "halfOpenRequests"),
146
+ )
147
+ if cb_raw
148
+ else None
149
+ ),
150
+ integrations=(IntegrationsConfig() if integrations_raw is not None else None),
151
+ performance=(
152
+ PerformanceConfig(
153
+ use_workers=_pick(perf_raw, "use_workers", "useWorkers"),
154
+ max_concurrent_flushes=_pick(perf_raw, "max_concurrent_flushes", "maxConcurrentFlushes"),
155
+ compression_level=_pick(perf_raw, "compression_level", "compressionLevel"),
156
+ max_workers=_pick(perf_raw, "max_workers", "maxWorkers"),
157
+ worker_timeout=_pick(perf_raw, "worker_timeout", "workerTimeout"),
158
+ )
159
+ if perf_raw
160
+ else None
161
+ ),
162
+ dead_letter_queue=(
163
+ DeadLetterQueueConfig(
164
+ enabled=_pick(dlq_raw, "enabled", "enabled"),
165
+ type=_pick(dlq_raw, "type", "type"),
166
+ max_size=_pick(dlq_raw, "max_size", "maxSize"),
167
+ max_file_size_mb=_pick(dlq_raw, "max_file_size_mb", "maxFileSizeMB"),
168
+ max_retries=_pick(dlq_raw, "max_retries", "maxRetries"),
169
+ base_path=_pick(dlq_raw, "base_path", "basePath"),
170
+ )
171
+ if dlq_raw
172
+ else None
173
+ ),
174
+ enable_metrics=_pick(user_config, "enable_metrics", "enableMetrics"),
175
+ enable_health_check=_pick(user_config, "enable_health_check", "enableHealthCheck"),
176
+ intercept_console=_pick(user_config, "intercept_console", "interceptConsole"),
177
+ preserve_original_console=_pick(
178
+ user_config,
179
+ "preserve_original_console",
180
+ "preserveOriginalConsole",
181
+ ),
182
+ debug=_pick(user_config, "debug", "debug"),
183
+ silent_errors=_pick(user_config, "silent_errors", "silentErrors"),
184
+ )
185
+
186
+
187
+ def init(user_config: LogsInterceptorConfig | dict[str, Any] | None = None) -> ILogger:
188
+ global _global_runtime
189
+
190
+ env_config = load_config_from_env()
191
+ if user_config is None:
192
+ merged_config = env_config
193
+ else:
194
+ coerced_user = _coerce_config(user_config)
195
+ merged_config = merge_configs(coerced_user, env_config)
196
+
197
+ errors = ConfigService.validate(merged_config)
198
+ if errors:
199
+ raise ValueError("Configuration errors:\n" + "\n".join(errors))
200
+
201
+ resolved = ConfigService.resolve(merged_config)
202
+
203
+ previous = _global_runtime
204
+ if previous is not None:
205
+ if previous.runtime_interceptor is not None:
206
+ previous.runtime_interceptor.restore()
207
+ try:
208
+ previous.logger.destroy()
209
+ except Exception:
210
+ pass
211
+
212
+ runtime = LogsInterceptorFactory.create(resolved)
213
+ _global_runtime = _RuntimeState(
214
+ logger=runtime.logger,
215
+ runtime_interceptor=runtime.runtime_interceptor,
216
+ )
217
+
218
+ return runtime.logger
219
+
220
+
221
+ def get_logger() -> ILogger:
222
+ if _global_runtime is None:
223
+ raise RuntimeError("LogsInterceptor not initialized. Call init() first.")
224
+ return _global_runtime.logger
225
+
226
+
227
+ def is_initialized() -> bool:
228
+ return _global_runtime is not None
229
+
230
+
231
+ def destroy() -> None:
232
+ global _global_runtime
233
+
234
+ if _global_runtime is None:
235
+ return
236
+
237
+ runtime = _global_runtime
238
+ _global_runtime = None
239
+
240
+ if runtime.runtime_interceptor is not None:
241
+ runtime.runtime_interceptor.restore()
242
+
243
+ runtime.logger.destroy()
244
+
245
+
246
+ async def adestroy() -> None:
247
+ await asyncio.to_thread(destroy)
248
+
249
+
250
+ class _GlobalLoggerProxy:
251
+ def debug(self, message: str, context: dict[str, Any] | None = None) -> None:
252
+ if _global_runtime is not None:
253
+ _global_runtime.logger.debug(message, context)
254
+
255
+ def info(self, message: str, context: dict[str, Any] | None = None) -> None:
256
+ if _global_runtime is not None:
257
+ _global_runtime.logger.info(message, context)
258
+
259
+ def warn(self, message: str, context: dict[str, Any] | None = None) -> None:
260
+ if _global_runtime is not None:
261
+ _global_runtime.logger.warn(message, context)
262
+
263
+ def error(self, message: str, context: dict[str, Any] | None = None) -> None:
264
+ if _global_runtime is not None:
265
+ _global_runtime.logger.error(message, context)
266
+
267
+ def fatal(self, message: str, context: dict[str, Any] | None = None) -> None:
268
+ if _global_runtime is not None:
269
+ _global_runtime.logger.fatal(message, context)
270
+
271
+ def log(self, level: LogLevel, message: str, context: dict[str, Any] | None = None) -> None:
272
+ if _global_runtime is not None:
273
+ _global_runtime.logger.log(level, message, context)
274
+
275
+ def track_event(self, event_name: str, properties: dict[str, Any] | None = None) -> None:
276
+ if _global_runtime is not None:
277
+ _global_runtime.logger.track_event(event_name, properties)
278
+
279
+ def with_context(self, context: dict[str, Any], fn: Any) -> Any:
280
+ if _global_runtime is None:
281
+ raise RuntimeError("LogsInterceptor not initialized")
282
+ return _global_runtime.logger.with_context(context, fn)
283
+
284
+ async def with_context_async(self, context: dict[str, Any], fn: Any) -> Any:
285
+ if _global_runtime is None:
286
+ raise RuntimeError("LogsInterceptor not initialized")
287
+ return await _global_runtime.logger.with_context_async(context, fn)
288
+
289
+ def flush(self) -> None:
290
+ if _global_runtime is not None:
291
+ _global_runtime.logger.flush()
292
+
293
+ async def aflush(self) -> None:
294
+ if _global_runtime is not None:
295
+ await _global_runtime.logger.aflush()
296
+
297
+ def get_metrics(self) -> LoggerMetrics:
298
+ if _global_runtime is None:
299
+ raise RuntimeError("LogsInterceptor not initialized")
300
+ return _global_runtime.logger.get_metrics()
301
+
302
+ def get_health(self) -> HealthStatus:
303
+ if _global_runtime is None:
304
+ raise RuntimeError("LogsInterceptor not initialized")
305
+ return _global_runtime.logger.get_health()
306
+
307
+ def destroy(self) -> None:
308
+ destroy()
309
+
310
+ async def adestroy(self) -> None:
311
+ await adestroy()
312
+
313
+
314
+ logger = _GlobalLoggerProxy()
315
+
316
+
317
+ def _auto_init_if_enabled() -> None:
318
+ if not parse_bool(os.getenv("LOGS_AUTO_INIT"), False):
319
+ return
320
+
321
+ env_config = load_config_from_env()
322
+ if not env_config.transport.url or not env_config.transport.tenant_id or not env_config.app_name:
323
+ internal_debug("Auto-init skipped due to missing required LOGS_* variables")
324
+ return
325
+
326
+ try:
327
+ init(env_config)
328
+ internal_debug("Auto-initialized from LOGS_* environment variables")
329
+ except Exception as exc:
330
+ internal_error("Auto-initialization failed", exc)
331
+
332
+
333
+ _auto_init_if_enabled()
@@ -0,0 +1,27 @@
1
+ from ..config import (
2
+ BufferConfig,
3
+ CircuitBreakerConfig,
4
+ DeadLetterQueueConfig,
5
+ FilterConfig,
6
+ IntegrationsConfig,
7
+ LogsInterceptorConfig,
8
+ PerformanceConfig,
9
+ ResolvedLogsInterceptorConfig,
10
+ TransportConfig,
11
+ )
12
+ from .config_service import ConfigService
13
+ from .log_service import LogService
14
+
15
+ __all__ = [
16
+ "ConfigService",
17
+ "LogService",
18
+ "LogsInterceptorConfig",
19
+ "ResolvedLogsInterceptorConfig",
20
+ "TransportConfig",
21
+ "BufferConfig",
22
+ "FilterConfig",
23
+ "CircuitBreakerConfig",
24
+ "DeadLetterQueueConfig",
25
+ "PerformanceConfig",
26
+ "IntegrationsConfig",
27
+ ]