Skip to content

Commit 942753d

Browse files
committed
Generalize usage of request correlation id to all integrations
1 parent 4bad625 commit 942753d

File tree

16 files changed

+115
-52
lines changed

16 files changed

+115
-52
lines changed

docs/django.rst

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -386,11 +386,17 @@ configure **at least** the ``request.summary`` logger that way::
386386
'logger_name': 'myproject'
387387
}
388388
},
389+
'filters': {
390+
'request_id': {
391+
'()': 'dockerflow.logging.RequestIdFilter',
392+
},
393+
},
389394
'handlers': {
390395
'console': {
391396
'level': 'DEBUG',
392397
'class': 'logging.StreamHandler',
393-
'formatter': 'json'
398+
'formatter': 'json',
399+
'filters': ['request_id']
394400
},
395401
},
396402
'loggers': {
@@ -408,6 +414,10 @@ In order to include querystrings in the request summary log, set this flag in se
408414
DOCKERFLOW_SUMMARY_LOG_QUERYSTRING = True
409415
410416
417+
A unique request ID is read from the `X-Request-ID` request header using the `RequestIdMiddleware` middleware (see :ref:`django-setup`), and a UUID4 value is generated if unset.
418+
419+
Leveraging the `RequestIdFilter` in logging configuration as shown above will add a ``rid`` attribute to all log messages.
420+
411421
.. _django-static:
412422

413423
Static content

docs/fastapi.rst

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ The package ``dockerflow.fastapi`` package implements various tools to support
2929
For more information see the :doc:`API documentation <api/fastapi>` for
3030
the ``dockerflow.fastapi`` module.
3131

32+
.. _fastapi-setup:
33+
3234
Setup
3335
-----
3436

@@ -39,12 +41,12 @@ To install ``python-dockerflow``'s FastAPI support please follow these steps:
3941

4042
from fastapi import FastAPI
4143
from dockerflow.fastapi import router
42-
from dockerflow.fastapi.middleware import MozlogRequestSummaryLogger, CorrelationIdMiddleware
44+
from dockerflow.fastapi.middleware import MozlogRequestSummaryLogger, RequestIdMiddleware
4345

4446
app = FastAPI()
4547
app.include_router(router)
4648
app.add_middleware(MozlogRequestSummaryLogger)
47-
app.add_middleware(CorrelationIdMiddleware) # see snok/asgi-correlation-id
49+
app.add_middleware(RequestIdMiddleware) # see snok/asgi-correlation-id
4850

4951
#. Make sure the app root path is set correctly as this will be used
5052
to locate the ``version.json`` file that is generated by
@@ -298,7 +300,7 @@ for at least the ``request.summary`` logger:
298300
},
299301
'filters': {
300302
'request_id': {
301-
'()': 'dockerflow.fastapi.RequestIdFilter',
303+
'()': 'dockerflow.logging.RequestIdFilter',
302304
},
303305
},
304306
'handlers': {
@@ -324,7 +326,10 @@ In order to include querystrings in the request summary log, set this flag in th
324326
325327
app.state.DOCKERFLOW_SUMMARY_LOG_QUERYSTRING = True
326328
327-
A unique ID is added to each log message, using the `asgi-correlation-id <https://github.com/snok/asgi-correlation-id>`_ package and with the `RequestIdFilter` added in logging configuration as shown above.
329+
A unique request ID is read from the `X-Request-ID` request header using the `RequestIdMiddleware` middleware (see :ref:`fastapi-setup`), and a UUID4 value is generated if unset.
330+
331+
Leveraging the `RequestIdFilter` in logging configuration as shown above will add a ``rid`` attribute to all log messages.
332+
328333

329334
.. _fastapi-static:
330335

docs/flask.rst

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,11 +441,17 @@ for at least the ``request.summary`` logger::
441441
'logger_name': 'myproject'
442442
}
443443
},
444+
'filters': {
445+
'request_id': {
446+
'()': 'dockerflow.logging.RequestIdFilter',
447+
},
448+
},
444449
'handlers': {
445450
'console': {
446451
'level': 'DEBUG',
447452
'class': 'logging.StreamHandler',
448-
'formatter': 'json'
453+
'formatter': 'json',
454+
'filters': ['request_id']
449455
},
450456
},
451457
'loggers': {
@@ -460,6 +466,10 @@ In order to include querystrings in the request summary log, set this flag in :r
460466

461467
DOCKERFLOW_SUMMARY_LOG_QUERYSTRING = True
462468

469+
A unique request ID is read from the `X-Request-ID` request header using the `RequestIdMiddleware` middleware (see :ref:`flask-setup`), and a UUID4 value is generated if unset.
470+
471+
Leveraging the `RequestIdFilter` in logging configuration as shown above will add a ``rid`` attribute to all log messages.
472+
463473
.. _flask-static:
464474

465475
Static content

docs/sanic.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,10 @@ In order to include querystrings in the request summary log, set this flag in :r
459459

460460
DOCKERFLOW_SUMMARY_LOG_QUERYSTRING = True
461461

462+
A unique request ID is read from the `X-Request-ID` request header using the `RequestIdMiddleware` middleware (see :ref:`django-setup`), and a UUID4 value is generated if unset.
463+
464+
Leveraging the `RequestIdFilter` in logging configuration as shown above will add a ``rid`` attribute to all log messages.
465+
462466

463467
.. _sanic-static:
464468

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def read(*parts):
5050
"django": ["django"],
5151
"flask": ["flask", "blinker"],
5252
"sanic": ["sanic"],
53-
"fastapi": ["fastapi", "asgiref", "asgi-correlation-id"],
53+
"fastapi": ["fastapi", "asgiref"],
5454
},
5555
zip_safe=False,
5656
python_requires=">=3.7,<4",

