Compare commits

..

No commits in common. "24df729eb64c85aa21ba0060616f44ca8314efc1" and "f5a1ba9c180bd8eb95d5b3ebf797b22c5b6aae1b" have entirely different histories.

6 changed files with 302 additions and 207 deletions

View File

@ -4,21 +4,3 @@
Mem: `go test -o tests/test.exe -memprofile tests/mem && go tool pprof -http : tests/mem` Mem: `go test -o tests/test.exe -memprofile tests/mem && go tool pprof -http : tests/mem`
Cpu: `go test -o tests/test.exe -cpuprofile tests/cpu && go tool pprof -http : tests/cpu` Cpu: `go test -o tests/test.exe -cpuprofile tests/cpu && go tool pprof -http : tests/cpu`
## Bench
```
go test -bench . -run none --benchmem
v0.0.1
BenchmarkDefault-8 8512 158272 ns/op
BenchmarkSub1-8 6140 166344 ns/op
BenchmarkDefault-8 6190 189078 ns/op 19522 B/op 387 allocs/op
BenchmarkSub1-8 6222 200761 ns/op 20511 B/op 435 allocs/op
go test -bench . -run none --benchmem
test(v0.0.2)
BenchmarkDefault-8 12410 109143 ns/op
BenchmarkSub1-8 10000 113340 ns/op
BenchmarkDefault-8 12186 112974 ns/op 21944 B/op 303 allocs/op
BenchmarkSub1-8 10000 114026 ns/op 22702 B/op 363 allocs/op
```

View File

@ -3,6 +3,7 @@ package klog
import ( import (
"fmt" "fmt"
"strconv" "strconv"
"text/template"
"kumoly.io/lib/klog/color" "kumoly.io/lib/klog/color"
) )
@ -23,3 +24,28 @@ func (l *Logger) M(s interface{}, attrs ...color.Attribute) string {
} }
return fmt.Sprintf("\033[%sm%s\033[0m", styles, s) return fmt.Sprintf("\033[%sm%s\033[0m", styles, s)
} }
func (l *Logger) setColorMap(funcMap template.FuncMap) {
funcMap["M"] = l.M
if l.color {
funcMap["red"] = func(s interface{}) string { return fmt.Sprintf("\033[91m%s\033[0m", s) }
funcMap["redl"] = func(s interface{}) string { return fmt.Sprintf("\033[31m%s\033[0m", s) }
funcMap["green"] = func(s interface{}) string { return fmt.Sprintf("\033[92m%s\033[0m", s) }
funcMap["yellow"] = func(s interface{}) string { return fmt.Sprintf("\033[93m%s\033[0m", s) }
funcMap["blue"] = func(s interface{}) string { return fmt.Sprintf("\033[94m%s\033[0m", s) }
funcMap["magenta"] = func(s interface{}) string { return fmt.Sprintf("\033[95m%s\033[0m", s) }
funcMap["cyan"] = func(s interface{}) string { return fmt.Sprintf("\033[96m%s\033[0m", s) }
funcMap["white"] = func(s interface{}) string { return fmt.Sprintf("\033[97m%s\033[0m", s) }
return
}
funcMap["red"] = func(s string) string { return s }
funcMap["redl"] = func(s string) string { return s }
funcMap["green"] = func(s string) string { return s }
funcMap["yellow"] = func(s string) string { return s }
funcMap["blue"] = func(s string) string { return s }
funcMap["magenta"] = func(s string) string { return s }
funcMap["cyan"] = func(s string) string { return s }
funcMap["white"] = func(s string) string { return s }
}
// func

308
log.go
View File

