Adding upstream version 2.52.6.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
a960158181
commit
6d002e9543
441 changed files with 95392 additions and 0 deletions
136
middleware/logger/config.go
Normal file
136
middleware/logger/config.go
Normal file
|
@ -0,0 +1,136 @@
|
|||
package logger
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
// Config defines the config for middleware.
|
||||
type Config struct {
|
||||
// Next defines a function to skip this middleware when returned true.
|
||||
//
|
||||
// Optional. Default: nil
|
||||
Next func(c *fiber.Ctx) bool
|
||||
|
||||
// Done is a function that is called after the log string for a request is written to Output,
|
||||
// and pass the log string as parameter.
|
||||
//
|
||||
// Optional. Default: nil
|
||||
Done func(c *fiber.Ctx, logString []byte)
|
||||
|
||||
// tagFunctions defines the custom tag action
|
||||
//
|
||||
// Optional. Default: map[string]LogFunc
|
||||
CustomTags map[string]LogFunc
|
||||
|
||||
// Format defines the logging tags
|
||||
//
|
||||
// Optional. Default: ${time} | ${status} | ${latency} | ${ip} | ${method} | ${path} | ${error}\n
|
||||
Format string
|
||||
|
||||
// TimeFormat https://programming.guide/go/format-parse-string-time-date-example.html
|
||||
//
|
||||
// Optional. Default: 15:04:05
|
||||
TimeFormat string
|
||||
|
||||
// TimeZone can be specified, such as "UTC" and "America/New_York" and "Asia/Chongqing", etc
|
||||
//
|
||||
// Optional. Default: "Local"
|
||||
TimeZone string
|
||||
|
||||
// TimeInterval is the delay before the timestamp is updated
|
||||
//
|
||||
// Optional. Default: 500 * time.Millisecond
|
||||
TimeInterval time.Duration
|
||||
|
||||
// Output is a writer where logs are written
|
||||
//
|
||||
// Default: os.Stdout
|
||||
Output io.Writer
|
||||
|
||||
// DisableColors defines if the logs output should be colorized
|
||||
//
|
||||
// Default: false
|
||||
DisableColors bool
|
||||
|
||||
enableColors bool
|
||||
enableLatency bool
|
||||
timeZoneLocation *time.Location
|
||||
}
|
||||
|
||||
const (
|
||||
startTag = "${"
|
||||
endTag = "}"
|
||||
paramSeparator = ":"
|
||||
)
|
||||
|
||||
type Buffer interface {
|
||||
Len() int
|
||||
ReadFrom(r io.Reader) (int64, error)
|
||||
WriteTo(w io.Writer) (int64, error)
|
||||
Bytes() []byte
|
||||
Write(p []byte) (int, error)
|
||||
WriteByte(c byte) error
|
||||
WriteString(s string) (int, error)
|
||||
Set(p []byte)
|
||||
SetString(s string)
|
||||
String() string
|
||||
}
|
||||
|
||||
type LogFunc func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error)
|
||||
|
||||
// ConfigDefault is the default config
|
||||
var ConfigDefault = Config{
|
||||
Next: nil,
|
||||
Done: nil,
|
||||
Format: "${time} | ${status} | ${latency} | ${ip} | ${method} | ${path} | ${error}\n",
|
||||
TimeFormat: "15:04:05",
|
||||
TimeZone: "Local",
|
||||
TimeInterval: 500 * time.Millisecond,
|
||||
Output: os.Stdout,
|
||||
DisableColors: false,
|
||||
enableColors: true,
|
||||
}
|
||||
|
||||
// Helper function to set default values
|
||||
func configDefault(config ...Config) Config {
|
||||
// Return default config if nothing provided
|
||||
if len(config) < 1 {
|
||||
return ConfigDefault
|
||||
}
|
||||
|
||||
// Override default config
|
||||
cfg := config[0]
|
||||
|
||||
// Set default values
|
||||
if cfg.Next == nil {
|
||||
cfg.Next = ConfigDefault.Next
|
||||
}
|
||||
if cfg.Done == nil {
|
||||
cfg.Done = ConfigDefault.Done
|
||||
}
|
||||
if cfg.Format == "" {
|
||||
cfg.Format = ConfigDefault.Format
|
||||
}
|
||||
if cfg.TimeZone == "" {
|
||||
cfg.TimeZone = ConfigDefault.TimeZone
|
||||
}
|
||||
if cfg.TimeFormat == "" {
|
||||
cfg.TimeFormat = ConfigDefault.TimeFormat
|
||||
}
|
||||
if int(cfg.TimeInterval) <= 0 {
|
||||
cfg.TimeInterval = ConfigDefault.TimeInterval
|
||||
}
|
||||
if cfg.Output == nil {
|
||||
cfg.Output = ConfigDefault.Output
|
||||
}
|
||||
|
||||
if !cfg.DisableColors && cfg.Output == ConfigDefault.Output {
|
||||
cfg.enableColors = true
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
16
middleware/logger/data.go
Normal file
16
middleware/logger/data.go
Normal file
|
@ -0,0 +1,16 @@
|
|||
package logger
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Data is a struct to define some variables to use in custom logger function.
|
||||
type Data struct {
|
||||
Pid string
|
||||
ErrPaddingStr string
|
||||
ChainErr error
|
||||
Start time.Time
|
||||
Stop time.Time
|
||||
Timestamp atomic.Value
|
||||
}
|
182
middleware/logger/logger.go
Normal file
182
middleware/logger/logger.go
Normal file
|
@ -0,0 +1,182 @@
|
|||
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
|
||||
}
|
650
middleware/logger/logger_test.go
Normal file
650
middleware/logger/logger_test.go
Normal file
|
@ -0,0 +1,650 @@
|
|||
//nolint:bodyclose // Much easier to just ignore memory leaks in tests
|
||||
package logger
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/requestid"
|
||||
"github.com/gofiber/fiber/v2/utils"
|
||||
|
||||
"github.com/valyala/bytebufferpool"
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
// go test -run Test_Logger
|
||||
func Test_Logger(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := fiber.New()
|
||||
|
||||
buf := bytebufferpool.Get()
|
||||
defer bytebufferpool.Put(buf)
|
||||
|
||||
app.Use(New(Config{
|
||||
Format: "${error}",
|
||||
Output: buf,
|
||||
}))
|
||||
|
||||
app.Get("/", func(c *fiber.Ctx) error {
|
||||
return errors.New("some random error")
|
||||
})
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, fiber.StatusInternalServerError, resp.StatusCode)
|
||||
utils.AssertEqual(t, "some random error", buf.String())
|
||||
}
|
||||
|
||||
// go test -run Test_Logger_locals
|
||||
func Test_Logger_locals(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := fiber.New()
|
||||
|
||||
buf := bytebufferpool.Get()
|
||||
defer bytebufferpool.Put(buf)
|
||||
|
||||
app.Use(New(Config{
|
||||
Format: "${locals:demo}",
|
||||
Output: buf,
|
||||
}))
|
||||
|
||||
app.Get("/", func(c *fiber.Ctx) error {
|
||||
c.Locals("demo", "johndoe")
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
})
|
||||
|
||||
app.Get("/int", func(c *fiber.Ctx) error {
|
||||
c.Locals("demo", 55)
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
})
|
||||
|
||||
app.Get("/empty", func(c *fiber.Ctx) error {
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
})
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode)
|
||||
utils.AssertEqual(t, "johndoe", buf.String())
|
||||
|
||||
buf.Reset()
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/int", nil))
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode)
|
||||
utils.AssertEqual(t, "55", buf.String())
|
||||
|
||||
buf.Reset()
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/empty", nil))
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode)
|
||||
utils.AssertEqual(t, "", buf.String())
|
||||
}
|
||||
|
||||
// go test -run Test_Logger_Next
|
||||
func Test_Logger_Next(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Next: func(_ *fiber.Ctx) bool {
|
||||
return true
|
||||
},
|
||||
}))
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, fiber.StatusNotFound, resp.StatusCode)
|
||||
}
|
||||
|
||||
// go test -run Test_Logger_Done
|
||||
func Test_Logger_Done(t *testing.T) {
|
||||
t.Parallel()
|
||||
buf := bytes.NewBuffer(nil)
|
||||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Done: func(c *fiber.Ctx, logString []byte) {
|
||||
if c.Response().StatusCode() == fiber.StatusOK {
|
||||
_, err := buf.Write(logString)
|
||||
utils.AssertEqual(t, nil, err)
|
||||
}
|
||||
},
|
||||
})).Get("/logging", func(ctx *fiber.Ctx) error {
|
||||
return ctx.SendStatus(fiber.StatusOK)
|
||||
})
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/logging", nil))
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode)
|
||||
utils.AssertEqual(t, true, buf.Len() > 0)
|
||||
}
|
||||
|
||||
// go test -run Test_Logger_ErrorTimeZone
|
||||
func Test_Logger_ErrorTimeZone(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
TimeZone: "invalid",
|
||||
}))
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, fiber.StatusNotFound, resp.StatusCode)
|
||||
}
|
||||
|
||||
type fakeOutput int
|
||||
|
||||
func (o *fakeOutput) Write([]byte) (int, error) {
|
||||
*o++
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// go test -run Test_Logger_ErrorOutput_WithoutColor
|
||||
func Test_Logger_ErrorOutput_WithoutColor(t *testing.T) {
|
||||
t.Parallel()
|
||||
o := new(fakeOutput)
|
||||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Output: o,
|
||||
DisableColors: true,
|
||||
}))
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, fiber.StatusNotFound, resp.StatusCode)
|
||||
|
||||
utils.AssertEqual(t, 1, int(*o))
|
||||
}
|
||||
|
||||
// go test -run Test_Logger_ErrorOutput
|
||||
func Test_Logger_ErrorOutput(t *testing.T) {
|
||||
t.Parallel()
|
||||
o := new(fakeOutput)
|
||||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Output: o,
|
||||
}))
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, fiber.StatusNotFound, resp.StatusCode)
|
||||
|
||||
utils.AssertEqual(t, 1, int(*o))
|
||||
}
|
||||
|
||||
// go test -run Test_Logger_All
|
||||
func Test_Logger_All(t *testing.T) {
|
||||
t.Parallel()
|
||||
buf := bytebufferpool.Get()
|
||||
defer bytebufferpool.Put(buf)
|
||||
|
||||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Format: "${pid}${reqHeaders}${referer}${protocol}${ip}${ips}${host}${url}${ua}${body}${route}${black}${red}${green}${yellow}${blue}${magenta}${cyan}${white}${reset}${error}${header:test}${query:test}${form:test}${cookie:test}${non}",
|
||||
Output: buf,
|
||||
}))
|
||||
|
||||
// Alias colors
|
||||
colors := app.Config().ColorScheme
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/?foo=bar", nil))
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, fiber.StatusNotFound, resp.StatusCode)
|
||||
|
||||
expected := fmt.Sprintf("%dHost=example.comhttp0.0.0.0example.com/?foo=bar/%s%s%s%s%s%s%s%s%sCannot GET /", os.Getpid(), colors.Black, colors.Red, colors.Green, colors.Yellow, colors.Blue, colors.Magenta, colors.Cyan, colors.White, colors.Reset)
|
||||
utils.AssertEqual(t, expected, buf.String())
|
||||
}
|
||||
|
||||
func getLatencyTimeUnits() []struct {
|
||||
unit string
|
||||
div time.Duration
|
||||
} {
|
||||
// windows does not support µs sleep precision
|
||||
// https://github.com/golang/go/issues/29485
|
||||
if runtime.GOOS == "windows" {
|
||||
return []struct {
|
||||
unit string
|
||||
div time.Duration
|
||||
}{
|
||||
{"ms", time.Millisecond},
|
||||
{"s", time.Second},
|
||||
}
|
||||
}
|
||||
return []struct {
|
||||
unit string
|
||||
div time.Duration
|
||||
}{
|
||||
{"µs", time.Microsecond},
|
||||
{"ms", time.Millisecond},
|
||||
{"s", time.Second},
|
||||
}
|
||||
}
|
||||
|
||||
// go test -run Test_Logger_WithLatency
|
||||
func Test_Logger_WithLatency(t *testing.T) {
|
||||
t.Parallel()
|
||||
buff := bytebufferpool.Get()
|
||||
defer bytebufferpool.Put(buff)
|
||||
app := fiber.New()
|
||||
logger := New(Config{
|
||||
Output: buff,
|
||||
Format: "${latency}",
|
||||
})
|
||||
app.Use(logger)
|
||||
|
||||
// Define a list of time units to test
|
||||
timeUnits := getLatencyTimeUnits()
|
||||
|
||||
// Initialize a new time unit
|
||||
sleepDuration := 1 * time.Nanosecond
|
||||
|
||||
// Define a test route that sleeps
|
||||
app.Get("/test", func(c *fiber.Ctx) error {
|
||||
time.Sleep(sleepDuration)
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
})
|
||||
|
||||
// Loop through each time unit and assert that the log output contains the expected latency value
|
||||
for _, tu := range timeUnits {
|
||||
// Update the sleep duration for the next iteration
|
||||
sleepDuration = 1 * tu.div
|
||||
|
||||
// Create a new HTTP request to the test route
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/test", nil), int(2*time.Second))
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode)
|
||||
|
||||
// Assert that the log output contains the expected latency value in the current time unit
|
||||
utils.AssertEqual(t, bytes.HasSuffix(buff.Bytes(), []byte(tu.unit)), true, fmt.Sprintf("Expected latency to be in %s, got %s", tu.unit, buff.String()))
|
||||
|
||||
// Reset the buffer
|
||||
buff.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
// go test -run Test_Logger_WithLatency_DefaultFormat
|
||||
func Test_Logger_WithLatency_DefaultFormat(t *testing.T) {
|
||||
t.Parallel()
|
||||
buff := bytebufferpool.Get()
|
||||
defer bytebufferpool.Put(buff)
|
||||
app := fiber.New()
|
||||
logger := New(Config{
|
||||
Output: buff,
|
||||
})
|
||||
app.Use(logger)
|
||||
|
||||
// Define a list of time units to test
|
||||
timeUnits := getLatencyTimeUnits()
|
||||
|
||||
// Initialize a new time unit
|
||||
sleepDuration := 1 * time.Nanosecond
|
||||
|
||||
// Define a test route that sleeps
|
||||
app.Get("/test", func(c *fiber.Ctx) error {
|
||||
time.Sleep(sleepDuration)
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
})
|
||||
|
||||
// Loop through each time unit and assert that the log output contains the expected latency value
|
||||
for _, tu := range timeUnits {
|
||||
// Update the sleep duration for the next iteration
|
||||
sleepDuration = 1 * tu.div
|
||||
|
||||
// Create a new HTTP request to the test route
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/test", nil), int(2*time.Second))
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode)
|
||||
|
||||
// Assert that the log output contains the expected latency value in the current time unit
|
||||
// parse out the latency value from the log output
|
||||
latency := bytes.Split(buff.Bytes(), []byte(" | "))[2]
|
||||
// Assert that the latency value is in the current time unit
|
||||
utils.AssertEqual(t, bytes.HasSuffix(latency, []byte(tu.unit)), true, fmt.Sprintf("Expected latency to be in %s, got %s", tu.unit, latency))
|
||||
|
||||
// Reset the buffer
|
||||
buff.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
// go test -run Test_Query_Params
|
||||
func Test_Query_Params(t *testing.T) {
|
||||
t.Parallel()
|
||||
buf := bytebufferpool.Get()
|
||||
defer bytebufferpool.Put(buf)
|
||||
|
||||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Format: "${queryParams}",
|
||||
Output: buf,
|
||||
}))
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/?foo=bar&baz=moz", nil))
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, fiber.StatusNotFound, resp.StatusCode)
|
||||
|
||||
expected := "foo=bar&baz=moz"
|
||||
utils.AssertEqual(t, expected, buf.String())
|
||||
}
|
||||
|
||||
// go test -run Test_Response_Body
|
||||
func Test_Response_Body(t *testing.T) {
|
||||
t.Parallel()
|
||||
buf := bytebufferpool.Get()
|
||||
defer bytebufferpool.Put(buf)
|
||||
|
||||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Format: "${resBody}",
|
||||
Output: buf,
|
||||
}))
|
||||
|
||||
app.Get("/", func(c *fiber.Ctx) error {
|
||||
return c.SendString("Sample response body")
|
||||
})
|
||||
|
||||
app.Post("/test", func(c *fiber.Ctx) error {
|
||||
return c.Send([]byte("Post in test"))
|
||||
})
|
||||
|
||||
_, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
|
||||
utils.AssertEqual(t, nil, err)
|
||||
|
||||
expectedGetResponse := "Sample response body"
|
||||
utils.AssertEqual(t, expectedGetResponse, buf.String())
|
||||
|
||||
buf.Reset() // Reset buffer to test POST
|
||||
|
||||
_, err = app.Test(httptest.NewRequest(fiber.MethodPost, "/test", nil))
|
||||
utils.AssertEqual(t, nil, err)
|
||||
|
||||
expectedPostResponse := "Post in test"
|
||||
utils.AssertEqual(t, expectedPostResponse, buf.String())
|
||||
}
|
||||
|
||||
// go test -run Test_Logger_AppendUint
|
||||
func Test_Logger_AppendUint(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := fiber.New()
|
||||
|
||||
buf := bytebufferpool.Get()
|
||||
defer bytebufferpool.Put(buf)
|
||||
|
||||
app.Use(New(Config{
|
||||
Format: "${bytesReceived} ${bytesSent} ${status}",
|
||||
Output: buf,
|
||||
}))
|
||||
|
||||
app.Get("/", func(c *fiber.Ctx) error {
|
||||
c.Response().Header.SetContentLength(5)
|
||||
return c.SendString("hello")
|
||||
})
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode)
|
||||
utils.AssertEqual(t, "-2 5 200", buf.String())
|
||||
}
|
||||
|
||||
// go test -run Test_Logger_Data_Race -race
|
||||
func Test_Logger_Data_Race(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := fiber.New()
|
||||
|
||||
buf := bytebufferpool.Get()
|
||||
defer bytebufferpool.Put(buf)
|
||||
|
||||
app.Use(New(ConfigDefault))
|
||||
app.Use(New(Config{
|
||||
Format: "${time} | ${pid} | ${locals:requestid} | ${status} | ${latency} | ${method} | ${path}\n",
|
||||
}))
|
||||
|
||||
app.Get("/", func(c *fiber.Ctx) error {
|
||||
return c.SendString("hello")
|
||||
})
|
||||
|
||||
var (
|
||||
resp1, resp2 *http.Response
|
||||
err1, err2 error
|
||||
)
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
resp1, err1 = app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
|
||||
wg.Done()
|
||||
}()
|
||||
resp2, err2 = app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
|
||||
wg.Wait()
|
||||
utils.AssertEqual(t, nil, err1)
|
||||
utils.AssertEqual(t, fiber.StatusOK, resp1.StatusCode)
|
||||
utils.AssertEqual(t, nil, err2)
|
||||
utils.AssertEqual(t, fiber.StatusOK, resp2.StatusCode)
|
||||
}
|
||||
|
||||
// go test -v -run=^$ -bench=Benchmark_Logger -benchmem -count=4
|
||||
func Benchmark_Logger(b *testing.B) {
|
||||
benchSetup := func(b *testing.B, app *fiber.App) {
|
||||
b.Helper()
|
||||
|
||||
h := app.Handler()
|
||||
|
||||
fctx := &fasthttp.RequestCtx{}
|
||||
fctx.Request.Header.SetMethod(fiber.MethodGet)
|
||||
fctx.Request.SetRequestURI("/")
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
h(fctx)
|
||||
}
|
||||
|
||||
utils.AssertEqual(b, 200, fctx.Response.Header.StatusCode())
|
||||
}
|
||||
|
||||
b.Run("Base", func(bb *testing.B) {
|
||||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Format: "${bytesReceived} ${bytesSent} ${status}",
|
||||
Output: io.Discard,
|
||||
}))
|
||||
app.Get("/", func(c *fiber.Ctx) error {
|
||||
c.Set("test", "test")
|
||||
return c.SendString("Hello, World!")
|
||||
})
|
||||
benchSetup(bb, app)
|
||||
})
|
||||
|
||||
b.Run("DefaultFormat", func(bb *testing.B) {
|
||||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Output: io.Discard,
|
||||
}))
|
||||
app.Get("/", func(c *fiber.Ctx) error {
|
||||
return c.SendString("Hello, World!")
|
||||
})
|
||||
benchSetup(bb, app)
|
||||
})
|
||||
|
||||
b.Run("WithTagParameter", func(bb *testing.B) {
|
||||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Format: "${bytesReceived} ${bytesSent} ${status} ${reqHeader:test}",
|
||||
Output: io.Discard,
|
||||
}))
|
||||
app.Get("/", func(c *fiber.Ctx) error {
|
||||
c.Set("test", "test")
|
||||
return c.SendString("Hello, World!")
|
||||
})
|
||||
benchSetup(bb, app)
|
||||
})
|
||||
}
|
||||
|
||||
// go test -run Test_Response_Header
|
||||
func Test_Response_Header(t *testing.T) {
|
||||
t.Parallel()
|
||||
buf := bytebufferpool.Get()
|
||||
defer bytebufferpool.Put(buf)
|
||||
|
||||
app := fiber.New()
|
||||
app.Use(requestid.New(requestid.Config{
|
||||
Next: nil,
|
||||
Header: fiber.HeaderXRequestID,
|
||||
Generator: func() string { return "Hello fiber!" },
|
||||
ContextKey: "requestid",
|
||||
}))
|
||||
app.Use(New(Config{
|
||||
Format: "${respHeader:X-Request-ID}",
|
||||
Output: buf,
|
||||
}))
|
||||
app.Get("/", func(c *fiber.Ctx) error {
|
||||
return c.SendString("Hello fiber!")
|
||||
})
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode)
|
||||
utils.AssertEqual(t, "Hello fiber!", buf.String())
|
||||
}
|
||||
|
||||
// go test -run Test_Req_Header
|
||||
func Test_Req_Header(t *testing.T) {
|
||||
t.Parallel()
|
||||
buf := bytebufferpool.Get()
|
||||
defer bytebufferpool.Put(buf)
|
||||
|
||||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Format: "${header:test}",
|
||||
Output: buf,
|
||||
}))
|
||||
app.Get("/", func(c *fiber.Ctx) error {
|
||||
return c.SendString("Hello fiber!")
|
||||
})
|
||||
headerReq := httptest.NewRequest(fiber.MethodGet, "/", nil)
|
||||
headerReq.Header.Add("test", "Hello fiber!")
|
||||
|
||||
resp, err := app.Test(headerReq)
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode)
|
||||
utils.AssertEqual(t, "Hello fiber!", buf.String())
|
||||
}
|
||||
|
||||
// go test -run Test_ReqHeader_Header
|
||||
func Test_ReqHeader_Header(t *testing.T) {
|
||||
t.Parallel()
|
||||
buf := bytebufferpool.Get()
|
||||
defer bytebufferpool.Put(buf)
|
||||
|
||||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Format: "${reqHeader:test}",
|
||||
Output: buf,
|
||||
}))
|
||||
app.Get("/", func(c *fiber.Ctx) error {
|
||||
return c.SendString("Hello fiber!")
|
||||
})
|
||||
reqHeaderReq := httptest.NewRequest(fiber.MethodGet, "/", nil)
|
||||
reqHeaderReq.Header.Add("test", "Hello fiber!")
|
||||
|
||||
resp, err := app.Test(reqHeaderReq)
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode)
|
||||
utils.AssertEqual(t, "Hello fiber!", buf.String())
|
||||
}
|
||||
|
||||
// go test -run Test_CustomTags
|
||||
func Test_CustomTags(t *testing.T) {
|
||||
t.Parallel()
|
||||
customTag := "it is a custom tag"
|
||||
|
||||
buf := bytebufferpool.Get()
|
||||
defer bytebufferpool.Put(buf)
|
||||
|
||||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Format: "${custom_tag}",
|
||||
CustomTags: map[string]LogFunc{
|
||||
"custom_tag": func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
|
||||
return output.WriteString(customTag)
|
||||
},
|
||||
},
|
||||
Output: buf,
|
||||
}))
|
||||
app.Get("/", func(c *fiber.Ctx) error {
|
||||
return c.SendString("Hello fiber!")
|
||||
})
|
||||
reqHeaderReq := httptest.NewRequest(fiber.MethodGet, "/", nil)
|
||||
reqHeaderReq.Header.Add("test", "Hello fiber!")
|
||||
|
||||
resp, err := app.Test(reqHeaderReq)
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode)
|
||||
utils.AssertEqual(t, customTag, buf.String())
|
||||
}
|
||||
|
||||
// go test -run Test_Logger_ByteSent_Streaming
|
||||
func Test_Logger_ByteSent_Streaming(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := fiber.New()
|
||||
|
||||
buf := bytebufferpool.Get()
|
||||
defer bytebufferpool.Put(buf)
|
||||
|
||||
app.Use(New(Config{
|
||||
Format: "${bytesReceived} ${bytesSent} ${status}",
|
||||
Output: buf,
|
||||
}))
|
||||
|
||||
app.Get("/", func(c *fiber.Ctx) error {
|
||||
c.Set("Connection", "keep-alive")
|
||||
c.Set("Transfer-Encoding", "chunked")
|
||||
c.Context().SetBodyStreamWriter(func(w *bufio.Writer) {
|
||||
var i int
|
||||
for {
|
||||
i++
|
||||
msg := fmt.Sprintf("%d - the time is %v", i, time.Now())
|
||||
fmt.Fprintf(w, "data: Message: %s\n\n", msg)
|
||||
err := w.Flush()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if i == 10 {
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
return nil
|
||||
})
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode)
|
||||
utils.AssertEqual(t, "-2 -1 200", buf.String())
|
||||
}
|
||||
|
||||
// go test -run Test_Logger_EnableColors
|
||||
func Test_Logger_EnableColors(t *testing.T) {
|
||||
t.Parallel()
|
||||
o := new(fakeOutput)
|
||||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Output: o,
|
||||
}))
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, fiber.StatusNotFound, resp.StatusCode)
|
||||
|
||||
utils.AssertEqual(t, 1, int(*o))
|
||||
}
|
209
middleware/logger/tags.go
Normal file
209
middleware/logger/tags.go
Normal file
|
@ -0,0 +1,209 @@
|
|||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
// Logger variables
|
||||
const (
|
||||
TagPid = "pid"
|
||||
TagTime = "time"
|
||||
TagReferer = "referer"
|
||||
TagProtocol = "protocol"
|
||||
TagPort = "port"
|
||||
TagIP = "ip"
|
||||
TagIPs = "ips"
|
||||
TagHost = "host"
|
||||
TagMethod = "method"
|
||||
TagPath = "path"
|
||||
TagURL = "url"
|
||||
TagUA = "ua"
|
||||
TagLatency = "latency"
|
||||
TagStatus = "status"
|
||||
TagResBody = "resBody"
|
||||
TagReqHeaders = "reqHeaders"
|
||||
TagQueryStringParams = "queryParams"
|
||||
TagBody = "body"
|
||||
TagBytesSent = "bytesSent"
|
||||
TagBytesReceived = "bytesReceived"
|
||||
TagRoute = "route"
|
||||
TagError = "error"
|
||||
// Deprecated: Use TagReqHeader instead
|
||||
TagHeader = "header:"
|
||||
TagReqHeader = "reqHeader:"
|
||||
TagRespHeader = "respHeader:"
|
||||
TagLocals = "locals:"
|
||||
TagQuery = "query:"
|
||||
TagForm = "form:"
|
||||
TagCookie = "cookie:"
|
||||
TagBlack = "black"
|
||||
TagRed = "red"
|
||||
TagGreen = "green"
|
||||
TagYellow = "yellow"
|
||||
TagBlue = "blue"
|
||||
TagMagenta = "magenta"
|
||||
TagCyan = "cyan"
|
||||
TagWhite = "white"
|
||||
TagReset = "reset"
|
||||
)
|
||||
|
||||
// createTagMap function merged the default with the custom tags
|
||||
func createTagMap(cfg *Config) map[string]LogFunc {
|
||||
// Set default tags
|
||||
tagFunctions := map[string]LogFunc{
|
||||
TagReferer: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
|
||||
return output.WriteString(c.Get(fiber.HeaderReferer))
|
||||
},
|
||||
TagProtocol: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
|
||||
return output.WriteString(c.Protocol())
|
||||
},
|
||||
TagPort: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
|
||||
return output.WriteString(c.Port())
|
||||
},
|
||||
TagIP: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
|
||||
return output.WriteString(c.IP())
|
||||
},
|
||||
TagIPs: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
|
||||
return output.WriteString(c.Get(fiber.HeaderXForwardedFor))
|
||||
},
|
||||
TagHost: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
|
||||
return output.WriteString(c.Hostname())
|
||||
},
|
||||
TagPath: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
|
||||
return output.WriteString(c.Path())
|
||||
},
|
||||
TagURL: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
|
||||
return output.WriteString(c.OriginalURL())
|
||||
},
|
||||
TagUA: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
|
||||
return output.WriteString(c.Get(fiber.HeaderUserAgent))
|
||||
},
|
||||
TagBody: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
|
||||
return output.Write(c.Body())
|
||||
},
|
||||
TagBytesReceived: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
|
||||
return output.WriteString(strconv.Itoa((c.Request().Header.ContentLength())))
|
||||
},
|
||||
TagBytesSent: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
|
||||
return output.WriteString(strconv.Itoa((c.Response().Header.ContentLength())))
|
||||
},
|
||||
TagRoute: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
|
||||
return output.WriteString(c.Route().Path)
|
||||
},
|
||||
TagResBody: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
|
||||
return output.Write(c.Response().Body())
|
||||
},
|
||||
TagReqHeaders: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
|
||||
reqHeaders := make([]string, 0)
|
||||
for k, v := range c.GetReqHeaders() {
|
||||
reqHeaders = append(reqHeaders, k+"="+strings.Join(v, ","))
|
||||
}
|
||||
return output.Write([]byte(strings.Join(reqHeaders, "&")))
|
||||
},
|
||||
TagQueryStringParams: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
|
||||
return output.WriteString(c.Request().URI().QueryArgs().String())
|
||||
},
|
||||
|
||||
TagBlack: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
|
||||
return output.WriteString(c.App().Config().ColorScheme.Black)
|
||||
},
|
||||
TagRed: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
|
||||
return output.WriteString(c.App().Config().ColorScheme.Red)
|
||||
},
|
||||
TagGreen: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
|
||||
return output.WriteString(c.App().Config().ColorScheme.Green)
|
||||
},
|
||||
TagYellow: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
|
||||
return output.WriteString(c.App().Config().ColorScheme.Yellow)
|
||||
},
|
||||
TagBlue: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
|
||||
return output.WriteString(c.App().Config().ColorScheme.Blue)
|
||||
},
|
||||
TagMagenta: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
|
||||
return output.WriteString(c.App().Config().ColorScheme.Magenta)
|
||||
},
|
||||
TagCyan: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
|
||||
return output.WriteString(c.App().Config().ColorScheme.Cyan)
|
||||
},
|
||||
TagWhite: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
|
||||
return output.WriteString(c.App().Config().ColorScheme.White)
|
||||
},
|
||||
TagReset: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
|
||||
return output.WriteString(c.App().Config().ColorScheme.Reset)
|
||||
},
|
||||
TagError: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
|
||||
if data.ChainErr != nil {
|
||||
if cfg.enableColors {
|
||||
colors := c.App().Config().ColorScheme
|
||||
return output.WriteString(fmt.Sprintf("%s%s%s", colors.Red, data.ChainErr.Error(), colors.Reset))
|
||||
}
|
||||
return output.WriteString(data.ChainErr.Error())
|
||||
}
|
||||
return output.WriteString("-")
|
||||
},
|
||||
TagReqHeader: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
|
||||
return output.WriteString(c.Get(extraParam))
|
||||
},
|
||||
TagHeader: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
|
||||
return output.WriteString(c.Get(extraParam))
|
||||
},
|
||||
TagRespHeader: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
|
||||
return output.WriteString(c.GetRespHeader(extraParam))
|
||||
},
|
||||
TagQuery: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
|
||||
return output.WriteString(c.Query(extraParam))
|
||||
},
|
||||
TagForm: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
|
||||
return output.WriteString(c.FormValue(extraParam))
|
||||
},
|
||||
TagCookie: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
|
||||
return output.WriteString(c.Cookies(extraParam))
|
||||
},
|
||||
TagLocals: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
|
||||
switch v := c.Locals(extraParam).(type) {
|
||||
case []byte:
|
||||
return output.Write(v)
|
||||
case string:
|
||||
return output.WriteString(v)
|
||||
case nil:
|
||||
return 0, nil
|
||||
default:
|
||||
return output.WriteString(fmt.Sprintf("%v", v))
|
||||
}
|
||||
},
|
||||
TagStatus: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
|
||||
if cfg.enableColors {
|
||||
colors := c.App().Config().ColorScheme
|
||||
return output.WriteString(fmt.Sprintf("%s%3d%s", statusColor(c.Response().StatusCode(), colors), c.Response().StatusCode(), colors.Reset))
|
||||
}
|
||||
return appendInt(output, c.Response().StatusCode())
|
||||
},
|
||||
TagMethod: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
|
||||
if cfg.enableColors {
|
||||
colors := c.App().Config().ColorScheme
|
||||
return output.WriteString(fmt.Sprintf("%s%s%s", methodColor(c.Method(), colors), c.Method(), colors.Reset))
|
||||
}
|
||||
return output.WriteString(c.Method())
|
||||
},
|
||||
TagPid: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
|
||||
return output.WriteString(data.Pid)
|
||||
},
|
||||
TagLatency: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
|
||||
latency := data.Stop.Sub(data.Start)
|
||||
return output.WriteString(fmt.Sprintf("%13v", latency))
|
||||
},
|
||||
TagTime: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
|
||||
return output.WriteString(data.Timestamp.Load().(string)) //nolint:forcetypeassert // We always store a string in here
|
||||
},
|
||||
}
|
||||
// merge with custom tags from user
|
||||
for k, v := range cfg.CustomTags {
|
||||
tagFunctions[k] = v
|
||||
}
|
||||
|
||||
return tagFunctions
|
||||
}
|
70
middleware/logger/template_chain.go
Normal file
70
middleware/logger/template_chain.go
Normal file
|
@ -0,0 +1,70 @@
|
|||
package logger
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
|
||||
"github.com/gofiber/fiber/v2/utils"
|
||||
)
|
||||
|
||||
// buildLogFuncChain analyzes the template and creates slices with the functions for execution and
|
||||
// slices with the fixed parts of the template and the parameters
|
||||
//
|
||||
// fixParts contains the fixed parts of the template or parameters if a function is stored in the funcChain at this position
|
||||
// funcChain contains for the parts which exist the functions for the dynamic parts
|
||||
// funcChain and fixParts always have the same length and contain nil for the parts where no data is required in the chain,
|
||||
// if a function exists for the part, a parameter for it can also exist in the fixParts slice
|
||||
func buildLogFuncChain(cfg *Config, tagFunctions map[string]LogFunc) ([][]byte, []LogFunc, error) {
|
||||
// process flow is copied from the fasttemplate flow https://github.com/valyala/fasttemplate/blob/2a2d1afadadf9715bfa19683cdaeac8347e5d9f9/template.go#L23-L62
|
||||
templateB := utils.UnsafeBytes(cfg.Format)
|
||||
startTagB := utils.UnsafeBytes(startTag)
|
||||
endTagB := utils.UnsafeBytes(endTag)
|
||||
paramSeparatorB := utils.UnsafeBytes(paramSeparator)
|
||||
|
||||
var fixParts [][]byte
|
||||
var funcChain []LogFunc
|
||||
|
||||
for {
|
||||
currentPos := bytes.Index(templateB, startTagB)
|
||||
if currentPos < 0 {
|
||||
// no starting tag found in the existing template part
|
||||
break
|
||||
}
|
||||
// add fixed part
|
||||
funcChain = append(funcChain, nil)
|
||||
fixParts = append(fixParts, templateB[:currentPos])
|
||||
|
||||
templateB = templateB[currentPos+len(startTagB):]
|
||||
currentPos = bytes.Index(templateB, endTagB)
|
||||
if currentPos < 0 {
|
||||
// cannot find end tag - just write it to the output.
|
||||
funcChain = append(funcChain, nil)
|
||||
fixParts = append(fixParts, startTagB)
|
||||
break
|
||||
}
|
||||
// ## function block ##
|
||||
// first check for tags with parameters
|
||||
if index := bytes.Index(templateB[:currentPos], paramSeparatorB); index != -1 {
|
||||
logFunc, ok := tagFunctions[utils.UnsafeString(templateB[:index+1])]
|
||||
if !ok {
|
||||
return nil, nil, errors.New("No parameter found in \"" + utils.UnsafeString(templateB[:currentPos]) + "\"")
|
||||
}
|
||||
funcChain = append(funcChain, logFunc)
|
||||
// add param to the fixParts
|
||||
fixParts = append(fixParts, templateB[index+1:currentPos])
|
||||
} else if logFunc, ok := tagFunctions[utils.UnsafeString(templateB[:currentPos])]; ok {
|
||||
// add functions without parameter
|
||||
funcChain = append(funcChain, logFunc)
|
||||
fixParts = append(fixParts, nil)
|
||||
}
|
||||
// ## function block end ##
|
||||
|
||||
// reduce the template string
|
||||
templateB = templateB[currentPos+len(endTagB):]
|
||||
}
|
||||
// set the rest
|
||||
funcChain = append(funcChain, nil)
|
||||
fixParts = append(fixParts, templateB)
|
||||
|
||||
return fixParts, funcChain, nil
|
||||
}
|
39
middleware/logger/utils.go
Normal file
39
middleware/logger/utils.go
Normal file
|
@ -0,0 +1,39 @@
|
|||
package logger
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func methodColor(method string, colors fiber.Colors) string {
|
||||
switch method {
|
||||
case fiber.MethodGet:
|
||||
return colors.Cyan
|
||||
case fiber.MethodPost:
|
||||
return colors.Green
|
||||
case fiber.MethodPut:
|
||||
return colors.Yellow
|
||||
case fiber.MethodDelete:
|
||||
return colors.Red
|
||||
case fiber.MethodPatch:
|
||||
return colors.White
|
||||
case fiber.MethodHead:
|
||||
return colors.Magenta
|
||||
case fiber.MethodOptions:
|
||||
return colors.Blue
|
||||
default:
|
||||
return colors.Reset
|
||||
}
|
||||
}
|
||||
|
||||
func statusColor(code int, colors fiber.Colors) string {
|
||||
switch {
|
||||
case code >= fiber.StatusOK && code < fiber.StatusMultipleChoices:
|
||||
return colors.Green
|
||||
case code >= fiber.StatusMultipleChoices && code < fiber.StatusBadRequest:
|
||||
return colors.Blue
|
||||
case code >= fiber.StatusBadRequest && code < fiber.StatusInternalServerError:
|
||||
return colors.Yellow
|
||||
default:
|
||||
return colors.Red
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue