Skip to content

Commit a8eef8f

Browse files
authored
Add ability to log request ID to all output messages (#3510)
* Add request ID to all log messages * Drive-by: add JSON formatter in config example * Document how to log requests IDs * make format * Add unit test that covers StreamHandler
1 parent 38cbeb7 commit a8eef8f

File tree

6 files changed

+67
-7
lines changed

6 files changed

+67
-7
lines changed

docs/configuration/settings.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,19 @@ Example output:
456456
{"Pid": 19240, "Type": "root", "Timestamp": 1489067817834153984, "Severity": 4, "Hostname": "pluo", "Logger": "%", "EnvVersion": "2.0", "Fields": {"perm": "read", "userid": "ldap:[email protected]", "message": "Permission not granted.", "uri": "/buckets/123"}}
457457

458458

459+
Request IDs
460+
:::::::::::
461+
462+
In order to add requests IDs to log messages, set the provided handler:
463+
464+
.. code-block:: ini
465+
466+
[handler_console]
467+
class = kinto.core.StreamHandlerWithRequestID
468+
469+
It will read the value from the ``X-Request-Id`` request header if present, or generate a random string when the request is received otherwise.
470+
471+
459472
.. _handling-exceptions-with-sentry:
460473

461474
Handling exceptions with Sentry

kinto/config/kinto.tpl

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ keys = root, kinto
245245
keys = console
246246

247247
[formatters]
248-
keys = color
248+
keys = color, json
249249

250250
[logger_root]
251251
level = INFO
@@ -258,10 +258,13 @@ qualname = kinto
258258
propagate = 0
259259

260260
[handler_console]
261-
class = StreamHandler
261+
class = kinto.core.StreamHandlerWithRequestID
262262
args = (sys.stderr,)
263263
level = NOTSET
264264
formatter = color
265265

266266
[formatter_color]
267267
class = logging_color_formatter.ColorFormatter
268+
269+
[formatter_json]
270+
class = kinto.core.JsonLogFormatter

kinto/core/__init__.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,20 @@ def __init__(self, fmt=None, datefmt=None, style="%"):
151151
self.logger_name = logger_name
152152

153153

154+
class StreamHandlerWithRequestID(logging.StreamHandler):
155+
"""
156+
A custom StreamHandler that adds the Dockerflow's `RequestIdLogFilter`.
157+
158+
Defining a custom handler seems to be the only way to bypass the fact that
159+
``logging.config.fileConfig()`` does not load filters from ``.ini`` files.
160+
"""
161+
162+
def __init__(self, *args, **kwargs):
163+
super().__init__(*args, **kwargs)
164+
filter_ = dockerflow_logging.RequestIdLogFilter()
165+
self.addFilter(filter_)
166+
167+
154168
def get_user_info(request):
155169
# Default user info (shown in hello view for example).
156170
user_info = {"id": request.prefixed_userid, "principals": request.prefixed_principals}

kinto/core/initialization.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
import urllib.parse
55
import warnings
66
from datetime import datetime
7-
from secrets import token_hex
87

98
from dateutil import parser as dateparser
9+
from dockerflow.logging import get_or_generate_request_id, request_id_context
1010
from pyramid.events import ApplicationCreated, NewRequest, NewResponse
1111
from pyramid.exceptions import ConfigurationError
1212
from pyramid.httpexceptions import (
@@ -374,12 +374,15 @@ def on_new_request(event):
374374
message="Invalid URL path.",
375375
)
376376

377+
rid = get_or_generate_request_id(headers=request.headers)
378+
request_id_context.set(rid)
379+
377380
request.log_context(
378381
agent=request.headers.get("User-Agent"),
379382
path=request_path,
380383
method=request.method,
381384
lang=request.headers.get("Accept-Language"),
382-
rid=request.headers.get("X-Request-Id", token_hex(16)),
385+
rid=rid,
383386
errno=0,
384387
)
385388
qs = dict(errors.request_GET(request))

tests/core/test_logging.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
1+
import io
12
import json
23
import logging
34
import unittest
45
from unittest import mock
56

7+
from dockerflow.logging import request_id_context
68
from pyramid import testing
79

8-
from kinto.core import DEFAULT_SETTINGS, JsonLogFormatter, initialization
10+
from kinto.core import (
11+
DEFAULT_SETTINGS,
12+
JsonLogFormatter,
13+
StreamHandlerWithRequestID,
14+
initialization,
15+
)
916

1017
from .support import BaseWebTest
1118

@@ -162,3 +169,20 @@ def test_logger_name(self):
162169
self.assertEqual(logged["Type"], "app.log")
163170
# See https://github.com/mozilla/mozilla-cloud-services-logger/issues/2
164171
self.assertEqual(logged["Fields"]["msg"], "coucou")
172+
173+
174+
class RequestIdFilter(BaseWebTest, unittest.TestCase):
175+
def test_stream_logs_rid_field(self):
176+
log_buffer = io.StringIO() # In-memory log capture
177+
stream_handler = StreamHandlerWithRequestID(log_buffer)
178+
stream_handler.setFormatter(JsonLogFormatter())
179+
logger = logging.getLogger(__name__)
180+
logger.addHandler(stream_handler)
181+
182+
request_id_context.set("abc") # As done in `kinto.core.initalization`.
183+
logger.error("Some message")
184+
185+
captured = log_buffer.getvalue()
186+
187+
logged = json.loads(captured)
188+
self.assertEqual(logged["Fields"]["rid"], "abc")

tests/test_configuration/test.ini

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ keys = root, kinto
245245
keys = console
246246

247247
[formatters]
248-
keys = color
248+
keys = color, json
249249

250250
[logger_root]
251251
level = INFO
@@ -258,10 +258,13 @@ qualname = kinto
258258
propagate = 0
259259

260260
[handler_console]
261-
class = StreamHandler
261+
class = kinto.core.StreamHandlerWithRequestID
262262
args = (sys.stderr,)
263263
level = NOTSET
264264
formatter = color
265265

266266
[formatter_color]
267267
class = logging_color_formatter.ColorFormatter
268+
269+
[formatter_json]
270+
class = kinto.core.JsonLogFormatter

0 commit comments

Comments
 (0)