线上为什么一定要 GIN_MODE=release?debug 到底多做了什么?这篇用代码里的三处判断、同一个程序两种模式的真实输出、模式选择的优先级和每个请求的耗时,把这个开关的真实影响列清楚。
debug.go: 22 | func IsDebugging() bool {
debug.go: 33 | if IsDebugging() {
debug.go: 45 | if IsDebugging() {
debug.go: 57 | if !IsDebugging() {
debug.go: 111 | if err != nil && IsDebugging() {
gin.go: 276 |
gin.go: 277 | if IsDebugging() {
gin.go: 278 | debugPrintLoadTemplate(templ)
gin.go: 288 | func (engine *Engine) LoadHTMLFiles(files ...string) {
gin.go: 289 | if IsDebugging() {
gin.go: 290 | engine.HTMLRender = render.HTMLDebug{Files: files, FuncMap: engine.FuncMap, Delims: engine.delims}
gin.go: 300 | func (engine *Engine) LoadHTMLFS(fs http.FileSystem, patterns ...string) {
gin.go: 301 | if IsDebugging() {
gin.go: 302 | engine.HTMLRender = render.HTMLDebug{FileSystem: fs, Patterns: patterns, FuncMap: engine.FuncMap, Delims: engine.delims}
recovery.go: 73 | } else if IsDebugging() {
mode.go 的 SetMode(Gin v1.12.0):
58 | func SetMode(value string) {
59 | if value == "" {
60 | if flag.Lookup("test.v") != nil {
61 | value = TestMode
62 | } else {
63 | value = DebugMode
64 | }
65 | }
66 |
67 | switch value {
68 | case DebugMode:
69 | atomic.StoreInt32(&ginMode, debugCode)
70 | case ReleaseMode:
71 | atomic.StoreInt32(&ginMode, releaseCode)
72 | case TestMode:
73 | atomic.StoreInt32(&ginMode, testCode)
74 | default:
75 | panic("gin mode unknown: " + value + " (available mode: debug release test)")
76 | }
77 | modeName.Store(value)
78 | }
79 | Gin 里对 IsDebugging() 的判断只有三类:所有 [GIN-debug] 输出;LoadHTMLGlob 等在 debug 下用每次重新解析的 HTMLDebug;Recovery 在日志里附上请求转储。路由匹配、中间件、绑定等核心路径对两种模式一视同仁。
[GIN_MODE=debug] 共 16 行输出(其中访问日志 1 行)
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /ping --> main.main.func1 (3 handlers)
[GIN-debug] POST /users --> main.main.func2 (3 handlers)
[GIN-debug] GET /api/items/:id --> main.main.func4 (4 handlers)
[GIN-debug] GET /assets/*filepath --> github.com/gin-gonic/gin.(*RouterGroup).createStaticHandler.func1 (3 handlers)
[GIN-debug] HEAD /assets/*filepath --> github.com/gin-gonic/gin.(*RouterGroup).createStaticHandler.func1 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on 127.0.0.1:9731
[GIN_MODE=release] 共 2 行输出(其中访问日志 1 行)启动时 debug 会打印默认中间件提示、模式提示、每条路由(方法、路径、handler 名、handler 个数),r.Run 还会打印“信任所有代理不安全”的警告和监听地址。这些输出本身没有害处,但会把配置细节(路由表、handler 名)写进标准输出,也会让测试日志很吵。
GIN_MODE="release" → 启动时 gin.Mode()="release" IsDebugging=false
SetMode(release) 之后 → "release" IsDebugging=false
SetMode(test) 之后 → "test" IsDebugging=false
SetMode("")(空串)→ "debug"
SetMode("prod") → panic: gin mode unknown: prod (available mode: debug release test)
GIN_MODE 未设置 → gin.Mode()="debug"
x_test.go:3: go test 里未设置 GIN_MODE → gin.Mode()="debug"环境变量 GIN_MODE 在包初始化时读取,代码里的 SetMode 随后覆盖它。没有设置时是 debug——包括 go test 里:实测不设置时 gin.Mode() 也是 debug,而不是源码里似乎设想的 test(init 时测试标志还没注册)。想让测试安静就显式 gin.SetMode(gin.TestMode)。非法的模式名会 panic。
debug 模式:DefaultWriter 里收到:
"[GIN-debug] [WARNING] Running in \"debug\" mode. Switch to \"release\" mode in production."
" - using env:\texport GIN_MODE=release"
" - using code:\tgin.SetMode(gin.ReleaseMode)"
"ROUTE GET /dir/ -> main.main.func2 (1)"
"[GIN-debug] redirecting request 301: /dir/ --> /dir/"
release 模式:DefaultWriter 里收到 0 字节debug 模式每次尾斜杠重定向都会打印一行,但这行里“前后路径”相同,因为打印时请求的路径已经被改写成目标。release 下整行都没有。
第 1 轮
debug GET /ok 中位 25 ns/请求
debug GET /dir 中位 501 ns/请求
release GET /ok 中位 25 ns/请求
release GET /dir 中位 362 ns/请求
第 2 轮
debug GET /ok 中位 25 ns/请求
debug GET /dir 中位 500 ns/请求
release GET /ok 中位 25 ns/请求
release GET /dir 中位 363 ns/请求热路径(命中路由)在两种模式下耗时相同。debug 额外付出的是模板的重复解析(见模板一篇,一次渲染相差约 70 倍)、Recovery 的请求转储,以及每次重定向的 debugPrint。所以 release 的意义主要是安静、安全(不泄漏路由表与请求头)和模板性能,而不是路由快了多少。