mirror of https://github.com/gofiber/fiber.git
✨ 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
parent
3157fb5f1c
commit
e8f8cb647b
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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())
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue