User’s Guide¶
Application Requirements¶
Your application needs to inherit from Recorder
to use divák.
The Recorder
class provides methods to inject observers into the
request processing pipeline. The only notable functionality that it provides
is to ensure that every request has a divak_request_id
attribute that is
set to None
by default and that the attribute is added to the logging
context when writing to tornado.log.access_log
. Beyond that, it is a
strictly opt-in interface in that you must explicitly add observers to
enable functionality.
Request Tracing¶
Correlation Headers¶
The simplest form of tracing a request is to pass a header with a correlation
ID through the system. The RequestIdPropagator
class simply
propagates a named header from the request into the response. The default
behavior is to relay a request header named Request-ID
from request to
response. This functionality is enabled by adding
a RequestIdPropagator
instance to your application by calling
add_divak_propagator()
as shown below.
class MyApplication(divak.api.Recorder, web.Application):
def __init__(self, *args, **kwargs):
super(MyApplication, self).__init__(
[web.url('/status', StatusHandler)],
*args, **kwargs)
self.set_divak_service('my-service')
self.add_divak_propagator(divak.api.RequestIdPropagator())
This simple change will copy the Request-ID
header from the request
headers to the response headers. Having a HTTP header that is relayed
from request to response makes it possible to trace the processing of
a request through multiple services provided that you record it somewhere.
GET /status HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: 127.0.0.1:8000
Request-ID: a6bd045cdcf64451a076a59bdb367ba6
User-Agent: HTTPie/0.9.9
HTTP/1.1 200 OK
Content-Length: 61
Content-Type: application/json
Date: Sat, 10 Feb 2018 13:48:41 GMT
Etag: "e3c1480268930c91ab9eb759a1fa5fcad3ab5e28"
Request-Id: a6bd045cdcf64451a076a59bdb367ba6
Server: TornadoServer/4.5.3
{
"service": "my-service",
"status": "ok",
"version": "0.0.0"
}
If the header is not included in the request, then it will generate a UUIDv4
value and insert it into the response. Of course, the value generation
function can be overridden to anything you want by passing the
value_factory
keyword to RequestIdPropagator
.
GET /status HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: 127.0.0.1:8000
User-Agent: HTTPie/0.9.9
HTTP/1.1 200 OK
Content-Length: 61
Content-Type: application/json
Date: Sat, 10 Feb 2018 13:52:42 GMT
Etag: "e3c1480268930c91ab9eb759a1fa5fcad3ab5e28"
Request-Id: 31510218-abb2-49b6-bfd3-a036fe4f6113
Server: TornadoServer/4.5.3
{
"service": "my-service",
"status": "ok",
"version": "0.0.0"
}
Request Logging¶
The divak.api.Recorder
class modifies the Python logging
module to ensure that you can use %(divak_request_id)s
in log formats.
It also overrides the log_request()
method and
outputs something closer to a standard access log line using the following log
format:
$REMOTE-IP "$METHOD $URI" $STATUS "$USER-AGENT" $PROCESSING-TIME
For example:
127.0.0.1 "GET /status" 200 "curl/7.54.0" 0.001341104507446289
The $PROCESSING-TIME
is in decimal seconds and the user-agent & request id
portions both default to "-"
if they are missing for some reason. You can
modify this behavior by overriding the method in your application class or by
adding a custom formatting pattern to tornado.log.access_log
. The
following dictionary is passed as the extra
parameter to the log method:
extra = {
'remoteip': '127.0.0.1',
'status': handler.get_status(),
'elapsed': request.request_time(),
'method': request.method,
'uri': request.uri,
'useragent': request.headers.get('User-Agent', '-'),
'divak_request_id': getattr(request, 'divak_request_id', '-'),
}
If you want to insert the request ID into the access log, then you should modify the default log format to include it when you initialize the logging module:
def main():
logging.basicConfig(
level=logging.INFO,
format=('%(levelname)1.1s - %(name)s: %(message)s '
'{%(divak_request_id)s}'))
app = MyApplication(debug=True)
app.listen(8000)
ioloop.IOLoop.current().start()
This will include the request ID on every log line that it is available on.
The divak.api.Logger
mix-in ensures that it is available on it’s
self.logger
instance. The divak.api.Recorder
class inserts the
request id into access log entries as well. Access logs entries from the
example look like:
127.0.0.1 "GET /status" 200 "curl/7.54.0" 0.001341104507446289 {84DC5B74-752A-468F-A786-806696A5DE01}