@ -6,45 +6,79 @@ import (
"io" "io"
"os" "os"
"sync" "sync"
"text/template"
"time" "time"
"github.com/mattn/go-isatty" "github.com/mattn/go-isatty"
"kumoly.io/lib/klog/color"
) )
type Llevel int
const ( const (
Lerror Llevel = 1 << iota Lerror = 1 << iota
Ldebug Ldebug
Lwarn Lwarn
Linfo Linfo
) )
type tout int
const (
terror tout = iota
tdebug
tinfo
twarn
)
var lock sync.Mutex var lock sync.Mutex
var PROD = true var PROD = true
var LEVEL = Lerror | Linfo var LEVEL = Lerror | Linfo
const (
DEFAULT_ERR = `{{Time}} [{{"ERROR"|red}}]` +
`{{if .System}}({{.System|cyan}}){{end}} ` +
`{{.Caller}} {{.Message}}` +
`{{if .Fields}} {{.Fields|json|green}}{{end}}{{"\n"}}` +
`{{if .Stack}}{{.Stack|redl}}{{end}}`
DEFAULT_DEBUG = `{{Time}} [{{"DEBUG"|magenta}}]` +
`{{if .System}}({{.System|cyan}}){{end}} ` +
`{{.Caller}} {{.Message}}` +
`{{if .Fields}} {{.Fields|json|green}}{{end}}{{"\n"}}` +
`{{if .Stack}}{{.Stack|redl}}{{end}}`
DEFAULT_WARN = `{{Time}} [{{"WARN"|yellow}} ]` +
`{{if .System}}({{.System|cyan}}){{end}} ` +
`{{.Message}}` +
`{{if .Fields}} {{.Fields|json|green}}{{end}}{{"\n"}}`
DEFAULT_INFO = `{{Time}} [{{"INFO"|blue}} ]` +
`{{if .System}}({{.System|cyan}}){{end}} ` +
`{{.Message}}` +
`{{if .Fields}} {{.Fields|json|green}}{{end}}{{"\n"}}`
)
type LogFormater struct {
ErrTmplStr string
WarnTmplStr string
InfoTmplStr string
DebugTmplStr string
}
type H map[string]interface{} type H map[string]interface{}
type Ldata struct { type Ldata struct {
Message string Message string
Time time.Time System string
Level Llevel
// Caller only evaluates in DEBUG, and ERROR calls // Caller only evaluates in DEBUG, and ERROR calls
Caller string Caller string
// Stack only eval when !PROD and {DEBUG, ERROR} // Stack only eval when !PROD and {DEBUG, ERROR}
Stack string Stack string
Fields *H Fields H
Color bool Color bool
} }
type Logger struct { type Logger struct {
System string system string
color bool color bool
explicit bool explicit bool
@ -52,103 +86,38 @@ type Logger struct {
err io.Writer err io.Writer
out io.Writer out io.Writer
printer Printer formatter *LogFormater
funcMap template.FuncMap
subs []*Logger tmpl *template.Template
} err_tmpl *template.Template
debug_tmpl *template.Template
type Printer func(io.Writer, *Ldata, *Logger) warn_tmpl *template.Template
info_tmpl *template.Template
func DefaultPrinter() Printer { subs []*Logger
return func(w io.Writer, d *Ldata, l *Logger) {
var level string
caller := ""
sys := ""
if l.System != "" {
sys = fmt.Sprintf("(%s)", l.System)
}
fields := []byte{}
if d.Fields != nil {
b, err := json.Marshal(d.Fields)
if err != nil {
fields = b
}
}
switch d.Level {
case Lerror:
level = l.M("ERROR", color.FgHiRed)
caller = d.Caller
case Ldebug:
level = l.M("DEBUG", color.FgHiMagenta)
caller = d.Caller
case Lwarn:
level = l.M("WARN ", color.FgHiYellow)
case Linfo:
level = l.M("INFO ", color.FgHiBlue)
}
_, err := fmt.Fprintf(w, "%s [%s]%s%s %s%s\n%s",
d.Time.Format("2006/01/02 15:04:05"),
level, l.M(sys, color.FgHiCyan),
caller, l.M(d.Message, color.FgHiGreen), fields,
l.M(d.Stack, color.FgRed),
)
if err != nil {
fmt.Println(err)
}
}
}
func (l *Logger) output(lev Llevel, depth int, fields H, v ...interface{}) {
if LEVEL&lev == 0 {
return
}
msg := fmt.Sprint(v...)
data := &Ldata{
Fields: &fields,
Message: msg,
Level: lev,
Time: time.Now(),
Caller: caller(depth),
Color: l.color,
}
switch lev {
case Lerror:
if !PROD {
data.Stack = stack()
}
case Ldebug:
if !PROD {
data.Stack = stack()
}
}
lock.Lock()
defer lock.Unlock()
if lev == Lerror || lev == Ldebug {
l.printer(l.err, data, l)
} else {
l.printer(l.out, data, l)
}
} }
func New(name string) *Logger { func New(name string) *Logger {
l := &Logger{ l := &Logger{
System: name, system: name,
err: os.Stderr, err: os.Stderr,
out: os.Stdout, out: os.Stdout,
color: true, color: true,
printer: DefaultPrinter(), formatter: NewLogFormater(),
} }
l.guessColor() l.Reload()
return l return l
} }
func (l *Logger) Sub(sys string) *Logger { func (l *Logger) Sub(sys string) *Logger {
ret := &Logger{ ret := &Logger{
System: sys, system: sys,
err: l.err, err: l.err,
out: l.out, out: l.out,
color: l.color, color: l.color,
printer: l.printer, formatter: l.formatter,
tmpl: l.tmpl,
} }
if l.subs == nil { if l.subs == nil {
l.subs = make([]*Logger, 0) l.subs = make([]*Logger, 0)
@ -181,73 +150,176 @@ func (l *Logger) SetColorAll(c bool) {
func (l *Logger) SetErrOutput(err io.Writer) { func (l *Logger) SetErrOutput(err io.Writer) {
l.err = err l.err = err
l.guessColor()
} }
func (l *Logger) SetErrOutputAll(err io.Writer) { func (l *Logger) SetErrOutputAll(err io.Writer) {
for _, v := range getAllOffsprings(l) { for _, v := range getAllOffsprings(l) {
v.SetErrOutput(err) v.err = err
} }
} }
func (l *Logger) SetOutput(out io.Writer) { func (l *Logger) SetOutput(out io.Writer) {
l.out = out l.out = out
l.guessColor()
} }
func (l *Logger) SetOutputAll(out io.Writer) { func (l *Logger) SetOutputAll(out io.Writer) {
for _, v := range getAllOffsprings(l) { for _, v := range getAllOffsprings(l) {
v.SetOutput(out) v.out = out
} }
} }
func (l *Logger) SetPrinter(p Printer) { func (l *Logger) Reload() error {
l.printer = p if !l.explicit {
l.guessColor()
}
return l.ParseTmpl()
} }
func (l *Logger) SetPrinterAll(p Printer) { func (l *Logger) ReloadAll() error {
for _, v := range getAllOffsprings(l) { for _, v := range getAllOffsprings(l) {
v.SetPrinter(p) if err := v.Reload(); err != nil {
return err
}
}
return nil
}
func NewLogFormater() *LogFormater {
return &LogFormater{
ErrTmplStr: DEFAULT_ERR,
WarnTmplStr: DEFAULT_WARN,
InfoTmplStr: DEFAULT_INFO,
DebugTmplStr: DEFAULT_DEBUG,
} }
} }
// func (l *Logger) DefaultFuncMap() template.FuncMap { func (l *Logger) DefaultFuncMap() template.FuncMap {
// funcMap := template.FuncMap{ funcMap := template.FuncMap{
// "Time": func() string { return time.Now().Format("2006/01/02 15:04:05") }, "Time": func() string { return time.Now().Format("2006/01/02 15:04:05") },
// "json": func(i interface{}) (string, error) { r, err := json.Marshal(i); return string(r), err }, "json": func(i interface{}) (string, error) { r, err := json.Marshal(i); return string(r), err },
// } }
// return funcMap return funcMap
// } }
func (l *Logger) ParseTmpl() error {
if l.funcMap == nil {
l.funcMap = l.DefaultFuncMap()
}
funcMap := copyFuncMap(l.funcMap)
l.setColorMap(funcMap)
tmpl, err := template.New("info_tmpl").Funcs(funcMap).Parse(l.formatter.InfoTmplStr)
if err != nil {
return err
}
tmpl, err = tmpl.New("err_tmpl").Parse(l.formatter.ErrTmplStr)
if err != nil {
return err
}
tmpl, err = tmpl.New("warn_tmpl").Parse(l.formatter.WarnTmplStr)
if err != nil {
return err
}
tmpl, err = tmpl.New("debug_tmpl").Parse(l.formatter.DebugTmplStr)
if err != nil {
return err
}
l.tmpl = tmpl
return nil
}
func (l *Logger) SetTmpl(formatter *LogFormater, funcMap template.FuncMap) {
l.formatter = formatter
if funcMap != nil {
l.funcMap = funcMap
}
}
func (l *Logger) output(t tout, depth int, stack string, fields H, v ...interface{}) {
msg := fmt.Sprint(v...)
data := Ldata{
Fields: fields,
Message: msg,
System: l.system,
Caller: caller(depth),
Color: l.color,
Stack: stack,
}
lock.Lock()
defer lock.Unlock()
var err error
switch t {
case terror:
if LEVEL&Lerror == 0 {
return
}
err = l.tmpl.ExecuteTemplate(l.err, "err_tmpl", data)
case tdebug:
if LEVEL&Ldebug == 0 {
return
}
err = l.tmpl.ExecuteTemplate(l.err, "debug_tmpl", data)
case twarn:
if LEVEL&Lwarn == 0 {
return
}
err = l.tmpl.ExecuteTemplate(l.out, "warn_tmpl", data)
case tinfo:
if LEVEL&Linfo == 0 {
return
}
err = l.tmpl.ExecuteTemplate(l.out, "info_tmpl", data)
}
if err != nil {
fmt.Fprintln(l.err, "[FATAL] Logger error:", err)
panic(err)
}
}
func (l *Logger) ErrorF(fields H, v ...interface{}) { func (l *Logger) ErrorF(fields H, v ...interface{}) {
l.output(Lerror, 3, fields, v...) if PROD {
l.output(terror, 3, "", fields, v...)
} else {
l.output(terror, 3, stack(), fields, v...)
}
} }
func (l *Logger) Error(v ...interface{}) { func (l *Logger) Error(v ...interface{}) {
l.output(Lerror, 3, H{}, v...) if PROD {
l.output(terror, 3, "", H{}, v...)
} else {
l.output(terror, 3, stack(), H{}, v...)
}
} }
func (l *Logger) DebugF(fields H, v ...interface{}) { func (l *Logger) DebugF(fields H, v ...interface{}) {
l.output(Ldebug, 3, fields, v...) if PROD {
l.output(tdebug, 3, "", fields, v...)
} else {
l.output(tdebug, 3, stack(), fields, v...)
}
} }
func (l *Logger) Debug(v ...interface{}) { func (l *Logger) Debug(v ...interface{}) {
l.output(Ldebug, 3, H{}, v...) if PROD {
l.output(tdebug, 3, "", H{}, v...)
} else {
l.output(tdebug, 3, stack(), H{}, v...)
}
} }
func (l *Logger) WarnF(fields H, v ...interface{}) { func (l *Logger) WarnF(fields H, v ...interface{}) {
l.output(Lwarn, 3, fields, v...) l.output(twarn, 3, "", fields, v...)
} }
func (l *Logger) Warn(v ...interface{}) { func (l *Logger) Warn(v ...interface{}) {
l.output(Lwarn, 3, H{}, v...) l.output(twarn, 3, "", H{}, v...)
} }
func (l *Logger) InfoF(fields H, v ...interface{}) { func (l *Logger) InfoF(fields H, v ...interface{}) {
l.output(Linfo, 3, fields, v...) l.output(tinfo, 3, "", fields, v...)
} }
func (l *Logger) Info(v ...interface{}) { func (l *Logger) Info(v ...interface{}) {
l.output(Linfo, 3, H{}, v...) l.output(tinfo, 3, "", H{}, v...)
} }

View File

@ -61,6 +61,32 @@ func TestSubProd(t *testing.T) {
l.InfoF(H{"Test": "set"}, "TestSubProd") l.InfoF(H{"Test": "set"}, "TestSubProd")
} }
func TestCustTmpl(t *testing.T) {
PROD = true
l := Sub("TestCustTmpl")
CustFormater := NewLogFormater()
CustFormater.InfoTmplStr = `{{Time}} [{{ M "INFO" 104 93 9}} ]` +
`{{if .System}}({{M .System 96}}){{end}} ` +
`Hello, {{.Fields.Name}}. {{.Message}}{{"\n"}}`
CustFormater.ErrTmplStr = `{{Time}} [{{ M "ERROR" 91 43 1}}]` +
`{{if .System}}({{M .System 96}}){{end}} ` +
`NO! {{.Fields.Name}} {{M .Message 31 3}} {{"\n"}}`
l.SetTmpl(CustFormater, nil)
l.Reload()
l.ErrorF(H{"Name": "Brandon"}, "TestCustTmpl")
l.InfoF(H{"Name": "Brandon"}, "TestCustTmpl")
SetTmpl(CustFormater, nil)
Reload()
ErrorF(H{"Name": "Brandon"}, "TestCustTmpl std")
InfoF(H{"Name": "Brandon"}, "TestCustTmpl std")
//reset
SetTmpl(NewLogFormater(), nil)
Reload()
}
func TestColoring(t *testing.T) { func TestColoring(t *testing.T) {
l := Sub("color") l := Sub("color")
l.Error(l.M("Hello", c.BgHiGreen, c.Italic, c.FgWhite), ", ", l.M("world", c.FgBlue, c.BgHiYellow)) l.Error(l.M("Hello", c.BgHiGreen, c.Italic, c.FgWhite), ", ", l.M("world", c.FgBlue, c.BgHiYellow))
@ -78,6 +104,7 @@ func TestToFile(t *testing.T) {
} }
l.SetOutput(f) l.SetOutput(f)
l.SetErrOutput(f) l.SetErrOutput(f)
l.Reload()
l.Error("TestToFile") l.Error("TestToFile")
l.Debug("TestToFile") l.Debug("TestToFile")
@ -85,6 +112,7 @@ func TestToFile(t *testing.T) {
l.Info("TestToFile") l.Info("TestToFile")
l.SetColor(true) l.SetColor(true)
l.Reload()
l.Error("TestToFile colored") l.Error("TestToFile colored")
l.Debug("TestToFile colored") l.Debug("TestToFile colored")
l.Warn("TestToFile colored") l.Warn("TestToFile colored")
@ -101,17 +129,17 @@ func TestApplyChild(t *testing.T) {
l2.Info("child2") l2.Info("child2")
l3.Info("child3") l3.Info("child3")
l.SetOutputAll(io.Discard) l.SetOutputAll(io.Discard)
l.ReloadAll()
l1.Info("child1-discard") l1.Info("child1-discard")
l2.Info("child2-discard") l2.Info("child2-discard")
l3.Info("child3-discard") l3.Info("child3-discard")
SetOutputAll(os.Stdout) SetOutputAll(os.Stdout)
ReloadAll()
l1.Info("child1-stdout") l1.Info("child1-stdout")
l2.Info("child2-stdout") l2.Info("child2-stdout")
l3.Info("child3-stdout") l3.Info("child3-stdout")
SetColorAll(false) SetColorAll(false)
ReloadAll()
l1.Info("child1-nocolor") l1.Info("child1-nocolor")
l2.Info("child2-nocolor") l2.Info("child2-nocolor")
l3.Info("child3-nocolor") l3.Info("child3-nocolor")
@ -124,52 +152,3 @@ func TestDefaultLogger(t *testing.T) {
l.Warn("TestDefaultLogger") l.Warn("TestDefaultLogger")
l.Info("TestDefaultLogger") l.Info("TestDefaultLogger")
} }
func BenchmarkDefault(b *testing.B) {
SetErrOutput(io.Discard)
SetOutput(io.Discard)
LEVEL = 15
for i := 0; i < b.N; i++ {
PROD = true
Error("err in", "BenchmarkDefault: ", i)
ErrorF(H{"test": "fields"}, "err in", "BenchmarkDefault: ", i)
Debug("err in", "BenchmarkDefault: ", i)
DebugF(H{"test": "fields"}, "err in", "BenchmarkDefault: ", i)
Warn("err in", "BenchmarkDefault: ", i)
WarnF(H{"test": "fields"}, "err in", "BenchmarkDefault: ", i)
}
for i := 0; i < b.N; i++ {
PROD = false
Error("err in", "BenchmarkDefault: ", i)
ErrorF(H{"test": "fields"}, "err in", "BenchmarkDefault: ", i)
Debug("err in", "BenchmarkDefault: ", i)
DebugF(H{"test": "fields"}, "err in", "BenchmarkDefault: ", i)
Warn("err in", "BenchmarkDefault: ", i)
WarnF(H{"test": "fields"}, "err in", "BenchmarkDefault: ", i)
}
}
func BenchmarkSub1(b *testing.B) {
LEVEL = 15
l := Sub("Sub1")
l.SetErrOutput(io.Discard)
l.SetOutput(io.Discard)
for i := 0; i < b.N; i++ {
PROD = true
l.Error("err in", "BenchmarkDefault: ", i)
l.ErrorF(H{"test": "fields"}, "err in", "BenchmarkSub1: ", i)
l.Debug("err in", "BenchmarkDefault: ", i)
l.DebugF(H{"test": "fields"}, "err in", "BenchmarkSub1: ", i)
l.Warn("err in", "BenchmarkDefault: ", i)
l.WarnF(H{"test": "fields"}, "err in", "BenchmarkSub1: ", i)
}
for i := 0; i < b.N; i++ {
PROD = false
l.Error("err in", "BenchmarkDefault: ", i)
l.ErrorF(H{"test": "fields"}, "err in", "BenchmarkSub1: ", i)
l.Debug("err in", "BenchmarkDefault: ", i)
l.DebugF(H{"test": "fields"}, "err in", "BenchmarkSub1: ", i)
l.Warn("err in", "BenchmarkDefault: ", i)
l.WarnF(H{"test": "fields"}, "err in", "BenchmarkSub1: ", i)
}
}

63
std.go
View File

@ -2,6 +2,7 @@ package klog
import ( import (
"io" "io"
"text/template"
"kumoly.io/lib/klog/color" "kumoly.io/lib/klog/color"
) )
@ -12,16 +13,6 @@ func DefaultLogger() *Logger {
return std return std
} }
func SetPrinter(p Printer) {
std.printer = p
}
func SetPrinterAll(p Printer) {
for _, v := range getAllOffsprings(std) {
v.SetPrinter(p)
}
}
func M(s interface{}, attrs ...color.Attribute) string { func M(s interface{}, attrs ...color.Attribute) string {
return std.M(s, attrs...) return std.M(s, attrs...)
} }
@ -54,34 +45,70 @@ func SetOutputAll(out io.Writer) {
std.SetOutputAll(out) std.SetOutputAll(out)
} }
func Reload() error {
return std.Reload()
}
func ReloadAll() error {
return std.ReloadAll()
}
func DefaultFuncMap() template.FuncMap {
return std.DefaultFuncMap()
}
func ParseTmpl() error {
return std.ParseTmpl()
}
func SetTmpl(formatter *LogFormater, funcMap template.FuncMap) {
std.SetTmpl(formatter, funcMap)
}
func ErrorF(fields H, v ...interface{}) { func ErrorF(fields H, v ...interface{}) {
std.output(Lerror, 3, fields, v...) if PROD {
std.output(terror, 3, "", fields, v...)
} else {
std.output(terror, 3, stack(), fields, v...)
}
} }
func Error(v ...interface{}) { func Error(v ...interface{}) {
std.output(Lerror, 3, H{}, v...) if PROD {
std.output(terror, 3, "", H{}, v...)
} else {
std.output(terror, 3, stack(), H{}, v...)
}
} }
func DebugF(fields H, v ...interface{}) { func DebugF(fields H, v ...interface{}) {
std.output(Ldebug, 3, fields, v...) if PROD {
std.output(tdebug, 3, "", fields, v...)
} else {
std.output(tdebug, 3, stack(), fields, v...)
}
} }
func Debug(v ...interface{}) { func Debug(v ...interface{}) {
std.output(Ldebug, 3, H{}, v...) if PROD {
std.output(tdebug, 3, "", H{}, v...)
} else {
std.output(tdebug, 3, stack(), H{}, v...)
}
} }
func WarnF(fields H, v ...interface{}) { func WarnF(fields H, v ...interface{}) {
std.output(Lwarn, 3, fields, v...) std.output(twarn, 3, "", fields, v...)
} }
func Warn(v ...interface{}) { func Warn(v ...interface{}) {
std.output(Lwarn, 3, H{}, v...) std.output(twarn, 3, "", H{}, v...)
} }
func InfoF(fields H, v ...interface{}) { func InfoF(fields H, v ...interface{}) {
std.output(Linfo, 3, fields, v...) std.output(tinfo, 3, "", fields, v...)
} }
func Info(v ...interface{}) { func Info(v ...interface{}) {
std.output(Linfo, 3, H{}, v...) std.output(tinfo, 3, "", H{}, v...)
} }

View File

@ -3,6 +3,7 @@ package klog
import ( import (
"fmt" "fmt"
"runtime" "runtime"
"text/template"
) )
func caller(depth int) string { func caller(depth int) string {
@ -38,3 +39,11 @@ func getAllOffsprings(l *Logger) []*Logger {
} }
return loggers return loggers
} }
func copyFuncMap(source template.FuncMap) template.FuncMap {
dest := template.FuncMap{}
for k, v := range source {
dest[k] = v
}
return dest
}