在Gorilla Handler中记录线程ID

huangapple go评论79阅读模式
英文:

Go log thread id in Gorilla Handler

问题

我们如何在Gorilla Handlers中的日志记录中获取正在处理的HTTP请求的线程ID或任何其他唯一ID?
在Java中,当Tomcat或其他容器处理多个HTTP请求时,线程ID有助于跟踪各个HTTP请求处理的所有日志消息。
Go中,等效的方式是什么?假设使用Gorilla库开发了一个REST API,如何跟踪处理程序内特定HTTP请求的所有日志语句?

英文:

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?
<br>
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.
<br>
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?

答案1

得分: 2

gorilla/handlers库默认情况下不提供这样的功能:其中的日志函数记录的是Apache格式的日志,不支持此功能。

另外要注意的是,在这里“线程ID”没有意义 - 你需要一个与*http.Request相关联的请求ID。

你可以编写自己的RequestID中间件,创建一个ID并将其存储在请求上下文中,以便其他中间件/处理程序在需要时检索:

package main

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

	"github.com/gorilla/context"
)

const ReqID string = "gorilla.RequestID"

// RequestID包装处理程序,并在请求上下文中提供一个唯一(32字节)的请求ID。
// 示例:
//      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)
		// 在请求生命周期结束时清除上下文
		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 ""
}

请注意,上述代码未经过测试。我在Playground上凭空编写的,请告诉我是否有错误。

除了基本示例之外,你还可以考虑以下改进:

  • 使用主机名作为ID的前缀 - 如果你正在从多个进程/机器聚合日志,这将很有帮助。
  • 提供时间戳或递增整数作为最终ID的一部分,以帮助跟踪请求的时间。
  • 进行基准测试。

请注意,在极高的负载下(例如每秒数万个请求 - 每天数千万次点击),这可能不够高效,但对于99%以上的用户来说,不太可能成为瓶颈。

PS:我可能会考虑在gorilla/handlers库中提供一个handlers.RequestID实现 - 如果你想看到它,请在存储库上提出问题,我会尽量找时间实现对上述内容的更完整的解决方案。

英文:

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 (
	&quot;crypto/rand&quot;
	&quot;encoding/base64&quot;
	&quot;net/http&quot;

	&quot;github.com/gorilla/context&quot;
)

const ReqID string = &quot;gorilla.RequestID&quot;

// RequestID wraps handlers and makes a unique (32-byte) request ID available in
// the request context.
// Example:
//      http.Handle(&quot;/&quot;, 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(&quot;%s | %s&quot;, 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(&amp;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 &quot;&quot;
}

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.

答案2

得分: 1

根据https://groups.google.com/forum/#!searchin/golang-nuts/Logging$20http$20thread/golang-nuts/vDNEH3_vMXQ/uyqGEwdchzgJ的内容,Go语言中不支持ThreadLocal的概念。在需要记录日志的地方,需要传递http的Request实例,以便可以检索与请求相关的上下文,并从该上下文中获取请求的唯一ID。但是,将Request实例传递给所有的层/方法是不切实际的。

英文:

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. <br><br>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.

huangapple
  • 本文由 发表于 2015年11月27日 05:55:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/33947545.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定