helpers/logging/formatter.go
// Copyright © 2016 Asteris, LLC
//
// 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 logging
import (
"bytes"
"fmt"
"os"
"runtime"
"sort"
"strings"
"sync"
"time"
"github.com/Sirupsen/logrus"
)
// Formatter is a compact output for logrus logs
type Formatter struct {
DisableColors bool
fancy bool
replacer *strings.Replacer
colors map[string]func(string) string
initter sync.Once
}
func (f *Formatter) init() {
isColorTerminal := logrus.IsTerminal(os.Stderr) && (runtime.GOOS != "windows")
f.fancy = (!f.DisableColors && isColorTerminal)
f.replacer = strings.NewReplacer(
`"`, `\"`,
`\n`, `\\n`,
)
// set up colors
reset := "\x1b[0m"
f.colors = map[string]func(string) string{
"blue": func(in string) string { return "\x1b[34m" + in + reset },
"red": func(in string) string { return "\x1b[31m" + in + reset },
"white": func(in string) string { return "\x1b[37;1m" + in + reset },
"yellow": func(in string) string { return "\x1b[33m" + in + reset },
"dim": func(in string) string { return "\x1b[2m" + in + reset },
}
}
// Format an entry for printing
func (f *Formatter) Format(entry *logrus.Entry) ([]byte, error) {
f.initter.Do(f.init)
var b bytes.Buffer
// first, a timestamp
b.WriteString(f.timestamp(entry.Time))
b.WriteByte(' ')
// then, our error level
b.WriteString(f.level(entry.Level))
b.WriteByte(' ')
// next, the message
b.WriteString(f.message(entry.Message))
if f.fancy {
b.WriteByte('\t')
} else {
b.WriteByte(' ')
}
// and our sorted keys
keys := make([]string, 0, len(entry.Data))
for k := range entry.Data {
keys = append(keys, k)
}
sort.Strings(keys)
last := len(keys) - 1
for i, k := range keys {
b.WriteString(f.kv(k, entry.Data[k]))
if i != last {
b.WriteByte(' ')
}
}
// end with a newline
b.WriteByte('\n')
return b.Bytes(), nil
}
func (f *Formatter) timestamp(t time.Time) string {
formatted := t.Format(time.RFC3339)
if f.fancy {
return f.colors["dim"](formatted)
}
return f.kv("timestamp", formatted)
}
func (f *Formatter) level(lvl logrus.Level) string {
var (
level string
colorize func(string) string
)
switch lvl {
case logrus.DebugLevel:
level = "DEBUG"
colorize = f.colors["white"]
case logrus.InfoLevel:
level = "INFO"
colorize = f.colors["blue"]
case logrus.WarnLevel:
level = "WARN"
colorize = f.colors["yellow"]
case logrus.ErrorLevel:
level = "ERROR"
colorize = f.colors["red"]
case logrus.FatalLevel:
level = "FATAL"
colorize = f.colors["red"]
default:
level = "UNKNOWN"
colorize = f.colors["red"]
}
if !f.fancy {
return f.kv("level", level)
}
return fmt.Sprintf("|%s|", colorize(level))
}
func (f *Formatter) message(msg string) string {
if !f.fancy {
return f.kv("msg", msg)
}
return f.colors["white"](msg)
}
func (f *Formatter) kv(key string, value interface{}) string {
if !f.fancy {
return fmt.Sprintf("%s=\"%v\"", key, f.replacer.Replace(fmt.Sprint(value)))
}
var color func(string) string
if key == "error" {
color = f.colors["red"]
} else {
color = f.colors["blue"]
}
return fmt.Sprintf("%s=%v", color(key), value)
}