package log import ( "encoding/json" "fmt" "io" "os" "sync" "text/template" "time" "github.com/mattn/go-isatty" ) const ( Lerror = 1 << iota Ldebug Lwarn Linfo ) type tout int const ( terror tout = iota tdebug tinfo twarn ) var lock sync.Mutex var PROD = true 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 Ldata struct { Message string System string // Caller only evaluates in DEBUG, and ERROR calls Caller string // Stack only eval when !PROD and {DEBUG, ERROR} Stack string Fields H Color bool } type Logger struct { system string color bool explicit bool err io.Writer out io.Writer formatter *LogFormater funcMap template.FuncMap err_tmpl *template.Template debug_tmpl *template.Template warn_tmpl *template.Template info_tmpl *template.Template subs []*Logger } func New(name string) *Logger { l := &Logger{ system: name, err: os.Stderr, out: os.Stdout, color: true, formatter: NewLogFormater(), } l.Reload() 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, } if l.subs == nil { l.subs = make([]*Logger, 0) } l.subs = append(l.subs, ret) return ret } func (l *Logger) guessColor() { l.color = true if w, ok := l.out.(*os.File); !ok || os.Getenv("TERM") == "dumb" || (!isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd())) { l.color = false } if w, ok := l.err.(*os.File); !ok || os.Getenv("TERM") == "dumb" || (!isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd())) { l.color = false } } func (l *Logger) SetColor(c bool) { l.explicit = true l.color = c } func (l *Logger) SetColorAll(c bool) { for _, v := range getAllOffsprings(l) { v.SetColor(c) } } func (l *Logger) SetErrOutput(err io.Writer) { l.err = err } func (l *Logger) SetErrOutputAll(err io.Writer) { for _, v := range getAllOffsprings(l) { v.err = err } } func (l *Logger) SetOutput(out io.Writer) { l.out = out } func (l *Logger) SetOutputAll(out io.Writer) { for _, v := range getAllOffsprings(l) { v.out = out } } func (l *Logger) Reload() error { if !l.explicit { l.guessColor() } return l.ParseTmpl() } func (l *Logger) ReloadAll() error { for _, v := range getAllOffsprings(l) { 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 { funcMap := template.FuncMap{ "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 }, } return funcMap } func (l *Logger) ParseTmpl() error { if l.funcMap == nil { l.funcMap = l.DefaultFuncMap() } funcMap := copyFuncMap(l.funcMap) l.setColorMap(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) 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.err_tmpl.Execute(l.err, data) case tdebug: if LEVEL&Ldebug == 0 { return } err = l.debug_tmpl.Execute(l.err, data) case twarn: if LEVEL&Lwarn == 0 { return } err = l.warn_tmpl.Execute(l.out, data) case tinfo: if LEVEL&Linfo == 0 { return } 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{}) { if PROD { l.output(terror, 3, "", fields, v...) } else { l.output(terror, 3, stack(), fields, v...) } } func (l *Logger) Error(v ...interface{}) { if PROD { l.output(terror, 3, "", H{}, v...) } else { l.output(terror, 3, stack(), H{}, v...) } } func (l *Logger) DebugF(fields H, v ...interface{}) { if PROD { l.output(tdebug, 3, "", fields, v...) } else { l.output(tdebug, 3, stack(), fields, v...) } } 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...) }