kon/vendor/github.com/gofiber/fiber/v2/middleware/logger/logger.go

183 lines
4.6 KiB
Go
Raw Permalink Normal View History

2024-07-02 16:36:04 +02:00
package logger
import (
"fmt"
"os"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/utils"
"github.com/mattn/go-colorable"
"github.com/mattn/go-isatty"
"github.com/valyala/bytebufferpool"
"github.com/valyala/fasthttp"
)
// New creates a new middleware handler
func New(config ...Config) fiber.Handler {
// Set default config
cfg := configDefault(config...)
// Get timezone location
tz, err := time.LoadLocation(cfg.TimeZone)
if err != nil || tz == nil {
cfg.timeZoneLocation = time.Local
} else {
cfg.timeZoneLocation = tz
}
// Check if format contains latency
cfg.enableLatency = strings.Contains(cfg.Format, "${"+TagLatency+"}")
var timestamp atomic.Value
// Create correct timeformat
timestamp.Store(time.Now().In(cfg.timeZoneLocation).Format(cfg.TimeFormat))
// Update date/time every 500 milliseconds in a separate go routine
if strings.Contains(cfg.Format, "${"+TagTime+"}") {
go func() {
for {
time.Sleep(cfg.TimeInterval)
timestamp.Store(time.Now().In(cfg.timeZoneLocation).Format(cfg.TimeFormat))
}
}()
}
// Set PID once
pid := strconv.Itoa(os.Getpid())
// Set variables
var (
once sync.Once
mu sync.Mutex
errHandler fiber.ErrorHandler
dataPool = sync.Pool{New: func() interface{} { return new(Data) }}
)
// If colors are enabled, check terminal compatibility
if cfg.enableColors {
cfg.Output = colorable.NewColorableStdout()
if os.Getenv("TERM") == "dumb" || os.Getenv("NO_COLOR") == "1" || (!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd())) {
cfg.Output = colorable.NewNonColorable(os.Stdout)
}
}
errPadding := 15
errPaddingStr := strconv.Itoa(errPadding)
// instead of analyzing the template inside(handler) each time, this is done once before
// and we create several slices of the same length with the functions to be executed and fixed parts.
templateChain, logFunChain, err := buildLogFuncChain(&cfg, createTagMap(&cfg))
if err != nil {
panic(err)
}
// Return new handler
return func(c *fiber.Ctx) error {
// Don't execute middleware if Next returns true
if cfg.Next != nil && cfg.Next(c) {
return c.Next()
}
// Set error handler once
once.Do(func() {
// get longested possible path
stack := c.App().Stack()
for m := range stack {
for r := range stack[m] {
if len(stack[m][r].Path) > errPadding {
errPadding = len(stack[m][r].Path)
errPaddingStr = strconv.Itoa(errPadding)
}
}
}
// override error handler
errHandler = c.App().ErrorHandler
})
// Logger data
data := dataPool.Get().(*Data) //nolint:forcetypeassert,errcheck // We store nothing else in the pool
// no need for a reset, as long as we always override everything
data.Pid = pid
data.ErrPaddingStr = errPaddingStr
data.Timestamp = timestamp
// put data back in the pool
defer dataPool.Put(data)
// Set latency start time
if cfg.enableLatency {
data.Start = time.Now()
}
// Handle request, store err for logging
chainErr := c.Next()
data.ChainErr = chainErr
// Manually call error handler
if chainErr != nil {
if err := errHandler(c, chainErr); err != nil {
_ = c.SendStatus(fiber.StatusInternalServerError) //nolint:errcheck // TODO: Explain why we ignore the error here
}
}
// Set latency stop time
if cfg.enableLatency {
data.Stop = time.Now()
}
// Get new buffer
buf := bytebufferpool.Get()
var err error
// Loop over template parts execute dynamic parts and add fixed parts to the buffer
for i, logFunc := range logFunChain {
if logFunc == nil {
_, _ = buf.Write(templateChain[i]) //nolint:errcheck // This will never fail
} else if templateChain[i] == nil {
_, err = logFunc(buf, c, data, "")
} else {
_, err = logFunc(buf, c, data, utils.UnsafeString(templateChain[i]))
}
if err != nil {
break
}
}
// Also write errors to the buffer
if err != nil {
_, _ = buf.WriteString(err.Error()) //nolint:errcheck // This will never fail
}
mu.Lock()
// Write buffer to output
if _, err := cfg.Output.Write(buf.Bytes()); err != nil {
// Write error to output
if _, err := cfg.Output.Write([]byte(err.Error())); err != nil {
// There is something wrong with the given io.Writer
_, _ = fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
}
}
mu.Unlock()
if cfg.Done != nil {
cfg.Done(c, buf.Bytes())
}
// Put buffer back to pool
bytebufferpool.Put(buf)
return nil
}
}
func appendInt(output Buffer, v int) (int, error) {
old := output.Len()
output.Set(fasthttp.AppendUint(output.Bytes(), v))
return output.Len() - old, nil
}