Microservices Part 6: Trace One Request Through the Logs

Part 6 of Microservices on Light Cloud. Give every request an ID, write logs as JSON with a severity, and find any order, good or failed, across services in seconds.

Microservices Part 6: Trace One Request Through the Logs
On this pageShow
  1. What you will build
  2. Before you start
  3. Step 1: Get the Part 6 code
  4. Step 2: How the request ID travels
  5. Step 3: Write logs the Logs tab understands
  6. Step 4: Send a traced order
  7. Step 5: Find it in both services
  8. Step 6: Find failures with the level filter

To follow one request across microservices on Light Cloud, give it an ID, pass that ID to every service in an x-request-id header, and print it in every log line as JSON with a severity field. Then search each service's Logs tab for the ID: every line the request produced appears, in every service, and the level filter narrows the view to warnings and errors.

This is Part 6, the last part of the series. It uses Bean There from Part 1, where the request ID was already built in.

What you will build

  • Log lines that the Logs tab understands: JSON with severity, service, requestId and a message.
  • A traced order: one ID, found in orders-api and in catalog-api.
  • A traced failure: an order for a product that does not exist, found with the warnings and above filter.

Source code: github.com/light-cloud-com/tutorial-microservices, tag part-6.

Before you start

Step 1: Get the Part 6 code

Sync your fork or pull, as in the earlier parts:

terminal
$ git pull upstream main
$ git push origin main

Both APIs changed, so both redeploy.

Step 2: How the request ID travels

Each API gives every request an ID, or keeps the one the caller sent, and returns it in the response. orders-api passes it on when it calls catalog-api:

orders-api/main.py
python
@app.middleware("http")
async def request_id(request: Request, call_next):
    request.state.request_id = request.headers.get("x-request-id") or str(uuid.uuid4())
    response = await call_next(request)
    response.headers["x-request-id"] = request.state.request_id
    return response
orders-api/main.py
python
reply = await client.post(
    f"{CATALOG_API_URL}/internal/products/{order.product_id}/reserve",
    json={"quantity": order.quantity},
    headers={"x-internal-secret": INTERNAL_SECRET, "x-request-id": request_id},
)

catalog-api does the same on its side, keeping the ID it receives:

catalog-api/server.js
javascript
app.use((req, res, next) => {
  req.requestId = req.get("x-request-id") || randomUUID();
  res.set("x-request-id", req.requestId);
  next();
});

Step 3: Write logs the Logs tab understands

Part 6 adds a severity field to every log line. The Logs tab reads it: lines get the right level and colour, and the level filter works. Without it, a line shows as DEFAULT.

catalog-api/server.js
javascript
// One JSON object per line. "severity" is what the Logs tab's level filter
// reads (INFO, WARNING, ERROR); requestId ties lines from all services together.
function log(req, message, extra = {}, severity = "INFO") {
  console.log(JSON.stringify({ severity, service: "catalog-api", requestId: req.requestId, message, ...extra }));
}
orders-api/main.py
python
def log(request_id: str, message: str, severity: str = "INFO", **extra):
    print(json.dumps({"severity": severity, "service": "orders-api", "requestId": request_id, "message": message, **extra}), flush=True)

Part 6 also fixes a bug from earlier parts: an order for a product that does not exist used to answer "Not enough stock". It now returns 404 Product not found and logs a warning in both services.

Step 4: Send a traced order

Place an order with an ID you choose, so it is easy to find. -i shows the response headers, including the ID coming back:

terminal
$ curl -s -i -X POST https://main-orders-api-yourworkspace.light-cloud.io/orders \
  -H "content-type: application/json" -H "x-request-id: order-7f3a" \
  -d '{"product_id":2,"quantity":1}'
HTTP/2 201
x-request-id: order-7f3a

{"id":9,"product":"Pour-over kettle","quantity":1,"total_cents":5900}

Other headers are left out here. Your order id will differ. In a real app the browser would not choose the ID; the first service creates one, and you find it in the response or in the first log line.

Step 5: Find it in both services

  1. Open orders-api, Production, then the Logs tab.
  2. Type the ID, order-7f3a, into search lines and press Enter.

orders-api's Logs tab searched for order-7f3a, showing placing order and order placed at INFO level

Two lines: placing order and order placed. Click a line to open it. Under JSON Payload you see every field the service logged, including the requestId:

An expanded log line with its metadata and the JSON payload showing requestId order-7f3a

  1. Now open catalog-api, Production, Logs, and search for the same ID.

catalog-api's Logs tab searched for order-7f3a, showing the reserved stock line

reserved stock: the same request, one service further along. Together the two searches show the order's whole path, in order, with timestamps.

Step 6: Find failures with the level filter

Send an order that fails:

terminal
$ curl -s -i -X POST https://main-orders-api-yourworkspace.light-cloud.io/orders \
  -H "content-type: application/json" -H "x-request-id: order-404b" \
  -d '{"product_id":999,"quantity":1}'
HTTP/2 404
x-request-id: order-404b

{"detail":"Product not found"}

Now find it without knowing the ID. In orders-api's Logs tab, clear the search, open the level filter and choose warnings and above:

The level filter open with presets all, info and above, warnings and above and errors only, and the individual levels below

Only warnings and errors remain. Open order for unknown product to see which product and which request:

The Logs tab filtered to warnings, with the order for unknown product line expanded and its JSON payload showing product_id 999 and requestId order-404b

Two kinds of lines show up. Your own order for unknown product warning, and a line Light Cloud writes for every HTTP request, here POST https://main-orders-api-yourworkspace.light-cloud.io/orders 404. Requests that end with a 4xx status are marked WARNING automatically, so this filter finds failed requests even in a service that logs nothing itself. With the requestId from the payload you can now search catalog-api for the other half of the story.