Go log thread id in Gorilla Handler

2019-07-13 16:12发布

How do we get the thread id or any other unique Id of the http request being handled by the handler in logging inside Gorilla Handlers?
In Java, when Tomcat or other container handles multiple http requests, thread id helps to track all the log messages for respective http request handling.
What is the equivalent in Go? Given a Rest API developed using Gorilla library, how do I track all log statements of specific http request inside a handler processing?

2条回答
在下西门庆
2楼-- · 2019-07-13 16:46

The gorilla/handlers library doesn't provide a way to do this by default: the logging functions there log in Apache formats, which don't provide for this.

Also keep in mind that a "thread ID" doesn't make sense here - you want a request ID that is associated with a *http.Request.

You could write your own RequestID middleware that creates an ID and stores in the request context for other middleware/handlers to retrieve as-needed:

package main

import (
    "crypto/rand"
    "encoding/base64"
    "net/http"

    "github.com/gorilla/context"
)

const ReqID string = "gorilla.RequestID"

// RequestID wraps handlers and makes a unique (32-byte) request ID available in
// the request context.
// Example:
//      http.Handle("/", RequestID(LoggingHandler(YourHandler)))
//
//      func LoggingHandler(h http.Handler) http.Handler {
//          fn := func(w http.ResponseWriter, r *http.Request) {
//              h.ServeHTTP(w, r)
//
//              id := GetRequestID(r)
//              log.Printf("%s | %s", id, r.RemoteAddr)
//          }
//
//          return http.HandlerFunc(fn)
//      }
func RequestID(h http.Handler) http.Handler {
    fn := func(w http.ResponseWriter, r *http.Request) {
        b := make([]byte, 8)
        _, err = rand.Read(&b)
        if err != nil {
            http.Error(w, http.StatusText(500), 500)
            return
        }

        base64ID := base64.URLEncoding.EncodeToString(b)

        context.Set(r, ReqID, base64ID)

        h.ServeHTTP(w, r)
        // Clear the context at the end of the request lifetime
        context.Clear(r)
    }

    return http.HandlerFunc(fn)
}

func GetRequestID(r *http.Request) string {
    if v, ok := context.GetOK(r, ReqID); ok {
        if id, ok := v.(string); ok {
            return id
        }
    }

    return ""
}

Keep in mind that the code above is not tested. Written off the top of my head in the Playground, so please let me know if there's a bug.

Improvements you could consider beyond this basic example:

  • Prefix the ID with the hostname - helpful if you are aggregating logs from multiple processes/machines)
  • Provide a timestamp or incrementing integer as part of the final ID to help trace requests over time
  • Benchmark it.

Note that under extremely high loads (e.g. tens of thousands of req/s - tens of millions of hits per day), this may not be performant, but is unlikely to be a bottleneck for > 99% of users.

PS: I may look at providing a handlers.RequestID implementation in the gorilla/handlers library at some point - if you'd like to see it, raise an issue on the repo and I'll see if I can find time to implement a more complete take on the above.

查看更多
淡お忘
3楼-- · 2019-07-13 17:07

Based on https://groups.google.com/forum/#!searchin/golang-nuts/Logging$20http$20thread/golang-nuts/vDNEH3_vMXQ/uyqGEwdchzgJ, ThreadLocal concept is not possible with Go.

Every where you need logging, it requires to pass in http Request instance so that the context associated with the request can be retrieved and one can fetch the unique ID from this context for the request. But its not practical to pass Request instance to all the layers/methods.

查看更多
登录 后发表回答