Skip to content

Commit

Permalink
✨ Add customTags in logger middleware Config
Browse files Browse the repository at this point in the history
  • Loading branch information
Skyenought committed Nov 15, 2022
1 parent 3157fb5 commit 02ac453
Show file tree
Hide file tree
Showing 5 changed files with 243 additions and 115 deletions.
18 changes: 17 additions & 1 deletion middleware/logger/README.md
Expand Up @@ -11,6 +11,7 @@ Logger middleware for [Fiber](https://github.com/gofiber/fiber) that logs HTTP r
- [Logging Request ID](#logging-request-id)
- [Changing TimeZone & TimeFormat](#changing-timezone--timeformat)
- [Custom File Writer](#custom-file-writer)
- [Add Custom Tags](#add-custom-tags)
- [Config](#config)
- [Default Config](#default-config-1)
- [Constants](#constants)
Expand Down Expand Up @@ -75,6 +76,16 @@ app.Use(logger.New(logger.Config{
Output: file,
}))
```
### Add Custom Tags
```go
app.Use(logger.New(logger.Config{
CustomTags: map[string]logger.LogFunc{
"custom_tag": func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, w io.Writer, tag string) (int, error) {
return buf.WriteString("it is a custom tag")
},
},
}))
```

## Config
```go
Expand All @@ -85,11 +96,16 @@ type Config struct {
// Optional. Default: nil
Next func(c *fiber.Ctx) bool

// CustomTags defines the custom tag action
//
// Optional. Default: map[string]LogFunc{}
CustomTags map[string]LogFunc

// Format defines the logging tags
//
// Optional. Default: [${time}] ${status} - ${latency} ${method} ${path}\n
Format string

// TimeFormat https://programming.guide/go/format-parse-string-time-date-example.html
//
// Optional. Default: 15:04:05
Expand Down
33 changes: 22 additions & 11 deletions middleware/logger/config.go
Expand Up @@ -5,7 +5,7 @@ import (
"os"
"strings"
"time"

"github.com/gofiber/fiber/v2"
)

Expand All @@ -15,38 +15,43 @@ type Config struct {
//
// 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: a function that does nothing.
Done func(c *fiber.Ctx, logString []byte)


// CustomTags defines the custom tag action
//
// Optional. Default: map[string]logFunc
CustomTags map[string]logFunc

// Format defines the logging tags
//
// Optional. Default: [${time}] ${status} - ${latency} ${method} ${path}\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

enableColors bool
enableLatency bool
timeZoneLocation *time.Location
Expand Down Expand Up @@ -84,15 +89,15 @@ func configDefault(config ...Config) Config {
if len(config) < 1 {
return ConfigDefault
}

// Override default config
cfg := config[0]

// Enable colors if no custom format or output is given
if validCustomFormat(cfg.Format) && cfg.Output == nil {
cfg.enableColors = true
}

// Set default values
if cfg.Next == nil {
cfg.Next = ConfigDefault.Next
Expand All @@ -115,5 +120,11 @@ func configDefault(config ...Config) Config {
if cfg.Output == nil {
cfg.Output = ConfigDefault.Output
}
// Set custom tags
if len(cfg.CustomTags) != 0 {
for k, v := range cfg.CustomTags {
tagMap[k] = v
}
}
return cfg
}
146 changes: 43 additions & 103 deletions middleware/logger/logger.go
Expand Up @@ -78,6 +78,24 @@ func New(config ...Config) fiber.Handler {
// Check if format contains latency
cfg.enableLatency = strings.Contains(cfg.Format, "${latency}")

// Add TagStatus Config
tagMap[TagStatus] = func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, w io.Writer, tag string) (int, error) {
colors := c.App().Config().ColorScheme
if cfg.enableColors {
return buf.WriteString(fmt.Sprintf("%s %3d %s", statusColor(c.Response().StatusCode(), colors), c.Response().StatusCode(), colors.Reset))
}
return appendInt(buf, c.Response().StatusCode())
}

// Add TagMethod Config
tagMap[TagMethod] = func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, w io.Writer, tag string) (int, error) {
colors := c.App().Config().ColorScheme
if cfg.enableColors {
return buf.WriteString(fmt.Sprintf("%s %-7s %s", methodColor(c.Method(), colors), c.Method(), colors.Reset))
}
return buf.WriteString(c.Method())
}

// Create template parser
tmpl := fasttemplate.New(cfg.Format, "${", "}")

Expand All @@ -98,6 +116,16 @@ func New(config ...Config) fiber.Handler {
// Set PID once
pid := strconv.Itoa(os.Getpid())

tagMap[TagPid] = func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, w io.Writer, tag string) (int, error) {
return buf.WriteString(pid)
}

tagMap[TagLatency] = func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, w io.Writer, tag string) (int, error) {
ctx := c.Context()
start, _ := time.ParseInLocation(cfg.TimeFormat, ctx.UserValue("start").(string), cfg.timeZoneLocation)
stop, _ := time.ParseInLocation(cfg.TimeFormat, ctx.UserValue("stop").(string), cfg.timeZoneLocation)
return buf.WriteString(fmt.Sprintf("%7v", stop.Sub(start)))
}
// Set variables
var (
once sync.Once
Expand All @@ -114,6 +142,11 @@ func New(config ...Config) fiber.Handler {
}
errPadding := 15
errPaddingStr := strconv.Itoa(errPadding)

tagMap[TagTime] = func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, w io.Writer, tag string) (int, error) {
return buf.WriteString(timestamp.Load().(string))
}

// Return new handler
return func(c *fiber.Ctx) (err error) {
// Don't execute middleware if Next returns true
Expand Down Expand Up @@ -145,13 +178,15 @@ func New(config ...Config) fiber.Handler {
// Set latency start time
if cfg.enableLatency {
start = time.Now()
c.Context().SetUserValue("start", start.String())
}

// Handle request, store err for logging
chainErr := c.Next()

// Manually call error handler
if chainErr != nil {
c.Context().SetUserValue("loggerChainError", chainErr.Error())
if err := errHandler(c, chainErr); err != nil {
_ = c.SendStatus(fiber.StatusInternalServerError)
}
Expand All @@ -160,6 +195,7 @@ func New(config ...Config) fiber.Handler {
// Set latency stop time
if cfg.enableLatency {
stop = time.Now()
c.Context().SetUserValue("stop", stop.String())
}

// Get new buffer
Expand Down Expand Up @@ -198,109 +234,13 @@ func New(config ...Config) fiber.Handler {

// Loop over template tags to replace it with the correct value
_, err = tmpl.ExecuteFunc(buf, func(w io.Writer, tag string) (int, error) {
switch tag {
case TagTime:
return buf.WriteString(timestamp.Load().(string))
case TagReferer:
return buf.WriteString(c.Get(fiber.HeaderReferer))
case TagProtocol:
return buf.WriteString(c.Protocol())
case TagPid:
return buf.WriteString(pid)
case TagPort:
return buf.WriteString(c.Port())
case TagIP:
return buf.WriteString(c.IP())
case TagIPs:
return buf.WriteString(c.Get(fiber.HeaderXForwardedFor))
case TagHost:
return buf.WriteString(c.Hostname())
case TagPath:
return buf.WriteString(c.Path())
case TagURL:
return buf.WriteString(c.OriginalURL())
case TagUA:
return buf.WriteString(c.Get(fiber.HeaderUserAgent))
case TagLatency:
return buf.WriteString(fmt.Sprintf("%7v", stop.Sub(start).Round(time.Millisecond)))
case TagBody:
return buf.Write(c.Body())
case TagBytesReceived:
return appendInt(buf, len(c.Request().Body()))
case TagBytesSent:
return appendInt(buf, len(c.Response().Body()))
case TagRoute:
return buf.WriteString(c.Route().Path)
case TagStatus:
if cfg.enableColors {
return buf.WriteString(fmt.Sprintf("%s %3d %s", statusColor(c.Response().StatusCode(), colors), c.Response().StatusCode(), colors.Reset))
}
return appendInt(buf, c.Response().StatusCode())
case TagResBody:
return buf.Write(c.Response().Body())
case TagReqHeaders:
reqHeaders := make([]string, 0)
for k, v := range c.GetReqHeaders() {
reqHeaders = append(reqHeaders, k+"="+v)
}
return buf.Write([]byte(strings.Join(reqHeaders, "&")))
case TagQueryStringParams:
return buf.WriteString(c.Request().URI().QueryArgs().String())
case TagMethod:
if cfg.enableColors {
return buf.WriteString(fmt.Sprintf("%s %-7s %s", methodColor(c.Method(), colors), c.Method(), colors.Reset))
}
return buf.WriteString(c.Method())
case TagBlack:
return buf.WriteString(colors.Black)
case TagRed:
return buf.WriteString(colors.Red)
case TagGreen:
return buf.WriteString(colors.Green)
case TagYellow:
return buf.WriteString(colors.Yellow)
case TagBlue:
return buf.WriteString(colors.Blue)
case TagMagenta:
return buf.WriteString(colors.Magenta)
case TagCyan:
return buf.WriteString(colors.Cyan)
case TagWhite:
return buf.WriteString(colors.White)
case TagReset:
return buf.WriteString(colors.Reset)
case TagError:
if chainErr != nil {
return buf.WriteString(chainErr.Error())
}
return buf.WriteString("-")
default:
// Check if we have a value tag i.e.: "reqHeader:x-key"
switch {
case strings.HasPrefix(tag, TagReqHeader):
return buf.WriteString(c.Get(tag[10:]))
case strings.HasPrefix(tag, TagHeader):
return buf.WriteString(c.Get(tag[7:]))
case strings.HasPrefix(tag, TagRespHeader):
return buf.WriteString(c.GetRespHeader(tag[11:]))
case strings.HasPrefix(tag, TagQuery):
return buf.WriteString(c.Query(tag[6:]))
case strings.HasPrefix(tag, TagForm):
return buf.WriteString(c.FormValue(tag[5:]))
case strings.HasPrefix(tag, TagCookie):
return buf.WriteString(c.Cookies(tag[7:]))
case strings.HasPrefix(tag, TagLocals):
switch v := c.Locals(tag[7:]).(type) {
case []byte:
return buf.Write(v)
case string:
return buf.WriteString(v)
case nil:
return 0, nil
default:
return buf.WriteString(fmt.Sprintf("%v", v))
}
}
if logFunc, ok := tagMap[tag]; ok {
return logFunc(buf, c, w, tag)
}
split := strings.SplitAfter(tag, ":")
if ok := strings.Contains(tag, split[0]); ok && len(split) > 1 {
logFunc, _ := tagMap[split[0]]
return logFunc(buf, c, w, tag)
}
return 0, nil
})
Expand Down
29 changes: 29 additions & 0 deletions middleware/logger/logger_test.go
Expand Up @@ -383,3 +383,32 @@ func Test_ReqHeader_Header(t *testing.T) {
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) {
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(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, w io.Writer, tag string) (int, error) {
return buf.WriteString(customTag)
},
},
Output: buf,
}))
app.Get("/", func(c *fiber.Ctx) error {
return c.SendString("Hello fiber!")
})
reqHeaderReq := httptest.NewRequest("GET", "/", 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())
}

0 comments on commit 02ac453

Please sign in to comment.