src/dockerflow/django/middleware.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
import re
33
import time
44
import urllib
5-
import uuid
65

76
from django.conf import settings
87
from django.utils.deprecation import MiddlewareMixin
98

9+
from dockerflow.logging import get_or_generate_request_id, request_id_context
10+
1011
from . import views
1112

1213

@@ -33,7 +34,10 @@ def process_request(self, request):
3334
if pattern.match(request.path_info):
3435
return view(request)
3536

36-
request._id = str(uuid.uuid4())
37+
rid = get_or_generate_request_id(request.headers)
38+
request_id_context.set(rid)
39+
request._id = rid # Used in tests.
40+
3741
request._start_timestamp = time.time()
3842
return None
3943

@@ -56,8 +60,7 @@ def _build_extra_meta(self, request):
5660
# attributes before trying to use them.
5761
if hasattr(request, "user"):
5862
out["uid"] = request.user.is_authenticated and request.user.pk or ""
59-
if hasattr(request, "_id"):
60-
out["rid"] = request._id
63+
out["rid"] = request_id_context.get()
6164
if hasattr(request, "_start_timestamp"):
6265
# Duration of request, in milliseconds.
6366
out["t"] = int(1000 * (time.time() - request._start_timestamp))

src/dockerflow/fastapi/__init__.py

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
from logging import Filter, LogRecord
2-
3-
from asgi_correlation_id import correlation_id
41
from fastapi import APIRouter
52
from fastapi.routing import APIRoute
63

@@ -15,14 +12,3 @@
1512
],
1613
)
1714
"""This router adds the Dockerflow views."""
18-
19-
20-
class RequestIdLogFilter(Filter):
21-
"""Logging filter to attach request IDs to log records"""
22-
23-
def filter(self, record: "LogRecord") -> bool:
24-
"""
25-
Attach the request ID to the log record.
26-
"""
27-
record.rid = correlation_id.get(None)
28-
return True

src/dockerflow/fastapi/middleware.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import urllib
77
from typing import Any, Dict
88

9-
from asgi_correlation_id import CorrelationIdMiddleware, correlation_id # noqa
109
from asgiref.typing import (
1110
ASGI3Application,
1211
ASGIReceiveCallable,
@@ -15,7 +14,30 @@
1514
HTTPScope,
1615
)
1716

