github.com/aldelo/common@v1.5.1/wrapper/gin/ginzap.go (about) 1 package gin 2 3 /* 4 * Copyright 2020-2023 Aldelo, LP 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 import ( 20 "fmt" 21 util "github.com/aldelo/common" 22 "go.uber.org/zap" 23 "net" 24 "net/http" 25 "net/http/httputil" 26 "os" 27 "runtime/debug" 28 "strings" 29 "time" 30 31 "github.com/aldelo/common/wrapper/zap" 32 "github.com/gin-gonic/gin" 33 ) 34 35 // NewGinZapMiddleware returns a newly created GinZap struct object 36 func NewGinZapMiddleware(logName string, outputToConsole bool) *GinZap { 37 return &GinZap{ 38 LogName: logName, 39 OutputToConsole: outputToConsole, 40 } 41 } 42 43 // GinZap struct defines logger middleware for use with Gin, using Zap logging package, 44 // CREDIT:this code is based and modified from github.com/gin-contrib/zap 45 // 46 // LogName = (required) specifies the log name being written to 47 // OutputToConsole = (required) specifies if logger writes to console or disk 48 // TimeFormat = (optional) default = time.RFC3339 49 // TimeUtc = (optional) default = false 50 // PanicStack = (optional) when panic, log to include stack 51 type GinZap struct { 52 LogName string 53 OutputToConsole bool 54 55 TimeFormat string // default = time.RFC3339 56 TimeUtc bool // default = false 57 PanicStack bool // default = false 58 59 _zapLog *data.ZapLog 60 } 61 62 // Init will initialize zap logger and prep ginzap struct object for middleware use 63 func (z *GinZap) Init() error { 64 if util.LenTrim(z.LogName) == 0 { 65 return fmt.Errorf("Log Name is Required") 66 } 67 68 z._zapLog = &data.ZapLog{ 69 DisableLogger: false, 70 OutputToConsole: z.OutputToConsole, 71 AppName: z.LogName, 72 } 73 74 if err := z._zapLog.Init(); err != nil { 75 z._zapLog = nil 76 return err 77 } else { 78 if util.LenTrim(z.TimeFormat) == 0 { 79 z.TimeFormat = time.RFC3339 80 } 81 return nil 82 } 83 } 84 85 // NormalLogger returns a gin.HandlerFunc (middleware) that logs requests using uber-go/zap. 86 // 87 // Requests with errors are logged using zap.Error(). 88 // Requests without errors are logged using zap.Info(). 89 func (z *GinZap) NormalLogger() gin.HandlerFunc { 90 return func(c *gin.Context) { 91 if z._zapLog == nil { 92 c.Next() 93 return 94 } 95 96 start := time.Now() 97 98 path := c.Request.URL.Path 99 query := c.Request.URL.RawQuery 100 101 path = strings.ReplaceAll(path, "\n", "") 102 path = strings.ReplaceAll(path, "\r", "") 103 104 query = strings.ReplaceAll(query, "\n", "") 105 query = strings.ReplaceAll(query, "\r", "") 106 107 c.Next() 108 109 end := time.Now() 110 latency := end.Sub(start) 111 112 if z.TimeUtc { 113 end = end.UTC() 114 } 115 116 hdr := "" 117 118 if c.Request.Header != nil { 119 for k, v := range c.Request.Header { 120 if len(v) >= 1 { 121 hdr += fmt.Sprintf("%s=%s, ", k, strings.ReplaceAll(strings.ReplaceAll(v[0], "\n", ""), "\r", "")) 122 } 123 } 124 } 125 126 if len(c.Errors) > 0 { 127 for _, e := range c.Errors.Errors() { 128 z._zapLog.Error(e, 129 zap.String("method", strings.ReplaceAll(strings.ReplaceAll(c.Request.Method, "\n", ""), "\r", "")), 130 zap.String("path", path), 131 zap.String("header", hdr), 132 zap.String("query", query), 133 zap.String("ip", strings.ReplaceAll(strings.ReplaceAll(c.ClientIP(), "\n", ""), "\r", "")), 134 zap.String("user-agent", strings.ReplaceAll(strings.ReplaceAll(c.Request.UserAgent(), "\n", ""), "\r", "")), 135 zap.String("time", end.Format(z.TimeFormat)), 136 zap.Duration("latency", latency)) 137 } 138 } else { 139 z._zapLog.Info(path, 140 zap.Int("status", c.Writer.Status()), 141 zap.String("method", strings.ReplaceAll(strings.ReplaceAll(c.Request.Method, "\n", ""), "\r", "")), 142 zap.String("path", path), 143 zap.String("header", hdr), 144 zap.String("query", query), 145 zap.String("ip", strings.ReplaceAll(strings.ReplaceAll(c.ClientIP(), "\n", ""), "\r", "")), 146 zap.String("user-agent", strings.ReplaceAll(strings.ReplaceAll(c.Request.UserAgent(), "\n", ""), "\r", "")), 147 zap.String("time", end.Format(z.TimeFormat)), 148 zap.Duration("latency", latency)) 149 } 150 } 151 } 152 153 // PanicLogger returns a gin.HandlerFunc (middleware) 154 // 155 // this logger recovers from any panics and logs requests using uber-go/zap 156 // 157 // All errors are logged using zap.Error() 158 func (z *GinZap) PanicLogger() gin.HandlerFunc { 159 return func(c *gin.Context) { 160 defer func() { 161 if z._zapLog == nil { 162 return 163 } 164 165 if err := recover(); err != nil { 166 // Check for a broken connection, as it is not really a 167 // condition that warrants a panic stack trace. 168 var brokenPipe bool 169 if ne, ok := err.(*net.OpError); ok { 170 if se, ok := ne.Err.(*os.SyscallError); ok { 171 if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") { 172 brokenPipe = true 173 } 174 } 175 } 176 177 httpRequest, _ := httputil.DumpRequest(c.Request, false) 178 if brokenPipe { 179 z._zapLog.Error(c.Request.URL.Path, 180 zap.Any("error", err), 181 zap.String("request", string(httpRequest)), 182 ) 183 184 // If the connection is dead, we can't write a status to it. 185 _ = c.Error(err.(error)) // nolint: errcheck 186 c.Abort() 187 return 188 } 189 190 t := time.Now() 191 192 if z.TimeUtc { 193 t = t.UTC() 194 } 195 196 if z.PanicStack { 197 z._zapLog.Error("[Recovery From Panic]", 198 zap.Time("time", t), 199 zap.Any("error", err), 200 zap.String("request", string(httpRequest)), 201 zap.String("stack", string(debug.Stack())), 202 ) 203 } else { 204 z._zapLog.Error("[Recovery From Panic]", 205 zap.Time("time", t), 206 zap.Any("error", err), 207 zap.String("request", string(httpRequest)), 208 ) 209 } 210 211 c.AbortWithStatus(http.StatusInternalServerError) 212 } 213 }() 214 215 c.Next() 216 } 217 }