From 00fcb4936669a40036c4d2d3985c6d45e1a926ea Mon Sep 17 00:00:00 2001 From: Guoqiang Chen Date: Tue, 23 Aug 2016 08:31:40 +0000 Subject: [PATCH] Default Log implementions --- log.go | 94 +++++++++++++++++++++ logger.go | 215 +++++++++++++++++++++++++++++++++++++++++++++++++ logger_test.go | 34 ++++++++ 3 files changed, 343 insertions(+) create mode 100644 log.go create mode 100644 logger.go create mode 100644 logger_test.go diff --git a/log.go b/log.go new file mode 100644 index 0000000..8219fb1 --- /dev/null +++ b/log.go @@ -0,0 +1,94 @@ +package log + +import ( + "io" + "os" +) + +// std is a default logger for console +var std = New(os.Stdout).EnableColorizedLevel(true).SkipCaller(3) + +// Get log level +func GetLevel() int { + return std.GetLevel() +} + +// Set log level +func SetLevel(level int) { + std.SetLevel(level) +} + +// Set a name to indicate a process +func SetName(name string) { + std.SetName(name) +} + +// Set time layout for log line +func SetTimeLayout(layout string) { + std.SetTimeLayout(layout) +} + +// EnableGoroutineId output goroutinue id +func EnableGoroutineId(enable bool) { + std.EnableGoroutineId(enable) +} + +// EnableLongFileFormat output long file name +func EnableLongFileFormat(enable bool) { + std.EnableLongFileFormat(enable) +} + +// EnableColorizedLevel output colorized level +func EnableColorizedLevel(enable bool) { + std.EnableColorizedLevel(enable) +} + +// Set a writer +func SetWriter(w io.Writer) { + std.SetWriter(w) +} + +// Indicate whether output debug message +func IsDebugEnabled() bool { + return std.IsDebugEnabled() +} + +// Indicate whether output info message +func IsInfoEnabled() bool { + return std.IsInfoEnabled() +} + +// Indicate whether output warning message +func IsWarnEnabled() bool { + return std.IsWarnEnabled() +} + +// Indicate whether output error message +func IsErrorEnabled() bool { + return std.IsErrorEnabled() +} + +// Output an debug message +func Debug(msg string, args ...interface{}) { + std.Debug(msg, args...) +} + +// Output an info message +func Info(msg string, args ...interface{}) { + std.Info(msg, args...) +} + +// Output a warning message +func Warn(msg string, args ...interface{}) { + std.Warn(msg, args...) +} + +// Output an error message +func Error(msg string, args ...interface{}) { + std.Error(msg, args) +} + +// Output a fatal message with full stack +func Fatal(msg string, args ...interface{}) { + std.Fatal(msg, args) +} diff --git a/logger.go b/logger.go new file mode 100644 index 0000000..7749541 --- /dev/null +++ b/logger.go @@ -0,0 +1,215 @@ +package log + +import ( + "bytes" + "fmt" + "io" + "os" + "runtime" + "strconv" + "strings" + "sync" + "time" + + "github.com/subchen/gstack/gls" +) + +// Log Level +const ( + DEBUG = iota + INFO + WARN + ERROR + FATAL +) + +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", + } +) + +func New(out io.Writer) *Logger { + return &Logger{ + out: out, + level: INFO, + pid: os.Getpid(), + name: "", + timeLayout: "2006-01-02 15:04:05.000", + goroutineId: false, + longFileFormat: false, + colorizedLevel: false, + callerSkip: 2, + } +} + +type Logger struct { + mu sync.Mutex + out io.Writer + level int + pid int + name string + timeLayout string + goroutineId bool + longFileFormat bool + colorizedLevel bool + callerSkip int +} + +func (l *Logger) GetLevel() int { + return l.level +} + +func (l *Logger) SetLevel(level int) *Logger { + l.level = level + return l +} + +func (l *Logger) SetName(name string) *Logger { + l.name = name + return l +} + +func (l *Logger) SetTimeLayout(layout string) *Logger { + l.timeLayout = layout + return l +} + +func (l *Logger) EnableGoroutineId(enable bool) *Logger { + l.goroutineId = enable + return l +} + +func (l *Logger) EnableLongFileFormat(enable bool) *Logger { + l.longFileFormat = enable + return l +} + +func (l *Logger) EnableColorizedLevel(enable bool) *Logger { + l.colorizedLevel = enable + return l +} + +func (l *Logger) SkipCaller(skip int) *Logger { + l.callerSkip = skip + return l +} + +func (l *Logger) SetWriter(w io.Writer) *Logger { + l.out = w + return l +} + +func (l *Logger) IsDebugEnabled() bool { + return l.level <= DEBUG +} + +func (l *Logger) IsInfoEnabled() bool { + return l.level <= INFO +} + +func (l *Logger) IsWarnEnabled() bool { + return l.level <= WARN +} + +func (l *Logger) IsErrorEnabled() bool { + return l.level <= ERROR +} + +func (l *Logger) Debug(msg string, args ...interface{}) { + if l.level <= DEBUG { + l.log(DEBUG, msg, args...) + } +} + +func (l *Logger) Info(msg string, args ...interface{}) { + if l.level <= INFO { + l.log(INFO, msg, args...) + } +} + +func (l *Logger) Warn(msg string, args ...interface{}) { + if l.level <= WARN { + l.log(WARN, msg, args...) + } +} + +func (l *Logger) Error(msg string, args ...interface{}) { + if l.level <= ERROR { + l.log(ERROR, msg, args...) + } +} + +func (l *Logger) Fatal(msg string, args ...interface{}) { + l.log(FATAL, msg, args...) +} + +func (l *Logger) log(level int, msg string, args ...interface{}) { + _, file, line, ok := runtime.Caller(l.callerSkip) + if !ok { + file = "???" + line = 0 + } else if !l.longFileFormat { + if index := strings.LastIndex(file, "/"); index >= 0 { + file = file[index+1:] + } else if index = strings.LastIndex(file, "\\"); index >= 0 { + file = file[index+1:] + } + } + + // 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 := new(bytes.Buffer) + buf.WriteByte(' ') + buf.WriteString(strconv.Itoa(l.pid)) + buf.WriteByte(' ') + if l.name != "" { + buf.WriteString(l.name) + buf.WriteByte(' ') + } + if l.goroutineId || l.level == DEBUG { + buf.WriteString(strconv.FormatUint(gls.GoroutineID(), 10)) + buf.WriteByte(' ') + } + if l.colorizedLevel { + buf.WriteString(levelStrWithColor[level]) + } else { + buf.WriteString(levelStr[level]) + } + buf.WriteByte(' ') + buf.WriteString(file) + buf.WriteByte(':') + buf.WriteString(strconv.Itoa(line)) + buf.WriteByte(' ') + fmt.Fprintf(buf, msg, args...) + buf.WriteByte('\n') + + if level == 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) + } + } + + l.mu.Lock() + defer l.mu.Unlock() + + timeStr := time.Now().Format(l.timeLayout) + + l.out.Write([]byte(timeStr)) + l.out.Write(buf.Bytes()) +} diff --git a/logger_test.go b/logger_test.go new file mode 100644 index 0000000..c12f455 --- /dev/null +++ b/logger_test.go @@ -0,0 +1,34 @@ +package log + +import ( + "os" + "testing" + "time" +) + +func TestLogger(t *testing.T) { + stdout := New(os.Stdout) + stdout.SetTimeLayout("15:04:05.999") + stdout.SetName("main") + stdout.SetLevel(DEBUG) + stdout.EnableColorizedLevel(true) + stdout.EnableGoroutineId(true) + for i := 0; i < 3; i++ { + go func(i int) { + stdout.Debug("i = %d", i) + stdout.Info("i = %d", i) + }(i) + } + for i := 0; i < 3; i++ { + go func(i int) { + stdout.Debug("i = %d", i) + stdout.Info("i = %d", i) + }(i) + } + + stdout.Warn("warning") + stdout.Error("error") + stdout.Fatal("fatal") + + time.Sleep(1 * time.Second) +}