Codebase list golang-github-go-kit-kit / 7a898b8
Add some polish. - Improved names. - Better docs. - More colors. - Fix Default color. - Support colored output on Windows. Chris Hines 8 years ago
13 changed file(s) with 302 addition(s) and 74 deletion(s). Raw diff Collapse all Expand all
88 "github.com/go-kit/kit/log"
99 )
1010
11 // Color is the abstract color, the zero value is the Default.
11 // Color represents an ANSI color. The zero value is Default.
1212 type Color uint8
1313
14 // ANSI colors.
1415 const (
15 NoColor = Color(iota)
16 Default = Color(iota)
17 Black
18 DarkRed
19 DarkGreen
20 Brown
21 DarkBlue
22 DarkMagenta
23 DarkCyan
24 Gray
25
26 DarkGray
1627 Red
1728 Green
1829 Yellow
2031 Magenta
2132 Cyan
2233 White
23 Default
2434
25 maxColor
35 numColors
2636 )
2737
28 var resetColorBytes = []byte("\x1b[0m")
38 var resetColorBytes = []byte("\x1b[39;49m")
2939 var fgColorBytes [][]byte
3040 var bgColorBytes [][]byte
3141
3242 func init() {
33 for color := NoColor; color < maxColor; color++ {
34 fgColorBytes = append(fgColorBytes, []byte(fmt.Sprintf("\x1b[%dm", 30+color)))
35 bgColorBytes = append(bgColorBytes, []byte(fmt.Sprintf("\x1b[%dm", 40+color)))
43 // Default
44 fgColorBytes = append(fgColorBytes, []byte("\x1b[39m"))
45 bgColorBytes = append(bgColorBytes, []byte("\x1b[49m"))
46
47 // dark colors
48 for color := Black; color < DarkGray; color++ {
49 fgColorBytes = append(fgColorBytes, []byte(fmt.Sprintf("\x1b[%dm", 30+color-Black)))
50 bgColorBytes = append(bgColorBytes, []byte(fmt.Sprintf("\x1b[%dm", 40+color-Black)))
51 }
52
53 // bright colors
54 for color := DarkGray; color < numColors; color++ {
55 fgColorBytes = append(fgColorBytes, []byte(fmt.Sprintf("\x1b[%d;1m", 30+color-DarkGray)))
56 bgColorBytes = append(bgColorBytes, []byte(fmt.Sprintf("\x1b[%d;1m", 40+color-DarkGray)))
3657 }
3758 }
3859
60 // FgBgColor represents a foreground and background color.
3961 type FgBgColor struct {
4062 Fg, Bg Color
4163 }
4264
43 func (c FgBgColor) IsZero() bool {
44 return c.Fg == NoColor && c.Bg == NoColor
65 func (c FgBgColor) isZero() bool {
66 return c.Fg == Default && c.Bg == Default
4567 }
4668
47 // NewColorLogger returns a log.Logger which writes colored logs to w. It
48 // colors whole records based on the FgBgColor returned by the color function.
49 // Log events are formatted by the Logger returned by newLogger.
69 // NewColorLogger returns a Logger which writes colored logs to w. ANSI color
70 // codes for the colors returned by color are added to the formatted output
71 // from the Logger returned by newLogger and the combined result written to w.
5072 func NewColorLogger(w io.Writer, newLogger func(io.Writer) log.Logger, color func(keyvals ...interface{}) FgBgColor) log.Logger {
5173 if color == nil {
5274 panic("color func nil")
7092
7193 func (l *colorLogger) Log(keyvals ...interface{}) error {
7294 color := l.color(keyvals...)
73 if color.IsZero() {
95 if color.isZero() {
7496 return l.noColorLogger.Log(keyvals...)
7597 }
7698
7799 lb := l.getLoggerBuf()
78100 defer l.putLoggerBuf(lb)
79 if color.Fg != NoColor {
101 if color.Fg != Default {
80102 lb.buf.Write(fgColorBytes[color.Fg])
81103 }
82 if color.Bg != NoColor {
104 if color.Bg != Default {
83105 lb.buf.Write(bgColorBytes[color.Bg])
84106 }
85107 err := lb.logger.Log(keyvals...)
86108 if err != nil {
87109 return err
88110 }
89 if color.Fg != NoColor || color.Bg != NoColor {
111 if color.Fg != Default || color.Bg != Default {
90112 lb.buf.Write(resetColorBytes)
91113 }
92114 _, err = io.Copy(l.w, lb.buf)
131153 }
132154 switch asString(keyvals[i+1]) {
133155 case "debug":
134 return FgBgColor{Fg: Green}
156 return FgBgColor{Fg: DarkGray}
135157 case "info":
136 return FgBgColor{Fg: White}
158 return FgBgColor{Fg: Gray}
137159 case "warn":
138160 return FgBgColor{Fg: Yellow}
139161 case "error":
140162 return FgBgColor{Fg: Red}
141163 case "crit":
142 return FgBgColor{Fg: Default, Bg: Red}
164 return FgBgColor{Fg: Gray, Bg: DarkRed}
143165 default:
144166 return FgBgColor{}
145167 }
11
22 import (
33 "bytes"
4 "errors"
54 "io"
65 "io/ioutil"
7 "os"
86 "strconv"
97 "sync"
108 "testing"
2523 t.Fatal(err)
2624 }
2725 if want, have := "hello=world\n", buf.String(); want != have {
28 t.Errorf("want %#v, have %#v", want, have)
26 t.Errorf("\nwant %#v\nhave %#v", want, have)
2927 }
3028
3129 buf.Reset()
32 if err := logger.Log("a", 1, "err", errors.New("error")); err != nil {
30 if err := logger.Log("a", 1); err != nil {
3331 t.Fatal(err)
3432 }
35 if want, have := "\u001b[32m\u001b[48ma=1 err=error\n\u001b[0m", buf.String(); want != have {
36 t.Errorf("want %#v, have %#v", want, have)
33 if want, have := "\x1b[32;1m\x1b[47;1ma=1\n\x1b[39;49m", buf.String(); want != have {
34 t.Errorf("\nwant %#v\nhave %#v", want, have)
3735 }
3836 }
3937
4038 func newColorLogger(w io.Writer) log.Logger {
4139 return term.NewColorLogger(w, log.NewLogfmtLogger,
4240 func(keyvals ...interface{}) term.FgBgColor {
43 for i := 0; i < len(keyvals); i += 2 {
44 key := keyvals[i]
45 if key == "a" {
46 return term.FgBgColor{Fg: term.Green, Bg: term.Default}
47 }
48 if key == "err" && keyvals[i+1] != nil {
49 return term.FgBgColor{Fg: term.White, Bg: term.Red}
50 }
41 if keyvals[0] == "a" {
42 return term.FgBgColor{Fg: term.Green, Bg: term.White}
5143 }
5244 return term.FgBgColor{}
5345 })
9789 logger.Log("a", strconv.FormatInt(int64(i), 10))
9890 }
9991 }
100
101 func ExampleNewColorLogger() {
102 // Color errors red
103 logger := term.NewColorLogger(os.Stdout, log.NewLogfmtLogger,
104 func(keyvals ...interface{}) term.FgBgColor {
105 for i := 1; i < len(keyvals); i += 2 {
106 if _, ok := keyvals[i].(error); ok {
107 return term.FgBgColor{Fg: term.White, Bg: term.Red}
108 }
109 }
110 return term.FgBgColor{}
111 })
112
113 logger.Log("c", "c is uncolored value", "err", nil)
114 logger.Log("c", "c is colored 'cause err colors it", "err", errors.New("coloring error"))
115 }
0 // +build !windows
1
2 package term
3
4 import "io"
5
6 // NewColorWriter returns an io.Writer that writes to w and provides cross
7 // platform support for ANSI color codes. If w is not a terminal it is
8 // returned unmodified.
9 func NewColorWriter(w io.Writer) io.Writer {
10 return w
11 }
0 // The code in this file is adapted from github.com/mattn/go-colorable.
1
2 // +build windows
3
4 package term
5
6 import (
7 "bytes"
8 "fmt"
9 "io"
10 "strconv"
11 "strings"
12 "syscall"
13 "unsafe"
14 )
15
16 const (
17 foregroundBlue = 0x1
18 foregroundGreen = 0x2
19 foregroundRed = 0x4
20 foregroundIntensity = 0x8
21 foregroundMask = (foregroundRed | foregroundBlue | foregroundGreen | foregroundIntensity)
22 backgroundBlue = 0x10
23 backgroundGreen = 0x20
24 backgroundRed = 0x40
25 backgroundIntensity = 0x80
26 backgroundMask = (backgroundRed | backgroundBlue | backgroundGreen | backgroundIntensity)
27 )
28
29 type (
30 wchar uint16
31 short int16
32 dword uint32
33 word uint16
34 )
35
36 type coord struct {
37 x short
38 y short
39 }
40
41 type smallRect struct {
42 left short
43 top short
44 right short
45 bottom short
46 }
47
48 type consoleScreenBufferInfo struct {
49 size coord
50 cursorPosition coord
51 attributes word
52 window smallRect
53 maximumWindowSize coord
54 }
55
56 var (
57 procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
58 procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute")
59 )
60
61 type colorWriter struct {
62 out io.Writer
63 handle syscall.Handle
64 lastbuf bytes.Buffer
65 oldattr word
66 }
67
68 // NewColorWriter returns an io.Writer that writes to w and provides cross
69 // platform support for ANSI color codes. If w is not a terminal it is
70 // returned unmodified.
71 func NewColorWriter(w FdWriter) io.Writer {
72 if !IsTerminal(w.Fd()) {
73 return w
74 }
75 var csbi consoleScreenBufferInfo
76 handle := syscall.Handle(w.Fd())
77 procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
78 return &colorWriter{out: w, handle: handle, oldattr: csbi.attributes}
79 }
80
81 func (w *colorWriter) Write(data []byte) (n int, err error) {
82 var csbi consoleScreenBufferInfo
83 procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
84
85 er := bytes.NewBuffer(data)
86 loop:
87 for {
88 r1, _, err := procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
89 if r1 == 0 {
90 break loop
91 }
92
93 c1, _, err := er.ReadRune()
94 if err != nil {
95 break loop
96 }
97 if c1 != 0x1b {
98 fmt.Fprint(w.out, string(c1))
99 continue
100 }
101 c2, _, err := er.ReadRune()
102 if err != nil {
103 w.lastbuf.WriteRune(c1)
104 break loop
105 }
106 if c2 != 0x5b {
107 w.lastbuf.WriteRune(c1)
108 w.lastbuf.WriteRune(c2)
109 continue
110 }
111
112 var buf bytes.Buffer
113 var m rune
114 for {
115 c, _, err := er.ReadRune()
116 if err != nil {
117 w.lastbuf.WriteRune(c1)
118 w.lastbuf.WriteRune(c2)
119 w.lastbuf.Write(buf.Bytes())
120 break loop
121 }
122 if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' {
123 m = c
124 break
125 }
126 buf.Write([]byte(string(c)))
127 }
128
129 switch m {
130 case 'm':
131 attr := csbi.attributes
132 cs := buf.String()
133 if cs == "" {
134 procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(w.oldattr))
135 continue
136 }
137 token := strings.Split(cs, ";")
138 intensityMode := word(0)
139 for _, ns := range token {
140 if n, err = strconv.Atoi(ns); err == nil {
141 switch {
142 case n == 0:
143 attr = w.oldattr
144 case n == 1:
145 attr |= intensityMode
146 case 30 <= n && n <= 37:
147 attr = (attr & backgroundMask)
148 if (n-30)&1 != 0 {
149 attr |= foregroundRed
150 }
151 if (n-30)&2 != 0 {
152 attr |= foregroundGreen
153 }
154 if (n-30)&4 != 0 {
155 attr |= foregroundBlue
156 }
157 intensityMode = foregroundIntensity
158 case n == 39: // reset foreground color
159 attr &= backgroundMask
160 attr |= w.oldattr & foregroundMask
161 case 40 <= n && n <= 47:
162 attr = (attr & foregroundMask)
163 if (n-40)&1 != 0 {
164 attr |= backgroundRed
165 }
166 if (n-40)&2 != 0 {
167 attr |= backgroundGreen
168 }
169 if (n-40)&4 != 0 {
170 attr |= backgroundBlue
171 }
172 intensityMode = backgroundIntensity
173 case n == 49: // reset background color
174 attr &= foregroundMask
175 attr |= w.oldattr & backgroundMask
176 }
177 procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(attr))
178 }
179 }
180 }
181 }
182 return len(data) - w.lastbuf.Len(), nil
183 }
0 package term_test
1
2 import (
3 "errors"
4 "os"
5
6 "github.com/go-kit/kit/log"
7 "github.com/go-kit/kit/log/term"
8 )
9
10 func ExampleNewLogger() {
11 // Color errors red
12 colorFn := func(keyvals ...interface{}) term.FgBgColor {
13 for i := 1; i < len(keyvals); i += 2 {
14 if _, ok := keyvals[i].(error); ok {
15 return term.FgBgColor{Fg: term.White, Bg: term.Red}
16 }
17 }
18 return term.FgBgColor{}
19 }
20
21 logger := term.NewLogger(os.Stdout, log.NewLogfmtLogger, colorFn)
22
23 logger.Log("msg", "default color", "err", nil)
24 logger.Log("msg", "colored because of error", "err", errors.New("coloring error"))
25 }
0 // Package term provides tools for logging to a terminal.
1 package term
2
3 import (
4 "io"
5
6 "github.com/go-kit/kit/log"
7 )
8
9 // NewLogger returns a Logger that takes advantage of terminal features if
10 // possible. Log events are formatted by the Logger returned by newLogger. If
11 // w is a terminal each log event is colored according to the color function.
12 func NewLogger(w io.Writer, newLogger func(io.Writer) log.Logger, color func(keyvals ...interface{}) FgBgColor) log.Logger {
13 fw, ok := w.(FdWriter)
14 if !ok || !IsTerminal(fw.Fd()) {
15 return newLogger(w)
16 }
17 return NewColorLogger(NewColorWriter(fw), newLogger, color)
18 }
19
20 // An FdWriter is a Writer that has a file descriptor.
21 type FdWriter interface {
22 io.Writer
23 Fd() uintptr
24 }
66
77 package term
88
9 // IsTty always returns false on AppEngine.
10 func IsTty(fd uintptr) bool {
9 // IsTerminal always returns false on AppEngine.
10 func IsTerminal(fd uintptr) bool {
1111 return false
1212 }
77 import "syscall"
88
99 const ioctlReadTermios = syscall.TIOCGETA
10
11 type Termios syscall.Termios
44 )
55
66 const ioctlReadTermios = syscall.TIOCGETA
7
8 // Go 1.2 doesn't include Termios for FreeBSD. This should be added in 1.3 and this could be merged with terminal_darwin.
9 type Termios struct {
10 Iflag uint32
11 Oflag uint32
12 Cflag uint32
13 Lflag uint32
14 Cc [20]uint8
15 Ispeed uint32
16 Ospeed uint32
17 }
99 import "syscall"
1010
1111 const ioctlReadTermios = syscall.TCGETS
12
13 type Termios syscall.Termios
1111 "unsafe"
1212 )
1313
14 // IsTty returns true if the given file descriptor is a terminal.
15 func IsTty(fd uintptr) bool {
16 var termios Termios
14 // IsTerminal returns true if the given file descriptor is a terminal.
15 func IsTerminal(fd uintptr) bool {
16 var termios syscall.Termios
1717 _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
1818 return err == 0
1919 }
22 import "syscall"
33
44 const ioctlReadTermios = syscall.TIOCGETA
5
6 type Termios syscall.Termios
1717 procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
1818 )
1919
20 // IsTty returns true if the given file descriptor is a terminal.
21 func IsTty(fd uintptr) bool {
20 // IsTerminal returns true if the given file descriptor is a terminal.
21 func IsTerminal(fd uintptr) bool {
2222 var st uint32
2323 r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0)
2424 return r != 0 && e == 0