From 5d6281758a55304438dfdcd2052246a27415e32d Mon Sep 17 00:00:00 2001 From: Tobie Morgan Hitchcock Date: Thu, 7 Apr 2016 19:38:24 +0100 Subject: [PATCH] Implement a global logging package --- log/fmt.go | 197 +++++++++++++++++++++++++++++++++++++++++++++++++++++ log/log.go | 197 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 394 insertions(+) create mode 100644 log/fmt.go create mode 100644 log/log.go diff --git a/log/fmt.go b/log/fmt.go new file mode 100644 index 00000000..90636757 --- /dev/null +++ b/log/fmt.go @@ -0,0 +1,197 @@ +// Copyright © 2016 Abcum Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package log + +import ( + "bytes" + "fmt" + "runtime" + "sort" + "strings" + "time" + + "github.com/Sirupsen/logrus" + "github.com/mgutz/ansi" +) + +const clear = ansi.Reset + +var ( + baseTimestamp time.Time + isTerminal bool +) + +func init() { + baseTimestamp = time.Now() + isTerminal = logrus.IsTerminal() +} + +func miniTS() int { + return int(time.Since(baseTimestamp) / time.Second) +} + +type TextFormatter struct { + // Set to true to bypass checking for a TTY before outputting colors. + ForceColors bool + + // Force disabling colors. + DisableColors bool + + // Disable timestamp logging. useful when output is redirected to logging + // system that already adds timestamps. + DisableTimestamp bool + + // Enable logging of just the time passed since beginning of execution. + ShortTimestamp bool + + // Timestamp format to use for display when a full timestamp is printed. + TimestampFormat string + + // The fields are sorted by default for a consistent output. For applications + // that log extremely frequently and don't use the JSON formatter this may not + // be desired. + DisableSorting bool +} + +func (f *TextFormatter) Format(entry *logrus.Entry) ([]byte, error) { + var keys []string = make([]string, 0, len(entry.Data)) + for k := range entry.Data { + if k != "prefix" { + keys = append(keys, k) + } + } + + if !f.DisableSorting { + sort.Strings(keys) + } + + b := &bytes.Buffer{} + + prefixFieldClashes(entry.Data) + + isColorTerminal := isTerminal && (runtime.GOOS != "windows") + isColored := (f.ForceColors || isColorTerminal) && !f.DisableColors + + timestampFormat := f.TimestampFormat + if timestampFormat == "" { + timestampFormat = time.Stamp + } + if isColored { + f.printColored(b, entry, keys, timestampFormat) + } else { + if !f.DisableTimestamp { + f.appendKeyValue(b, "time", entry.Time.Format(timestampFormat)) + } + f.appendKeyValue(b, "level", entry.Level.String()) + if entry.Message != "" { + f.appendKeyValue(b, "msg", entry.Message) + } + for _, key := range keys { + f.appendKeyValue(b, key, entry.Data[key]) + } + } + + b.WriteByte('\n') + return b.Bytes(), nil +} + +func (f *TextFormatter) printColored(b *bytes.Buffer, entry *logrus.Entry, keys []string, timestampFormat string) { + var levelColor string + var levelText string + switch entry.Level { + case logrus.InfoLevel: + levelColor = ansi.Green + case logrus.WarnLevel: + levelColor = ansi.Yellow + case logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel: + levelColor = ansi.Red + default: + levelColor = ansi.Blue + } + + if entry.Level != logrus.WarnLevel { + levelText = strings.ToUpper(entry.Level.String()) + } else { + levelText = "WARN" + } + + prefix := "" + prefixValue, ok := entry.Data["prefix"] + if ok { + prefix = fmt.Sprint(" ", ansi.Cyan, prefixValue, ":", clear) + } + + if f.ShortTimestamp { + fmt.Fprintf(b, "%s[%04d]%s %s%+5s%s%s %s", ansi.LightBlack, miniTS(), clear, levelColor, levelText, clear, prefix, entry.Message) + } else { + fmt.Fprintf(b, "%s[%s]%s %s%+5s%s%s %s", ansi.LightBlack, entry.Time.Format(timestampFormat), clear, levelColor, levelText, clear, prefix, entry.Message) + } + for _, k := range keys { + v := entry.Data[k] + fmt.Fprintf(b, " %s%s%s=%+v", levelColor, k, clear, v) + } +} + +func needsQuoting(text string) bool { + for _, ch := range text { + if !((ch >= 'a' && ch <= 'z') || + (ch >= 'A' && ch <= 'Z') || + (ch >= '0' && ch <= '9') || + ch == '-' || ch == '.') { + return false + } + } + return true +} + +func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) { + b.WriteString(key) + b.WriteByte('=') + + switch value := value.(type) { + case string: + if needsQuoting(value) { + b.WriteString(value) + } else { + fmt.Fprintf(b, "%q", value) + } + case error: + errmsg := value.Error() + if needsQuoting(errmsg) { + b.WriteString(errmsg) + } else { + fmt.Fprintf(b, "%q", value) + } + default: + fmt.Fprint(b, value) + } + + b.WriteByte(' ') +} + +func prefixFieldClashes(data logrus.Fields) { + _, ok := data["time"] + if ok { + data["fields.time"] = data["time"] + } + _, ok = data["msg"] + if ok { + data["fields.msg"] = data["msg"] + } + _, ok = data["level"] + if ok { + data["fields.level"] = data["level"] + } +} diff --git a/log/log.go b/log/log.go new file mode 100644 index 00000000..1f0537b2 --- /dev/null +++ b/log/log.go @@ -0,0 +1,197 @@ +// Copyright © 2016 Abcum Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package log + +import ( + "os" + "time" + + "github.com/Sirupsen/logrus" +) + +var log *Logger +var old logrus.Level + +// Logger ... +type Logger struct { + *logrus.Logger +} + +func init() { + log = New() +} + +func Instance() *logrus.Logger { + return log.Logger +} + +func Debug(v ...interface{}) { + log.Debug(v...) +} + +func Debugf(format string, v ...interface{}) { + log.Debugf(format, v...) +} + +func Debugln(v ...interface{}) { + log.Debugln(v...) +} + +func Error(v ...interface{}) { + log.Error(v...) +} + +func Errorf(format string, v ...interface{}) { + log.Errorf(format, v...) +} + +func Errorln(v ...interface{}) { + log.Errorln(v...) +} + +func Fatal(v ...interface{}) { + log.Fatal(v...) +} + +func Fatalf(format string, v ...interface{}) { + log.Fatalf(format, v...) +} + +func Fatalln(v ...interface{}) { + log.Fatalln(v...) +} + +func Info(v ...interface{}) { + log.Info(v...) +} + +func Infof(format string, v ...interface{}) { + log.Infof(format, v...) +} + +func Infoln(v ...interface{}) { + log.Infoln(v...) +} + +func Panic(v ...interface{}) { + log.Panic(v...) +} + +func Panicf(format string, v ...interface{}) { + log.Panicf(format, v...) +} + +func Panicln(v ...interface{}) { + log.Panicln(v...) +} + +func Print(v ...interface{}) { + log.Print(v...) +} + +func Printf(format string, v ...interface{}) { + log.Printf(format, v...) +} + +func Println(v ...interface{}) { + log.Println(v...) +} + +func Warn(v ...interface{}) { + log.Warn(v...) +} + +func Warnf(format string, v ...interface{}) { + log.Warnf(format, v...) +} + +func Warnln(v ...interface{}) { + log.Warnln(v...) +} + +func WithField(key string, value interface{}) *logrus.Entry { + return log.WithField(key, value) +} + +func WithFields(fields map[string]interface{}) *logrus.Entry { + return log.WithFields(fields) +} + +func SetLevel(v string) { + log.SetLevel(v) +} + +func SetFormat(v string) { + log.SetFormat(v) +} + +func SetOutput(v string) { + log.SetOutput(v) +} + +// New returns a new Logger instance. +func New() *Logger { + + return &Logger{ + &logrus.Logger{ + Out: os.Stderr, + Level: logrus.ErrorLevel, + Hooks: logrus.LevelHooks{}, + Formatter: &TextFormatter{ + TimestampFormat: time.RFC3339, + }, + }, + } +} + +// SetLevel sets the logging level. +func (l *Logger) SetLevel(v string) { + switch v { + case "debug", "DEBUG": + l.Level = logrus.DebugLevel + case "info", "INFO": + l.Level = logrus.InfoLevel + case "warning", "WARNING": + l.Level = logrus.WarnLevel + case "error", "ERROR": + l.Level = logrus.ErrorLevel + case "fatal", "FATAL": + l.Level = logrus.FatalLevel + case "panic", "PANIC": + l.Level = logrus.PanicLevel + } +} + +// SetFormat sets the logging format. +func (l *Logger) SetFormat(v string) { + switch v { + case "json": + l.Formatter = &logrus.JSONFormatter{} + case "text": + l.Formatter = &TextFormatter{ + TimestampFormat: time.RFC3339, + } + } +} + +// SetOutput sets the logging output. +func (l *Logger) SetOutput(v string) { + switch v { + case "stdout": + l.Out = os.Stdout + case "stderr": + l.Out = os.Stderr + } +}