package log import ( "bytes" "fmt" "io" "os" "runtime" "strconv" "strings" "sync" "time" "github.com/subchen/gstack/gls" ) const ( // log level L_DEBUG = iota L_INFO L_WARN L_ERROR L_FATAL // Bits or'ed together to control what's printed. F_TIME = 1 << iota F_LONG_FILE F_SHORT_FILE F_PID F_GID F_COLOR // default flags DEFAULT_FLAGS = F_TIME | F_SHORT_FILE | F_PID // default time format DEFAULT_TIME_FORMAT = "2006-01-02 15:04:05,000" ) var ( levelStr = []string{ "DEBUG", "INFO", "WARN", "ERROR", "FATAL", } levelStrWithColor = []string{ "\033[34mDEBUG\033[0m", "\033[32mINFO\033[0m", "\033[33mWARN\033[0m", "\033[31mERROR\033[0m", "\033[35mFATAL\033[0m", } buffer = sync.Pool{ New: func() interface{} { return new(bytes.Buffer) }, } ) func New(out io.Writer) *Logger { return &Logger{ writer: out, level: L_INFO, pid: os.Getpid(), name: "", timeFormat: DEFAULT_TIME_FORMAT, flags: DEFAULT_FLAGS, callerSkip: 2, } } type Logger struct { m sync.Mutex writer io.Writer level int pid int name string timeFormat string flags int callerSkip int } func (l *Logger) GetLevel() int { return l.level } func (l *Logger) SetLevel(level int) *Logger { if level < L_DEBUG || level > L_FATAL { panic("unknown log level") } l.m.Lock() l.level = level l.m.Unlock() return l } func (l *Logger) GetLevelName() string { return levelStr[l.level] } func (l *Logger) SetLevelName(level string) { level = strings.ToUpper(level) for i, v := range levelStr { if v == level { l.SetLevel(i) return } } panic("unknown log level: " + level) } func (l *Logger) GetFlags() int { return l.flags } func (l *Logger) SetFlags(flags int) *Logger { l.m.Lock() l.flags = flags l.m.Unlock() return l } func (l *Logger) GetAppName() string { return l.name } func (l *Logger) SetAppName(name string) *Logger { l.m.Lock() l.name = name l.m.Unlock() return l } func (l *Logger) GetTimeFormat() string { return l.timeFormat } func (l *Logger) SetTimeFormat(format string) *Logger { l.m.Lock() l.timeFormat = format l.m.Unlock() return l } func (l *Logger) SkipCaller(skip int) *Logger { l.m.Lock() defer l.m.Unlock() l.callerSkip = skip return l } func (l *Logger) SetWriter(w io.Writer) *Logger { l.m.Lock() defer l.m.Unlock() l.writer = w return l } func (l *Logger) IsDebugEnabled() bool { return l.level <= L_DEBUG } func (l *Logger) IsInfoEnabled() bool { return l.level <= L_INFO } func (l *Logger) IsWarnEnabled() bool { return l.level <= L_WARN } func (l *Logger) IsErrorEnabled() bool { return l.level <= L_ERROR } func (l *Logger) Debug(obj ...interface{}) { if l.level <= L_DEBUG { l.log(L_DEBUG, fmt.Sprint(obj...)) } } func (l *Logger) Info(obj ...interface{}) { if l.level <= L_INFO { l.log(L_INFO, fmt.Sprint(obj...)) } } func (l *Logger) Warn(obj ...interface{}) { if l.level <= L_WARN { l.log(L_WARN, fmt.Sprint(obj...)) } } func (l *Logger) Error(obj ...interface{}) { if l.level <= L_ERROR { l.log(L_ERROR, fmt.Sprint(obj...)) } } func (l *Logger) Fatal(obj ...interface{}) { if l.level <= L_FATAL { l.log(L_FATAL, fmt.Sprint(obj...)) } } func (l *Logger) Debugf(msg string, args ...interface{}) { if l.level <= L_DEBUG { l.log(L_DEBUG, fmt.Sprintf(msg, args...)) } } func (l *Logger) Infof(msg string, args ...interface{}) { if l.level <= L_INFO { l.log(L_INFO, fmt.Sprintf(msg, args...)) } } func (l *Logger) Warnf(msg string, args ...interface{}) { if l.level <= L_WARN { l.log(L_WARN, fmt.Sprintf(msg, args...)) } } func (l *Logger) Errorf(msg string, args ...interface{}) { if l.level <= L_ERROR { l.log(L_ERROR, fmt.Sprintf(msg, args...)) } } func (l *Logger) Fatalf(msg string, args ...interface{}) { l.log(L_FATAL, fmt.Sprintf(msg, args...)) } func (l *Logger) log(level int, msg string) { // output format: DATE PID [NAME] [GID] LEVEL file:line message // 2001-10-10 12:00:00,000+0800 1234 app 987 INFO main.go:1234 log message ... buf := buffer.Get().(*bytes.Buffer) defer buffer.Put(buf) if l.flags&F_TIME != 0 { timeStr := time.Now().Format(l.timeFormat) buf.WriteString(timeStr) buf.WriteByte(' ') } if l.flags&F_PID != 0 { buf.WriteString(strconv.Itoa(l.pid)) buf.WriteByte(' ') } if l.name != "" { buf.WriteString(l.name) buf.WriteByte(' ') } if l.flags&F_GID != 0 { buf.WriteString(strconv.FormatUint(gls.GoroutineID(), 10)) buf.WriteByte(' ') } if l.flags&F_COLOR != 0 { buf.WriteString(levelStrWithColor[level]) } else { buf.WriteString(levelStr[level]) } buf.WriteByte(' ') if l.flags&(F_LONG_FILE|F_SHORT_FILE) != 0 { _, file, line, ok := runtime.Caller(l.callerSkip) if !ok { file = "???" line = 0 } else if l.flags&F_SHORT_FILE != 0 { if index := strings.LastIndex(file, "/"); index >= 0 { file = file[index+1:] } else if index = strings.LastIndex(file, "\\"); index >= 0 { file = file[index+1:] } } buf.WriteString(file) buf.WriteByte(':') buf.WriteString(strconv.Itoa(line)) buf.WriteByte(' ') } buf.WriteString(msg) buf.WriteByte('\n') if level == L_FATAL { for i := l.callerSkip; ; i++ { pc, file, line, ok := runtime.Caller(i) if !ok { break } fmt.Fprintf(buf, "\tat %s:%d (0x%x)\n", file, line, pc) } } line := buf.Bytes() buf.Reset() l.m.Lock() defer l.m.Unlock() l.writer.Write(line) }