xakki-log-python 0.4.0__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.
- log_python/__init__.py +160 -0
- log_python/config.py +144 -0
- log_python/const.py +41 -0
- log_python/context.py +575 -0
- log_python/formatters.py +217 -0
- log_python/handlers/__init__.py +1 -0
- log_python/handlers/redis.py +42 -0
- log_python/heartbeat.py +52 -0
- log_python/integrations/__init__.py +1 -0
- log_python/integrations/sqlalchemy.py +87 -0
- log_python/logtype.py +171 -0
- log_python/manager.py +394 -0
- log_python/py.typed +0 -0
- log_python/redactor.py +261 -0
- log_python/trace.py +504 -0
- xakki_log_python-0.4.0.dist-info/METADATA +275 -0
- xakki_log_python-0.4.0.dist-info/RECORD +19 -0
- xakki_log_python-0.4.0.dist-info/WHEEL +4 -0
- xakki_log_python-0.4.0.dist-info/licenses/LICENSE +674 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: xakki-log-python
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Structured logging library for Python — GELF/Graylog-oriented, Python port of xakki/log-laravel
|
|
5
|
+
Project-URL: Homepage, https://github.com/Xakki/log-python
|
|
6
|
+
Project-URL: Source, https://github.com/Xakki/log-python
|
|
7
|
+
Author: xakki
|
|
8
|
+
License-Expression: GPL-3.0-or-later
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: fluent-bit,gelf,graylog,logging,structured-logging
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Classifier: Topic :: System :: Logging
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: mypy>=1.11; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
25
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: ruff>=0.6.0; extra == 'dev'
|
|
27
|
+
Provides-Extra: redis
|
|
28
|
+
Requires-Dist: redis>=5.0; extra == 'redis'
|
|
29
|
+
Provides-Extra: sqlalchemy
|
|
30
|
+
Requires-Dist: sqlalchemy>=2.0; extra == 'sqlalchemy'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# xakki-log-python
|
|
34
|
+
|
|
35
|
+
Structured JSON logging for Python, compatible with the field and type contract
|
|
36
|
+
of [`xakki/log-laravel`](https://github.com/Xakki/log-laravel) and intended for
|
|
37
|
+
stdout → fluent-bit → Graylog/GELF pipelines.
|
|
38
|
+
|
|
39
|
+
Distribution: `xakki-log-python` (formerly `xakki-pylog`); import name: `log_python` (formerly `pylog`).
|
|
40
|
+
|
|
41
|
+
The core uses only the Python standard library. Redis and SQLAlchemy support are
|
|
42
|
+
optional extras.
|
|
43
|
+
|
|
44
|
+
The release version has one editable source: `[project].version` in
|
|
45
|
+
`pyproject.toml`. `LOGGER_VER` reads the installed distribution metadata,
|
|
46
|
+
`log_python.__version__` aliases it, and every record emits it as `extra.log_ver`.
|
|
47
|
+
|
|
48
|
+
## Requirements
|
|
49
|
+
|
|
50
|
+
- Python 3.11+
|
|
51
|
+
- No runtime dependencies for the core package
|
|
52
|
+
|
|
53
|
+
## Installation
|
|
54
|
+
|
|
55
|
+
The package is published on [PyPI](https://pypi.org/project/xakki-log-python/):
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
pip install xakki-log-python
|
|
59
|
+
# optional integrations
|
|
60
|
+
pip install 'xakki-log-python[redis]'
|
|
61
|
+
pip install 'xakki-log-python[sqlalchemy]'
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The same package name works with other package managers:
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
uv add xakki-log-python
|
|
68
|
+
poetry add xakki-log-python
|
|
69
|
+
pdm add xakki-log-python
|
|
70
|
+
pipenv install xakki-log-python
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Extras use the same syntax, e.g. `uv add 'xakki-log-python[redis]'`.
|
|
74
|
+
|
|
75
|
+
To install the unreleased development version straight from Git:
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
pip install git+https://github.com/Xakki/log-python
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Quick start
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
import logging
|
|
85
|
+
import log_python
|
|
86
|
+
|
|
87
|
+
log_python.setup()
|
|
88
|
+
|
|
89
|
+
logger = logging.getLogger("orders")
|
|
90
|
+
logger.info(
|
|
91
|
+
"order paid",
|
|
92
|
+
extra={"context": {"order_id": "42", "order_money": "19.99"}},
|
|
93
|
+
)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`order_id` is emitted as an integer and `order_money` as a float according to
|
|
97
|
+
the shared context field naming contract. Every record also receives
|
|
98
|
+
`log_type`, `file`, `request_id`, stable process metadata and, at warning or
|
|
99
|
+
above, a bounded trace.
|
|
100
|
+
|
|
101
|
+
`log_python.setup()` configures one idempotent root `StreamHandler`. It preserves
|
|
102
|
+
unrelated handlers and writes one newline-terminated JSON object per event.
|
|
103
|
+
|
|
104
|
+
### Configuration
|
|
105
|
+
|
|
106
|
+
`PylogConfig.from_env()` reads a startup snapshot:
|
|
107
|
+
|
|
108
|
+
| Variable | Purpose | Default |
|
|
109
|
+
|---|---|---|
|
|
110
|
+
| `APP_NAME` | `extra.app_name` | unset |
|
|
111
|
+
| `APP_ENV` | `extra.app_env` | unset |
|
|
112
|
+
| `APP_VERSION` | `extra.app_ver` | unset |
|
|
113
|
+
| `LOG_TIER` | `extra.tier` (`web`, `api`, `worker`, `cron`) | unset |
|
|
114
|
+
| `RELEASE_TAG` | release/build id | unset |
|
|
115
|
+
| `RELEASE_TIME` | release time | unset |
|
|
116
|
+
| `LOG_MESSAGE_LIMIT` | source message character limit | `3024` |
|
|
117
|
+
| `LOG_ALLOW_MEMORY` | add process memory fields | `false` |
|
|
118
|
+
| `LOG_SNAKE_CASE` | normalize context keys before coercion | `false` |
|
|
119
|
+
| `LOG_CAPTURE_HANDLERS` | install chained uncaught-exception hooks | `false` |
|
|
120
|
+
|
|
121
|
+
Boolean parsing accepts `1/true/yes/on` and `0/false/no/off` (case-insensitive).
|
|
122
|
+
Invalid values raise `ValueError` during configuration instead of silently
|
|
123
|
+
changing behavior.
|
|
124
|
+
|
|
125
|
+
Programmatic configuration:
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
from log_python import ExtraConfig, PylogConfig, setup
|
|
129
|
+
|
|
130
|
+
config = PylogConfig(
|
|
131
|
+
extra=ExtraConfig(
|
|
132
|
+
app_name="orders",
|
|
133
|
+
app_env="prod",
|
|
134
|
+
app_ver="2026.08.13",
|
|
135
|
+
tier="worker",
|
|
136
|
+
),
|
|
137
|
+
capture_handlers=True,
|
|
138
|
+
)
|
|
139
|
+
setup(config=config)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Available formatters are `JsonFormatter` (default), `LineFormatter`, and
|
|
143
|
+
`ConsoleFormatter`:
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
log_python.setup(formatter="console", replace=True)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Context and request correlation
|
|
150
|
+
|
|
151
|
+
Bound context is isolated with `contextvars`, so concurrent threads and async
|
|
152
|
+
tasks do not overwrite one another:
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
from log_python import bind, bound_context, clear, set_request_id
|
|
156
|
+
|
|
157
|
+
set_request_id(incoming_request_id)
|
|
158
|
+
bind(user_id=7)
|
|
159
|
+
|
|
160
|
+
with bound_context(order_id=42):
|
|
161
|
+
logger.info("order loaded")
|
|
162
|
+
|
|
163
|
+
clear()
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Web middleware can call `remote_ip(value)`. Queue workers should call
|
|
167
|
+
`reset_request_id()` and `clear()` between jobs.
|
|
168
|
+
|
|
169
|
+
## Exceptions and redaction
|
|
170
|
+
|
|
171
|
+
Use normal stdlib exception logging:
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
try:
|
|
175
|
+
run_job()
|
|
176
|
+
except RuntimeError:
|
|
177
|
+
logger.exception("job failed", extra={"context": {"job_id": 42}})
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Exception class, sanitized message, code, origin and coalesced trace are emitted
|
|
181
|
+
as structured context. Sensitive keys and recognizable Bearer/Basic tokens,
|
|
182
|
+
JWTs, key/value credentials and URL passwords are redacted before
|
|
183
|
+
serialization. `skipTrace` can suppress trace collection for a specific event
|
|
184
|
+
and is never emitted itself.
|
|
185
|
+
|
|
186
|
+
When `LOG_CAPTURE_HANDLERS=true` (or setup enables capture explicitly), log_python
|
|
187
|
+
chains the process/thread exception hooks and `warnings.showwarning`. Uncaught
|
|
188
|
+
exceptions get `log_type=exception`; runtime warnings get `log_type=trigger`.
|
|
189
|
+
Python cannot reliably turn native crashes into structured JSON, so supervisors
|
|
190
|
+
and `faulthandler` remain the fallback for those failures.
|
|
191
|
+
|
|
192
|
+
## Optional integrations
|
|
193
|
+
|
|
194
|
+
### Redis
|
|
195
|
+
|
|
196
|
+
`log_python.handlers.redis.RedisHandler` accepts any redis-py-compatible client,
|
|
197
|
+
uses `LPUSH`, and applies `LTRIM` in the same transaction when `cap_size > 0`.
|
|
198
|
+
It stores exactly the result of its configured formatter, so attach
|
|
199
|
+
`JsonFormatter` (or reuse the formatter installed by `log_python.setup()`):
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
from redis import Redis
|
|
203
|
+
from log_python.handlers.redis import RedisHandler
|
|
204
|
+
|
|
205
|
+
handler = RedisHandler(Redis.from_url("redis://localhost"), key="logs", cap_size=10_000)
|
|
206
|
+
handler.setFormatter(log_python.JsonFormatter(log_python.PylogConfig.from_env()))
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### SQLAlchemy
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
from log_python.integrations.sqlalchemy import install_sqlalchemy_logging
|
|
213
|
+
|
|
214
|
+
install_sqlalchemy_logging(
|
|
215
|
+
engine,
|
|
216
|
+
select_threshold_ms=200,
|
|
217
|
+
all_threshold_ms=500,
|
|
218
|
+
include_bindings=False, # keep disabled in production
|
|
219
|
+
)
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
The integration emits `db_table`, `db_time_ms`, `db_bindings`, `sql_type`, and
|
|
223
|
+
`tag=sql`. Telescope queries are ignored; long or numerous bindings are not
|
|
224
|
+
expanded. Bindings are disabled by default and pass through the secret redactor
|
|
225
|
+
before serialization when explicitly enabled.
|
|
226
|
+
|
|
227
|
+
### Worker heartbeat
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
from log_python import Heartbeat
|
|
231
|
+
|
|
232
|
+
heartbeat = Heartbeat(logging.getLogger("worker"), name="default", interval=120)
|
|
233
|
+
heartbeat.start()
|
|
234
|
+
# ...
|
|
235
|
+
heartbeat.stop(status=0)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Wire contract
|
|
239
|
+
|
|
240
|
+
The normative, language-independent specification remains in the reference
|
|
241
|
+
repository and is not duplicated here:
|
|
242
|
+
|
|
243
|
+
- [LoggingRules.md](https://github.com/Xakki/log-laravel/blob/main/docs/LoggingRules.md)
|
|
244
|
+
- [ContextFieldNaming.md](https://github.com/Xakki/log-laravel/blob/main/docs/ContextFieldNaming.md)
|
|
245
|
+
- [Graylog.md](https://github.com/Xakki/log-laravel/blob/main/docs/Graylog.md)
|
|
246
|
+
- [PhytonLog.md](https://github.com/Xakki/log-laravel/blob/main/docs/PhytonLog.md)
|
|
247
|
+
|
|
248
|
+
The JSON formatter provides lowercase `level_name`, Monolog-compatible numeric
|
|
249
|
+
`level` values (`100`–`600`), RFC-3339 timestamps with timezone and
|
|
250
|
+
microseconds, canonical `app_name`/`app_env` extras, one coalesced trace field,
|
|
251
|
+
and iterative UTF-8-aware trimming to the configured wire budget (1400 bytes by
|
|
252
|
+
default) without dropping the required process-extra fields.
|
|
253
|
+
|
|
254
|
+
## Development
|
|
255
|
+
|
|
256
|
+
The repository is Docker-only. Do not run Python tooling directly on the host.
|
|
257
|
+
|
|
258
|
+
```sh
|
|
259
|
+
make docker-build
|
|
260
|
+
make deps-i
|
|
261
|
+
make test # ruff format/check + mypy --strict + pytest
|
|
262
|
+
make build
|
|
263
|
+
make test-install # uv, Poetry, PDM, Pipenv and pip
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Run `make help` for all targets.
|
|
267
|
+
|
|
268
|
+
# Related projects
|
|
269
|
+
|
|
270
|
+
Sibling logging libraries sharing one field contract ([Logging Rules](https://github.com/Xakki/log-laravel/blob/main/docs/LoggingRules.md)):
|
|
271
|
+
|
|
272
|
+
* [log-laravel](https://github.com/Xakki/log-laravel) — `xakki/log-laravel`. Laravel / Monolog: custom `LogManager`, processors, redaction, SQL and queue logging.
|
|
273
|
+
* [log-symfony](https://github.com/Xakki/log-symfony) — `xakki/log-symfony`. PSR-3 / Symfony / Monolog 3: context enrichment, type coercion, redaction.
|
|
274
|
+
* [log-php](https://github.com/Xakki/log-php) — `xakki/log-php`. Plain PHP (no framework, PSR-3; PHP 5.6+ on branch `master56`): error/exception catcher with pluggable storages.
|
|
275
|
+
* [log-fluent](https://github.com/Xakki/log-fluent) — `xakki/log-fluent`. Fluent Bit config bundle that ships these logs to Graylog (GELF) and Prometheus.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
log_python/__init__.py,sha256=3lCnzY_wUI9HWTOo9Ux7Rvy86zbg9Pjz0EmWH3bO1OE,4333
|
|
2
|
+
log_python/config.py,sha256=YRYptW5dZ1jLzywyFhLwpvS-UQmSZC6mXwSTZHclNTA,5713
|
|
3
|
+
log_python/const.py,sha256=ZB3aQU-mH7Ot6kPpGX8vkWy1WL8GgTRirhLU41IMlfo,1587
|
|
4
|
+
log_python/context.py,sha256=xv7Alw8s0kXmyJ0emmKH_JBqnu_54j6GTjOeySPyY64,27603
|
|
5
|
+
log_python/formatters.py,sha256=fIxjUZ_vgsb_HTSB0ZvnQtd-Qw1lkRuUtv1kUFQH70E,8887
|
|
6
|
+
log_python/heartbeat.py,sha256=5u0PWE-G_E-Sc74hb2DAV_PIFxtsSEtNlbmgis-rGN0,1913
|
|
7
|
+
log_python/logtype.py,sha256=NiiW_F_ftZYwiZWYyJhJ2pyJnBR5T550P9TnVX-XakY,7176
|
|
8
|
+
log_python/manager.py,sha256=eawC43t6dzlc7nUYHWMmgoWFrYG5hsjkq1ZYqjiOJps,20825
|
|
9
|
+
log_python/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
log_python/redactor.py,sha256=0-8xsFEyfvOct-PEoBDvXqbP0WxhAzEp6X6So3DFJak,13584
|
|
11
|
+
log_python/trace.py,sha256=ngOa1TzBkANQ325bqBMW95lOHDtbOjf4HfRayVFsnbs,25477
|
|
12
|
+
log_python/handlers/__init__.py,sha256=8tI5Ju8PY8KnoqfU093056XGymjrJjdNUmH7XmvzWbE,33
|
|
13
|
+
log_python/handlers/redis.py,sha256=DfeAK63gUg6kdnkZK-SiJ3WkNyviGynj8h13jW2mxxc,1452
|
|
14
|
+
log_python/integrations/__init__.py,sha256=OSJkNfP33ZiZVYRru_-r_wI8Wa_PvyaoWsiuXBKCroY,39
|
|
15
|
+
log_python/integrations/sqlalchemy.py,sha256=9UXcHtjKwjbv6jVZvsXGAoypOiOBM65u9_IEecCWcC4,3336
|
|
16
|
+
xakki_log_python-0.4.0.dist-info/METADATA,sha256=fZNN0zGfpmDX7H9CDP3fwSBXbk8liKZuN5b6LcvV4ZQ,9323
|
|
17
|
+
xakki_log_python-0.4.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
18
|
+
xakki_log_python-0.4.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
19
|
+
xakki_log_python-0.4.0.dist-info/RECORD,,
|