slpy-log 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
slpy_log-0.1.0/LICENSE ADDED
@@ -0,0 +1,146 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship made available under
36
+ the License, as indicated by a copyright notice that is included in
37
+ or attached to the work (an example is provided in the Appendix below).
38
+
39
+ "Derivative Works" shall mean any work, whether in Source or Object
40
+ form, that is based on (or derived from) the Work and for which the
41
+ editorial revisions, annotations, elaborations, or other
42
+ transformations represent, as a whole, an original work of authorship.
43
+
44
+ "Contribution" shall mean, as defined by the copyright owner,
45
+ any work of authorship, including the original version of the Work
46
+ and any modifications or additions to that Work or Derivative Works
47
+ of the Work, that is intentionally submitted to the Licensor for
48
+ inclusion in the Work by or copyright owner or by an individual or
49
+ Legal Entity authorized to submit on behalf of the copyright owner.
50
+
51
+ "Contributor" shall mean Licensor and any Legal Entity on behalf of
52
+ whom a Contribution has been received by the Licensor and included
53
+ within the Work.
54
+
55
+ 2. Grant of Copyright License. Subject to the terms and conditions of
56
+ this License, each Contributor hereby grants to You a perpetual,
57
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
58
+ copyright license to reproduce, prepare Derivative Works of,
59
+ publicly display, publicly perform, sublicense, and distribute the
60
+ Work and such Derivative Works in Source or Object form.
61
+
62
+ 3. Grant of Patent License. Subject to the terms and conditions of
63
+ this License, each Contributor hereby grants to You a perpetual,
64
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
65
+ (except as stated in this section) patent license to make, have made,
66
+ use, offer to sell, sell, import, and otherwise transfer the Work,
67
+ where such license applies only to those patent contributions
68
+ Licensable by such Contributor that are necessarily infringed by
69
+ their Contribution(s) alone or by the combination of their
70
+ Contribution(s) with the Work to which such Contribution(s)
71
+ were submitted.
72
+
73
+ 4. Redistribution. You may reproduce and distribute copies of the
74
+ Work or Derivative Works thereof in any medium, with or without
75
+ modifications, and in Source or Object form, provided that You
76
+ meet the following conditions:
77
+
78
+ (a) You must give any other recipients of the Work or Derivative
79
+ Works a copy of the License; and
80
+
81
+ (b) You must cause any modified files to carry prominent notices
82
+ stating that You changed the files; and
83
+
84
+ (c) You must retain, in the Source form of any Derivative Works
85
+ that You distribute, all copyright, patent, trademark, and
86
+ attribution notices from the Source form of the Work; and
87
+
88
+ (d) If the Work includes a "NOTICE" text file, you must include a
89
+ readable copy of the attribution notices contained within such
90
+ NOTICE file, in all Source form Derivative Works that You
91
+ distribute.
92
+
93
+ You may add Your own attribution notices within Derivative Works
94
+ that You distribute, alongside or in addition to the NOTICE text
95
+ from the Work, provided that such additional attribution notices
96
+ cannot be construed as modifying the License.
97
+
98
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
99
+ any Contribution intentionally submitted for inclusion in the Work
100
+ by You to the Licensor shall be under the terms and conditions of
101
+ this License, without any additional terms or conditions.
102
+
103
+ 6. Trademarks. This License does not grant permission to use the trade
104
+ names, trademarks, service marks, or product names of the Licensor,
105
+ except as required for reasonable and customary use in describing the
106
+ origin of the Work and reproducing the content of the NOTICE file.
107
+
108
+ 7. Disclaimer of Warranty. Unless required by applicable law or
109
+ agreed to in writing, Licensor provides the Work (and each
110
+ Contributor provides its Contributions) on an "AS IS" BASIS,
111
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
112
+ implied. See the License for the specific language governing
113
+ permissions and limitations under the License.
114
+
115
+ 8. Limitation of Liability. In no event and under no legal theory,
116
+ whether in tort (including negligence), contract, or otherwise,
117
+ unless required by applicable law (such as deliberate and grossly
118
+ negligent acts) or agreed to in writing, shall any Contributor be
119
+ liable to You for damages, including any direct, indirect, special,
120
+ incidental, or exemplary damages of any character arising as a
121
+ result of this License or out of the use or inability to use the
122
+ Work (even if such Contributor has been advised of the possibility
123
+ of such damages).
124
+
125
+ 9. Accepting Warranty or Additional Liability. While redistributing
126
+ the Work or Derivative Works thereof, You may wish to offer, and
127
+ charge a fee for, acceptance of support, warranty, indemnity,
128
+ or other liability obligations and/or rights consistent with this
129
+ License. However, in accepting such obligations, You may offer only
130
+ conditions that are consistent with this terms of this License.
131
+
132
+ END OF TERMS AND CONDITIONS
133
+
134
+ Copyright 2026 Syntropysoft
135
+
136
+ Licensed under the Apache License, Version 2.0 (the "License");
137
+ you may not use this file except in compliance with the License.
138
+ You may obtain a copy of the License at
139
+
140
+ http://www.apache.org/licenses/LICENSE-2.0
141
+
142
+ Unless required by applicable law or agreed to in writing, software
143
+ distributed under the License is distributed on an "AS IS" BASIS,
144
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
145
+ See the License for the specific language governing permissions and
146
+ limitations under the License.
@@ -0,0 +1,529 @@
1
+ Metadata-Version: 2.4
2
+ Name: slpy-log
3
+ Version: 0.1.0
4
+ Summary: The Declarative Observability Framework for Python
5
+ Home-page: https://github.com/Syntropysoft/slpy
6
+ Author: Syntropysoft
7
+ Author-email: Syntropysoft <info@syntropysoft.com>
8
+ License: Apache-2.0
9
+ Project-URL: Homepage, https://github.com/Syntropysoft/slpy
10
+ Project-URL: Repository, https://github.com/Syntropysoft/slpy
11
+ Project-URL: Issues, https://github.com/Syntropysoft/slpy/issues
12
+ Keywords: logging,observability,masking,context,fastapi,async
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: Apache Software License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.7
19
+ Classifier: Programming Language :: Python :: 3.8
20
+ Classifier: Programming Language :: Python :: 3.9
21
+ Classifier: Programming Language :: Python :: 3.10
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
25
+ Classifier: Topic :: System :: Logging
26
+ Classifier: Topic :: System :: Monitoring
27
+ Requires-Python: >=3.7
28
+ Description-Content-Type: text/markdown
29
+ License-File: LICENSE
30
+ Provides-Extra: fastapi
31
+ Requires-Dist: fastapi>=0.100.0; extra == "fastapi"
32
+ Provides-Extra: dev
33
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
34
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
35
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
36
+ Dynamic: author
37
+ Dynamic: home-page
38
+ Dynamic: license-file
39
+ Dynamic: requires-python
40
+
41
+ <p align="center">
42
+ <img src="https://syntropysoft.com/syntropylog-logo.png" alt="SyntropyLog Logo" width="170"/>
43
+ </p>
44
+
45
+ <h1 align="center">slpy</h1>
46
+
47
+ <p align="center">
48
+ <strong>The Declarative Observability Framework for Python.</strong>
49
+ <br />
50
+ You declare what each log should carry. slpy handles the rest.
51
+ </p>
52
+
53
+ <p align="center">
54
+ <a href="#"><img src="https://img.shields.io/badge/status-alpha-orange.svg" alt="Alpha"></a>
55
+ <a href="https://github.com/Syntropysoft/SyntropyLog/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-blue.svg" alt="License"></a>
56
+ <a href="#"><img src="https://img.shields.io/badge/python-3.7+-blue.svg" alt="Python 3.7+"></a>
57
+ <a href="#"><img src="https://img.shields.io/badge/dependencies-zero-brightgreen.svg" alt="Zero dependencies"></a>
58
+ </p>
59
+
60
+ ---
61
+
62
+ ## What is slpy?
63
+
64
+ Every Python team writes the same boilerplate: thread `request_id` through every function signature, scrub `password` fields before logging, remember to call `logger.info()` instead of `print()`, repeat the same `extra={"service": "payment"}` on every call.
65
+
66
+ slpy solves the boilerplate problem declaratively. You declare the rules once at startup. The framework applies them consistently on every log call, in every coroutine, across every service — without you thinking about it again.
67
+
68
+ ```python
69
+ from slpy import slpy
70
+
71
+ await slpy.init({
72
+ 'logger': {'level': 'info'},
73
+ 'masking': {'enable_default_rules': True},
74
+ })
75
+
76
+ logger = slpy.get_logger('payment-service')
77
+
78
+ async with slpy.context(request_id='req-001', user_id='usr-42'):
79
+ logger.info('Card charged', amount=299.90, email='john@example.com')
80
+ # → {"level":"info","message":"Card charged","service":"payment-service",
81
+ # "request_id":"req-001","user_id":"usr-42",
82
+ # "amount":299.9,"email":"j******n@example.com"}
83
+ ```
84
+
85
+ The `request_id` propagated automatically. The `email` was masked automatically. The `service` field appeared automatically. You wrote none of that explicitly.
86
+
87
+ ---
88
+
89
+ ## The declarative shift
90
+
91
+ | Instead of... | You declare... | slpy does automatically |
92
+ |---------------|----------------|------------------------|
93
+ | Threading `request_id` through every function | `async with slpy.context(request_id=id)` | Propagates to all logs in scope via `contextvars` |
94
+ | Scrubbing sensitive fields before logging | `masking: {enable_default_rules: True}` | Masks email, password, token, credit card on every log |
95
+ | Repeating `extra={"service": "payment"}` | `slpy.get_logger('payment-service')` | `service` field on every log from that logger |
96
+ | Copying context into child functions | `logger.child(order_id='123')` | All bindings carried automatically on every subsequent call |
97
+ | Routing compliance logs manually | `logger.with_meta({"regulation": "GDPR"})` | `meta` payload travels sanitized to all transports |
98
+ | Writing a transport class per destination | `AdapterTransport(adapter=UniversalAdapter(executor=fn))` | Your executor receives the clean entry — connect to anything |
99
+
100
+ ---
101
+
102
+ ## Quick start
103
+
104
+ ```bash
105
+ pip install slpy
106
+ ```
107
+
108
+ ```python
109
+ import asyncio
110
+ from slpy import slpy
111
+
112
+ async def main():
113
+ await slpy.init({
114
+ 'logger': {'level': 'info'},
115
+ 'masking': {'enable_default_rules': True},
116
+ })
117
+
118
+ logger = slpy.get_logger('my-service')
119
+ logger.info('Service started')
120
+
121
+ await slpy.shutdown()
122
+
123
+ asyncio.run(main())
124
+ ```
125
+
126
+ No transport configured → `ConsoleTransport` (structured JSON) is used automatically.
127
+
128
+ ---
129
+
130
+ ## Named loggers and child()
131
+
132
+ Each component gets its own named logger. `child()` binds context once — every log from that instance carries it automatically. Bindings are immutable and composable.
133
+
134
+ ```python
135
+ async def process_order(order_id: str, user_id: str) -> None:
136
+ # Bind once — no need to repeat on every call
137
+ logger = slpy.get_logger('order-service').child(
138
+ order_id=order_id,
139
+ user_id=user_id,
140
+ )
141
+
142
+ logger.info('Processing') # carries order_id, user_id
143
+ logger.info('Calculated', total=299.90, items=3) # carries order_id, user_id
144
+
145
+ payment = logger.child(step='payment') # adds step, keeps the rest
146
+ payment.info('Charging card') # carries order_id, user_id, step
147
+ payment.info('Approved', amount=299.90)
148
+ ```
149
+
150
+ `child()` never mutates the parent. Each call returns a new logger with merged bindings.
151
+
152
+ ---
153
+
154
+ ## Context propagation
155
+
156
+ slpy uses Python's native `contextvars` — the same mechanism as `AsyncLocalStorage` in Node.js. Context propagates correctly across `asyncio.gather()`, `asyncio.create_task()`, and thread-pool executors.
157
+
158
+ ```python
159
+ async def handle_request(request_id: str, user_id: str) -> None:
160
+ async with slpy.context(request_id=request_id, user_id=user_id):
161
+ logger.info('Request received') # request_id, user_id here
162
+ await fetch_from_db() # request_id, user_id here too
163
+ logger.info('Request complete')
164
+
165
+ async def fetch_from_db() -> None:
166
+ # No function argument needed — context is already here
167
+ logger.debug('Running query') # request_id, user_id propagated
168
+ ```
169
+
170
+ Concurrent requests are fully isolated. Each `async with slpy.context(...)` opens its own scope; inner scopes do not leak into outer ones.
171
+
172
+ ---
173
+
174
+ ## Data masking
175
+
176
+ Masking runs automatically on every log entry. Default rules cover the most common sensitive fields. The engine flattens nested objects, applies rules by field name, then reconstructs the original structure — at any depth.
177
+
178
+ ```python
179
+ await slpy.init({
180
+ 'masking': {
181
+ 'enable_default_rules': True, # email, password, token, credit card, SSN, phone
182
+ 'rules': [
183
+ # Add your own — patterns compiled once at init()
184
+ {'pattern': r'internal_code', 'strategy': 'token'},
185
+ ],
186
+ },
187
+ })
188
+
189
+ logger.info('Payment', credit_card_number='4111-1111-1111-1234', amount=299.90)
190
+ # → credit_card_number: "************1234" amount: 299.9 (not masked)
191
+
192
+ logger.info('User', email='john@example.com', name='John Doe')
193
+ # → email: "j******n@example.com" name: "John Doe" (not masked)
194
+
195
+ logger.info('Order', order={'user': {'token': 'abc123', 'id': 'USR-1'}})
196
+ # → order.user.token: "******" order.user.id: "USR-1" (not masked)
197
+ ```
198
+
199
+ **Default rules**
200
+
201
+ | Field pattern | Strategy | Example result |
202
+ |---------------|----------|----------------|
203
+ | `email`, `mail` | Email | `j******n@example.com` |
204
+ | `password`, `pass`, `pwd`, `secret` | Full mask | `************` |
205
+ | `token`, `key`, `auth`, `jwt`, `bearer` | Full mask | `**********` |
206
+ | `credit_card`, `card_number` | Last 4 | `************1234` |
207
+ | `ssn`, `social_security` | Last 4 | `*****6789` |
208
+ | `phone`, `mobile`, `tel` | Last 4 | `*******4567` |
209
+
210
+ ---
211
+
212
+ ## Audit level and with_meta()
213
+
214
+ `audit` bypasses the configured level filter — it is always emitted. Use it for compliance events that must always be recorded regardless of the runtime log level.
215
+
216
+ `with_meta(payload)` attaches arbitrary structured metadata to every log from that logger instance. The payload travels sanitized to all transports as `logEntry['meta']`.
217
+
218
+ ```python
219
+ audit_logger = (
220
+ slpy.get_logger('compliance')
221
+ .with_meta({
222
+ 'ttl_days': 730,
223
+ 'regulation': 'GDPR',
224
+ 'data_class': 'PII',
225
+ 'destination': 'audit-store',
226
+ })
227
+ .child(user_id='USR-42')
228
+ )
229
+
230
+ audit_logger.audit('Data exported', records=1500)
231
+ # → level: "audit" meta: {ttl_days: 730, regulation: "GDPR", ...}
232
+ # user_id: "USR-42" records: 1500
233
+
234
+ # audit always appears — even when level is 'error'
235
+ await slpy.init({'logger': {'level': 'error'}})
236
+ logger.info('hidden') # not emitted
237
+ logger.audit('visible') # always emitted
238
+ ```
239
+
240
+ **with_meta() use cases**
241
+
242
+ | Use case | Payload |
243
+ |----------|---------|
244
+ | Log retention routing | `{"ttl_days": 730, "destination": "cold-storage"}` |
245
+ | Compliance tagging | `{"regulation": "GDPR", "data_class": "PII"}` |
246
+ | Experiment tracking | `{"experiment": "checkout-v2", "variant": "B"}` |
247
+ | Release context | `{"version": "1.4.0", "deploy_id": "d-001"}` |
248
+
249
+ ---
250
+
251
+ ## FastAPI / ASGI middleware
252
+
253
+ One line wires automatic context propagation into every request.
254
+
255
+ ```bash
256
+ pip install slpy[fastapi]
257
+ ```
258
+
259
+ ```python
260
+ from fastapi import FastAPI
261
+ from slpy import slpy
262
+ from slpy.fastapi import SyntropyMiddleware
263
+
264
+ app = FastAPI()
265
+ app.add_middleware(SyntropyMiddleware)
266
+ ```
267
+
268
+ Every log emitted during a request automatically carries `correlation_id`, `method`, and `path` — extracted from the incoming header or generated if absent. The `X-Correlation-ID` header is forwarded in the response.
269
+
270
+ ```python
271
+ @app.get('/orders/{order_id}')
272
+ async def get_order(order_id: str):
273
+ logger.info('Fetching', order_id=order_id)
274
+ # → {"level":"info","message":"Fetching","service":"api",
275
+ # "correlation_id":"req-abc","method":"GET","path":"/orders/123",
276
+ # "order_id":"123"}
277
+ ```
278
+
279
+ `SyntropyMiddleware` is pure ASGI — it works with FastAPI, Starlette, Litestar, and any ASGI server (uvicorn, hypercorn) without framework-specific dependencies.
280
+
281
+ ---
282
+
283
+ ## Runtime level change
284
+
285
+ ```python
286
+ sl.set_level('debug') # all existing loggers update immediately
287
+ sl.set_level('error') # back to quiet
288
+ ```
289
+
290
+ ---
291
+
292
+ ## Transports
293
+
294
+ | Transport | Output | Use case |
295
+ |-----------|--------|----------|
296
+ | `ConsoleTransport` | Structured JSON | Production, CI, log collectors — **default** |
297
+ | `PrettyConsoleTransport` | Colored human-readable | Local development |
298
+ | `AdapterTransport` | Any destination | Databases, HTTP APIs, queues, multiple targets |
299
+
300
+ ### AdapterTransport + UniversalAdapter
301
+
302
+ The most powerful routing primitive. You provide an `executor` function — sync or async — that receives the clean, already-masked log entry and sends it anywhere. slpy handles context propagation, masking, level filtering, error isolation, and fanout.
303
+
304
+ ```python
305
+ from slpy import slpy, AdapterTransport, UniversalAdapter, ConsoleTransport
306
+
307
+ async def my_executor(data: dict) -> None:
308
+ # One function. Any number of destinations. Fully async.
309
+ await asyncio.gather(
310
+ prisma.system_log.create(data=data),
311
+ mongo_collection.insert_one(data),
312
+ es.index(index='logs', body=data),
313
+ )
314
+
315
+ db_transport = AdapterTransport(
316
+ name='db',
317
+ adapter=UniversalAdapter(executor=my_executor),
318
+ formatter=lambda e: {**e, 'timestamp': datetime.fromisoformat(e['timestamp'])},
319
+ )
320
+
321
+ await slpy.init({
322
+ 'logger': {
323
+ 'transports': [
324
+ db_transport, # → Postgres + MongoDB + Elasticsearch
325
+ ConsoleTransport(), # → stdout
326
+ ],
327
+ },
328
+ 'masking': {'enable_default_rules': True},
329
+ })
330
+ ```
331
+
332
+ When `my_executor` is called, the entry is already:
333
+ - **Masked** — `email`, `password`, `token`, credit card fields scrubbed
334
+ - **Context-enriched** — `request_id`, `user_id`, any `slpy.context()` fields attached
335
+ - **Formatted** — optionally transformed by your `formatter` to match your DB schema
336
+
337
+ The executor is the only thing you write. Connect to Postgres, MongoDB, Elasticsearch, Datadog, OpenTelemetry, Kafka, a REST API, or all of them at once — slpy does not know or care.
338
+
339
+ **`formatter`** (optional) — transform the entry before it reaches the executor. Use it to map field names, convert types, or add schema-specific fields:
340
+
341
+ ```python
342
+ def db_formatter(entry: dict) -> dict:
343
+ return {
344
+ **entry,
345
+ 'timestamp': datetime.fromtimestamp(entry['timestamp'] / 1000),
346
+ 'env': os.getenv('APP_ENV', 'production'),
347
+ }
348
+ ```
349
+
350
+ ### PrettyConsoleTransport
351
+
352
+ Human-readable colored output. Zero external dependencies — pure ANSI codes.
353
+
354
+ ```python
355
+ from slpy import slpy, PrettyConsoleTransport
356
+
357
+ await slpy.init({
358
+ 'logger': {
359
+ 'level': 'debug',
360
+ 'transports': [PrettyConsoleTransport()],
361
+ },
362
+ })
363
+ ```
364
+
365
+ ```
366
+ 12:00:00.123 DEBUG payment-service Starting up version=0.1.0
367
+ 12:00:00.124 INFO payment-service Service ready port=8080
368
+ 12:00:00.124 WARN payment-service High memory usage heap_mb=420
369
+ 12:00:00.124 ERROR payment-service Connection refused host=db.internal
370
+ 12:00:00.124 INFO payment-service User login email=j**n@example.com
371
+ 12:00:00.124 AUDIT payment-service Data exported meta={"regulation":"GDPR"}
372
+ ```
373
+
374
+ **Auto-detects TTY** — when stdout is not a terminal (CI, pipes, production), falls back to JSON
375
+ automatically. No code change needed between environments.
376
+
377
+ Masking remains active in pretty output — sensitive fields are masked regardless of transport.
378
+
379
+ ```python
380
+ PrettyConsoleTransport() # auto-detect TTY (recommended)
381
+ PrettyConsoleTransport(colors=True) # force colors on
382
+ PrettyConsoleTransport(colors=False)# force JSON (same as ConsoleTransport)
383
+ ```
384
+
385
+ ### Custom transports
386
+
387
+ For full control, extend `Transport` and implement `log(entry)`:
388
+
389
+ ```python
390
+ from slpy.transport import Transport
391
+
392
+ class ElasticsearchTransport(Transport):
393
+ def log(self, entry: dict) -> None:
394
+ self._es_client.index(index='logs', body=entry)
395
+ ```
396
+
397
+ For most cases, `AdapterTransport` + `UniversalAdapter` is simpler — no subclassing needed.
398
+
399
+ Multiple transports are supported — entries are sent to all of them.
400
+
401
+ ---
402
+
403
+ ## Performance
404
+
405
+ slpy includes an optional Rust addon (`slpy-native`) that accelerates the masking engine. When installed, it is used automatically with no code changes.
406
+
407
+ ```
408
+ Simple log Complex object + masking
409
+ --------------------- ------------------------------------
410
+ slpy 6.2 µs ① structlog 10.8 µs (no masking)
411
+ structlog 8.0 µs slpy 18.0 µs ② ✅ masking ON
412
+ logging 18.8 µs logging 20.4 µs (no masking)
413
+ loguru 58.9 µs loguru 61.8 µs (no masking)
414
+ ```
415
+
416
+ > pyperf, null transport, Windows 11 local — numbers above are conservative.
417
+
418
+ **slpy is the fastest structured logger on simple logs** — faster than structlog, which is the Python performance reference.
419
+
420
+ **slpy with masking fully active is faster than the stdlib `logging` module without masking.** It is 3.4x faster than loguru without masking.
421
+
422
+ The Rust addon reduced masking overhead from 29 µs to 12 µs (59% improvement). If the addon is not installed, slpy falls back to the pure Python engine transparently.
423
+
424
+ ### Sustained throughput — official results (GitHub Actions, Ubuntu, Python 3.12)
425
+
426
+ | Scenario | logs/sec | µs/log | degradation |
427
+ |----------|----------|--------|-------------|
428
+ | Simple log | **85,774** | 11.7 | — |
429
+ | MaskingEngine only | **59,369** | 16.8 | — |
430
+ | child() + log | **58,165** | 17.2 | 0 B heap |
431
+ | Complex log + masking | **33,472** | 29.9 | — |
432
+ | Async context scope | **22,237** | 45.0 | none (1M → 10M: 44.97 → 44.76 µs) |
433
+
434
+ Zero degradation across all scenarios. `child()` and async context scope allocate **0 bytes** at sustained volume — no GC pressure regardless of call volume.
435
+
436
+ See [benchmark/README.md](benchmark/README.md) for full methodology and how to reproduce.
437
+
438
+ ---
439
+
440
+ ## What slpy is not
441
+
442
+ slpy is a structured logging and context propagation framework. It is not:
443
+
444
+ - A log aggregation backend (use Elasticsearch, Loki, CloudWatch)
445
+ - A distributed tracing system (use OpenTelemetry)
446
+ - A metrics collector (use Prometheus, Datadog)
447
+
448
+ It is the component that makes every log line correct, consistent, and safe before it reaches any of those systems.
449
+
450
+ ---
451
+
452
+ ## Security
453
+
454
+ **No network I/O at runtime.** slpy does not contact any external URLs. The only output is what your transports produce.
455
+
456
+ **Zero runtime dependencies.** The core package has no `install_requires`. The optional `[fastapi]` extra adds only `starlette` (already a FastAPI dependency).
457
+
458
+ **Masking and custom functions.** The `custom_mask` function in masking rules is consumer-supplied configuration — it is not influenced by external input. See [socket.dev note](#) for full analysis.
459
+
460
+ ---
461
+
462
+ ## Installation
463
+
464
+ ```bash
465
+ # Core (zero dependencies)
466
+ pip install slpy
467
+
468
+ # With FastAPI middleware
469
+ pip install slpy[fastapi]
470
+
471
+ # Development
472
+ pip install slpy[dev]
473
+ ```
474
+
475
+ Requires Python 3.7+.
476
+
477
+ ---
478
+
479
+ ## Running the examples
480
+
481
+ ```bash
482
+ git clone https://github.com/Syntropysoft/slpy
483
+ cd slpy
484
+
485
+ # Basic setup
486
+ py examples/01_basic_setup.py
487
+
488
+ # Named loggers and child()
489
+ py examples/02_named_loggers_and_child.py
490
+
491
+ # Context propagation with asyncio.gather()
492
+ py examples/03_context_propagation.py
493
+
494
+ # Data masking
495
+ py examples/04_masking.py
496
+
497
+ # with_meta() and audit level
498
+ py examples/05_with_meta_and_audit.py
499
+
500
+ # FastAPI middleware (requires: pip install fastapi uvicorn)
501
+ uvicorn examples.06_fastapi_middleware:app --reload
502
+
503
+ # PrettyConsoleTransport — colored output (run in a real terminal)
504
+ py examples/07_pretty_console.py
505
+
506
+ # AdapterTransport + UniversalAdapter — fanout to multiple destinations
507
+ py examples/08_adapter_transport.py
508
+ ```
509
+
510
+ ## Running the tests
511
+
512
+ ```bash
513
+ pip install slpy[dev]
514
+ pytest
515
+ ```
516
+
517
+ ---
518
+
519
+ ## Documentation
520
+
521
+ - **[Refactor plan and architecture](docs/slpy-refactor-plan.md)** — Design decisions and what was removed and why.
522
+ - **[Data masking](docs/data_masking.md)** — Masking strategies, default rules, custom rules.
523
+ - **[Logger matrix](docs/logger_matrix.md)** — Per-level field filtering.
524
+
525
+ ---
526
+
527
+ ## License
528
+
529
+ Apache 2.0 — see [LICENSE](./LICENSE).