diff --git a/.github/README.md b/.github/README.md index 43f675f1a0..acced73074 100644 --- a/.github/README.md +++ b/.github/README.md @@ -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) diff --git a/.github/README_ckb.md b/.github/README_ckb.md index c62c5d1230..9c5841340a 100644 --- a/.github/README_ckb.md +++ b/.github/README_ckb.md @@ -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) diff --git a/.github/README_de.md b/.github/README_de.md index aca4a2faeb..c3c33942bd 100644 --- a/.github/README_de.md +++ b/.github/README_de.md @@ -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) diff --git a/.github/README_es.md b/.github/README_es.md index 0d3d36a230..918207b59f 100644 --- a/.github/README_es.md +++ b/.github/README_es.md @@ -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) diff --git a/.github/README_fa.md b/.github/README_fa.md index 7fe0475fa7..5cbaba189f 100644 --- a/.github/README_fa.md +++ b/.github/README_fa.md @@ -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) diff --git a/.github/README_fr.md b/.github/README_fr.md index 8ced8bc23e..4ecf6da868 100644 --- a/.github/README_fr.md +++ b/.github/README_fr.md @@ -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) diff --git a/.github/README_he.md b/.github/README_he.md index 3a8a7c6780..5457e55eb3 100644 --- a/.github/README_he.md +++ b/.github/README_he.md @@ -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) diff --git a/.github/README_id.md b/.github/README_id.md index 4d4fc3f7be..98f5fdf409 100644 --- a/.github/README_id.md +++ b/.github/README_id.md @@ -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) diff --git a/.github/README_it.md b/.github/README_it.md index b3df35e5da..5f8b06c0b0 100644 --- a/.github/README_it.md +++ b/.github/README_it.md @@ -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) diff --git a/.github/README_ja.md b/.github/README_ja.md index 29e3f3a15f..4264f83736 100644 --- a/.github/README_ja.md +++ b/.github/README_ja.md @@ -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) diff --git a/.github/README_ko.md b/.github/README_ko.md index c2479baee1..ca2008d55d 100644 --- a/.github/README_ko.md +++ b/.github/README_ko.md @@ -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) diff --git a/.github/README_nl.md b/.github/README_nl.md index 854e360c66..85a7888f11 100644 --- a/.github/README_nl.md +++ b/.github/README_nl.md @@ -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) diff --git a/.github/README_pt.md b/.github/README_pt.md index 52df5a58ac..4568b145d3 100644 --- a/.github/README_pt.md +++ b/.github/README_pt.md @@ -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) diff --git a/.github/README_ru.md b/.github/README_ru.md index dad365ad20..a87dcb94a4 100644 --- a/.github/README_ru.md +++ b/.github/README_ru.md @@ -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) diff --git a/.github/README_sa.md b/.github/README_sa.md index efc80c4bfe..740c754e20 100644 --- a/.github/README_sa.md +++ b/.github/README_sa.md @@ -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) diff --git a/.github/README_tr.md b/.github/README_tr.md index c425d40c11..4e4864c786 100644 --- a/.github/README_tr.md +++ b/.github/README_tr.md @@ -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) diff --git a/.github/README_zh-CN.md b/.github/README_zh-CN.md index 2a64bd04fd..0936f09f2f 100644 --- a/.github/README_zh-CN.md +++ b/.github/README_zh-CN.md @@ -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) diff --git a/.github/README_zh-TW.md b/.github/README_zh-TW.md index c1999289aa..68d16bdd23 100644 --- a/.github/README_zh-TW.md +++ b/.github/README_zh-TW.md @@ -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) diff --git a/internal/fasttemplate/LICENSE b/internal/fasttemplate/LICENSE deleted file mode 100644 index 7125a63c4c..0000000000 --- a/internal/fasttemplate/LICENSE +++ /dev/null @@ -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. - diff --git a/internal/fasttemplate/template.go b/internal/fasttemplate/template.go deleted file mode 100644 index d4559feef8..0000000000 --- a/internal/fasttemplate/template.go +++ /dev/null @@ -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)) - } -} diff --git a/internal/fasttemplate/unsafe.go b/internal/fasttemplate/unsafe.go deleted file mode 100644 index 1d0bc9e857..0000000000 --- a/internal/fasttemplate/unsafe.go +++ /dev/null @@ -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 -} diff --git a/internal/fasttemplate/unsafe_gae.go b/internal/fasttemplate/unsafe_gae.go deleted file mode 100644 index dbfed3bac4..0000000000 --- a/internal/fasttemplate/unsafe_gae.go +++ /dev/null @@ -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) -} diff --git a/middleware/logger/README.md b/middleware/logger/README.md index 63c1530fda..f6a7011711 100644 --- a/middleware/logger/README.md +++ b/middleware/logger/README.md @@ -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 diff --git a/middleware/logger/config.go b/middleware/logger/config.go index 0e8aaa5caf..685ce618f4 100644 --- a/middleware/logger/config.go +++ b/middleware/logger/config.go @@ -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 } diff --git a/middleware/logger/data.go b/middleware/logger/data.go new file mode 100644 index 0000000000..611a0aeee2 --- /dev/null +++ b/middleware/logger/data.go @@ -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 +} diff --git a/middleware/logger/logger.go b/middleware/logger/logger.go index dc348bfebe..41d2564a04 100644 --- a/middleware/logger/logger.go +++ b/middleware/logger/logger.go @@ -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}") - - // Create template parser - tmpl := fasttemplate.New(cfg.Format, "${", "}") + cfg.enableLatency = strings.Contains(cfg.Format, "${"+TagLatency+"}") - // 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()) diff --git a/middleware/logger/logger_test.go b/middleware/logger/logger_test.go index ba566d2c2e..eae71bb313 100644 --- a/middleware/logger/logger_test.go +++ b/middleware/logger/logger_test.go @@ -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() - - app.Use(New(Config{ - Format: "${bytesReceived} ${bytesSent} ${status}", - Output: io.Discard, - })) - app.Get("/", func(c *fiber.Ctx) error { - return c.SendString("Hello, World!") - }) + benchSetup := func(bb *testing.B, app *fiber.App) { + h := app.Handler() - h := app.Handler() + fctx := &fasthttp.RequestCtx{} + fctx.Request.Header.SetMethod("GET") + fctx.Request.SetRequestURI("/") - fctx := &fasthttp.RequestCtx{} - fctx.Request.Header.SetMethod("GET") - fctx.Request.SetRequestURI("/") + bb.ReportAllocs() + bb.ResetTimer() - b.ReportAllocs() - b.ResetTimer() + for n := 0; n < bb.N; n++ { + h(fctx) + } - 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()) +} diff --git a/middleware/logger/tags.go b/middleware/logger/tags.go new file mode 100644 index 0000000000..db72c43f4e --- /dev/null +++ b/middleware/logger/tags.go @@ -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 +} diff --git a/middleware/logger/template_chain.go b/middleware/logger/template_chain.go new file mode 100644 index 0000000000..ceb31a3356 --- /dev/null +++ b/middleware/logger/template_chain.go @@ -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 +}