Skip to content
This repository has been archived by the owner on Mar 31, 2023. It is now read-only.

Activity tracking

Yamamoto, Hirotaka edited this page Oct 21, 2018 · 3 revisions

This page describes how to track important activities between servers and between function/method calls.

Backgrounds

Modern web backends typically consist of many services. A request to an application often involves several other requests to internal services. We want to track such requests between servers.

Further, Go does not provide a way to identify goroutines. There is no goroutine ID. In order to track activities of a goroutine, we need to pass some ID between functions and/or methods.

Below we call such an identifier a request ID.

How to propagate request ID

Between function/method calls, we can use context of course. The context key of request ID is RequestIDContextKey.

Since the framework provides contexts everywhere, it should be easy to keep one for ID propagation. Note that the context of HTTP request can be obtained by http.Request.Context and be set by http.Request.WithContext.

Between HTTP servers, we use a special header "X-Cybozu-Request-ID". The value of the header is used as request ID, if the header exists.

HTTPClient, a wrapper for http.Client, automatically adds the header if the context of the request passed to Do has a request ID.

ID generation

Although the framework comes with an ultra fast ID generator, for most cases request ID is generated automatically.

HTTPServer, a wrapper for http.Server, issues a new request ID if an incoming request does not have "X-Cybozu-Request-ID" header.

Server issues a new request ID for each new connection and passes it to the handler function.

Environment.GoWithID issues/overwrites a new request ID for each goroutine.

Logging

HTTPServer, HTTPClient, LogCmd record their activities into logs. The logs have request ID field if contexts given to them have request IDs.

You need to set a context in http.Request before calling HTTPClient.Do as follows:

// Initialize at some point.
var client *well.HTTPClient

func foo(ctx context.Context) error {
    r, err := http.NewRequest("GET", "http://.../", nil)
    if err != nil {
        return err
    }

    // Set context.
    // You can also specify request timeout by:
    // ctx, cancel := context.WithCancel(ctx, timeout)
    r = r.WithContext(ctx)

    resp, err := client.Do(r)
    if err != nil {
        return err
    }
    defer resp.Body.Close()

    // use resp
}

Use FieldsWithContext function to prepare log fields if you want to record logs by yourself:

func foo(ctx context.Context) {
    fields := well.FieldsWithContext(ctx)
    fields["other_field"] = "other value"
    log.Info("log message", fields)
}
Clone this wiki locally