klog/log.go

326 lines
6.3 KiB
Go

package klog
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
tmpl *template.Template
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,
tmpl: l.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)
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{}) {
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...)
}