package log import ( "fmt" "io" "os" "runtime" "text/template" "time" "github.com/mattn/go-isatty" ) const ( Lerror = 1 << iota Lwarn Linfo Ldebug ) 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}}` ) 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 err io.Writer out io.Writer err_tmpl *template.Template warn_tmpl *template.Template info_tmpl *template.Template debug_tmpl *template.Template } func New() *logger { l := &logger{ err: os.Stderr, out: os.Stdout, color: true, } l.guessColor() l.SetTmpl(NewLogFormater()) return l } 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) { l.color = c } func NewLogFormater() *LogFormater { return &LogFormater{ ErrTmplStr: DEFAULT_ERR, WarnTmplStr: DEFAULT_WARN, InfoTmplStr: DEFAULT_INFO, DebugTmplStr: DEFAULT_DEBUG, } } func (l *logger) SetTmpl(tmpl *LogFormater) error { 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" }, } if l.color { } err_tmpl, err := template.New("err_tmpl").Funcs(funcMap).Parse(tmpl.ErrTmplStr) if err != nil { return err } l.err_tmpl = err_tmpl return nil } func (l *logger) error(depth int, stack string, fields H, v ...interface{}) { msg := fmt.Sprintln(v...) data := Ldata{ Fields: fields, Message: msg, System: l.system, Caller: caller(depth), Color: l.color, Stack: stack, } err := l.err_tmpl.Execute(os.Stdout, data) if err != nil { panic(err) } } func (l *logger) ErrorF(fields H, v ...interface{}) { stak := "" if !PROD { stak = stack() } l.error(3, stak, fields, v...) } func (l *logger) Error(v ...interface{}) { stak := "" if !PROD { stak = stack() } 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 } } 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)) } }