Skip to content

Commit 5155ec3

Browse files
authored
Parse external request id from request headers, and print it in access log (#22906)
Close: #22890. --- ### Configure in .ini file: ```ini [log] REQUEST_ID_HEADERS = X-Request-ID, X-Trace-Id ``` ### Params in Request Header ``` X-Trace-ID: trace-id-1q2w3e4r ``` ![image](https://user-images.githubusercontent.com/33891828/218665296-8fd19a0f-ada6-4236-8bdb-f99201c703e8.png) ### Log output: ![image](https://user-images.githubusercontent.com/33891828/218665225-cc242a57-4ffc-449a-a1f6-f45ded0ead60.png)
1 parent cf29ee6 commit 5155ec3

File tree

5 files changed

+74
-1
lines changed

5 files changed

+74
-1
lines changed

custom/conf/app.example.ini

+16
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,22 @@ ROUTER = console
576576
;; The routing level will default to that of the system but individual router level can be set in
577577
;; [log.<mode>.router] LEVEL
578578
;;
579+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
580+
;;
581+
;; Print request id which parsed from request headers in access log, when access log is enabled.
582+
;; * E.g:
583+
;; * In request Header: X-Request-ID: test-id-123
584+
;; * Configuration in app.ini: REQUEST_ID_HEADERS = X-Request-ID
585+
;; * Print in log: 127.0.0.1:58384 - - [14/Feb/2023:16:33:51 +0800] "test-id-123"
586+
;;
587+
;; If you configure more than one in the .ini file, it will match in the order of configuration,
588+
;; and the first match will be finally printed in the log.
589+
;; * E.g:
590+
;; * In reuqest Header: X-Trace-ID: trace-id-1q2w3e4r
591+
;; * Configuration in app.ini: REQUEST_ID_HEADERS = X-Request-ID, X-Trace-ID, X-Req-ID
592+
;; * Print in log: 127.0.0.1:58384 - - [14/Feb/2023:16:33:51 +0800] "trace-id-1q2w3e4r"
593+
;;
594+
;; REQUEST_ID_HEADERS =
579595

580596
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
581597
;;

docs/content/doc/advanced/config-cheat-sheet.en-us.md

+6
Original file line numberDiff line numberDiff line change
@@ -881,7 +881,13 @@ Default templates for project boards:
881881
- `Identity`: the SignedUserName or `"-"` if not logged in.
882882
- `Start`: the start time of the request.
883883
- `ResponseWriter`: the responseWriter from the request.
884+
- `RequestID`: the value matching REQUEST_ID_HEADERS(default: `-`, if not matched).
884885
- You must be very careful to ensure that this template does not throw errors or panics as this template runs outside of the panic/recovery script.
886+
- `REQUEST_ID_HEADERS`: **\<empty\>**: You can configure multiple values that are splited by comma here. It will match in the order of configuration, and the first match will be finally printed in the access log.
887+
- e.g.
888+
- In the Request Header: X-Request-ID: **test-id-123**
889+
- Configuration in app.ini: REQUEST_ID_HEADERS = X-Request-ID
890+
- Print in log: 127.0.0.1:58384 - - [14/Feb/2023:16:33:51 +0800] "**test-id-123**" ...
885891

886892
### Log subsections (`log.name`, `log.name.*`)
887893

docs/content/doc/advanced/config-cheat-sheet.zh-cn.md

+16-1
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,22 @@ test01.xls: application/vnd.ms-excel; charset=binary
262262

263263
- `ROOT_PATH`: 日志文件根目录。
264264
- `MODE`: 日志记录模式,默认是为 `console`。如果要写到多个通道,用逗号分隔
265-
- `LEVEL`: 日志级别,默认为`Trace`
265+
- `LEVEL`: 日志级别,默认为 `Trace`
266+
- `DISABLE_ROUTER_LOG`: 关闭日志中的路由日志。
267+
- `ENABLE_ACCESS_LOG`: 是否开启 Access Log, 默认为 false。
268+
- `ACCESS_LOG_TEMPLATE`: `access.log` 输出内容的模板,默认模板:**`{{.Ctx.RemoteAddr}} - {{.Identity}} {{.Start.Format "[02/Jan/2006:15:04:05 -0700]" }} "{{.Ctx.Req.Method}} {{.Ctx.Req.URL.RequestURI}} {{.Ctx.Req.Proto}}" {{.ResponseWriter.Status}} {{.ResponseWriter.Size}} "{{.Ctx.Req.Referer}}\" \"{{.Ctx.Req.UserAgent}}"`**
269+
模板支持以下参数:
270+
- `Ctx`: 请求上下文。
271+
- `Identity`: 登录用户名,默认: “`-`”。
272+
- `Start`: 请求开始时间。
273+
- `ResponseWriter`:
274+
- `RequestID`: 从请求头中解析得到的与 `REQUEST_ID_HEADERS` 匹配的值,默认: “`-`”。
275+
- 一定要谨慎配置该模板,否则可能会引起panic.
276+
- `REQUEST_ID_HEADERS`: 从 Request Header 中匹配指定 Key,并将匹配到的值输出到 `access.log` 中(需要在 `ACCESS_LOG_TEMPLATE` 中指定输出位置)。如果在该参数中配置多个 Key, 请用逗号分割,程序将按照配置的顺序进行匹配。
277+
- 示例:
278+
- 请求头: X-Request-ID: **test-id-123**
279+
- 配置文件: REQUEST_ID_HEADERS = X-Request-ID
280+
- 日志输出: 127.0.0.1:58384 - - [14/Feb/2023:16:33:51 +0800] "**test-id-123**" ...
266281

267282
## Cron (`cron`)
268283

modules/context/access_log.go

+34
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ package context
66
import (
77
"bytes"
88
"context"
9+
"fmt"
910
"net/http"
11+
"strings"
1012
"text/template"
1113
"time"
1214

@@ -20,20 +22,51 @@ type routerLoggerOptions struct {
2022
Start *time.Time
2123
ResponseWriter http.ResponseWriter
2224
Ctx map[string]interface{}
25+
RequestID *string
2326
}
2427

2528
var signedUserNameStringPointerKey interface{} = "signedUserNameStringPointerKey"
2629

30+
const keyOfRequestIDInTemplate = ".RequestID"
31+
32+
// According to:
33+
// TraceId: A valid trace identifier is a 16-byte array with at least one non-zero byte
34+
// MD5 output is 16 or 32 bytes: md5-bytes is 16, md5-hex is 32
35+
// SHA1: similar, SHA1-bytes is 20, SHA1-hex is 40.
36+
// UUID is 128-bit, 32 hex chars, 36 ASCII chars with 4 dashes
37+
// So, we accept a Request ID with a maximum character length of 40
38+
const maxRequestIDByteLength = 40
39+
40+
func parseRequestIDFromRequestHeader(req *http.Request) string {
41+
requestID := "-"
42+
for _, key := range setting.Log.RequestIDHeaders {
43+
if req.Header.Get(key) != "" {
44+
requestID = req.Header.Get(key)
45+
break
46+
}
47+
}
48+
if len(requestID) > maxRequestIDByteLength {
49+
requestID = fmt.Sprintf("%s...", requestID[:maxRequestIDByteLength])
50+
}
51+
return requestID
52+
}
53+
2754
// AccessLogger returns a middleware to log access logger
2855
func AccessLogger() func(http.Handler) http.Handler {
2956
logger := log.GetLogger("access")
57+
needRequestID := len(setting.Log.RequestIDHeaders) > 0 && strings.Contains(setting.Log.AccessLogTemplate, keyOfRequestIDInTemplate)
3058
logTemplate, _ := template.New("log").Parse(setting.Log.AccessLogTemplate)
3159
return func(next http.Handler) http.Handler {
3260
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
3361
start := time.Now()
3462
identity := "-"
3563
r := req.WithContext(context.WithValue(req.Context(), signedUserNameStringPointerKey, &identity))
3664

65+
var requestID string
66+
if needRequestID {
67+
requestID = parseRequestIDFromRequestHeader(req)
68+
}
69+
3770
next.ServeHTTP(w, r)
3871
rw := w.(ResponseWriter)
3972

@@ -47,6 +80,7 @@ func AccessLogger() func(http.Handler) http.Handler {
4780
"RemoteAddr": req.RemoteAddr,
4881
"Req": req,
4982
},
83+
RequestID: &requestID,
5084
})
5185
if err != nil {
5286
log.Error("Could not set up chi access logger: %v", err.Error())

modules/setting/log.go

+2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ var Log struct {
3838
EnableAccessLog bool
3939
AccessLogTemplate string
4040
BufferLength int64
41+
RequestIDHeaders []string
4142
}
4243

4344
// GetLogDescriptions returns a race safe set of descriptions
@@ -153,6 +154,7 @@ func loadLogFrom(rootCfg ConfigProvider) {
153154
Log.AccessLogTemplate = sec.Key("ACCESS_LOG_TEMPLATE").MustString(
154155
`{{.Ctx.RemoteAddr}} - {{.Identity}} {{.Start.Format "[02/Jan/2006:15:04:05 -0700]" }} "{{.Ctx.Req.Method}} {{.Ctx.Req.URL.RequestURI}} {{.Ctx.Req.Proto}}" {{.ResponseWriter.Status}} {{.ResponseWriter.Size}} "{{.Ctx.Req.Referer}}\" \"{{.Ctx.Req.UserAgent}}"`,
155156
)
157+
Log.RequestIDHeaders = sec.Key("REQUEST_ID_HEADERS").Strings(",")
156158
// the `MustString` updates the default value, and `log.ACCESS` is used by `generateNamedLogger("access")` later
157159
_ = rootCfg.Section("log").Key("ACCESS").MustString("file")
158160

0 commit comments

Comments
 (0)