-
Notifications
You must be signed in to change notification settings - Fork 14
Activity tracking
This page describes how to track important activities between servers and between function/method calls.
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.
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.
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.
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)
}