Skip to content

Tutorial 1: Propagate Traces

With this tutorial you can solve the problem of distributed tracing

You have applied the Microservice architecture pattern. Requests often span multiple services. Each service handles a request by performing one or more operations, e.g. database queries, publishes messages, etc.

PyMS injects a unique request ID with opentracing and passes the external request id to all services that are involved in handling the current request with the service request

1. Simple Trace

  • Install PyMS bash pip install py-ms[all]

  • Create a config file with traces and requests enabled config.yml:

pyms:
  services:
    requests:
      propagate_headers: true
    tracer:
      client: "jaeger"
      host: "localhost"
      component_name: "Python Microservice"
  config:
    debug: true

main.py

from flask import jsonify, current_app, request

from pyms.flask.app import Microservice

ms = Microservice()
app = ms.create_app()


@app.route("/")
def index():
    app.logger.info("There are my headers: \n{}".format(request.headers))
    return jsonify({"main": "hello world {}".format(current_app.config["APP_NAME"])})


if __name__ == '__main__':
    app.run()

Run this script with:

python main.py

In another terminal, run this command:

curl 'http://localhost:5000/'
{
  "main": "hello world Python Microservice"
}

Your script main.py return a log like:

{
    "message": "There are my headers: \nHost: localhost:5000\r\nUser-Agent: curl/7.58.0\r\nAccept: */*\r\n\r\n", 
    "timestamp": "2020-03-14T10:31:55.430289Z", 
    "severity": "INFO", 
    "service": "python microservice", 
    "trace": "90999056092ac078", 
    "span": "d7e15e52c8c27214", 
    "parent": ""
}

2. Propagate Trace

Create a second script with this config and this code in main.py

pyms:
  services:
    requests:
      propagate_headers: true
    tracer:
      client: "jaeger"
      host: "localhost"
      component_name: "Python Microservice"
  config:
    debug: true

main.py

from flask import jsonify, current_app, request

from pyms.flask.app import Microservice

ms = Microservice()
app = ms.create_app()


@app.route("/")
def index():
    app.logger.info("There are my headers: \n{}".format(request.headers))
    response = app.ms.requests.get("http://localhost:5000/")
    return jsonify({"response": response.json()})


if __name__ == '__main__':
    app.run(port=5001)

Now, run both this script and the first script. You should have the first one running on http://localhost:5000/ and this new one on http://localhost:5001/

In another terminal, run this command:

curl 'http://localhost:5001/'
{
  "main": "hello world Python Microservice"
}

Terminal

The second MS will print these logs:

{"message": "There are my headers: \nHost: localhost:5001\r\nUser-Agent: curl/7.58.0\r\nAccept: */*\r\n\r\n", 
"timestamp": "2020-03-14T10:52:25.522844Z", 
"severity": "INFO", 
"service": "python microservice2", 
"trace": "bb785b88d0456d69", 
"span": "c92e9babb0d002de", 
"parent": ""
}
{"message": "Get with url http://localhost:5000/, params None, headers {'X-B3-TraceId': 'bb785b88d0456d69', 'X-B3-SpanId': 'c92e9babb0d002de', 'X-B3-Sampled': '1'}, kwargs {}", 
"timestamp": "2020-03-14T10:52:25.523169Z", 
"severity": "DEBUG", 
"service": "python microservice2", 
"trace": "bb785b88d0456d69", 
"span": "c92e9babb0d002de", 
"parent": ""}
{"message": "Response <Response [200]>", 
"timestamp": "2020-03-14T10:52:25.528784Z", 
"severity": "DEBUG", 
"service": "python microservice2", 
"trace": "bb785b88d0456d69", 
"span": "c92e9babb0d002de", 
"parent": ""}

And the first MS return:

{"message": "There are my headers: \nHost: localhost:5000\r\nUser-Agent: python-requests/2.23.0\r\nAccept-Encoding: gzip, deflate\r\nAccept: */*\r\nConnection: keep-alive\r\nX-B3-Traceid: bb785b88d0456d69\r\nX-B3-Spanid: c92e9babb0d002de\r\nX-B3-Sampled: 1\r\n\r\n", 
"timestamp": "2020-03-14T10:52:25.527177Z", 
"severity": "INFO", 
"service": "python microservice", 
"trace": "bb785b88d0456d69", 
"span": "a01a2ea1a908f997", 
"parent": "c92e9babb0d002de"}

As you can see, both microservices have "trace": "bb785b88d0456d69".

Flow

You can see the flow of these requests in this diagram:

Distributed tracing

Code

You can check this example on this Github page

The simplest way to start the all-in-one is to use the pre-built image published to DockerHub (a single command line).

Tracer server

You can see the traces with jaeger server:

$ docker run -d --name jaeger \
  -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
  -p 5775:5775/udp \
  -p 6831:6831/udp \
  -p 6832:6832/udp \
  -p 5778:5778 \
  -p 16686:16686 \
  -p 14268:14268 \
  -p 14250:14250 \
  -p 9411:9411 \
  jaegertracing/all-in-one:1.20

You can then navigate to http://localhost:16686 to access the Jaeger UI.