package term import ( "bytes" "fmt" "io" "sync" "github.com/go-kit/kit/log" ) // Color represents an ANSI color. The zero value is Default. type Color uint8 // ANSI colors. const ( Default = Color(iota) Black DarkRed DarkGreen Brown DarkBlue DarkMagenta DarkCyan Gray DarkGray Red Green Yellow Blue Magenta Cyan White numColors ) // For more on ANSI escape codes see // https://en.wikipedia.org/wiki/ANSI_escape_code. See in particular // https://en.wikipedia.org/wiki/ANSI_escape_code#Colors. var ( resetColorBytes = []byte("\x1b[39;49;22m") fgColorBytes [][]byte bgColorBytes [][]byte ) func init() { // Default fgColorBytes = append(fgColorBytes, []byte("\x1b[39m")) bgColorBytes = append(bgColorBytes, []byte("\x1b[49m")) // dark colors for color := Black; color < DarkGray; color++ { fgColorBytes = append(fgColorBytes, []byte(fmt.Sprintf("\x1b[%dm", 30+color-Black))) bgColorBytes = append(bgColorBytes, []byte(fmt.Sprintf("\x1b[%dm", 40+color-Black))) } // bright colors for color := DarkGray; color < numColors; color++ { fgColorBytes = append(fgColorBytes, []byte(fmt.Sprintf("\x1b[%d;1m", 30+color-DarkGray))) bgColorBytes = append(bgColorBytes, []byte(fmt.Sprintf("\x1b[%d;1m", 40+color-DarkGray))) } } // FgBgColor represents a foreground and background color. type FgBgColor struct { Fg, Bg Color } func (c FgBgColor) isZero() bool { return c.Fg == Default && c.Bg == Default } // NewColorLogger returns a Logger which writes colored logs to w. ANSI color // codes for the colors returned by color are added to the formatted output // from the Logger returned by newLogger and the combined result written to w. func NewColorLogger(w io.Writer, newLogger func(io.Writer) log.Logger, color func(keyvals ...interface{}) FgBgColor) log.Logger { if color == nil { panic("color func nil") } return &colorLogger{ w: w, newLogger: newLogger, color: color, bufPool: sync.Pool{New: func() interface{} { return &loggerBuf{} }}, noColorLogger: newLogger(w), } } type colorLogger struct { w io.Writer newLogger func(io.Writer) log.Logger color func(keyvals ...interface{}) FgBgColor bufPool sync.Pool noColorLogger log.Logger } func (l *colorLogger) Log(keyvals ...interface{}) error { color := l.color(keyvals...) if color.isZero() { return l.noColorLogger.Log(keyvals...) } lb := l.getLoggerBuf() defer l.putLoggerBuf(lb) if color.Fg != Default { lb.buf.Write(fgColorBytes[color.Fg]) } if color.Bg != Default { lb.buf.Write(bgColorBytes[color.Bg]) } err := lb.logger.Log(keyvals...) if err != nil { return err } if color.Fg != Default || color.Bg != Default { lb.buf.Write(resetColorBytes) } _, err = io.Copy(l.w, lb.buf) return err } type loggerBuf struct { buf *bytes.Buffer logger log.Logger } func (l *colorLogger) getLoggerBuf() *loggerBuf { lb := l.bufPool.Get().(*loggerBuf) if lb.buf == nil { lb.buf = &bytes.Buffer{} lb.logger = l.newLogger(lb.buf) } else { lb.buf.Reset() } return lb } func (l *colorLogger) putLoggerBuf(cb *loggerBuf) { l.bufPool.Put(cb) }