diff --git a/color.go b/color.go index ce515b3..fdace8f 100644 --- a/color.go +++ b/color.go @@ -1,5 +1,7 @@ package log +import "text/template" + type Attribute int // Base attributes @@ -63,3 +65,29 @@ const ( BgHiCyan BgHiWhite ) + +func setColorMap(funcMap template.FuncMap, color bool) { + if color { + funcMap["red"] = func() string { return "\033[91m" } + funcMap["redl"] = func() string { return "\033[31m" } + funcMap["green"] = func() string { return "\033[92m" } + funcMap["yellow"] = func() string { return "\033[93m" } + funcMap["blue"] = func() string { return "\033[94m" } + funcMap["magenta"] = func() string { return "\033[95m" } + funcMap["cyan"] = func() string { return "\033[96m" } + funcMap["white"] = func() string { return "\033[97m" } + funcMap["reset"] = func() string { return "\033[0m" } + return + } + funcMap["red"] = func() string { return "" } + funcMap["redl"] = func() string { return "" } + funcMap["green"] = func() string { return "" } + funcMap["yellow"] = func() string { return "" } + funcMap["blue"] = func() string { return "" } + funcMap["magenta"] = func() string { return "" } + funcMap["cyan"] = func() string { return "" } + funcMap["white"] = func() string { return "" } + funcMap["reset"] = func() string { return "" } +} + +// func diff --git a/go.mod b/go.mod index 4d6c830..68d6978 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,6 @@ module kumoly.io/core/log go 1.17 -require ( - github.com/mattn/go-isatty v0.0.14 // indirect - golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect -) +require github.com/mattn/go-isatty v0.0.14 + +require golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect diff --git a/log.go b/log.go index 47ea0a9..9000fc0 100644 --- a/log.go +++ b/log.go @@ -4,7 +4,6 @@ import ( "fmt" "io" "os" - "runtime" "text/template" "time" @@ -13,19 +12,42 @@ import ( const ( Lerror = 1 << iota + Ldebug Lwarn Linfo - Ldebug +) + +type tout int + +const ( + terror tout = iota + tdebug + tinfo + twarn ) var PROD = true var LEVEL = Lerror | Linfo const ( - DEFAULT_ERR = `{{Time}} [{{red}}ERROR{{reset}}]({{cyan}}{{.System}}{{reset}}) {{.Caller}} {{.Message}}{{if .Fields}}{{printf "%v\n" .Fields}}{{end}}{{if .Stack}}{{redl}}{{.Stack}}{{reset}}{{end}}` - DEFAULT_DEBUG = `{{Time}} [{{magenta}}DEBUG{{reset}}]({{cyan}}{{.System}}{{reset}}) {{.Caller}} {{.Message}}{{if .Fields}}{{printf "%v\n" .Fields}}{{end}}{{if .Stack}}{{.Stack}}{{end}}` - DEFAULT_WARN = `{{Time}} [{{yellow}}WARN {{reset}}]({{cyan}}{{.System}}{{reset}}) {{.Message}}{{if .Fields}}{{printf "%v\n" .Fields}}{{end}}` - DEFAULT_INFO = `{{Time}} [{{blue}}INFO {{reset}}]({{cyan}}{{.System}}{{reset}}) {{.Message}}{{if .Fields}}{{printf "%v\n" .Fields}}{{end}}` + DEFAULT_ERR = `{{Time}} [{{red}}ERROR{{reset}}]` + + `{{if .System}}({{cyan}}{{.System}}{{reset}}){{end}} ` + + `{{.Caller}} {{.Message}}{{"\n"}}` + + `{{if .Fields}}{{printf "%v\n" .Fields}}{{end}}` + + `{{if .Stack}}{{redl}}{{.Stack}}{{reset}}{{end}}` + DEFAULT_DEBUG = `{{Time}} [{{magenta}}DEBUG{{reset}}]` + + `{{if .System}}({{cyan}}{{.System}}{{reset}}){{end}} ` + + `{{.Caller}} {{.Message}}{{"\n"}}` + + `{{if .Fields}}{{green}}{{printf "%v\n" .Fields}}{{reset}}{{end}}` + + `{{if .Stack}}{{redl}}{{.Stack}}{{reset}}{{end}}` + DEFAULT_WARN = `{{Time}} [{{yellow}}WARN {{reset}}]` + + `{{if .System}}({{cyan}}{{.System}}{{reset}}){{end}} ` + + `{{.Message}}{{"\n"}}` + + `{{if .Fields}}{{green}}{{printf "%v\n" .Fields}}{{reset}}{{end}}` + DEFAULT_INFO = `{{Time}} [{{blue}}INFO {{reset}}]` + + `{{if .System}}({{cyan}}{{.System}}{{reset}}){{end}} ` + + `{{.Message}}{{"\n"}}` + + `{{if .Fields}}{{green}}{{printf "%v\n" .Fields}}{{reset}}{{end}}` ) type LogFormater struct { @@ -58,30 +80,48 @@ type logger struct { err io.Writer out io.Writer + formatter *LogFormater err_tmpl *template.Template + debug_tmpl *template.Template warn_tmpl *template.Template info_tmpl *template.Template - debug_tmpl *template.Template } -func New() *logger { +func Default() *logger { l := &logger{ - err: os.Stderr, - out: os.Stdout, - color: true, + err: os.Stderr, + out: os.Stdout, + color: true, + formatter: NewLogFormater(), } l.guessColor() - l.SetTmpl(NewLogFormater()) + l.ParseTmpl() return l } +func (l *logger) Sub(sys string) *logger { + ret := &logger{ + system: sys, + err: l.err, + out: l.out, + color: l.color, + formatter: l.formatter, + + err_tmpl: l.err_tmpl, + debug_tmpl: l.debug_tmpl, + warn_tmpl: l.warn_tmpl, + info_tmpl: l.info_tmpl, + } + return ret +} + func (l *logger) guessColor() { if w, ok := l.out.(*os.File); !ok || os.Getenv("TERM") == "dumb" || (!isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd())) { l.color = false } } -func (l *logger) SetColor(c bool) { +func (l *logger) Color(c bool) { l.color = c } @@ -94,29 +134,46 @@ func NewLogFormater() *LogFormater { } } -func (l *logger) SetTmpl(tmpl *LogFormater) error { +func (l *logger) FuncMap() template.FuncMap { funcMap := template.FuncMap{ - "Time": func() string { return time.Now().Format("2006/01/02 15:04:05") }, - "red": func() string { return "\033[91m" }, - "redl": func() string { return "\033[31m" }, - "green": func() string { return "\033[92m" }, - "yellow": func() string { return "\033[93m" }, - "blue": func() string { return "\033[94m" }, - "magenta": func() string { return "\033[95m" }, - "cyan": func() string { return "\033[96m" }, - "white": func() string { return "\033[97m" }, - "reset": func() string { return "\033[0m" }, + "Time": func() string { return time.Now().Format("2006/01/02 15:04:05") }, } - err_tmpl, err := template.New("err_tmpl").Funcs(funcMap).Parse(tmpl.ErrTmplStr) + setColorMap(funcMap, l.color) + return funcMap +} + +func (l *logger) ParseTmpl() error { + funcMap := l.FuncMap() + err_tmpl, err := template.New("err_tmpl").Funcs(funcMap).Parse(l.formatter.ErrTmplStr) if err != nil { return err } l.err_tmpl = err_tmpl + warn_tmpl, err := template.New("warn_tmpl").Funcs(funcMap).Parse(l.formatter.WarnTmplStr) + if err != nil { + return err + } + l.warn_tmpl = warn_tmpl + info_tmpl, err := template.New("info_tmpl").Funcs(funcMap).Parse(l.formatter.InfoTmplStr) + if err != nil { + return err + } + l.info_tmpl = info_tmpl + debug_tmpl, err := template.New("debug_tmpl").Funcs(funcMap).Parse(l.formatter.DebugTmplStr) + if err != nil { + return err + } + l.debug_tmpl = debug_tmpl return nil } -func (l *logger) error(depth int, stack string, fields H, v ...interface{}) { - msg := fmt.Sprintln(v...) +func (l *logger) SetTmpl(formatter *LogFormater) error { + l.formatter = formatter + return l.ParseTmpl() +} + +func (l *logger) output(t tout, depth int, stack string, fields H, v ...interface{}) { + msg := fmt.Sprint(v...) data := Ldata{ Fields: fields, Message: msg, @@ -125,48 +182,67 @@ func (l *logger) error(depth int, stack string, fields H, v ...interface{}) { Color: l.color, Stack: stack, } - err := l.err_tmpl.Execute(os.Stdout, data) + var err error + switch t { + case terror: + err = l.err_tmpl.Execute(l.err, data) + case tdebug: + err = l.debug_tmpl.Execute(l.err, data) + case twarn: + err = l.warn_tmpl.Execute(l.out, data) + case tinfo: + err = l.info_tmpl.Execute(l.out, data) + } if err != nil { + fmt.Fprintln(l.err, "[FATAL] logger error:", err) panic(err) } - } func (l *logger) ErrorF(fields H, v ...interface{}) { - stak := "" - if !PROD { - stak = stack() + if PROD { + l.output(terror, 3, "", fields, v...) + } else { + l.output(terror, 3, stack(), fields, v...) } - l.error(3, stak, fields, v...) } func (l *logger) Error(v ...interface{}) { - stak := "" - if !PROD { - stak = stack() + if PROD { + l.output(terror, 3, "", H{}, v...) + } else { + l.output(terror, 3, stack(), H{}, v...) } - l.error(3, stak, H{}, v...) } -func caller(depth int) string { - _, file, line, _ := runtime.Caller(depth) - short := file - for i := len(file) - 1; i > 0; i-- { - if file[i] == '/' { - short = file[i+1:] - break - } +func (l *logger) DebugF(fields H, v ...interface{}) { + if PROD { + l.output(tdebug, 3, "", fields, v...) + } else { + l.output(tdebug, 3, stack(), fields, v...) } - return fmt.Sprintf("%s:%d", short, line) } -func stack() string { - buf := make([]byte, 1024) - for { - n := runtime.Stack(buf, false) - if n < len(buf) { - return string(buf[:n]) - } - buf = make([]byte, 2*len(buf)) +func (l *logger) Debug(v ...interface{}) { + if PROD { + l.output(tdebug, 3, "", H{}, v...) + } else { + l.output(tdebug, 3, stack(), H{}, v...) } } + +func (l *logger) WarnF(fields H, v ...interface{}) { + l.output(twarn, 3, "", fields, v...) +} + +func (l *logger) Warn(v ...interface{}) { + l.output(twarn, 3, "", H{}, v...) +} + +func (l *logger) InfoF(fields H, v ...interface{}) { + l.output(tinfo, 3, "", fields, v...) +} + +func (l *logger) Info(v ...interface{}) { + l.output(tinfo, 3, "", H{}, v...) +} diff --git a/log_test.go b/log_test.go index 8e85ffa..0cb7b26 100644 --- a/log_test.go +++ b/log_test.go @@ -2,20 +2,72 @@ package log import "testing" -func TestError(t *testing.T) { +func TestDev(t *testing.T) { PROD = false - log := New() - log.system = "test" - log.Error("d", "sdf", "sdfsdf") - - log.ErrorF(H{"Test": "set"}, "d", "sdf", "sdfsdf") + // log.system = "dev" + Error("d", "sdf", "sdfsdf") + ErrorF(H{"Test": "set"}, "d", "sdf", "sdfsdf") + Debug("d", "sdf", "sdfsdf") + DebugF(H{"Test": "set"}, "d", "sdf", "sdfsdf") + Warn("d", "sdf", "sdfsdf") + WarnF(H{"Test": "set"}, "d", "sdf", "sdfsdf") + Info("d", "sdf", "sdfsdf") + InfoF(H{"Test": "set"}, "d", "sdf", "sdfsdf") } -func TestErrorProd(t *testing.T) { +func TestProd(t *testing.T) { PROD = true - log := New() - log.system = "test" - log.Error("d", "sdf", "sdfsdf") - - log.ErrorF(H{"Test": "set"}, "d", "sdf", "sdfsdf") + // log.system = "prod" + Error("d", "sdf", "sdfsdf") + ErrorF(H{"Test": "set"}, "d", "sdf", "sdfsdf") + Debug("d", "sdf", "sdfsdf") + DebugF(H{"Test": "set"}, "d", "sdf", "sdfsdf") + Warn("d", "sdf", "sdfsdf") + WarnF(H{"Test": "set"}, "d", "sdf", "sdfsdf") + Info("d", "sdf", "sdfsdf") + InfoF(H{"Test": "set"}, "d", "sdf", "sdfsdf") +} + +func TestSubDev(t *testing.T) { + PROD = false + l := Sub("TestSubDev") + l.Error("d", "sdf", "sdfsdf") + l.ErrorF(H{"Test": "set"}, "d", "sdf", "sdfsdf") + l.Debug("d", "sdf", "sdfsdf") + l.DebugF(H{"Test": "set"}, "d", "sdf", "sdfsdf") + l.Warn("d", "sdf", "sdfsdf") + l.WarnF(H{"Test": "set"}, "d", "sdf", "sdfsdf") + l.Info("d", "sdf", "sdfsdf") + l.InfoF(H{"Test": "set"}, "d", "sdf", "sdfsdf") +} + +func TestSubProd(t *testing.T) { + PROD = true + l := Sub("TestSubProd") + l.Error("d", "sdf", "sdfsdf") + l.ErrorF(H{"Test": "set"}, "d", "sdf", "sdfsdf") + l.Debug("d", "sdf", "sdfsdf") + l.DebugF(H{"Test": "set"}, "d", "sdf", "sdfsdf") + l.Warn("d", "sdf", "sdfsdf") + l.WarnF(H{"Test": "set"}, "d", "sdf", "sdfsdf") + l.Info("d", "sdf", "sdfsdf") + l.InfoF(H{"Test": "set"}, "d", "sdf", "sdfsdf") +} + +func TestCustTmpl(t *testing.T) { + PROD = true + l := Sub("TestCustTmpl") + CustFormater := NewLogFormater() + CustFormater.InfoTmplStr = `{{Time}} [{{blue}}INFO {{reset}}]` + + `{{if .System}}({{cyan}}{{.System}}{{reset}}){{end}} ` + + `key: test, value:{{.Fields.Test}}{{"\n"}}` + l.SetTmpl(CustFormater) + l.Error("d", "sdf", "sdfsdf") + l.ErrorF(H{"Test": "set"}, "d", "sdf", "sdfsdf") + l.Debug("d", "sdf", "sdfsdf") + l.DebugF(H{"Test": "set"}, "d", "sdf", "sdfsdf") + l.Warn("d", "sdf", "sdfsdf") + l.WarnF(H{"Test": "set"}, "d", "sdf", "sdfsdf") + l.Info("d", "sdf", "sdfsdf") + l.InfoF(H{"Test": "set"}, "d", "sdf", "sdfsdf") } diff --git a/std.go b/std.go new file mode 100644 index 0000000..68e836b --- /dev/null +++ b/std.go @@ -0,0 +1,55 @@ +package log + +var std = Default() + +func Sub(sys string) *logger { + return std.Sub(sys) +} + +func ErrorF(fields H, v ...interface{}) { + if PROD { + std.output(terror, 3, "", fields, v...) + } else { + std.output(terror, 3, stack(), fields, v...) + } +} + +func Error(v ...interface{}) { + if PROD { + std.output(terror, 3, "", H{}, v...) + } else { + std.output(terror, 3, stack(), H{}, v...) + } +} + +func DebugF(fields H, v ...interface{}) { + if PROD { + std.output(tdebug, 3, "", fields, v...) + } else { + std.output(tdebug, 3, stack(), fields, v...) + } +} + +func Debug(v ...interface{}) { + if PROD { + std.output(tdebug, 3, "", H{}, v...) + } else { + std.output(tdebug, 3, stack(), H{}, v...) + } +} + +func WarnF(fields H, v ...interface{}) { + std.output(twarn, 3, "", fields, v...) +} + +func Warn(v ...interface{}) { + std.output(twarn, 3, "", H{}, v...) +} + +func InfoF(fields H, v ...interface{}) { + std.output(tinfo, 3, "", fields, v...) +} + +func Info(v ...interface{}) { + std.output(tinfo, 3, "", H{}, v...) +} diff --git a/util.go b/util.go new file mode 100644 index 0000000..563c4ef --- /dev/null +++ b/util.go @@ -0,0 +1,29 @@ +package log + +import ( + "fmt" + "runtime" +) + +func caller(depth int) string { + _, file, line, _ := runtime.Caller(depth) + short := file + for i := len(file) - 1; i > 0; i-- { + if file[i] == '/' { + short = file[i+1:] + break + } + } + return fmt.Sprintf("%s:%d", short, line) +} + +func stack() string { + buf := make([]byte, 1024) + for { + n := runtime.Stack(buf, false) + if n < len(buf) { + return string(buf[:n]) + } + buf = make([]byte, 2*len(buf)) + } +}