.. :py:currentmodule:: divak.api

Request Tracing
===============

Correlation Headers
-------------------
.. index:: RequestIdPropagator
.. index:: HTTP Header;Request-ID

The simplest form of tracing a request is to pass a header with a correlation
ID through the system.  The :class:`.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 :class:`.RequestIdPropagator` instance to your application by calling
:meth:`~.Recorder.add_divak_propagator` as shown below.

.. literalinclude:: ../examples/request_tracing.py
   :pyobject: MyApplication
   :caption: examples/request_tracing.py

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.

.. code-block:: http
   :caption: Request with header
   :emphasize-lines: 6

   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

.. code-block:: http
   :caption: Response with copied header
   :emphasize-lines: 6

   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 :class:`.RequestIdPropagator`.

.. code-block:: http
   :caption: Request without header

   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

.. code-block:: http
   :caption: Response with generated header
   :emphasize-lines: 6

   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"
   }

.. index:: Logging;Request ID
.. _request_logging:

Request Logging
---------------
The :class:`divak.api.Recorder` class modifies the Python :mod:`logging`
module to ensure that you can use ``%(divak_request_id)s`` in log formats.
It also overrides the :meth:`~tornado.web.Application.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:

.. code-block:: python

   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:

.. literalinclude:: ../examples/request_tracing.py
   :pyobject: main
   :caption: examples/request_tracing.py
   :emphasize-lines: 4-5

This will include the request ID on every log line that it is available on.
The :class:`divak.api.Logger` mix-in ensures that it is available on it's
``self.logger`` instance.  The :class:`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}