18-
from ..logging import JsonLogFormatter
17+
from ..logging import JsonLogFormatter, get_or_generate_request_id, request_id_context
18+
19+
20+
class RequestIdMiddleware:
21+
def __init__(
22+
self,
23+
app: ASGI3Application,
24+
) -> None:
25+
self.app = app
26+
27+
async def __call__(
28+
self, scope: HTTPScope, receive: ASGIReceiveCallable, send: ASGISendCallable
29+
) -> None:
30+
if scope["type"] != "http":
31+
return await self.app(scope, receive, send)
32+
33+
headers = {}
34+
for name, value in scope["headers"]:
35+
header_key = name.decode("latin1").lower()
36+
header_val = value.decode("latin1")
37+
headers[header_key] = header_val
38+
39+
request_id_context.set(get_or_generate_request_id(headers))
40+
await self.app(scope, receive, send)
1941

2042

2143
class MozlogRequestSummaryLogger:
@@ -75,7 +97,7 @@ def _format(self, scope: HTTPScope, info) -> Dict[str, Any]:
7597
"code": info["response"]["status"],
7698
"lang": info["request_headers"].get("accept-language"),
7799
"t": int(request_duration_ms),
78-
"rid": correlation_id.get(),
100+
"rid": request_id_context.get(),
79101
}
80102

81103
if getattr(scope["app"].state, "DOCKERFLOW_SUMMARY_LOG_QUERYSTRING", False):

src/dockerflow/flask/app.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44
import logging
55
import os
66
import time
7-
import uuid
87
import warnings
98

109
import flask
1110
from werkzeug.exceptions import InternalServerError
1211

1312
from dockerflow import checks
13+
from dockerflow.logging import get_or_generate_request_id, request_id_context
1414

1515
from .. import version
1616
from .checks import (
@@ -188,9 +188,12 @@ def _before_request(self):
188188
"""
189189
The before_request callback.
190190
"""
191-
flask.g._request_id = str(uuid.uuid4())
191+
rid = get_or_generate_request_id(flask.request.headers)
192+
request_id_context.set(rid)
193+
flask.g._request_id = rid # For retro-compatibility and tests.
192194
if not hasattr(flask.g, "request_id"):
193-
flask.g.request_id = flask.g._request_id
195+
flask.g.request_id = rid
196+
194197
flask.g._start_timestamp = time.time()
195198

196199
def _after_request(self, response):
@@ -269,9 +272,7 @@ def summary_extra(self):
269272
out["uid"] = user_id
270273

271274
# the rid value to the current request ID
272-
request_id = flask.g.get("_request_id", None)
273-
if request_id is not None:
274-
out["rid"] = request_id
275+
out["rid"] = request_id_context.get()
275276

276277
# and the t value to the time it took to render
277278
start_timestamp = flask.g.get("_start_timestamp", None)

src/dockerflow/logging.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
import socket
99
import sys
1010
import traceback
11+
import uuid
12+
from contextvars import ContextVar
13+
from typing import Optional
1114

1215

1316
class SafeJSONEncoder(json.JSONEncoder):
@@ -153,3 +156,25 @@ def safer_format_traceback(exc_typ, exc_val, exc_tb):
153156
lines.append("%r\n" % (exc_typ,))
154157
lines.append("%r\n" % (exc_val,))
155158
return "".join(lines)
159+
160+
161+
request_id_context: ContextVar[Optional[str]] = ContextVar("request_id", default=None)
162+
163+
164+
def get_or_generate_request_id(headers: dict) -> str:
165+
header_name = "x-request-id"
166+
rid = headers.get(header_name, "")
167+
if not rid:
168+
rid = str(uuid.uuid4())
169+
return rid
170+
171+
172+
class RequestIdLogFilter(logging.Filter):
173+
"""Logging filter to attach request IDs to log records"""
174+
175+
def filter(self, record: "logging.LogRecord") -> bool:
176+
"""
177+
Attach the request ID to the log record.
178+
"""
179+
record.rid = request_id_context.get(None)
180+
return True

0 commit comments

Comments
 (0)