Add customTags in logger middleware Config (#2188)

*  Add customTags in logger middleware Config

* improve tags with parameter

* improve logger performance

* improve logger performance

* improve logger performance

* improve logger performance

Co-authored-by: René Werner <rene@gofiber.io>
pull/2224/head
skyenought 2022-11-18 20:10:43 +08:00 committed by GitHub
parent 3157fb5f1c
commit e8f8cb647b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 447 additions and 702 deletions

1
.github/README.md vendored
View File

@ -692,7 +692,6 @@ Copyright (c) 2019-present [Fenny](https://github.com/fenny) and [Contributors](
- [fasthttp](https://github.com/valyala/fasthttp/blob/master/LICENSE)
- [bytebufferpool](https://github.com/valyala/bytebufferpool/blob/master/LICENSE)
- [dictpool](https://github.com/savsgio/dictpool/blob/master/LICENSE)
- [fasttemplate](https://github.com/valyala/fasttemplate/blob/master/LICENSE)
- [fwd](https://github.com/philhofer/fwd/blob/master/LICENSE.md)
- [go-ole](https://github.com/go-ole/go-ole/blob/master/LICENSE)
- [gopsutil](https://github.com/shirou/gopsutil/blob/master/LICENSE)

View File

@ -691,7 +691,6 @@ For more articles, middlewares, examples or tools check our [awesome list](https
- [fasthttp](https://github.com/valyala/fasthttp/blob/master/LICENSE)
- [bytebufferpool](https://github.com/valyala/bytebufferpool/blob/master/LICENSE)
- [dictpool](https://github.com/savsgio/dictpool/blob/master/LICENSE)
- [fasttemplate](https://github.com/valyala/fasttemplate/blob/master/LICENSE)
- [fwd](https://github.com/philhofer/fwd/blob/master/LICENSE.md)
- [go-ole](https://github.com/go-ole/go-ole/blob/master/LICENSE)
- [gopsutil](https://github.com/shirou/gopsutil/blob/master/LICENSE)

View File

@ -661,7 +661,6 @@ Copyright (c) 2019-present [Fenny](https://github.com/fenny) and [Contributors](
- [fasthttp](https://github.com/valyala/fasthttp/blob/master/LICENSE)
- [bytebufferpool](https://github.com/valyala/bytebufferpool/blob/master/LICENSE)
- [dictpool](https://github.com/savsgio/dictpool/blob/master/LICENSE)
- [fasttemplate](https://github.com/valyala/fasttemplate/blob/master/LICENSE)
- [fwd](https://github.com/philhofer/fwd/blob/master/LICENSE.md)
- [go-ole](https://github.com/go-ole/go-ole/blob/master/LICENSE)
- [gopsutil](https://github.com/shirou/gopsutil/blob/master/LICENSE)

View File

@ -661,7 +661,6 @@ Copyright (c) 2019-presente [Fenny](https://github.com/fenny) y [contribuyentes]
- [fasthttp](https://github.com/valyala/fasthttp/blob/master/LICENSE)
- [bytebufferpool](https://github.com/valyala/bytebufferpool/blob/master/LICENSE)
- [dictpool](https://github.com/savsgio/dictpool/blob/master/LICENSE)
- [fasttemplate](https://github.com/valyala/fasttemplate/blob/master/LICENSE)
- [fwd](https://github.com/philhofer/fwd/blob/master/LICENSE.md)
- [go-ole](https://github.com/go-ole/go-ole/blob/master/LICENSE)
- [gopsutil](https://github.com/shirou/gopsutil/blob/master/LICENSE)

View File

@ -813,7 +813,6 @@ Copyright (c) 2019-present [Fenny](https://github.com/fenny) and [Contributors](
- [fasthttp](https://github.com/valyala/fasthttp/blob/master/LICENSE)
- [bytebufferpool](https://github.com/valyala/bytebufferpool/blob/master/LICENSE)
- [dictpool](https://github.com/savsgio/dictpool/blob/master/LICENSE)
- [fasttemplate](https://github.com/valyala/fasttemplate/blob/master/LICENSE)
- [fwd](https://github.com/philhofer/fwd/blob/master/LICENSE.md)
- [go-ole](https://github.com/go-ole/go-ole/blob/master/LICENSE)
- [gopsutil](https://github.com/shirou/gopsutil/blob/master/LICENSE)

View File

@ -663,7 +663,6 @@ Copyright (c) 2019-present [Fenny](https://github.com/fenny) and [Contributors](
- [fasthttp](https://github.com/valyala/fasthttp/blob/master/LICENSE)
- [bytebufferpool](https://github.com/valyala/bytebufferpool/blob/master/LICENSE)
- [dictpool](https://github.com/savsgio/dictpool/blob/master/LICENSE)
- [fasttemplate](https://github.com/valyala/fasttemplate/blob/master/LICENSE)
- [fwd](https://github.com/philhofer/fwd/blob/master/LICENSE.md)
- [go-ole](https://github.com/go-ole/go-ole/blob/master/LICENSE)
- [gopsutil](https://github.com/shirou/gopsutil/blob/master/LICENSE)

View File

@ -838,7 +838,6 @@ Copyright (c) 2019-present [Fenny](https://github.com/fenny) and [Contributors](
- [fasthttp](https://github.com/valyala/fasthttp/blob/master/LICENSE)
- [bytebufferpool](https://github.com/valyala/bytebufferpool/blob/master/LICENSE)
- [dictpool](https://github.com/savsgio/dictpool/blob/master/LICENSE)
- [fasttemplate](https://github.com/valyala/fasttemplate/blob/master/LICENSE)
- [fwd](https://github.com/philhofer/fwd/blob/master/LICENSE.md)
- [go-ole](https://github.com/go-ole/go-ole/blob/master/LICENSE)
- [gopsutil](https://github.com/shirou/gopsutil/blob/master/LICENSE)

View File

@ -664,7 +664,6 @@ Copyright (c) 2019-present [Fenny](https://github.com/fenny) and [Contributors](
- [fasthttp](https://github.com/valyala/fasthttp/blob/master/LICENSE)
- [bytebufferpool](https://github.com/valyala/bytebufferpool/blob/master/LICENSE)
- [dictpool](https://github.com/savsgio/dictpool/blob/master/LICENSE)
- [fasttemplate](https://github.com/valyala/fasttemplate/blob/master/LICENSE)
- [fwd](https://github.com/philhofer/fwd/blob/master/LICENSE.md)
- [go-ole](https://github.com/go-ole/go-ole/blob/master/LICENSE)
- [gopsutil](https://github.com/shirou/gopsutil/blob/master/LICENSE)

View File

@ -687,7 +687,6 @@ Copyright (c) 2019-ora [Fenny](https://github.com/fenny) e [Contributors](https:
- [fasthttp](https://github.com/valyala/fasthttp/blob/master/LICENSE)
- [bytebufferpool](https://github.com/valyala/bytebufferpool/blob/master/LICENSE)
- [dictpool](https://github.com/savsgio/dictpool/blob/master/LICENSE)
- [fasttemplate](https://github.com/valyala/fasttemplate/blob/master/LICENSE)
- [fwd](https://github.com/philhofer/fwd/blob/master/LICENSE.md)
- [go-ole](https://github.com/go-ole/go-ole/blob/master/LICENSE)
- [gopsutil](https://github.com/shirou/gopsutil/blob/master/LICENSE)

View File

@ -666,7 +666,6 @@ Copyright (c) 2019-present [Fenny](https://github.com/fenny) and [Contributors](
- [fasthttp](https://github.com/valyala/fasthttp/blob/master/LICENSE)
- [bytebufferpool](https://github.com/valyala/bytebufferpool/blob/master/LICENSE)
- [dictpool](https://github.com/savsgio/dictpool/blob/master/LICENSE)
- [fasttemplate](https://github.com/valyala/fasttemplate/blob/master/LICENSE)
- [fwd](https://github.com/philhofer/fwd/blob/master/LICENSE.md)
- [go-ole](https://github.com/go-ole/go-ole/blob/master/LICENSE)
- [gopsutil](https://github.com/shirou/gopsutil/blob/master/LICENSE)

View File

@ -667,7 +667,6 @@ Copyright (c) 2019-present [Fenny](https://github.com/fenny) and [Contributors](
- [fasthttp](https://github.com/valyala/fasthttp/blob/master/LICENSE)
- [bytebufferpool](https://github.com/valyala/bytebufferpool/blob/master/LICENSE)
- [dictpool](https://github.com/savsgio/dictpool/blob/master/LICENSE)
- [fasttemplate](https://github.com/valyala/fasttemplate/blob/master/LICENSE)
- [fwd](https://github.com/philhofer/fwd/blob/master/LICENSE.md)
- [go-ole](https://github.com/go-ole/go-ole/blob/master/LICENSE)
- [gopsutil](https://github.com/shirou/gopsutil/blob/master/LICENSE)

View File

@ -667,7 +667,6 @@ Copyright (c) 2019-present [Fenny](https://github.com/fenny) and [Contributors](
- [fasthttp](https://github.com/valyala/fasthttp/blob/master/LICENSE)
- [bytebufferpool](https://github.com/valyala/bytebufferpool/blob/master/LICENSE)
- [dictpool](https://github.com/savsgio/dictpool/blob/master/LICENSE)
- [fasttemplate](https://github.com/valyala/fasttemplate/blob/master/LICENSE)
- [fwd](https://github.com/philhofer/fwd/blob/master/LICENSE.md)
- [go-ole](https://github.com/go-ole/go-ole/blob/master/LICENSE)
- [gopsutil](https://github.com/shirou/gopsutil/blob/master/LICENSE)

View File

@ -663,7 +663,6 @@ O logo oficial foi criado por [Vic Shóstak](https://github.com/koddr) e distrib
- [fasthttp](https://github.com/valyala/fasthttp/blob/master/LICENSE)
- [bytebufferpool](https://github.com/valyala/bytebufferpool/blob/master/LICENSE)
- [dictpool](https://github.com/savsgio/dictpool/blob/master/LICENSE)
- [fasttemplate](https://github.com/valyala/fasttemplate/blob/master/LICENSE)
- [fwd](https://github.com/philhofer/fwd/blob/master/LICENSE.md)
- [go-ole](https://github.com/go-ole/go-ole/blob/master/LICENSE)
- [gopsutil](https://github.com/shirou/gopsutil/blob/master/LICENSE)

View File

@ -670,7 +670,6 @@ Copyright (c) 2019-present [Fenny](https://github.com/fenny) and [Contributors](
- [fasthttp](https://github.com/valyala/fasthttp/blob/master/LICENSE)
- [bytebufferpool](https://github.com/valyala/bytebufferpool/blob/master/LICENSE)
- [dictpool](https://github.com/savsgio/dictpool/blob/master/LICENSE)
- [fasttemplate](https://github.com/valyala/fasttemplate/blob/master/LICENSE)
- [fwd](https://github.com/philhofer/fwd/blob/master/LICENSE.md)
- [go-ole](https://github.com/go-ole/go-ole/blob/master/LICENSE)
- [gopsutil](https://github.com/shirou/gopsutil/blob/master/LICENSE)

View File

@ -732,7 +732,6 @@ Copyright (c) 2019-present [Fenny](https://github.com/fenny) and [Contributors](
- [fasthttp](https://github.com/valyala/fasthttp/blob/master/LICENSE)
- [bytebufferpool](https://github.com/valyala/bytebufferpool/blob/master/LICENSE)
- [dictpool](https://github.com/savsgio/dictpool/blob/master/LICENSE)
- [fasttemplate](https://github.com/valyala/fasttemplate/blob/master/LICENSE)
- [fwd](https://github.com/philhofer/fwd/blob/master/LICENSE.md)
- [go-ole](https://github.com/go-ole/go-ole/blob/master/LICENSE)
- [gopsutil](https://github.com/shirou/gopsutil/blob/master/LICENSE)

View File

@ -660,7 +660,6 @@ Telif (c) 2019-günümüz [Fenny](https://github.com/fenny) ve [katkıda bulunan
- [fasthttp](https://github.com/valyala/fasthttp/blob/master/LICENSE)
- [bytebufferpool](https://github.com/valyala/bytebufferpool/blob/master/LICENSE)
- [dictpool](https://github.com/savsgio/dictpool/blob/master/LICENSE)
- [fasttemplate](https://github.com/valyala/fasttemplate/blob/master/LICENSE)
- [fwd](https://github.com/philhofer/fwd/blob/master/LICENSE.md)
- [go-ole](https://github.com/go-ole/go-ole/blob/master/LICENSE)
- [gopsutil](https://github.com/shirou/gopsutil/blob/master/LICENSE)

View File

@ -670,7 +670,6 @@ Copyright (c) 2019-present [Fenny](https://github.com/fenny) and [Contributors](
- [fasthttp](https://github.com/valyala/fasthttp/blob/master/LICENSE)
- [bytebufferpool](https://github.com/valyala/bytebufferpool/blob/master/LICENSE)
- [dictpool](https://github.com/savsgio/dictpool/blob/master/LICENSE)
- [fasttemplate](https://github.com/valyala/fasttemplate/blob/master/LICENSE)
- [fwd](https://github.com/philhofer/fwd/blob/master/LICENSE.md)
- [go-ole](https://github.com/go-ole/go-ole/blob/master/LICENSE)
- [gopsutil](https://github.com/shirou/gopsutil/blob/master/LICENSE)

View File

@ -663,7 +663,6 @@ Fiber 是一個以贊助維生的開源專案,像是: 網域、gitbook、netli
- [fasthttp](https://github.com/valyala/fasthttp/blob/master/LICENSE)
- [bytebufferpool](https://github.com/valyala/bytebufferpool/blob/master/LICENSE)
- [dictpool](https://github.com/savsgio/dictpool/blob/master/LICENSE)
- [fasttemplate](https://github.com/valyala/fasttemplate/blob/master/LICENSE)
- [fwd](https://github.com/philhofer/fwd/blob/master/LICENSE.md)
- [go-ole](https://github.com/go-ole/go-ole/blob/master/LICENSE)
- [gopsutil](https://github.com/shirou/gopsutil/blob/master/LICENSE)

View File

@ -1,22 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015 Aliaksandr Valialkin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,437 +0,0 @@
// Package fasttemplate implements simple and fast template library.
//
// Fasttemplate is faster than text/template, strings.Replace
// and strings.Replacer.
//
// Fasttemplate ideally fits for fast and simple placeholders' substitutions.
package fasttemplate
import (
"bytes"
"fmt"
"io"
"github.com/gofiber/fiber/v2/internal/bytebufferpool"
)
// ExecuteFunc calls f on each template tag (placeholder) occurrence.
//
// Returns the number of bytes written to w.
//
// This function is optimized for constantly changing templates.
// Use Template.ExecuteFunc for frozen templates.
func ExecuteFunc(template, startTag, endTag string, w io.Writer, f TagFunc) (int64, error) {
s := unsafeString2Bytes(template)
a := unsafeString2Bytes(startTag)
b := unsafeString2Bytes(endTag)
var nn int64
var ni int
var err error
for {
n := bytes.Index(s, a)
if n < 0 {
break
}
ni, err = w.Write(s[:n])
nn += int64(ni)
if err != nil {
return nn, err
}
s = s[n+len(a):]
n = bytes.Index(s, b)
if n < 0 {
// cannot find end tag - just write it to the output.
ni, _ = w.Write(a)
nn += int64(ni)
break
}
ni, err = f(w, unsafeBytes2String(s[:n]))
nn += int64(ni)
if err != nil {
return nn, err
}
s = s[n+len(b):]
}
ni, err = w.Write(s)
nn += int64(ni)
return nn, err
}
// Execute substitutes template tags (placeholders) with the corresponding
// values from the map m and writes the result to the given writer w.
//
// Substitution map m may contain values with the following types:
// - []byte - the fastest value type
// - string - convenient value type
// - TagFunc - flexible value type
//
// Returns the number of bytes written to w.
//
// This function is optimized for constantly changing templates.
// Use Template.Execute for frozen templates.
func Execute(template, startTag, endTag string, w io.Writer, m map[string]interface{}) (int64, error) {
return ExecuteFunc(template, startTag, endTag, w, func(w io.Writer, tag string) (int, error) { return stdTagFunc(w, tag, m) })
}
// ExecuteStd works the same way as Execute, but keeps the unknown placeholders.
// This can be used as a drop-in replacement for strings.Replacer
//
// Substitution map m may contain values with the following types:
// - []byte - the fastest value type
// - string - convenient value type
// - TagFunc - flexible value type
//
// Returns the number of bytes written to w.
//
// This function is optimized for constantly changing templates.
// Use Template.ExecuteStd for frozen templates.
func ExecuteStd(template, startTag, endTag string, w io.Writer, m map[string]interface{}) (int64, error) {
return ExecuteFunc(template, startTag, endTag, w, func(w io.Writer, tag string) (int, error) { return keepUnknownTagFunc(w, startTag, endTag, tag, m) })
}
// ExecuteFuncString calls f on each template tag (placeholder) occurrence
// and substitutes it with the data written to TagFunc's w.
//
// Returns the resulting string.
//
// This function is optimized for constantly changing templates.
// Use Template.ExecuteFuncString for frozen templates.
func ExecuteFuncString(template, startTag, endTag string, f TagFunc) string {
s, err := ExecuteFuncStringWithErr(template, startTag, endTag, f)
if err != nil {
panic(fmt.Sprintf("unexpected error: %s", err))
}
return s
}
// ExecuteFuncStringWithErr is nearly the same as ExecuteFuncString
// but when f returns an error, ExecuteFuncStringWithErr won't panic like ExecuteFuncString
// it just returns an empty string and the error f returned
func ExecuteFuncStringWithErr(template, startTag, endTag string, f TagFunc) (string, error) {
tagsCount := bytes.Count(unsafeString2Bytes(template), unsafeString2Bytes(startTag))
if tagsCount == 0 {
return template, nil
}
bb := byteBufferPool.Get()
if _, err := ExecuteFunc(template, startTag, endTag, bb, f); err != nil {
bb.Reset()
byteBufferPool.Put(bb)
return "", err
}
s := string(bb.B)
bb.Reset()
byteBufferPool.Put(bb)
return s, nil
}
var byteBufferPool bytebufferpool.Pool
// ExecuteString substitutes template tags (placeholders) with the corresponding
// values from the map m and returns the result.
//
// Substitution map m may contain values with the following types:
// - []byte - the fastest value type
// - string - convenient value type
// - TagFunc - flexible value type
//
// This function is optimized for constantly changing templates.
// Use Template.ExecuteString for frozen templates.
func ExecuteString(template, startTag, endTag string, m map[string]interface{}) string {
return ExecuteFuncString(template, startTag, endTag, func(w io.Writer, tag string) (int, error) { return stdTagFunc(w, tag, m) })
}
// ExecuteStringStd works the same way as ExecuteString, but keeps the unknown placeholders.
// This can be used as a drop-in replacement for strings.Replacer
//
// Substitution map m may contain values with the following types:
// - []byte - the fastest value type
// - string - convenient value type
// - TagFunc - flexible value type
//
// This function is optimized for constantly changing templates.
// Use Template.ExecuteStringStd for frozen templates.
func ExecuteStringStd(template, startTag, endTag string, m map[string]interface{}) string {
return ExecuteFuncString(template, startTag, endTag, func(w io.Writer, tag string) (int, error) { return keepUnknownTagFunc(w, startTag, endTag, tag, m) })
}
// Template implements simple template engine, which can be used for fast
// tags' (aka placeholders) substitution.
type Template struct {
template string
startTag string
endTag string
texts [][]byte
tags []string
byteBufferPool bytebufferpool.Pool
}
// New parses the given template using the given startTag and endTag
// as tag start and tag end.
//
// The returned template can be executed by concurrently running goroutines
// using Execute* methods.
//
// New panics if the given template cannot be parsed. Use NewTemplate instead
// if template may contain errors.
func New(template, startTag, endTag string) *Template {
t, err := NewTemplate(template, startTag, endTag)
if err != nil {
panic(err)
}
return t
}
// NewTemplate parses the given template using the given startTag and endTag
// as tag start and tag end.
//
// The returned template can be executed by concurrently running goroutines
// using Execute* methods.
func NewTemplate(template, startTag, endTag string) (*Template, error) {
var t Template
err := t.Reset(template, startTag, endTag)
if err != nil {
return nil, err
}
return &t, nil
}
// TagFunc can be used as a substitution value in the map passed to Execute*.
// Execute* functions pass tag (placeholder) name in 'tag' argument.
//
// TagFunc must be safe to call from concurrently running goroutines.
//
// TagFunc must write contents to w and return the number of bytes written.
type TagFunc func(w io.Writer, tag string) (int, error)
// Reset resets the template t to new one defined by
// template, startTag and endTag.
//
// Reset allows Template object re-use.
//
// Reset may be called only if no other goroutines call t methods at the moment.
func (t *Template) Reset(template, startTag, endTag string) error {
// Keep these vars in t, so GC won't collect them and won't break
// vars derived via unsafe*
t.template = template
t.startTag = startTag
t.endTag = endTag
t.texts = t.texts[:0]
t.tags = t.tags[:0]
if len(startTag) == 0 {
panic("startTag cannot be empty")
}
if len(endTag) == 0 {
panic("endTag cannot be empty")
}
s := unsafeString2Bytes(template)
a := unsafeString2Bytes(startTag)
b := unsafeString2Bytes(endTag)
tagsCount := bytes.Count(s, a)
if tagsCount == 0 {
return nil
}
if tagsCount+1 > cap(t.texts) {
t.texts = make([][]byte, 0, tagsCount+1)
}
if tagsCount > cap(t.tags) {
t.tags = make([]string, 0, tagsCount)
}
for {
n := bytes.Index(s, a)
if n < 0 {
t.texts = append(t.texts, s)
break
}
t.texts = append(t.texts, s[:n])
s = s[n+len(a):]
n = bytes.Index(s, b)
if n < 0 {
return fmt.Errorf("cannot find end tag=%q in the template=%q starting from %q", endTag, template, s)
}
t.tags = append(t.tags, unsafeBytes2String(s[:n]))
s = s[n+len(b):]
}
return nil
}
// ExecuteFunc calls f on each template tag (placeholder) occurrence.
//
// Returns the number of bytes written to w.
//
// This function is optimized for frozen templates.
// Use ExecuteFunc for constantly changing templates.
func (t *Template) ExecuteFunc(w io.Writer, f TagFunc) (int64, error) {
var nn int64
n := len(t.texts) - 1
if n == -1 {
ni, err := w.Write(unsafeString2Bytes(t.template))
return int64(ni), err
}
for i := 0; i < n; i++ {
ni, err := w.Write(t.texts[i])
nn += int64(ni)
if err != nil {
return nn, err
}
ni, err = f(w, t.tags[i])
nn += int64(ni)
if err != nil {
return nn, err
}
}
ni, err := w.Write(t.texts[n])
nn += int64(ni)
return nn, err
}
// Execute substitutes template tags (placeholders) with the corresponding
// values from the map m and writes the result to the given writer w.
//
// Substitution map m may contain values with the following types:
// - []byte - the fastest value type
// - string - convenient value type
// - TagFunc - flexible value type
//
// Returns the number of bytes written to w.
func (t *Template) Execute(w io.Writer, m map[string]interface{}) (int64, error) {
return t.ExecuteFunc(w, func(w io.Writer, tag string) (int, error) { return stdTagFunc(w, tag, m) })
}
// ExecuteStd works the same way as Execute, but keeps the unknown placeholders.
// This can be used as a drop-in replacement for strings.Replacer
//
// Substitution map m may contain values with the following types:
// - []byte - the fastest value type
// - string - convenient value type
// - TagFunc - flexible value type
//
// Returns the number of bytes written to w.
func (t *Template) ExecuteStd(w io.Writer, m map[string]interface{}) (int64, error) {
return t.ExecuteFunc(w, func(w io.Writer, tag string) (int, error) { return keepUnknownTagFunc(w, t.startTag, t.endTag, tag, m) })
}
// ExecuteFuncString calls f on each template tag (placeholder) occurrence
// and substitutes it with the data written to TagFunc's w.
//
// Returns the resulting string.
//
// This function is optimized for frozen templates.
// Use ExecuteFuncString for constantly changing templates.
func (t *Template) ExecuteFuncString(f TagFunc) string {
s, err := t.ExecuteFuncStringWithErr(f)
if err != nil {
panic(fmt.Sprintf("unexpected error: %s", err))
}
return s
}
// ExecuteFuncStringWithErr calls f on each template tag (placeholder) occurrence
// and substitutes it with the data written to TagFunc's w.
//
// Returns the resulting string.
//
// This function is optimized for frozen templates.
// Use ExecuteFuncString for constantly changing templates.
func (t *Template) ExecuteFuncStringWithErr(f TagFunc) (string, error) {
bb := t.byteBufferPool.Get()
if _, err := t.ExecuteFunc(bb, f); err != nil {
bb.Reset()
t.byteBufferPool.Put(bb)
return "", err
}
s := string(bb.Bytes())
bb.Reset()
t.byteBufferPool.Put(bb)
return s, nil
}
// ExecuteString substitutes template tags (placeholders) with the corresponding
// values from the map m and returns the result.
//
// Substitution map m may contain values with the following types:
// - []byte - the fastest value type
// - string - convenient value type
// - TagFunc - flexible value type
//
// This function is optimized for frozen templates.
// Use ExecuteString for constantly changing templates.
func (t *Template) ExecuteString(m map[string]interface{}) string {
return t.ExecuteFuncString(func(w io.Writer, tag string) (int, error) { return stdTagFunc(w, tag, m) })
}
// ExecuteStringStd works the same way as ExecuteString, but keeps the unknown placeholders.
// This can be used as a drop-in replacement for strings.Replacer
//
// Substitution map m may contain values with the following types:
// - []byte - the fastest value type
// - string - convenient value type
// - TagFunc - flexible value type
//
// This function is optimized for frozen templates.
// Use ExecuteStringStd for constantly changing templates.
func (t *Template) ExecuteStringStd(m map[string]interface{}) string {
return t.ExecuteFuncString(func(w io.Writer, tag string) (int, error) { return keepUnknownTagFunc(w, t.startTag, t.endTag, tag, m) })
}
func stdTagFunc(w io.Writer, tag string, m map[string]interface{}) (int, error) {
v := m[tag]
if v == nil {
return 0, nil
}
switch value := v.(type) {
case []byte:
return w.Write(value)
case string:
return w.Write([]byte(value))
case TagFunc:
return value(w, tag)
default:
panic(fmt.Sprintf("tag=%q contains unexpected value type=%#v. Expected []byte, string or TagFunc", tag, v))
}
}
func keepUnknownTagFunc(w io.Writer, startTag, endTag, tag string, m map[string]interface{}) (int, error) {
v, ok := m[tag]
if !ok {
if _, err := w.Write(unsafeString2Bytes(startTag)); err != nil {
return 0, err
}
if _, err := w.Write(unsafeString2Bytes(tag)); err != nil {
return 0, err
}
if _, err := w.Write(unsafeString2Bytes(endTag)); err != nil {
return 0, err
}
return len(startTag) + len(tag) + len(endTag), nil
}
if v == nil {
return 0, nil
}
switch value := v.(type) {
case []byte:
return w.Write(value)
case string:
return w.Write([]byte(value))
case TagFunc:
return value(w, tag)
default:
panic(fmt.Sprintf("tag=%q contains unexpected value type=%#v. Expected []byte, string or TagFunc", tag, v))
}
}

View File

@ -1,22 +0,0 @@
//go:build !appengine
// +build !appengine
package fasttemplate
import (
"reflect"
"unsafe"
)
func unsafeBytes2String(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}
func unsafeString2Bytes(s string) (b []byte) {
sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
bh.Data = sh.Data
bh.Cap = sh.Len
bh.Len = sh.Len
return b
}

View File

@ -1,12 +0,0 @@
//go:build appengine
// +build appengine
package fasttemplate
func unsafeBytes2String(b []byte) string {
return string(b)
}
func unsafeString2Bytes(s string) []byte {
return []byte(s)
}

View File

@ -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)
@ -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, data *Data, extraParam string) (int, error) {
return buf.WriteString("it is a custom tag")
},
},
}))
```
## Config
```go
@ -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

View File

@ -7,6 +7,7 @@ import (
"time"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/internal/bytebufferpool"
)
// Config defines the config for middleware.
@ -22,6 +23,11 @@ type Config struct {
// Optional. Default: a function that does nothing.
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} ${method} ${path}\n
@ -52,6 +58,14 @@ type Config struct {
timeZoneLocation *time.Location
}
const (
startTag = "${"
endTag = "}"
paramSeparator = ":"
)
type LogFunc func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, data *Data, extraParam string) (int, error)
// ConfigDefault is the default config
var ConfigDefault = Config{
Next: nil,
@ -65,11 +79,8 @@ var ConfigDefault = Config{
}
// Function to check if the logger format is compatible for coloring
func validCustomFormat(format string) bool {
func checkColorEnable(format string) bool {
validTemplates := []string{"${status}", "${method}"}
if format == "" {
return true
}
for _, template := range validTemplates {
if strings.Contains(format, template) {
return true
@ -88,11 +99,6 @@ func configDefault(config ...Config) Config {
// 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
@ -103,6 +109,7 @@ func configDefault(config ...Config) Config {
if cfg.Format == "" {
cfg.Format = ConfigDefault.Format
}
if cfg.TimeZone == "" {
cfg.TimeZone = ConfigDefault.TimeZone
}
@ -115,5 +122,11 @@ func configDefault(config ...Config) Config {
if cfg.Output == nil {
cfg.Output = ConfigDefault.Output
}
// Enable colors if no custom format or output is given
if cfg.Output == nil && checkColorEnable(cfg.Format) {
cfg.enableColors = true
}
return cfg
}

19
middleware/logger/data.go Normal file
View File

@ -0,0 +1,19 @@
package logger
import (
"sync"
"sync/atomic"
"time"
)
var DataPool = sync.Pool{New: func() interface{} { return new(Data) }}
// 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
}

View File

@ -2,7 +2,6 @@ package logger
import (
"fmt"
"io"
"os"
"strconv"
"strings"
@ -10,56 +9,13 @@ import (
"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/fasthttp"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/internal/bytebufferpool"
"github.com/gofiber/fiber/v2/internal/fasttemplate"
)
// 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"
)
// New creates a new middleware handler
@ -76,17 +32,14 @@ func New(config ...Config) fiber.Handler {
}
// Check if format contains latency
cfg.enableLatency = strings.Contains(cfg.Format, "${latency}")
cfg.enableLatency = strings.Contains(cfg.Format, "${"+TagLatency+"}")
// Create template parser
tmpl := fasttemplate.New(cfg.Format, "${", "}")
// Create correct timeformat
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, "${time}") {
if strings.Contains(cfg.Format, "${"+TagTime+"}") {
go func() {
for {
time.Sleep(cfg.TimeInterval)
@ -114,6 +67,14 @@ func New(config ...Config) fiber.Handler {
}
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) (err error) {
// Don't execute middleware if Next returns true
@ -140,16 +101,24 @@ func New(config ...Config) fiber.Handler {
errHandler = c.App().ErrorHandler
})
var start, stop time.Time
// Logger data
data := DataPool.Get().(*Data)
// 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 {
start = time.Now()
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 {
@ -159,7 +128,7 @@ func New(config ...Config) fiber.Handler {
// Set latency stop time
if cfg.enableLatency {
stop = time.Now()
data.Stop = time.Now()
}
// Get new buffer
@ -177,7 +146,7 @@ func New(config ...Config) fiber.Handler {
_, _ = buf.WriteString(fmt.Sprintf("%s |%s %3d %s| %7v | %15s |%s %-7s %s| %-"+errPaddingStr+"s %s\n",
timestamp.Load().(string),
statusColor(c.Response().StatusCode(), colors), c.Response().StatusCode(), colors.Reset,
stop.Sub(start).Round(time.Millisecond),
data.Stop.Sub(data.Start).Round(time.Millisecond),
c.IP(),
methodColor(c.Method(), colors), c.Method(), colors.Reset,
c.Path(),
@ -196,114 +165,20 @@ func New(config ...Config) fiber.Handler {
return nil
}
// 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))
}
}
// 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])
} else if templateChain[i] == nil {
_, err = logFunc(buf, c, data, "")
} else {
_, err = logFunc(buf, c, data, utils.UnsafeString(templateChain[i]))
}
return 0, nil
})
if err != nil {
break
}
}
// Also write errors to the buffer
if err != nil {
_, _ = buf.WriteString(err.Error())

View File

@ -287,30 +287,59 @@ func Test_Logger_Data_Race(t *testing.T) {
// go test -v -run=^$ -bench=Benchmark_Logger -benchmem -count=4
func Benchmark_Logger(b *testing.B) {
app := fiber.New()
benchSetup := func(bb *testing.B, app *fiber.App) {
h := app.Handler()
app.Use(New(Config{
Format: "${bytesReceived} ${bytesSent} ${status}",
Output: io.Discard,
}))
app.Get("/", func(c *fiber.Ctx) error {
return c.SendString("Hello, World!")
})
fctx := &fasthttp.RequestCtx{}
fctx.Request.Header.SetMethod("GET")
fctx.Request.SetRequestURI("/")
h := app.Handler()
bb.ReportAllocs()
bb.ResetTimer()
fctx := &fasthttp.RequestCtx{}
fctx.Request.Header.SetMethod("GET")
fctx.Request.SetRequestURI("/")
for n := 0; n < bb.N; n++ {
h(fctx)
}
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
h(fctx)
utils.AssertEqual(bb, 200, fctx.Response.Header.StatusCode())
}
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
@ -383,3 +412,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, data *Data, extraParam 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())
}

208
middleware/logger/tags.go Normal file
View File

@ -0,0 +1,208 @@
package logger
import (
"fmt"
"strings"
"time"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/internal/bytebufferpool"
)
// 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(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
return buf.WriteString(c.Get(fiber.HeaderReferer))
},
TagProtocol: func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
return buf.WriteString(c.Protocol())
},
TagPort: func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
return buf.WriteString(c.Port())
},
TagIP: func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
return buf.WriteString(c.IP())
},
TagIPs: func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
return buf.WriteString(c.Get(fiber.HeaderXForwardedFor))
},
TagHost: func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
return buf.WriteString(c.Hostname())
},
TagPath: func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
return buf.WriteString(c.Path())
},
TagURL: func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
return buf.WriteString(c.OriginalURL())
},
TagUA: func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
return buf.WriteString(c.Get(fiber.HeaderUserAgent))
},
TagBody: func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
return buf.Write(c.Body())
},
TagBytesReceived: func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
return appendInt(buf, len(c.Request().Body()))
},
TagBytesSent: func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
return appendInt(buf, len(c.Response().Body()))
},
TagRoute: func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
return buf.WriteString(c.Route().Path)
},
TagResBody: func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
return buf.Write(c.Response().Body())
},
TagReqHeaders: func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
reqHeaders := make([]string, 0)
for k, v := range c.GetReqHeaders() {
reqHeaders = append(reqHeaders, k+"="+v)
}
return buf.Write([]byte(strings.Join(reqHeaders, "&")))
},
TagQueryStringParams: func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
return buf.WriteString(c.Request().URI().QueryArgs().String())
},
TagBlack: func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
return buf.WriteString(c.App().Config().ColorScheme.Black)
},
TagRed: func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
return buf.WriteString(c.App().Config().ColorScheme.Red)
},
TagGreen: func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
return buf.WriteString(c.App().Config().ColorScheme.Green)
},
TagYellow: func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
return buf.WriteString(c.App().Config().ColorScheme.Yellow)
},
TagBlue: func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
return buf.WriteString(c.App().Config().ColorScheme.Blue)
},
TagMagenta: func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
return buf.WriteString(c.App().Config().ColorScheme.Magenta)
},
TagCyan: func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
return buf.WriteString(c.App().Config().ColorScheme.Cyan)
},
TagWhite: func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
return buf.WriteString(c.App().Config().ColorScheme.White)
},
TagReset: func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
return buf.WriteString(c.App().Config().ColorScheme.Reset)
},
TagError: func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
if data.ChainErr != nil {
return buf.WriteString(data.ChainErr.Error())
}
return buf.WriteString("-")
},
TagReqHeader: func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
return buf.WriteString(c.Get(extraParam))
},
TagHeader: func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
return buf.WriteString(c.Get(extraParam))
},
TagRespHeader: func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
return buf.WriteString(c.GetRespHeader(extraParam))
},
TagQuery: func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
return buf.WriteString(c.Query(extraParam))
},
TagForm: func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
return buf.WriteString(c.FormValue(extraParam))
},
TagCookie: func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
return buf.WriteString(c.Cookies(extraParam))
},
TagLocals: func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
switch v := c.Locals(extraParam).(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))
}
},
TagStatus: func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
if cfg.enableColors {
colors := c.App().Config().ColorScheme
return buf.WriteString(fmt.Sprintf("%s %3d %s", statusColor(c.Response().StatusCode(), colors), c.Response().StatusCode(), colors.Reset))
}
return appendInt(buf, c.Response().StatusCode())
},
TagMethod: func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
if cfg.enableColors {
colors := c.App().Config().ColorScheme
return buf.WriteString(fmt.Sprintf("%s %-7s %s", methodColor(c.Method(), colors), c.Method(), colors.Reset))
}
return buf.WriteString(c.Method())
},
TagPid: func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
return buf.WriteString(data.Pid)
},
TagLatency: func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
latency := data.Stop.Sub(data.Start).Round(time.Millisecond)
return buf.WriteString(fmt.Sprintf("%7v", latency))
},
TagTime: func(buf *bytebufferpool.ByteBuffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
return buf.WriteString(data.Timestamp.Load().(string))
},
}
// merge with custom tags from user
if cfg.CustomTags != nil {
for k, v := range cfg.CustomTags {
tagFunctions[k] = v
}
}
return tagFunctions
}

View File

@ -0,0 +1,67 @@
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) (fixParts [][]byte, funcChain []LogFunc, err 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)
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 {
if logFunc, ok := tagFunctions[utils.UnsafeString(templateB[:index+1])]; ok {
funcChain = append(funcChain, logFunc)
// add param to the fixParts
fixParts = append(fixParts, templateB[index+1:currentPos])
} else {
return nil, nil, errors.New("No parameter found in \"" + utils.UnsafeString(templateB[: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
}