aiohttp-request-id-logging 1.0.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.
- aiohttp_request_id_logging-1.0.0/LICENSE +21 -0
- aiohttp_request_id_logging-1.0.0/PKG-INFO +487 -0
- aiohttp_request_id_logging-1.0.0/README.md +460 -0
- aiohttp_request_id_logging-1.0.0/aiohttp_request_id_logging/__init__.py +71 -0
- aiohttp_request_id_logging-1.0.0/aiohttp_request_id_logging/context.py +24 -0
- aiohttp_request_id_logging-1.0.0/aiohttp_request_id_logging/errors.py +15 -0
- aiohttp_request_id_logging-1.0.0/aiohttp_request_id_logging/logging_setup.py +66 -0
- aiohttp_request_id_logging-1.0.0/aiohttp_request_id_logging/middleware.py +398 -0
- aiohttp_request_id_logging-1.0.0/aiohttp_request_id_logging/py.typed +0 -0
- aiohttp_request_id_logging-1.0.0/aiohttp_request_id_logging/request_id_factories.py +74 -0
- aiohttp_request_id_logging-1.0.0/aiohttp_request_id_logging.egg-info/PKG-INFO +487 -0
- aiohttp_request_id_logging-1.0.0/aiohttp_request_id_logging.egg-info/SOURCES.txt +19 -0
- aiohttp_request_id_logging-1.0.0/aiohttp_request_id_logging.egg-info/dependency_links.txt +1 -0
- aiohttp_request_id_logging-1.0.0/aiohttp_request_id_logging.egg-info/requires.txt +1 -0
- aiohttp_request_id_logging-1.0.0/aiohttp_request_id_logging.egg-info/top_level.txt +1 -0
- aiohttp_request_id_logging-1.0.0/pyproject.toml +65 -0
- aiohttp_request_id_logging-1.0.0/setup.cfg +4 -0
- aiohttp_request_id_logging-1.0.0/tests/test_middleware.py +290 -0
- aiohttp_request_id_logging-1.0.0/tests/test_request_id_factories.py +6 -0
- aiohttp_request_id_logging-1.0.0/tests/test_sentry.py +88 -0
- aiohttp_request_id_logging-1.0.0/tests/test_smoke.py +14 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2019 Petr Messner
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aiohttp-request-id-logging
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Setup proper request id logging for your Aiohttp app
|
|
5
|
+
Maintainer-email: Petr Messner <petr.messner@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/messa/aiohttp-request-id-logging
|
|
8
|
+
Project-URL: Bug Reports, https://github.com/messa/aiohttp-request-id-logging/issues
|
|
9
|
+
Project-URL: Source, https://github.com/messa/aiohttp-request-id-logging
|
|
10
|
+
Keywords: aiohttp,logging
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Framework :: AsyncIO
|
|
13
|
+
Classifier: Framework :: aiohttp
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: aiohttp
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
aiohttp-request-id-logging
|
|
29
|
+
==========================
|
|
30
|
+
|
|
31
|
+
Motivation
|
|
32
|
+
----------
|
|
33
|
+
|
|
34
|
+
When logging from an async application (e.g. aiohttp web application),
|
|
35
|
+
log messages from different async tasks, http requests etc. are intertwined and you cannot surely tell which line was generated by what request.
|
|
36
|
+
For example:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
2020-01-15 15:35:37,501 INFO: Processing transfer id 1234
|
|
40
|
+
2020-01-15 15:35:37,976 INFO: Processing transfer id 5678
|
|
41
|
+
2020-01-15 15:35:38,201 ERROR: Oh no, something bad has happened! Cannot finish the transfer.
|
|
42
|
+
2020-01-15 15:35:38,504 INFO: 127.0.0.1 [15/Jan/2020:14:35:36 +0000] "POST / HTTP/1.1" 200 165 "-" "curl/7.68.0"
|
|
43
|
+
2020-01-15 15:35:38,982 INFO: 127.0.0.1 [15/Jan/2020:14:35:36 +0000] "POST / HTTP/1.1" 500 165 "-" "curl/7.68.0"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
So, which transfer has failed? The one with id 1234, or the one with id 5678?
|
|
47
|
+
|
|
48
|
+
Of course, ideally the error message should contain also the transfer id.
|
|
49
|
+
But it is not always this case, or you may want to inspect also other log messages that were logged by that specific task/request.
|
|
50
|
+
|
|
51
|
+
When you start to use this library in your aiohttp web application, this is how your log messages will look like:
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
2020-01-15 15:58:47,238 INFO: [req:kbyLG8d] Processing GET / (__main__:hello)
|
|
55
|
+
2020-01-15 15:58:47,950 INFO: [req:xtMacpA] Processing GET / (__main__:hello)
|
|
56
|
+
2020-01-15 15:58:48,240 INFO: [req:kbyLG8d] Processing transfer id 1234
|
|
57
|
+
2020-01-15 15:58:48,953 INFO: [req:xtMacpA] Processing transfer id 5678
|
|
58
|
+
2020-01-15 15:58:49,182 ERROR: [req:xtMacpA] Oh no, something bad has happened! Cannot finish the transfer.
|
|
59
|
+
2020-01-15 15:58:49,242 INFO: [req:kbyLG8d] 127.0.0.1 "GET / HTTP/1.1" 200 165 "-" "curl/7.68.0"
|
|
60
|
+
2020-01-15 15:58:49,959 INFO: [req:xtMacpA] 127.0.0.1 "GET / HTTP/1.1" 500 165 "-" "curl/7.68.0"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
Installation
|
|
65
|
+
------------
|
|
66
|
+
|
|
67
|
+
```shell
|
|
68
|
+
$ uv add https://github.com/messa/aiohttp-request-id-logging/archive/v1.0.0.zip
|
|
69
|
+
# or
|
|
70
|
+
$ python3 -m pip install https://github.com/messa/aiohttp-request-id-logging/archive/v1.0.0.zip
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Or add this line to `requirements.txt`:
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
aiohttp-request-id-logging @ https://github.com/messa/aiohttp-request-id-logging/archive/v1.0.0.zip
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
Usage
|
|
81
|
+
-----
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from aiohttp.web import Application, Response, RouteTableDef, run_app
|
|
85
|
+
from logging import DEBUG, basicConfig
|
|
86
|
+
from aiohttp_request_id_logging import (
|
|
87
|
+
setup_logging_request_id_prefix,
|
|
88
|
+
RequestIdMiddleware,
|
|
89
|
+
RequestIdAccessLogger,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
routes = RouteTableDef()
|
|
93
|
+
|
|
94
|
+
@routes.get('/')
|
|
95
|
+
async def hello(request):
|
|
96
|
+
return Response(text="Hello, world!\n")
|
|
97
|
+
|
|
98
|
+
basicConfig(
|
|
99
|
+
level=DEBUG,
|
|
100
|
+
format='%(asctime)s [%(threadName)s] %(name)-37s %(levelname)5s: %(requestIdPrefix)s%(message)s')
|
|
101
|
+
|
|
102
|
+
setup_logging_request_id_prefix()
|
|
103
|
+
|
|
104
|
+
app = Application(middlewares=[RequestIdMiddleware()])
|
|
105
|
+
app.router.add_routes(routes)
|
|
106
|
+
|
|
107
|
+
run_app(app, access_log_class=RequestIdAccessLogger)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
For more complete example see [examples/demo.py](examples/demo.py).
|
|
111
|
+
|
|
112
|
+
Customization of the middleware behavior is demonstrated in
|
|
113
|
+
[`examples/demo_customization_injection.py`](examples/demo_customization_injection.py)
|
|
114
|
+
(constructor parameters) and
|
|
115
|
+
[`examples/demo_customization_subclassing.py`](examples/demo_customization_subclassing.py)
|
|
116
|
+
(subclassing).
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
How it works
|
|
120
|
+
------------
|
|
121
|
+
|
|
122
|
+
This library helps you to add request (correlation) id to the log messages in a few steps:
|
|
123
|
+
|
|
124
|
+
1. **`RequestIdMiddleware`:** Generate random `request_id` for each aiohttp request and
|
|
125
|
+
|
|
126
|
+
- store it in a ContextVar `aiohttp_request_id_logging.request_id`
|
|
127
|
+
- store it also in the request storage: `request[aiohttp_request_id_logging.REQUEST_ID_KEY]`
|
|
128
|
+
(and, by default, also under the plain string key `request['request_id']` for backward compatibility)
|
|
129
|
+
- return it to the client in the `X-Request-Id` response header
|
|
130
|
+
|
|
131
|
+
2. **`setup_logging_request_id_prefix()`:** Modify logging record factory so that the `request_id` is attached to every logging record created
|
|
132
|
+
|
|
133
|
+
- so you should modify your log format, for example `logging.basicConfig(format=... %(levelname)5s: %(requestIdPrefix)s%(message)s')`
|
|
134
|
+
|
|
135
|
+
3. Because the aiohttp access logging happens out of the middleware scope, the request id ContextVar would be already resetted. So **`RequestIdAccessLogger`** is provided that adds the request_id to the access log message.
|
|
136
|
+
|
|
137
|
+
4. Unhandled exceptions ("500 errors") are logged and converted to a 500 response directly by **`RequestIdMiddleware`** – if they were left to the standard aiohttp error handling, the traceback would be logged outside of the middleware scope, without the request id (see the [Reference](#requestidmiddleware) for details).
|
|
138
|
+
|
|
139
|
+
5. If you use **[Sentry](https://docs.sentry.io/platforms/python/aiohttp/)**, a `request_id` [tag](https://docs.sentry.io/enriching-error-data/context/?platform=python#tagging-events) is added when the request is processed.
|
|
140
|
+
|
|
141
|
+
Sentry integration will be active only if you have `sentry_sdk` installed.
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
Reference
|
|
145
|
+
---------
|
|
146
|
+
|
|
147
|
+
All names are importable directly from the `aiohttp_request_id_logging` package.
|
|
148
|
+
|
|
149
|
+
### `RequestIdMiddleware`
|
|
150
|
+
|
|
151
|
+
The aiohttp middleware. Generates a request id for every request, stores it
|
|
152
|
+
in the `request_id` ContextVar and in the request, adds an `X-Request-Id`
|
|
153
|
+
response header, and (if Sentry is installed) creates a Sentry scope with
|
|
154
|
+
a `request_id` tag.
|
|
155
|
+
|
|
156
|
+
Exceptions raised from the handler are processed by the middleware too:
|
|
157
|
+
|
|
158
|
+
- an `HTTPException` (the aiohttp way of returning error responses from
|
|
159
|
+
anywhere in the handler) is re-raised for aiohttp to process, with the
|
|
160
|
+
request id response header added to it first
|
|
161
|
+
- any other unhandled exception is logged together with its traceback and
|
|
162
|
+
converted to a plain text `500 Internal Server Error` response (also
|
|
163
|
+
carrying the request id header) right in the middleware
|
|
164
|
+
|
|
165
|
+
The latter is deliberate: if the exception were left to the standard aiohttp
|
|
166
|
+
error handling, it would be logged outside of the middleware scope, where the
|
|
167
|
+
`request_id` ContextVar is already reset (and the Sentry scope with the
|
|
168
|
+
`request_id` tag closed) – so the traceback, usually the log message you need
|
|
169
|
+
to pair with a request the most, would be the one line without the request id.
|
|
170
|
+
Override `get_response_for_exception(request, exc)` to customize the error
|
|
171
|
+
response (a nicer error page, JSON for API paths...).
|
|
172
|
+
|
|
173
|
+
Constructor parameters (all keyword-only):
|
|
174
|
+
|
|
175
|
+
- `request_id_factory` – a zero-argument callable returning the request id string,
|
|
176
|
+
used when `get_request_id` returns `None` (which the default implementation
|
|
177
|
+
always does); default: `random_request_id_factory`
|
|
178
|
+
- `get_request_id` – a callable `(request)` returning the request id for the
|
|
179
|
+
given request, or `None` to have one generated with `request_id_factory`;
|
|
180
|
+
if you adopt an incoming header value here, validate it – it is controlled
|
|
181
|
+
by the client
|
|
182
|
+
- `log_request_start` – a callable `(request, handler)` that logs the request
|
|
183
|
+
start message, replacing the default `Processing GET / (...)` one;
|
|
184
|
+
pass `noop` to disable the message
|
|
185
|
+
- `log_function_name` – include the handler name in the default request start
|
|
186
|
+
message; default: `True`
|
|
187
|
+
- `add_response_request_id_header` – a callable `(response, req_id)` that adds
|
|
188
|
+
the request id header to the response, replacing the default behavior
|
|
189
|
+
(which keeps a header already set by the handler); pass `noop` to disable
|
|
190
|
+
the header
|
|
191
|
+
- `request_id_header_name` – name of the response header with the request id;
|
|
192
|
+
default: `X-Request-Id`
|
|
193
|
+
- `no_fallback_request_id_key` – if `True`, the request id is stored in the request
|
|
194
|
+
only under `REQUEST_ID_KEY`, not also under the backward compatibility plain
|
|
195
|
+
string key `request['request_id']`; default: `False`
|
|
196
|
+
|
|
197
|
+
The behavior can also be customized by subclassing – overriding the class
|
|
198
|
+
attributes (`request_id_header_name`, `log_function_name`) or the methods:
|
|
199
|
+
`get_request_id`, `before_request`, `after_request`,
|
|
200
|
+
`get_response_for_exception`, `log_request_start`, `set_request_keys`,
|
|
201
|
+
`setup_sentry_scope`, `add_response_request_id_header`, `get_function_name`.
|
|
202
|
+
|
|
203
|
+
Both approaches are demonstrated in
|
|
204
|
+
[`examples/demo_customization_injection.py`](examples/demo_customization_injection.py) and
|
|
205
|
+
[`examples/demo_customization_subclassing.py`](examples/demo_customization_subclassing.py).
|
|
206
|
+
|
|
207
|
+
Functions stored in class attributes are tricky (Python would bind them
|
|
208
|
+
as methods), that is why callables like `request_id_factory` are passed
|
|
209
|
+
via the constructor parameters instead.
|
|
210
|
+
|
|
211
|
+
The `get_request_id`, `log_request_start` and `add_response_request_id_header`
|
|
212
|
+
parameters take precedence over the methods of the same name – when the
|
|
213
|
+
parameter is passed, the method (even one overridden in a subclass) is not
|
|
214
|
+
called.
|
|
215
|
+
|
|
216
|
+
The middleware does not adopt a request id sent by the client – how (and
|
|
217
|
+
whether) to trust such a value depends on the deployment. If you want that,
|
|
218
|
+
pass (or override) `get_request_id(request)` and validate the incoming value
|
|
219
|
+
there; see
|
|
220
|
+
[`examples/demo_customization_subclassing.py`](examples/demo_customization_subclassing.py).
|
|
221
|
+
|
|
222
|
+
`request_id_middleware()` is a backward compatibility wrapper of this class;
|
|
223
|
+
unlike the constructor, its `log_request_start` parameter is a bool –
|
|
224
|
+
`log_request_start=False` translates to `log_request_start=noop`.
|
|
225
|
+
|
|
226
|
+
### `setup_logging_request_id_prefix()`
|
|
227
|
+
|
|
228
|
+
Wraps the logging record factory so that every log record gets two extra
|
|
229
|
+
attributes usable in the log format:
|
|
230
|
+
|
|
231
|
+
- `%(requestIdPrefix)s` – `"[req:kbyLG8d] "`, or an empty string outside of a request
|
|
232
|
+
- `%(request_id)s` – the raw request id, or `None`
|
|
233
|
+
|
|
234
|
+
The prefix can be customized with the `prefix_format` parameter
|
|
235
|
+
(default: `"[req:{request_id}] "`).
|
|
236
|
+
|
|
237
|
+
Safe to call multiple times (does the setup only once).
|
|
238
|
+
|
|
239
|
+
### `RequestIdAccessLogger`
|
|
240
|
+
|
|
241
|
+
Subclass of `aiohttp.web_log.AccessLogger` that sets the `request_id`
|
|
242
|
+
ContextVar while writing the access log line. Needed because aiohttp writes
|
|
243
|
+
the access log outside of the middleware scope. Pass it to
|
|
244
|
+
`run_app(app, access_log_class=RequestIdAccessLogger)`.
|
|
245
|
+
|
|
246
|
+
### `request_id`
|
|
247
|
+
|
|
248
|
+
`ContextVar` holding the request id of the currently processed request.
|
|
249
|
+
Read it with `request_id.get()` – it returns `None` outside of a request.
|
|
250
|
+
|
|
251
|
+
### `REQUEST_ID_KEY`
|
|
252
|
+
|
|
253
|
+
Key under which the request id is stored in the request:
|
|
254
|
+
`request[REQUEST_ID_KEY]`. It is a `web.RequestKey` instance on aiohttp
|
|
255
|
+
versions that support it, otherwise the plain string `'request_id'`.
|
|
256
|
+
(The plain string key `request['request_id']` is set as well by default,
|
|
257
|
+
for backward compatibility; pass `RequestIdMiddleware(no_fallback_request_id_key=True)`
|
|
258
|
+
to store the id only under `REQUEST_ID_KEY`.)
|
|
259
|
+
|
|
260
|
+
`FALLBACK_REQUEST_ID_KEY` is that backward compatibility plain string key –
|
|
261
|
+
`'request_id'`, or `None` on aiohttp versions without `web.RequestKey`
|
|
262
|
+
(there `REQUEST_ID_KEY` itself is the plain string, so there is no fallback).
|
|
263
|
+
|
|
264
|
+
### Request id factories
|
|
265
|
+
|
|
266
|
+
- `random_request_id_factory(length=7)` – the default; returns a random URL-safe
|
|
267
|
+
string of the given length, avoiding visually ambiguous characters
|
|
268
|
+
(`1`/`l`/`I`, `2`/`Z`, `O`/`0`)
|
|
269
|
+
(`generate_request_id` is its backward compatibility alias)
|
|
270
|
+
- `sequential_request_id_factory` / `SequentialRequestIdFactory` – alternative
|
|
271
|
+
factory producing ids like `Wxyz0000`, `Wxyz0001`… – a random per-process
|
|
272
|
+
prefix followed by a sequential number
|
|
273
|
+
|
|
274
|
+
Usage: `RequestIdMiddleware(request_id_factory=sequential_request_id_factory)`
|
|
275
|
+
|
|
276
|
+
Caveat: if the request ids are ever exposed to clients (response header,
|
|
277
|
+
error page…), sequential ids reveal how many requests the server processes
|
|
278
|
+
and how many server processes there are. Note that the middleware sends the
|
|
279
|
+
request id in the `X-Request-Id` response header by default – pass
|
|
280
|
+
`add_response_request_id_header=noop` to disable that. If the exposure is
|
|
281
|
+
a concern, stick with the default random factory.
|
|
282
|
+
|
|
283
|
+
### `noop`
|
|
284
|
+
|
|
285
|
+
A do-nothing function. Pass it as the `log_request_start` or
|
|
286
|
+
`add_response_request_id_header` constructor parameter to disable
|
|
287
|
+
the corresponding default behavior.
|
|
288
|
+
|
|
289
|
+
### `RequestIdKeyAlreadySetError`
|
|
290
|
+
|
|
291
|
+
Raised by the middleware when the request already contains a request id –
|
|
292
|
+
most likely the middleware is applied twice, or something else sets it too.
|
|
293
|
+
The existing id is available as the `existing_request_id` attribute.
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
Development
|
|
297
|
+
-----------
|
|
298
|
+
|
|
299
|
+
The project uses [uv](https://docs.astral.sh/uv/) for dependency and environment
|
|
300
|
+
management, [Ruff](https://docs.astral.sh/ruff/) for linting and formatting
|
|
301
|
+
and [ty](https://docs.astral.sh/ty/) for type checking.
|
|
302
|
+
|
|
303
|
+
```shell
|
|
304
|
+
$ make check # lint + ty check + tests (pytest)
|
|
305
|
+
$ make lint # ruff check + ruff format --check
|
|
306
|
+
$ make lint-fix # apply ruff fixes and formatting
|
|
307
|
+
$ make typecheck # ty check
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
The Makefile targets use `uv run`, which automatically creates the `.venv`
|
|
311
|
+
virtualenv and installs the dependencies (including the `dev` dependency group)
|
|
312
|
+
on the first run.
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
Version changelog
|
|
316
|
+
-----------------
|
|
317
|
+
|
|
318
|
+
### 1.0.0 (2026-07-16)
|
|
319
|
+
|
|
320
|
+
- Added `aiohttp_request_id_logging.__version__`
|
|
321
|
+
- `RequestIdContextAccessLogger` was renamed to `RequestIdAccessLogger`;
|
|
322
|
+
the old name still works as a backward compatibility alias
|
|
323
|
+
- The middleware was refactored into a class `RequestIdMiddleware` so that it can be
|
|
324
|
+
subclassed and individual parts of the behavior customized by overriding class
|
|
325
|
+
attributes and methods (`before_request`, `after_request`,
|
|
326
|
+
`log_request_start`, `set_request_keys`, `setup_sentry_scope`,
|
|
327
|
+
`add_response_request_id_header`...)
|
|
328
|
+
- `request_id_middleware()` is kept as a backward compatibility wrapper function
|
|
329
|
+
- the deprecated-in-aiohttp-4 `@web.middleware` decorator is no longer used
|
|
330
|
+
- see the new examples
|
|
331
|
+
[`examples/demo_customization_injection.py`](examples/demo_customization_injection.py) and
|
|
332
|
+
[`examples/demo_customization_subclassing.py`](examples/demo_customization_subclassing.py)
|
|
333
|
+
- The middleware now returns the request id to the client in the `X-Request-Id`
|
|
334
|
+
response header (a header already set by the handler is not overwritten);
|
|
335
|
+
the header name can be changed with the `request_id_header_name`
|
|
336
|
+
parameter, or the header disabled completely with `add_response_request_id_header=noop`
|
|
337
|
+
- New hook `get_request_id(request)` (a constructor parameter or an overridable
|
|
338
|
+
method) – returns the request id for the given request, e.g. adopted from
|
|
339
|
+
an incoming header, or `None` to have a new id generated with
|
|
340
|
+
`request_id_factory` (if you adopt an incoming value, validate it – see
|
|
341
|
+
[`examples/demo_customization_subclassing.py`](examples/demo_customization_subclassing.py))
|
|
342
|
+
- The response request id header is silently skipped for responses that
|
|
343
|
+
were already prepared (streaming/WebSocket handlers)
|
|
344
|
+
- The request start message ("Processing ...") can be disabled
|
|
345
|
+
(`request_id_middleware(log_request_start=False)`,
|
|
346
|
+
`RequestIdMiddleware(log_request_start=noop)`) or replaced with a custom callable
|
|
347
|
+
(`RequestIdMiddleware(log_request_start=my_log_function)`)
|
|
348
|
+
- The middleware now raises `RequestIdKeyAlreadySetError` when the request
|
|
349
|
+
already contains a request id, for example when the middleware is applied twice
|
|
350
|
+
or something else also sets the request id
|
|
351
|
+
- New parameter `RequestIdMiddleware(no_fallback_request_id_key=True)` stores the
|
|
352
|
+
request id only under `REQUEST_ID_KEY`, skipping the backward compatibility
|
|
353
|
+
plain string key `request['request_id']` (the plain string key is now also
|
|
354
|
+
exported as `FALLBACK_REQUEST_ID_KEY`)
|
|
355
|
+
- Breaking: all `request_id_middleware()` parameters are now keyword-only; positional calls
|
|
356
|
+
like `request_id_middleware(my_factory)` raise `TypeError` – use
|
|
357
|
+
`request_id_middleware(request_id_factory=my_factory)` instead
|
|
358
|
+
- `HTTPException` raised from a handler is re-raised as before, but the request id
|
|
359
|
+
response header is now added to it first (the exception is also the response
|
|
360
|
+
aiohttp sends to the client)
|
|
361
|
+
- The 500 response for an unhandled exception from the handler can be customized
|
|
362
|
+
by overriding the `get_response_for_exception(request, exc)` method
|
|
363
|
+
- New parameter `setup_logging_request_id_prefix(prefix_format=...)` customizes the log
|
|
364
|
+
record prefix (default: `"[req:{request_id}] "`)
|
|
365
|
+
- The `request_id` ContextVar now has a default of `None`, so it can be read
|
|
366
|
+
with just `request_id.get()` – no more `LookupError` (or the `request_id.get(None)`
|
|
367
|
+
workaround) outside of a request
|
|
368
|
+
- Breaking: `random_request_id_factory()` has a new `length` parameter, replacing the
|
|
369
|
+
removed module-level variables `request_id_default_length` and
|
|
370
|
+
`default_request_id_factory`; the module-level function `get_function_name`
|
|
371
|
+
was also removed (it is now a `RequestIdMiddleware` static method)
|
|
372
|
+
- Generated request ids now avoid visually ambiguous characters
|
|
373
|
+
(`1`/`l`/`I`, `2`/`Z`, `O`/`0`)
|
|
374
|
+
- The package was split into more modules (`middleware.py`, `errors.py`, `context.py`,
|
|
375
|
+
`logging_setup.py`, `request_id_factories.py`); everything is
|
|
376
|
+
still importable directly from `aiohttp_request_id_logging`, which now also defines `__all__`
|
|
377
|
+
- Added type hints, together with the `py.typed` marker so that the types
|
|
378
|
+
are visible to type checkers (mypy, pyright) in projects using this library
|
|
379
|
+
- Added example [examples/demo_legacy.py](examples/demo_legacy.py) demonstrating
|
|
380
|
+
the backward compatible `request_id_middleware()` usage
|
|
381
|
+
- Tests: every example in [examples/](examples/) is now run and checked
|
|
382
|
+
(the request id in the response header, the log line prefixes, clean stderr);
|
|
383
|
+
added tests for the HTTPException and 500 error handling
|
|
384
|
+
- Development: the project is now managed with [uv](https://docs.astral.sh/uv/)
|
|
385
|
+
(`uv.lock`, dev dependencies in the `[dependency-groups]` table, which replaced
|
|
386
|
+
the `test` extra); linting was migrated from flake8 to
|
|
387
|
+
[Ruff](https://docs.astral.sh/ruff/) and the codebase was reformatted
|
|
388
|
+
with `ruff format` (see the new `make lint` and `make lint-fix` targets)
|
|
389
|
+
- Development: added type checking with [ty](https://docs.astral.sh/ty/)
|
|
390
|
+
(`make typecheck`, also run in CI); `sentry-sdk` was added to the dev
|
|
391
|
+
dependencies so that the Sentry integration is type checked too
|
|
392
|
+
- Packaging: modernized the pyproject metadata – SPDX license expression
|
|
393
|
+
(`license = "MIT"`), added the Development Status, Framework and
|
|
394
|
+
Typing :: Typed classifiers
|
|
395
|
+
- Github Actions: lint runs in a separate job; uv is used to install Python
|
|
396
|
+
and the dependencies; the test matrix also includes the latest aiohttp
|
|
397
|
+
release (unpinned) to catch regressions with new aiohttp versions early
|
|
398
|
+
|
|
399
|
+
### 0.0.8 (2026-07-14)
|
|
400
|
+
|
|
401
|
+
- Store the request id in the request under a `web.RequestKey` instance,
|
|
402
|
+
exported as `aiohttp_request_id_logging.REQUEST_ID_KEY` – fixes `NotAppKeyWarning`
|
|
403
|
+
emitted by aiohttp 3.13/3.14
|
|
404
|
+
- the plain string key `request['request_id']` is still set for backward compatibility
|
|
405
|
+
- Moved `demo.py` to [examples/](examples/)
|
|
406
|
+
- Fixed pyproject metadata
|
|
407
|
+
- Tests: run the demo in Python dev mode and assert clean stderr
|
|
408
|
+
(aiohttp-internal warnings are whitelisted)
|
|
409
|
+
- Github Actions: added Python 3.14, updated action versions
|
|
410
|
+
|
|
411
|
+
### 0.0.7 (2025-05-12)
|
|
412
|
+
|
|
413
|
+
- Support new scope handling (`isolation_scope`) in sentry_sdk 2.x
|
|
414
|
+
and removed an incorrect upgrade warning
|
|
415
|
+
([#6](https://github.com/messa/aiohttp-request-id-logging/pull/6), thanks @martinhanzik)
|
|
416
|
+
- Github Actions: added Python 3.13
|
|
417
|
+
|
|
418
|
+
### 0.0.6 (2024-06-02)
|
|
419
|
+
|
|
420
|
+
- Added `sequential_request_id_factory` as an alternative request id generator
|
|
421
|
+
- Improved `demo.py` and added a test running it
|
|
422
|
+
|
|
423
|
+
### 0.0.5 (2024-05-02)
|
|
424
|
+
|
|
425
|
+
- `RequestIdContextAccessLogger` no longer fails when the request id is not set,
|
|
426
|
+
for example when an error occurs in a middleware
|
|
427
|
+
- Github Actions: added Python 3.12
|
|
428
|
+
|
|
429
|
+
### 0.0.4 (2023-05-11)
|
|
430
|
+
|
|
431
|
+
- Fixed `request_id_default_length` handling: the generated request id is now
|
|
432
|
+
exactly that many characters long (and the default was changed from 5 to 7)
|
|
433
|
+
|
|
434
|
+
### 0.0.3 (2023-05-11)
|
|
435
|
+
|
|
436
|
+
- Added `req:` to the log record prefix: `[x12y3]` → `[req:x12y3]`
|
|
437
|
+
|
|
438
|
+
### 0.0.2 (2023-05-11)
|
|
439
|
+
|
|
440
|
+
- Migrated packaging from `setup.py` to `pyproject.toml`
|
|
441
|
+
- Added flake8 lint configuration
|
|
442
|
+
- Github Actions: added Python 3.10 and 3.11, added aiohttp version matrix
|
|
443
|
+
|
|
444
|
+
### 0.0.1 (2021-05-18)
|
|
445
|
+
|
|
446
|
+
- Initial release: `request_id_middleware()`, `setup_logging_request_id_prefix()`,
|
|
447
|
+
`RequestIdContextAccessLogger` and the Sentry `request_id` tag integration
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
Alternatives
|
|
451
|
+
------------
|
|
452
|
+
|
|
453
|
+
- [belvaio-request-id](https://pypi.org/project/belvaio-request-id/) – another
|
|
454
|
+
request id middleware + access logger for aiohttp; it also adopts the id
|
|
455
|
+
from the incoming `X-Request-Id` header and attaches it to log records via
|
|
456
|
+
a logging filter (instead of the log record factory used here); last
|
|
457
|
+
released in 2021 and its source repository is no longer available
|
|
458
|
+
- [asgi-correlation-id](https://github.com/snok/asgi-correlation-id) – the
|
|
459
|
+
same idea for the ASGI world (FastAPI, Starlette, Django...; aiohttp is not
|
|
460
|
+
ASGI, so it cannot be used here) – correlation id middleware, a logging
|
|
461
|
+
filter and a Sentry integration
|
|
462
|
+
- [django-guid](https://github.com/snok/django-guid) – correlation id
|
|
463
|
+
middleware and logging integration for Django
|
|
464
|
+
- [structlog contextvars](https://www.structlog.org/en/stable/contextvars.html) –
|
|
465
|
+
if you log via structlog, you can bind a request id to the context-local
|
|
466
|
+
context yourself (`structlog.contextvars.bind_contextvars(request_id=...)`)
|
|
467
|
+
in a small custom middleware
|
|
468
|
+
- [aiotask-context](https://github.com/Skyscanner/aiotask-context) – stores
|
|
469
|
+
contextual data (e.g. a request id) directly in the asyncio task and
|
|
470
|
+
includes an aiohttp request id logging example; predates `contextvars`,
|
|
471
|
+
its last release was in 2020 and the repository is now archived
|
|
472
|
+
- [OpenTelemetry Python](https://opentelemetry.io/docs/languages/python/) –
|
|
473
|
+
the heavyweight solution: full distributed tracing with an aiohttp server
|
|
474
|
+
instrumentation, and the logging instrumentation can optionally inject
|
|
475
|
+
`otelTraceID`/`otelSpanID` into log records (using the same log record
|
|
476
|
+
factory technique as this library)
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
Links
|
|
480
|
+
-----
|
|
481
|
+
|
|
482
|
+
In general, you should be familiar with Aiohttp documentation related to production deployment:
|
|
483
|
+
|
|
484
|
+
- https://docs.aiohttp.org/en/stable/web_advanced.html#deploying-behind-a-proxy
|
|
485
|
+
- https://docs.aiohttp.org/en/stable/deployment.html
|
|
486
|
+
|
|
487
|
+
This is where this started: https://stackoverflow.com/a/58801740/196206
|