github.com/gofiber/fiber/v2@v2.47.0/middleware/logger/logger.go (about) 1 package logger 2 3 import ( 4 "fmt" 5 "os" 6 "strconv" 7 "strings" 8 "sync" 9 "sync/atomic" 10 "time" 11 12 "github.com/gofiber/fiber/v2" 13 "github.com/gofiber/fiber/v2/utils" 14 15 "github.com/mattn/go-colorable" 16 "github.com/mattn/go-isatty" 17 "github.com/valyala/bytebufferpool" 18 "github.com/valyala/fasthttp" 19 ) 20 21 // New creates a new middleware handler 22 func New(config ...Config) fiber.Handler { 23 // Set default config 24 cfg := configDefault(config...) 25 26 // Get timezone location 27 tz, err := time.LoadLocation(cfg.TimeZone) 28 if err != nil || tz == nil { 29 cfg.timeZoneLocation = time.Local 30 } else { 31 cfg.timeZoneLocation = tz 32 } 33 34 // Check if format contains latency 35 cfg.enableLatency = strings.Contains(cfg.Format, "${"+TagLatency+"}") 36 37 var timestamp atomic.Value 38 // Create correct timeformat 39 timestamp.Store(time.Now().In(cfg.timeZoneLocation).Format(cfg.TimeFormat)) 40 41 // Update date/time every 500 milliseconds in a separate go routine 42 if strings.Contains(cfg.Format, "${"+TagTime+"}") { 43 go func() { 44 for { 45 time.Sleep(cfg.TimeInterval) 46 timestamp.Store(time.Now().In(cfg.timeZoneLocation).Format(cfg.TimeFormat)) 47 } 48 }() 49 } 50 51 // Set PID once 52 pid := strconv.Itoa(os.Getpid()) 53 54 // Set variables 55 var ( 56 once sync.Once 57 mu sync.Mutex 58 errHandler fiber.ErrorHandler 59 60 dataPool = sync.Pool{New: func() interface{} { return new(Data) }} 61 ) 62 63 // If colors are enabled, check terminal compatibility 64 if cfg.enableColors { 65 cfg.Output = colorable.NewColorableStdout() 66 if os.Getenv("TERM") == "dumb" || os.Getenv("NO_COLOR") == "1" || (!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd())) { 67 cfg.Output = colorable.NewNonColorable(os.Stdout) 68 } 69 } 70 71 errPadding := 15 72 errPaddingStr := strconv.Itoa(errPadding) 73 74 // instead of analyzing the template inside(handler) each time, this is done once before 75 // and we create several slices of the same length with the functions to be executed and fixed parts. 76 templateChain, logFunChain, err := buildLogFuncChain(&cfg, createTagMap(&cfg)) 77 if err != nil { 78 panic(err) 79 } 80 81 // Return new handler 82 return func(c *fiber.Ctx) error { 83 // Don't execute middleware if Next returns true 84 if cfg.Next != nil && cfg.Next(c) { 85 return c.Next() 86 } 87 88 // Alias colors 89 colors := c.App().Config().ColorScheme 90 91 // Set error handler once 92 once.Do(func() { 93 // get longested possible path 94 stack := c.App().Stack() 95 for m := range stack { 96 for r := range stack[m] { 97 if len(stack[m][r].Path) > errPadding { 98 errPadding = len(stack[m][r].Path) 99 errPaddingStr = strconv.Itoa(errPadding) 100 } 101 } 102 } 103 // override error handler 104 errHandler = c.App().ErrorHandler 105 }) 106 107 // Logger data 108 data := dataPool.Get().(*Data) //nolint:forcetypeassert,errcheck // We store nothing else in the pool 109 // no need for a reset, as long as we always override everything 110 data.Pid = pid 111 data.ErrPaddingStr = errPaddingStr 112 data.Timestamp = timestamp 113 // put data back in the pool 114 defer dataPool.Put(data) 115 116 // Set latency start time 117 if cfg.enableLatency { 118 data.Start = time.Now() 119 } 120 121 // Handle request, store err for logging 122 chainErr := c.Next() 123 124 data.ChainErr = chainErr 125 // Manually call error handler 126 if chainErr != nil { 127 if err := errHandler(c, chainErr); err != nil { 128 _ = c.SendStatus(fiber.StatusInternalServerError) //nolint:errcheck // TODO: Explain why we ignore the error here 129 } 130 } 131 132 // Set latency stop time 133 if cfg.enableLatency { 134 data.Stop = time.Now() 135 } 136 137 // Get new buffer 138 buf := bytebufferpool.Get() 139 140 // Default output when no custom Format or io.Writer is given 141 if cfg.Format == ConfigDefault.Format { 142 // Format error if exist 143 formatErr := "" 144 if cfg.enableColors { 145 if chainErr != nil { 146 formatErr = colors.Red + " | " + chainErr.Error() + colors.Reset 147 } 148 _, _ = buf.WriteString( //nolint:errcheck // This will never fail 149 fmt.Sprintf("%s |%s %3d %s| %7v | %15s |%s %-7s %s| %-"+errPaddingStr+"s %s\n", 150 timestamp.Load().(string), 151 statusColor(c.Response().StatusCode(), colors), c.Response().StatusCode(), colors.Reset, 152 data.Stop.Sub(data.Start).Round(time.Millisecond), 153 c.IP(), 154 methodColor(c.Method(), colors), c.Method(), colors.Reset, 155 c.Path(), 156 formatErr, 157 ), 158 ) 159 } else { 160 if chainErr != nil { 161 formatErr = " | " + chainErr.Error() 162 } 163 _, _ = buf.WriteString( //nolint:errcheck // This will never fail 164 fmt.Sprintf("%s | %3d | %7v | %15s | %-7s | %-"+errPaddingStr+"s %s\n", 165 timestamp.Load().(string), 166 c.Response().StatusCode(), 167 data.Stop.Sub(data.Start).Round(time.Millisecond), 168 c.IP(), 169 c.Method(), 170 c.Path(), 171 formatErr, 172 ), 173 ) 174 } 175 176 // Write buffer to output 177 _, _ = cfg.Output.Write(buf.Bytes()) //nolint:errcheck // This will never fail 178 179 if cfg.Done != nil { 180 cfg.Done(c, buf.Bytes()) 181 } 182 183 // Put buffer back to pool 184 bytebufferpool.Put(buf) 185 186 // End chain 187 return nil 188 } 189 190 var err error 191 // Loop over template parts execute dynamic parts and add fixed parts to the buffer 192 for i, logFunc := range logFunChain { 193 if logFunc == nil { 194 _, _ = buf.Write(templateChain[i]) //nolint:errcheck // This will never fail 195 } else if templateChain[i] == nil { 196 _, err = logFunc(buf, c, data, "") 197 } else { 198 _, err = logFunc(buf, c, data, utils.UnsafeString(templateChain[i])) 199 } 200 if err != nil { 201 break 202 } 203 } 204 205 // Also write errors to the buffer 206 if err != nil { 207 _, _ = buf.WriteString(err.Error()) //nolint:errcheck // This will never fail 208 } 209 mu.Lock() 210 // Write buffer to output 211 if _, err := cfg.Output.Write(buf.Bytes()); err != nil { 212 // Write error to output 213 if _, err := cfg.Output.Write([]byte(err.Error())); err != nil { 214 // There is something wrong with the given io.Writer 215 _, _ = fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err) 216 } 217 } 218 mu.Unlock() 219 220 if cfg.Done != nil { 221 cfg.Done(c, buf.Bytes()) 222 } 223 224 // Put buffer back to pool 225 bytebufferpool.Put(buf) 226 227 return nil 228 } 229 } 230 231 func appendInt(output Buffer, v int) (int, error) { 232 old := output.Len() 233 output.Set(fasthttp.AppendUint(output.Bytes(), v)) 234 return output.Len() - old, nil 235 }