diff --git a/log/fmt.go b/log/fmt.go deleted file mode 100644 index 0b1ac0e3..00000000 --- a/log/fmt.go +++ /dev/null @@ -1,199 +0,0 @@ -// 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" - "os" - "runtime" - "sort" - "strings" - "time" - - "github.com/mgutz/ansi" - - "github.com/Sirupsen/logrus" -) - -const clear = ansi.Reset - -var ( - baseTimestamp time.Time - isTerminal bool -) - -func init() { - baseTimestamp = time.Now() - isTerminal = logrus.IsTerminal(os.Stdout) -} - -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/json.go b/log/json.go new file mode 100644 index 00000000..dbc6e080 --- /dev/null +++ b/log/json.go @@ -0,0 +1,78 @@ +// 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 ( + "time" + + "encoding/json" + + "github.com/Sirupsen/logrus" +) + +type JSONFormatter struct { + IgnoreFields []string + TimestampFormat string +} + +func (f *JSONFormatter) ignore(field string) bool { + for _, ignore := range f.IgnoreFields { + if field == ignore { + return true + } + } + return false +} + +func (f *JSONFormatter) include(field string) bool { + for _, ignore := range f.IgnoreFields { + if field == ignore { + return false + } + } + return true +} + +func (f *JSONFormatter) Format(entry *logrus.Entry) (data []byte, err error) { + + if f.TimestampFormat == "" { + f.TimestampFormat = time.RFC3339Nano + } + + obj := make(map[string]interface{}) + + obj["msg"] = entry.Message + obj["time"] = entry.Time.Format(f.TimestampFormat) + obj["level"] = entry.Level.String() + + for k, v := range entry.Data { + if f.include(k) { + switch x := v.(type) { + case error: + obj[k] = x.Error() + default: + obj[k] = x + } + } + } + + data, err = json.Marshal(obj) + if err != nil { + return nil, err + } + + return append(data, '\n'), nil + +} diff --git a/log/log.go b/log/log.go index 0a08e001..1a5afca2 100644 --- a/log/log.go +++ b/log/log.go @@ -191,6 +191,7 @@ func New() *Logger { Level: logrus.ErrorLevel, Hooks: logrus.LevelHooks{}, Formatter: &TextFormatter{ + IgnoreFields: []string{"ctx"}, TimestampFormat: time.RFC3339, }, }, @@ -219,11 +220,13 @@ func (l *Logger) SetLevel(v string) { func (l *Logger) SetFormat(v string) { switch v { case "json": - l.Formatter = &logrus.JSONFormatter{ + l.Formatter = &JSONFormatter{ + IgnoreFields: []string{"ctx"}, TimestampFormat: time.RFC3339, } case "text": l.Formatter = &TextFormatter{ + IgnoreFields: []string{"ctx"}, TimestampFormat: time.RFC3339, } } diff --git a/log/text.go b/log/text.go new file mode 100644 index 00000000..5ee91d19 --- /dev/null +++ b/log/text.go @@ -0,0 +1,179 @@ +// 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" + "os" + "runtime" + "sort" + "strings" + "time" + + "github.com/mgutz/ansi" + + "github.com/Sirupsen/logrus" +) + +const clear = ansi.Reset + +var ( + isTerminal bool + isColoured bool +) + +func init() { + isTerminal = logrus.IsTerminal(os.Stdout) + isColoured = isTerminal && (runtime.GOOS != "windows") +} + +type TextFormatter struct { + IgnoreFields []string + TimestampFormat string +} + +func (f *TextFormatter) ignore(field string) bool { + for _, ignore := range f.IgnoreFields { + if field == ignore { + return true + } + } + return false +} + +func (f *TextFormatter) include(field string) bool { + for _, ignore := range f.IgnoreFields { + if field == ignore { + return false + } + } + return true +} + +func (f *TextFormatter) Format(entry *logrus.Entry) ([]byte, error) { + + if f.TimestampFormat == "" { + f.TimestampFormat = time.RFC3339Nano + } + + var keys []string = make([]string, 0) + + for k := range entry.Data { + if f.include(k) { + if k != "prefix" { + keys = append(keys, k) + } + } + } + + sort.Strings(keys) + + b := &bytes.Buffer{} + + switch isColoured { + case false: + f.printBasic(b, entry, keys) + case true: + f.printColored(b, entry, keys) + } + + b.WriteByte('\n') + + return b.Bytes(), nil + +} + +func (f *TextFormatter) printField(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 (f *TextFormatter) printBasic(b *bytes.Buffer, entry *logrus.Entry, keys []string) { + + f.printField(b, "time", entry.Time.Format(f.TimestampFormat)) + f.printField(b, "level", entry.Level.String()) + f.printField(b, "msg", entry.Message) + for _, key := range keys { + f.printField(b, key, entry.Data[key]) + } + +} + +func (f *TextFormatter) printColored(b *bytes.Buffer, entry *logrus.Entry, keys []string) { + + var color string + var prefix string + + switch entry.Level { + case logrus.InfoLevel: + color = ansi.Green + case logrus.WarnLevel: + color = ansi.Yellow + case logrus.ErrorLevel: + color = ansi.Red + case logrus.FatalLevel: + color = ansi.Red + case logrus.PanicLevel: + color = ansi.Red + default: + color = ansi.Blue + } + + level := strings.ToUpper(entry.Level.String())[0:4] + + if value, ok := entry.Data["prefix"]; ok { + prefix = fmt.Sprint(" ", ansi.Cyan, value, ":", clear) + } + + fmt.Fprintf(b, "%s[%s]%s %s%+5s%s%s %s", ansi.LightBlack, entry.Time.Format(f.TimestampFormat), clear, color, level, clear, prefix, entry.Message) + + for _, k := range keys { + fmt.Fprintf(b, " %s%s%s=%+v", color, k, clear, entry.Data[k]) + } + +} + +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 +}