semlog 0.2.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.
- semlog-0.2.0/LICENSE +202 -0
- semlog-0.2.0/PKG-INFO +337 -0
- semlog-0.2.0/README.md +312 -0
- semlog-0.2.0/pyproject.toml +46 -0
- semlog-0.2.0/pyproject.toml.orig +73 -0
- semlog-0.2.0/src/semlog/__init__.py +45 -0
- semlog-0.2.0/src/semlog/__main__.py +21 -0
- semlog-0.2.0/src/semlog/_baggage.py +65 -0
- semlog-0.2.0/src/semlog/_config.py +192 -0
- semlog-0.2.0/src/semlog/_context.py +157 -0
- semlog-0.2.0/src/semlog/_format.py +392 -0
- semlog-0.2.0/src/semlog/_identity.py +145 -0
- semlog-0.2.0/src/semlog/_middleware.py +214 -0
- semlog-0.2.0/src/semlog/_modes.py +176 -0
- semlog-0.2.0/src/semlog/_request_id.py +22 -0
- semlog-0.2.0/src/semlog/_trace.py +42 -0
- semlog-0.2.0/src/semlog/_transport.py +352 -0
- semlog-0.2.0/src/semlog/_uuid7_fallback.py +44 -0
- semlog-0.2.0/src/semlog/agent_guide.md +363 -0
semlog-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
semlog-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: semlog
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Zero-dependency structured JSON logging for Python, built entirely on the standard library, aligned with OpenTelemetry's log data model and W3C Trace Context.
|
|
5
|
+
Author: flaviopifiator
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Framework :: Django
|
|
10
|
+
Classifier: Framework :: FastAPI
|
|
11
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Topic :: System :: Logging
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Project-URL: Homepage, https://github.com/flaviopifiator/semlog
|
|
21
|
+
Project-URL: Repository, https://github.com/flaviopifiator/semlog
|
|
22
|
+
Project-URL: Issues, https://github.com/flaviopifiator/semlog/issues
|
|
23
|
+
Project-URL: Changelog, https://github.com/flaviopifiator/semlog/blob/main/CHANGELOG.md
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
**English** | [Español](README.es.md)
|
|
27
|
+
|
|
28
|
+
<p align="center">
|
|
29
|
+
<picture>
|
|
30
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/assets/semlog-logo-dark.svg">
|
|
31
|
+
<img alt="semlog" src="docs/assets/semlog-logo-light.svg" width="360">
|
|
32
|
+
</picture>
|
|
33
|
+
</p>
|
|
34
|
+
|
|
35
|
+
# semlog
|
|
36
|
+
|
|
37
|
+
**Structured JSON logging for Python's standard `logging`: a stable, schema-backed record contract with OpenTelemetry field names, W3C Trace Context and zero dependencies.**
|
|
38
|
+
|
|
39
|
+
[](https://github.com/flaviopifiator/semlog/actions/workflows/ci.yml)
|
|
40
|
+
[](.github/workflows/ci.yml)
|
|
41
|
+
[](.github/workflows/ci.yml)
|
|
42
|
+
[](.github/workflows/ci.yml)
|
|
43
|
+
[](LICENSE)
|
|
44
|
+
[](pyproject.toml)
|
|
45
|
+
[](STANDARDS.md)
|
|
46
|
+
<!-- Enable after the first PyPI release:
|
|
47
|
+
[](https://pypi.org/project/semlog/)
|
|
48
|
+
-->
|
|
49
|
+
|
|
50
|
+
## Why semlog
|
|
51
|
+
|
|
52
|
+
Application code keeps using `logging.getLogger(__name__)`; semlog decides what every record looks like and makes that shape a contract.
|
|
53
|
+
|
|
54
|
+
- **A stable JSON contract.** Field order, types and `null` rules are specified in [STANDARDS.md](STANDARDS.md) and published as a JSON Schema ([`schemas/log-record.schema.json`](schemas/log-record.schema.json)). Removing or renaming a field is a major version change.
|
|
55
|
+
- **OpenTelemetry field names.** `severity_text`, `severity_number`, `trace_id`, `span_id`, `service.*` and `telemetry.sdk.*` follow the OpenTelemetry Logs Data Model, with Semantic Conventions pinned to v1.44.0.
|
|
56
|
+
- **W3C Trace Context built in.** The WSGI and ASGI middlewares read `traceparent`, `tracestate` and allowlisted `baggage`, and `inject()` propagates them on outbound calls. No tracing SDK is needed.
|
|
57
|
+
- **Zero runtime dependencies.** semlog is built entirely on the standard library. Third-party loggers that propagate to the root logger go through the same pipeline, and `capture_loggers` covers the ones that install their own handlers.
|
|
58
|
+
- **Requirement traceability.** Every normative requirement in STANDARDS.md has an ID and at least one test that cites it; the test suite fails when a requirement has no citing test.
|
|
59
|
+
|
|
60
|
+
## Installation
|
|
61
|
+
|
|
62
|
+
semlog requires Python 3.10 or later. It is not on PyPI yet, so install it from the Git repository:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pip install "git+https://github.com/flaviopifiator/semlog.git"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Quick start
|
|
69
|
+
|
|
70
|
+
Save this as `app.py`:
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
import logging
|
|
74
|
+
|
|
75
|
+
import semlog
|
|
76
|
+
|
|
77
|
+
semlog.configure(service_name="checkout")
|
|
78
|
+
|
|
79
|
+
logger = logging.getLogger(__name__)
|
|
80
|
+
logger.info("order.created", extra={"app.order.id": "ord_42", "app.order.total": 1500})
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Run `python app.py`. It prints one JSON line:
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{"timestamp":"2026-09-14T18:49:31.659966Z","severity_text":"INFO","severity_number":9,"event_name":"order.created","body":null,"otel.scope.name":"__main__","app.order.id":"ord_42","app.order.total":1500,"service.name":"checkout","service.namespace":null,"service.version":null,"service.instance.id":"b821b60b-7f5e-44a9-88f8-7cf734286abe","deployment.environment.name":null,"telemetry.sdk.name":"semlog","telemetry.sdk.version":"0.2.0","telemetry.sdk.language":"python"}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Three rules keep records useful:
|
|
90
|
+
|
|
91
|
+
- The message is a static event name, such as `order.created`, never an f-string.
|
|
92
|
+
- Variables go in `extra`, under `app.` or an OpenTelemetry key such as `url.path`.
|
|
93
|
+
- `logger.exception(...)` is called once, where the exception is handled.
|
|
94
|
+
|
|
95
|
+
### Public API
|
|
96
|
+
|
|
97
|
+
semlog exposes exactly eight names. Everything else stays standard `logging`.
|
|
98
|
+
|
|
99
|
+
| Name | Use it to |
|
|
100
|
+
|---|---|
|
|
101
|
+
| `configure(...)` | Configure the process once, at startup |
|
|
102
|
+
| `WSGIMiddleware(app)` | Wrap a WSGI application: trace context and `http.request.id` on every record of a request |
|
|
103
|
+
| `ASGIMiddleware(app)` | The same, for an ASGI application |
|
|
104
|
+
| `operation(headers=None)` | Correlate work outside HTTP, such as jobs, queue consumers and CLI commands |
|
|
105
|
+
| `bind(attributes)` | Add fields to every later record of the current request or operation |
|
|
106
|
+
| `inject(headers, *, trusted=True)` | Propagate trace context to an outbound call |
|
|
107
|
+
| `flush(timeout=None)` | Wait until every queued record is written, before `os._exit()` or in tests |
|
|
108
|
+
| `llm()` | Return the agent guide bundled with the installed version (also `python -m semlog llm`) |
|
|
109
|
+
|
|
110
|
+
Full signatures and semantics are in the [agent guide](src/semlog/agent_guide.md).
|
|
111
|
+
|
|
112
|
+
## Framework recipes
|
|
113
|
+
|
|
114
|
+
### FastAPI
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
import logging
|
|
118
|
+
|
|
119
|
+
import semlog
|
|
120
|
+
from fastapi import FastAPI
|
|
121
|
+
from semlog import ASGIMiddleware
|
|
122
|
+
|
|
123
|
+
semlog.configure(service_name="my-fastapi-service")
|
|
124
|
+
|
|
125
|
+
app = FastAPI()
|
|
126
|
+
app = ASGIMiddleware(app, log_requests=True)
|
|
127
|
+
# log_requests=True emits one http.server.request event per request (INFO on
|
|
128
|
+
# success, ERROR when the application raises an unhandled exception), carrying
|
|
129
|
+
# http.request.method, url.path, http.response.status_code and event.duration
|
|
130
|
+
# (nanoseconds). It never includes url.query. Default is False: no extra event.
|
|
131
|
+
|
|
132
|
+
logger = logging.getLogger(__name__)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@app.get("/orders/{order_id}")
|
|
136
|
+
async def get_order(order_id: str):
|
|
137
|
+
logger.info("order.lookup.started", extra={"app.order.id": order_id})
|
|
138
|
+
...
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@app.get("/reports/{report_id}")
|
|
142
|
+
def generate_report_sync(report_id: str):
|
|
143
|
+
# A sync `def` endpoint also keeps trace context: Starlette runs it in a
|
|
144
|
+
# threadpool worker with an explicit contextvars.copy_context().
|
|
145
|
+
logger.info("report.generation.started", extra={"app.report.id": report_id})
|
|
146
|
+
...
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Django
|
|
150
|
+
|
|
151
|
+
Wrap the WSGI or ASGI entry point, and turn off Django's own logging configuration so it does not replace the root handler that `configure()` installs.
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
# wsgi.py
|
|
155
|
+
import semlog
|
|
156
|
+
from django.core.wsgi import get_wsgi_application
|
|
157
|
+
from semlog import WSGIMiddleware
|
|
158
|
+
|
|
159
|
+
semlog.configure(service_name="my-django-service")
|
|
160
|
+
application = WSGIMiddleware(get_wsgi_application(), log_requests=True)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
# asgi.py
|
|
165
|
+
import semlog
|
|
166
|
+
from django.core.asgi import get_asgi_application
|
|
167
|
+
from semlog import ASGIMiddleware
|
|
168
|
+
|
|
169
|
+
semlog.configure(service_name="my-django-service")
|
|
170
|
+
application = ASGIMiddleware(get_asgi_application(), log_requests=True)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
# settings.py
|
|
175
|
+
LOGGING_CONFIG = None # Django must not replace the root handler configure() installs
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Configuration
|
|
179
|
+
|
|
180
|
+
Every `configure()` parameter is keyword-only; there is no settings object or dictionary. An invalid value or combination raises `ValueError` immediately, at startup, never later at runtime.
|
|
181
|
+
|
|
182
|
+
| Group | Parameter | Default | Notes |
|
|
183
|
+
|---|---|---|---|
|
|
184
|
+
| Identity | `service_name` | detected | parameter > `OTEL_SERVICE_NAME` > `OTEL_RESOURCE_ATTRIBUTES` > `pyproject.toml` |
|
|
185
|
+
| Identity | `service_version` | detected | parameter > `OTEL_RESOURCE_ATTRIBUTES` > installed package version > `pyproject.toml` |
|
|
186
|
+
| Identity | `service_namespace` | `None` | parameter > `OTEL_RESOURCE_ATTRIBUTES` |
|
|
187
|
+
| Identity | `service_instance_id` | one UUIDv4 per process | parameter > `OTEL_RESOURCE_ATTRIBUTES` |
|
|
188
|
+
| Identity | `environment` | `None` | parameter > `OTEL_RESOURCE_ATTRIBUTES` |
|
|
189
|
+
| Identity | `identity` | `None` | one value per level named in `identity_levels` |
|
|
190
|
+
| Identity | `identity_levels` | `("role", "component")` | level names composed into one `{namespace}.identity` field |
|
|
191
|
+
| Output | `level` | `"INFO"` | effective root logger level |
|
|
192
|
+
| Output | `namespace` | `"app"` | root of the custom attribute namespace |
|
|
193
|
+
| Output | `capture_loggers` | `()` | third-party loggers whose own handlers are removed, with propagation turned on |
|
|
194
|
+
| Output | `search_dir` | `None` (current working directory) | directory that `pyproject.toml` detection searches upward from |
|
|
195
|
+
| Privacy | `redact_keys` | `()` | added to the built-in redaction list, which cannot be turned off |
|
|
196
|
+
| Distributed context | `baggage_allow` | `()` | baggage keys copied into attributes; process-wide default for the middlewares and `operation()` |
|
|
197
|
+
| Distributed context | `baggage_prefix` | `"baggage."` | attribute-key prefix for copied baggage members |
|
|
198
|
+
| Distributed context | `accept_inbound_baggage` | `True` | `False` ignores an inbound `baggage` header entirely, for public trust boundaries |
|
|
199
|
+
| Limits | `max_attributes` | 128 (or environment variable) | see STANDARDS.md section 6 |
|
|
200
|
+
| Limits | `max_attribute_length` | no limit (or environment variable) | see STANDARDS.md section 6 |
|
|
201
|
+
| Transport | `queue` | `True` | `False` writes synchronously, with no internal queue and no writer thread |
|
|
202
|
+
| Transport | `queue_size` | `10000` | maximum number of queued lines |
|
|
203
|
+
| Transport | `overflow` | `"block"` | `"block"` or `"drop"`, see [Queue overflow](#queue-overflow) |
|
|
204
|
+
| Mode | `mode` | `None` (resolves to `"full"`) | `"full"`, `"hybrid"` or `"off"`; parameter > `SEMLOG_MODE` > `[tool.semlog].mode` (3.11+) > `"full"` |
|
|
205
|
+
| Catalog | `catalog` | `None` | event catalog document (JSON) |
|
|
206
|
+
| Catalog | `catalog_mode` | `"off"` without a catalog, `"warn"` with one | `"off"`, `"warn"` or `"strict"` |
|
|
207
|
+
|
|
208
|
+
### Precedence and environment variables
|
|
209
|
+
|
|
210
|
+
For the identity and limit parameters below, precedence, from highest to lowest, is always:
|
|
211
|
+
|
|
212
|
+
1. an explicit `configure()` parameter;
|
|
213
|
+
2. the `OTEL_*` environment variable;
|
|
214
|
+
3. `pyproject.toml`, on Python 3.11 and later only;
|
|
215
|
+
4. the built-in default.
|
|
216
|
+
|
|
217
|
+
`mode` follows its own precedence chain instead, described in [Modes](#modes).
|
|
218
|
+
|
|
219
|
+
Recognized environment variables:
|
|
220
|
+
|
|
221
|
+
| Variable | Sets |
|
|
222
|
+
|---|---|
|
|
223
|
+
| `OTEL_SERVICE_NAME` | `service.name` (`service_name`) |
|
|
224
|
+
| `OTEL_RESOURCE_ATTRIBUTES` | `service.namespace` (`service_namespace`), `service.version` (`service_version`), `service.instance.id` (`service_instance_id`) and `deployment.environment.name` (`environment`); also the fallback for `service.name` (`service_name`) |
|
|
225
|
+
| `OTEL_LOGRECORD_ATTRIBUTE_COUNT_LIMIT`, `OTEL_ATTRIBUTE_COUNT_LIMIT` | the `max_attributes` limit |
|
|
226
|
+
| `OTEL_LOGRECORD_ATTRIBUTE_VALUE_LENGTH_LIMIT`, `OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT` | the `max_attribute_length` limit |
|
|
227
|
+
| `SEMLOG_MODE` | `mode` (see [Modes](#modes)) |
|
|
228
|
+
|
|
229
|
+
When both variables of a limit are set, the `OTEL_LOGRECORD_*` variable wins over the generic `OTEL_ATTRIBUTE_*` one.
|
|
230
|
+
|
|
231
|
+
`pyproject.toml` detection:
|
|
232
|
+
|
|
233
|
+
- On Python 3.11 and later, `service_name` and `service_version` are read from `[project]` with the standard library's `tomllib`; `service_name` falls back to `[tool.poetry]`.
|
|
234
|
+
- Python 3.10 has no `tomllib`, so on 3.10 `pyproject.toml` is not read and a startup diagnostic says why.
|
|
235
|
+
|
|
236
|
+
### Queue overflow
|
|
237
|
+
|
|
238
|
+
Each record is rendered on the calling thread and queued for a single writer thread that writes it to `stdout`. When the queue is full, `overflow` decides what happens:
|
|
239
|
+
|
|
240
|
+
| Mode | Behavior | Use it when |
|
|
241
|
+
|---|---|---|
|
|
242
|
+
| `overflow="block"` (default) | The call waits for room in the queue; no record is lost | No record may be lost and an occasional wait is acceptable |
|
|
243
|
+
| `overflow="drop"` | The call never waits. At 90% capacity, records below `WARNING` are dropped; the last 10% is reserved for `WARNING`, `ERROR` and `CRITICAL`. Every drop is counted and reported | Application latency matters more than log completeness |
|
|
244
|
+
| `queue=False` | Synchronous write, with no queue and no writer thread | Short scripts, debugging, environments without threads |
|
|
245
|
+
|
|
246
|
+
## Modes
|
|
247
|
+
|
|
248
|
+
`mode` selects how much of semlog is active in a process: `"full"` (the default, and the right choice for a new service), `"hybrid"`, and `"off"`.
|
|
249
|
+
|
|
250
|
+
For a service already running in production, with its own existing log lines, `hybrid` is the adoption path: every line the service already prints keeps printing byte-identically, and a call written with the `semlog=True` keyword is hidden from that same printed output and instead becomes one JSON record. `logger.info("event.name", extra={...}, semlog=True)` is the shape of a call that adopts semlog this way. `off` behaves as if semlog were never installed at all, except that the keyword itself never raises, so it stays safe to leave in call sites while rolling back. `full` is the end state, and the default for a new service that has no existing log lines to preserve: the adoption path is `hybrid`, then `full` once its output has been reviewed. `mode` is switched by environment, with no code change required.
|
|
251
|
+
|
|
252
|
+
- **`full`**: every log call goes through semlog's pipeline and becomes one JSON record per line, exactly as shown in [Output](#output).
|
|
253
|
+
- **`hybrid`**: `configure()` never touches the root logger's existing handlers or level. A call made with `semlog=True` is hidden from every `StreamHandler` and instead emitted as one semlog JSON record; every other call keeps printing exactly as it did before semlog was installed. See [Limitations](#limitations) for the exact scope of this suppression.
|
|
254
|
+
- **`off`**: `configure()` installs nothing and does not touch the root logger. `semlog=True` still never raises, but produces no JSON output and no other side effect of its own; `operation()`, `bind()` and both middlewares keep working as inert pass-throughs.
|
|
255
|
+
|
|
256
|
+
### Configuration sources
|
|
257
|
+
|
|
258
|
+
`mode` resolves from, in order of precedence:
|
|
259
|
+
|
|
260
|
+
1. the `mode` keyword argument to `configure()`;
|
|
261
|
+
2. the `SEMLOG_MODE` environment variable;
|
|
262
|
+
3. the `mode` key under `[tool.semlog]` in `pyproject.toml`;
|
|
263
|
+
4. the default, `"full"`.
|
|
264
|
+
|
|
265
|
+
```toml
|
|
266
|
+
[tool.semlog]
|
|
267
|
+
mode = "hybrid"
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Reading `pyproject.toml` needs the standard library's `tomllib`, available on Python 3.11 and later; on Python 3.10 this source is skipped entirely, and resolution falls through to the next one. The file is searched starting from the current working directory (or `configure(search_dir=...)`, when given) and upward through its parent directories; a container image built without the project's source tree present, or without the working directory set to it, often has no `pyproject.toml` to find, in which case this source is silently skipped, the same as on Python 3.10. An empty `SEMLOG_MODE` is treated as absent, the same as leaving it unset, and falls through to the next source; this does not apply to `[tool.semlog].mode`, where an empty string is a real declared value. A value that is present but outside `"full"`, `"hybrid"`, `"off"`, from any of the three sources, raises `ValueError` naming both the invalid value and the source it came from.
|
|
271
|
+
|
|
272
|
+
### Limitations
|
|
273
|
+
|
|
274
|
+
- **Hybrid's suppression is scoped to `StreamHandler.handle`.** Only `logging.StreamHandler` instances and subclasses that reach that method are hidden from a marked record. A handler outside that synchronous dispatch, such as a `logging.handlers.QueueHandler` paired with a `QueueListener`, or a `logging.handlers.MemoryHandler`, may still render a marked record as text; so may a `StreamHandler` subclass that overrides `handle()` without calling `super().handle()`. None of this is a defect, only the documented edge of what a method-level patch can reach.
|
|
275
|
+
- **`off` is a process-start switch, not a live toggle.** A process that starts in `off` mode (or with `SEMLOG_MODE=off`) behaves as if semlog were never installed. Reconfiguring an already-running process from `full` or `hybrid` to `off` leaves the JSON pipeline already installed on the root logger attached; it does not tear it down.
|
|
276
|
+
- **Uninstalling semlog while marked calls remain raises `TypeError`.** If `semlog` is removed from a service that still has `semlog=True` call sites, an enabled call at that call site fails with `TypeError`, rather than failing silently. Remove the keyword from call sites before uninstalling.
|
|
277
|
+
|
|
278
|
+
## Output
|
|
279
|
+
|
|
280
|
+
Every record is one JSON object per line, in UTF-8. This record was logged inside an `operation()` that received a `traceparent` header, and is indented here for reading; semlog never indents its output:
|
|
281
|
+
|
|
282
|
+
```json
|
|
283
|
+
{
|
|
284
|
+
"timestamp": "2026-09-14T18:49:31.719135Z",
|
|
285
|
+
"severity_text": "INFO",
|
|
286
|
+
"severity_number": 9,
|
|
287
|
+
"event_name": "payment.authorization.completed",
|
|
288
|
+
"body": null,
|
|
289
|
+
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
|
|
290
|
+
"span_id": "6914640b649ae6de",
|
|
291
|
+
"trace_flags": "01",
|
|
292
|
+
"http.request.id": "01a0a140-c547-72e5-9455-a8b81f4a94d7",
|
|
293
|
+
"otel.scope.name": "payments.service",
|
|
294
|
+
"app.payment.amount": 15000,
|
|
295
|
+
"app.identity": "api.payments",
|
|
296
|
+
"service.name": "payments",
|
|
297
|
+
"service.namespace": null,
|
|
298
|
+
"service.version": "1.4.0",
|
|
299
|
+
"service.instance.id": "936c21f2-7be1-4006-933a-e84d39621fe7",
|
|
300
|
+
"deployment.environment.name": "production",
|
|
301
|
+
"telemetry.sdk.name": "semlog",
|
|
302
|
+
"telemetry.sdk.version": "0.2.0",
|
|
303
|
+
"telemetry.sdk.language": "python"
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Keys are flat, dotted strings. Trace fields appear only when a trace context is bound, and an optional value that is unknown is `null`, never an empty string. Sensitive keys such as `password` or `token` are always replaced by `"REDACTED"`. The full field order, the severity mapping and the presence rules are in [STANDARDS.md](STANDARDS.md), and the formal schema is [`schemas/log-record.schema.json`](schemas/log-record.schema.json).
|
|
308
|
+
|
|
309
|
+
## Compatibility
|
|
310
|
+
|
|
311
|
+
The test suite runs in CI on CPython 3.10, 3.11, 3.12, 3.13 and 3.14; 3.15 also runs there and is allowed to fail. The package is pure Python (`py3-none-any` wheel). Framework integration is tested in CI against these versions:
|
|
312
|
+
|
|
313
|
+
| Framework | Version | Python | Role |
|
|
314
|
+
|---|---|---|---|
|
|
315
|
+
| FastAPI (Starlette 0.17.1) | 0.71.0 | 3.10 | minimum |
|
|
316
|
+
| FastAPI (Starlette 1.6.0) | 0.141.1 | 3.10, 3.14 | latest |
|
|
317
|
+
| Django | 3.2.9 | 3.10 | minimum |
|
|
318
|
+
| Django | 5.2 LTS | 3.10, 3.14 | latest |
|
|
319
|
+
| Django | 6.1 | 3.12, 3.14 | latest |
|
|
320
|
+
|
|
321
|
+
FastAPI is tested with `async def` and sync `def` endpoints. Django is tested with sync views over WSGI and with async and sync views over ASGI.
|
|
322
|
+
|
|
323
|
+
## Documentation
|
|
324
|
+
|
|
325
|
+
- [STANDARDS.md](STANDARDS.md): the normative record contract, severity mapping, extension limits, pipeline guarantees and compatibility policy, with requirement IDs and a traceability annex.
|
|
326
|
+
- [`schemas/log-record.schema.json`](schemas/log-record.schema.json) and [`schemas/event-catalog.schema.json`](schemas/event-catalog.schema.json): JSON Schema 2020-12 for every record and for the event catalog.
|
|
327
|
+
- [Agent guide](src/semlog/agent_guide.md): the call-site reference for coding agents, with rules, migration and review checklists, shipped inside the package (`python -m semlog llm`). [llms.txt](llms.txt) is the agent-facing entry point to the repository.
|
|
328
|
+
- [SECURITY.md](SECURITY.md): how to report a vulnerability privately.
|
|
329
|
+
- [CHANGELOG.md](CHANGELOG.md): notable changes, in the [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) format. semlog follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
330
|
+
|
|
331
|
+
## Contributing
|
|
332
|
+
|
|
333
|
+
Contributions are welcome as pull requests to `main`. Commit messages follow Conventional Commits, and every code change follows strict test-driven development. [AGENTS.md](AGENTS.md) has the setup, test, lint and build commands, the tooling policy and the branch protection rules. Report security vulnerabilities privately, as described in [SECURITY.md](SECURITY.md), never in a public issue.
|
|
334
|
+
|
|
335
|
+
## License
|
|
336
|
+
|
|
337
|
+
semlog is licensed under the [Apache License 2.0](LICENSE) (SPDX: `Apache-2.0`).
|