给接口打监控的时候,用 c.Request.URL.Path 当标签,每个用户 ID 都会变成一个新时间序列。c.FullPath() 返回的是路由模板 /users/:id,正好能避免这个问题。这篇验证它在哪些情况下有值、哪些情况下是空串。
context.go 的 FullPath(Gin v1.12.0):
177 | func (c *Context) FullPath() string {
178 | return c.fullPath
179 | }
context.go: 109 | c.fullPath = ""
gin.go: 721 | c.fullPath = value.fullPathFullPath() 本身只是返回字段。它的值在请求开头被清空,查到路由后由路由节点上存着的 fullPath(注册时的完整模板,包含 Group 前缀)赋值,因此在所有中间件和 handler 里都可以取到——前提是路由已经匹配。
GET /users/42 中间件里 Next 之前 FullPath="/users/:id" 之后 FullPath="/users/:id" 状态=200
GET /files/a/b.txt 中间件里 Next 之前 FullPath="/files/*path" 之后 FullPath="/files/*path" 状态=200
GET /api/v1/orders/7/items/9 中间件里 Next 之前 FullPath="/api/v1/orders/:oid/items/:iid" 之后 FullPath="/api/v1/orders/:oid/items/:iid" 状态=200
GET /nope 中间件里 Next 之前 FullPath="" 之后 FullPath="" 状态=404
GET /dir 重定向 301 → /dir/(没有经过中间件)
GET /users/ 中间件里 Next 之前 FullPath="" 之后 FullPath="" 状态=404
共 6 个请求,中间件执行了 5 次匹配到路由时,不管是参数路由、通配路由还是 Group 里的路由,返回的都是带 :oid、*path 的模板。404 时是空串。被重定向的请求(尾斜杠 301)在进入全局中间件之前就返回了,所以中间件根本拿不到它,监控里看不到这类请求。
按 URL.Path 打标签:16000 个不同的值
按 FullPath 打标签:3 个不同的值:map[(未匹配):1000 /users/:id:10000 /users/:id/orders/:oid:5000]标签的基数决定监控系统的开销:按 URL.Path 打标签,一万个用户就是一万个时间序列;按 FullPath 只有几条。404 时 FullPath 是空串,标签要换成一个固定值(比如“(未匹配)”);如果退回去用原始 URL.Path 兜底,扫描器乱扫的路径又会把基数撑爆。
GET /users/:id handler=main.getUser
GET /api/list handler=main.S.List-fm
POST /users handler=main.main.func1
PUT /api/x handler=github.com/gin-gonic/gin.CustomRecoveryWithWriter.func1r.Routes() 返回每条路由的方法、路径和 handler 名。名字来自链上最后一个函数,所以中间件不会出现在里面;如果一条路由的最后一个 handler 本身是中间件(比如上面的 gin.Recovery()),显示的就是中间件的闭包名。方法值带 -fm 后缀。