Start learning free

LESSON 142 OF 173 · BACKEND: TESTS AND RUNNING IT

A Request ID in Go

Practice

When many customers order at the same time, their log lines get mixed together. When one of them writes that their order went wrong, you can't tell which lines are theirs. The fix is a request ID: a random name made for each request, sent back with the answer in a header and written in every log line about that request. The customer gives you the id, and you search the log for it.

Go can run code around every request with a middleware: a function that takes the shop's handler, next, and returns a new handler that does its own work and calls next.ServeHTTP(w, r) to run the route. For example, to log how long each request took:

func withTiming(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        started := time.Now()
        next.ServeHTTP(w, r)
        log.Printf("%s took %v", r.URL.Path, time.Since(started))
    })
}

A header must be set before the route writes the answer: once writeJSON calls WriteHeader, later header changes are not sent. Each request has its own w, and the route gets that same w, so it can read the id back with w.Header().Get. The given newRequestID() makes 32 random characters of 0–9 and a–f, practically never the same twice. A test can't know a random id in advance, so it checks a part: s.Contains(text, part) passes when part is found inside text. To read the log in a test, send it into a strings.Builder with log.SetOutput.

The check sends four requests. The ids are random, so it shows them as <id 1>, <id 2> and so on:

POST /orders {"product_id": 1, "quantity": 2}  →  201 | X-Request-Id: <id 1>
  log: [<id 1>] Order placed: 2 x Mug, total 24
POST /orders {"product_id": 3, "quantity": 1}  →  201 | X-Request-Id: <id 2>
  log: [<id 2>] Order placed: 1 x Lamp, total 29
POST /orders {"product_id": 99, "quantity": 1}  →  404 | X-Request-Id: <id 3>
GET /about  →  404 | X-Request-Id: <id 4>

Your task: write withRequestID, which makes an id for every request and sets it in the X-Request-Id header before passing the request on (newApp already returns withRequestID(app)), and write the id in square brackets at the start of the order's log line. Then write TestLogHasRequestID, which checks that the log contains the header's id. The check runs it on the shop, where it must pass, and on two broken copies, one that leaves the id out of the log and one that makes a second id for the answer, where it must fail.


Assignment
Follow the Coding Tutorial to give every request an id, put it in the answer's header and in the order's log line, and test that the two match.


Hint
Set the header before next.ServeHTTP, so it is in every answer, the 404s too. In the test, keep the answer of the post in response: its X-Request-Id header holds the id.


STEP 1 OF 11
    Ctrl + Enter to check

    Log Request Id in Go

    Keep going with Go1,600+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free