Codebase list golang-github-go-kit-kit / 667b0bf
log: support writing to stdlib logger Peter Bourgon 9 years ago
5 changed file(s) with 262 addition(s) and 236 deletion(s). Raw diff Collapse all Expand all
00 package log
11
22 import (
3 "bytes"
34 "fmt"
45 "io"
56 )
1819 if len(keyvals)%2 == 1 {
1920 panic("odd number of keyvals")
2021 }
22 buf := &bytes.Buffer{}
2123 for i := 0; i < len(keyvals); i += 2 {
2224 if i != 0 {
23 if _, err := fmt.Fprint(l.Writer, " "); err != nil {
25 if _, err := fmt.Fprint(buf, " "); err != nil {
2426 return err
2527 }
2628 }
27 if _, err := fmt.Fprintf(l.Writer, "%s=%v", keyvals[i], keyvals[i+1]); err != nil {
29 if _, err := fmt.Fprintf(buf, "%s=%v", keyvals[i], keyvals[i+1]); err != nil {
2830 return err
2931 }
3032 }
31 if _, err := fmt.Fprintln(l.Writer); err != nil {
33 if _, err := fmt.Fprintln(l.Writer, buf.String()); err != nil {
3234 return err
3335 }
3436 return nil
0 package log
1
2 import (
3 "io"
4 "log"
5 "regexp"
6 "strings"
7 )
8
9 // StdlibWriter implements io.Writer by invoking the stdlib log.Printf. It's
10 // designed to be passed to a gokit logger as the writer, for cases where it's
11 // desirable to pipe all log output to the same, canonical destination.
12 type StdlibWriter struct{}
13
14 // Write implements io.Writer.
15 func (w StdlibWriter) Write(p []byte) (int, error) {
16 log.Printf(strings.TrimSpace(string(p)))
17 return len(p), nil
18 }
19
20 // StdlibAdapter wraps a Logger and allows it to be passed to the stdlib
21 // logger's SetOutput. It will extract date/timestamps, filenames, and
22 // messages, and place them under relevant keys.
23 type StdlibAdapter struct {
24 Logger
25 timestampKey string
26 fileKey string
27 messageKey string
28 }
29
30 // StdlibAdapterOption sets a parameter for the StdlibAdapter.
31 type StdlibAdapterOption func(*StdlibAdapter)
32
33 // TimestampKey sets the key for the timestamp field. By default, it's "ts".
34 func TimestampKey(key string) StdlibAdapterOption {
35 return func(a *StdlibAdapter) { a.timestampKey = key }
36 }
37
38 // FileKey sets the key for the file and line field. By default, it's "file".
39 func FileKey(key string) StdlibAdapterOption {
40 return func(a *StdlibAdapter) { a.fileKey = key }
41 }
42
43 // MessageKey sets the key for the actual log message. By default, it's "msg".
44 func MessageKey(key string) StdlibAdapterOption {
45 return func(a *StdlibAdapter) { a.messageKey = key }
46 }
47
48 // NewStdlibAdapter returns a new StdlibAdapter wrapper around the passed
49 // logger. It's designed to be passed to log.SetOutput.
50 func NewStdlibAdapter(logger Logger, options ...StdlibAdapterOption) io.Writer {
51 a := StdlibAdapter{
52 Logger: logger,
53 timestampKey: "ts",
54 fileKey: "file",
55 messageKey: "msg",
56 }
57 for _, option := range options {
58 option(&a)
59 }
60 return a
61 }
62
63 func (a StdlibAdapter) Write(p []byte) (int, error) {
64 result := subexps(p)
65 keyvals := []interface{}{}
66 var timestamp string
67 if date, ok := result["date"]; ok && date != "" {
68 timestamp = date
69 }
70 if time, ok := result["time"]; ok && time != "" {
71 if timestamp != "" {
72 timestamp += " "
73 }
74 timestamp += time
75 }
76 if timestamp != "" {
77 keyvals = append(keyvals, a.timestampKey, timestamp)
78 }
79 if file, ok := result["file"]; ok && file != "" {
80 keyvals = append(keyvals, a.fileKey, file)
81 }
82 if msg, ok := result["msg"]; ok {
83 keyvals = append(keyvals, a.messageKey, msg)
84 }
85 if err := a.Logger.Log(keyvals...); err != nil {
86 return 0, err
87 }
88 return len(p), nil
89 }
90
91 const (
92 logRegexpDate = `(?P<date>[0-9]{4}/[0-9]{2}/[0-9]{2})?[ ]?`
93 logRegexpTime = `(?P<time>[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]+)?)?[ ]?`
94 logRegexpFile = `(?P<file>[^:]+:[0-9]+)?`
95 logRegexpMsg = `(: )?(?P<msg>.*)`
96 )
97
98 var (
99 logRegexp = regexp.MustCompile(logRegexpDate + logRegexpTime + logRegexpFile + logRegexpMsg)
100 )
101
102 func subexps(line []byte) map[string]string {
103 m := logRegexp.FindSubmatch(line)
104 if len(m) < len(logRegexp.SubexpNames()) {
105 return map[string]string{}
106 }
107 result := map[string]string{}
108 for i, name := range logRegexp.SubexpNames() {
109 result[name] = string(m[i])
110 }
111 return result
112 }
0 package log
1
2 import (
3 "bytes"
4 "fmt"
5 "log"
6 "testing"
7 "time"
8 )
9
10 func TestStdlibWriter(t *testing.T) {
11 buf := &bytes.Buffer{}
12 log.SetOutput(buf)
13 logger := NewPrefixLogger(StdlibWriter{})
14 logger.Log("key", "val")
15 timestamp := time.Now().Format("2006/01/02 15:04:05")
16 if want, have := timestamp+" key=val\n", buf.String(); want != have {
17 t.Errorf("want %q, have %q", want, have)
18 }
19 }
20
21 func TestStdlibAdapterUsage(t *testing.T) {
22 buf := &bytes.Buffer{}
23 logger := NewPrefixLogger(buf)
24 writer := NewStdlibAdapter(logger)
25 log.SetOutput(writer)
26
27 now := time.Now()
28 date := now.Format("2006/01/02")
29 time := now.Format("15:04:05")
30
31 for flag, want := range map[int]string{
32 0: "msg=hello\n",
33 log.Ldate: "ts=" + date + " msg=hello\n",
34 log.Ltime: "ts=" + time + " msg=hello\n",
35 log.Ldate | log.Ltime: "ts=" + date + " " + time + " msg=hello\n",
36 log.Lshortfile: "file=stdlib_test.go:43 msg=hello\n",
37 log.Lshortfile | log.Ldate: "ts=" + date + " file=stdlib_test.go:43 msg=hello\n",
38 log.Lshortfile | log.Ldate | log.Ltime: "ts=" + date + " " + time + " file=stdlib_test.go:43 msg=hello\n",
39 } {
40 buf.Reset()
41 log.SetFlags(flag)
42 log.Print("hello")
43 if have := buf.String(); want != have {
44 t.Errorf("flag=%d: want %#v, have %#v", flag, want, have)
45 }
46 }
47 }
48
49 func TestStdLibAdapterExtraction(t *testing.T) {
50 buf := &bytes.Buffer{}
51 logger := NewPrefixLogger(buf)
52 writer := NewStdlibAdapter(logger)
53 for input, want := range map[string]string{
54 "hello": "msg=hello\n",
55 "2009/01/23: hello": "ts=2009/01/23 msg=hello\n",
56 "2009/01/23 01:23:23: hello": "ts=2009/01/23 01:23:23 msg=hello\n",
57 "01:23:23: hello": "ts=01:23:23 msg=hello\n",
58 "2009/01/23 01:23:23.123123: hello": "ts=2009/01/23 01:23:23.123123 msg=hello\n",
59 "2009/01/23 01:23:23.123123 /a/b/c/d.go:23: hello": "ts=2009/01/23 01:23:23.123123 file=/a/b/c/d.go:23 msg=hello\n",
60 "01:23:23.123123 /a/b/c/d.go:23: hello": "ts=01:23:23.123123 file=/a/b/c/d.go:23 msg=hello\n",
61 "2009/01/23 01:23:23 /a/b/c/d.go:23: hello": "ts=2009/01/23 01:23:23 file=/a/b/c/d.go:23 msg=hello\n",
62 "2009/01/23 /a/b/c/d.go:23: hello": "ts=2009/01/23 file=/a/b/c/d.go:23 msg=hello\n",
63 "/a/b/c/d.go:23: hello": "file=/a/b/c/d.go:23 msg=hello\n",
64 } {
65 buf.Reset()
66 fmt.Fprintf(writer, input)
67 if have := buf.String(); want != have {
68 t.Errorf("%q: want %#v, have %#v", input, want, have)
69 }
70 }
71 }
72
73 func TestStdlibAdapterSubexps(t *testing.T) {
74 for input, wantMap := range map[string]map[string]string{
75 "hello world": map[string]string{
76 "date": "",
77 "time": "",
78 "file": "",
79 "msg": "hello world",
80 },
81 "2009/01/23: hello world": map[string]string{
82 "date": "2009/01/23",
83 "time": "",
84 "file": "",
85 "msg": "hello world",
86 },
87 "2009/01/23 01:23:23: hello world": map[string]string{
88 "date": "2009/01/23",
89 "time": "01:23:23",
90 "file": "",
91 "msg": "hello world",
92 },
93 "01:23:23: hello world": map[string]string{
94 "date": "",
95 "time": "01:23:23",
96 "file": "",
97 "msg": "hello world",
98 },
99 "2009/01/23 01:23:23.123123: hello world": map[string]string{
100 "date": "2009/01/23",
101 "time": "01:23:23.123123",
102 "file": "",
103 "msg": "hello world",
104 },
105 "2009/01/23 01:23:23.123123 /a/b/c/d.go:23: hello world": map[string]string{
106 "date": "2009/01/23",
107 "time": "01:23:23.123123",
108 "file": "/a/b/c/d.go:23",
109 "msg": "hello world",
110 },
111 "01:23:23.123123 /a/b/c/d.go:23: hello world": map[string]string{
112 "date": "",
113 "time": "01:23:23.123123",
114 "file": "/a/b/c/d.go:23",
115 "msg": "hello world",
116 },
117 "2009/01/23 01:23:23 /a/b/c/d.go:23: hello world": map[string]string{
118 "date": "2009/01/23",
119 "time": "01:23:23",
120 "file": "/a/b/c/d.go:23",
121 "msg": "hello world",
122 },
123 "2009/01/23 /a/b/c/d.go:23: hello world": map[string]string{
124 "date": "2009/01/23",
125 "time": "",
126 "file": "/a/b/c/d.go:23",
127 "msg": "hello world",
128 },
129 "/a/b/c/d.go:23: hello world": map[string]string{
130 "date": "",
131 "time": "",
132 "file": "/a/b/c/d.go:23",
133 "msg": "hello world",
134 },
135 } {
136 haveMap := subexps([]byte(input))
137 for key, want := range wantMap {
138 if have := haveMap[key]; want != have {
139 t.Errorf("%q: %q: want %q, have %q", input, key, want, have)
140 }
141 }
142 }
143 }
+0
-100
log/stdlib_writer.go less more
0 package log
1
2 import (
3 "io"
4 "regexp"
5 )
6
7 // StdlibWriter wraps a Logger and allows it to be passed to the stdlib
8 // logger's SetOutput. It will extract date/timestamps, filenames, and
9 // messages, and place them under relevant keys.
10 type StdlibWriter struct {
11 Logger
12 timestampKey string
13 fileKey string
14 messageKey string
15 }
16
17 // StdlibWriterOption sets a parameter for the StdlibWriter.
18 type StdlibWriterOption func(*StdlibWriter)
19
20 // TimestampKey sets the key for the timestamp field. By default, it's "ts".
21 func TimestampKey(key string) StdlibWriterOption {
22 return func(w *StdlibWriter) { w.timestampKey = key }
23 }
24
25 // FileKey sets the key for the file and line field. By default, it's "file".
26 func FileKey(key string) StdlibWriterOption {
27 return func(w *StdlibWriter) { w.fileKey = key }
28 }
29
30 // MessageKey sets the key for the actual log message. By default, it's "msg".
31 func MessageKey(key string) StdlibWriterOption {
32 return func(w *StdlibWriter) { w.messageKey = key }
33 }
34
35 // NewStdlibWriter returns a new StdlibWriter wrapper around the passed
36 // logger. It's designed to be passed to log.SetOutput.
37 func NewStdlibWriter(logger Logger, options ...StdlibWriterOption) io.Writer {
38 w := StdlibWriter{
39 Logger: logger,
40 timestampKey: "ts",
41 fileKey: "file",
42 messageKey: "msg",
43 }
44 for _, option := range options {
45 option(&w)
46 }
47 return w
48 }
49
50 func (w StdlibWriter) Write(p []byte) (int, error) {
51 result := subexps(p)
52 keyvals := []interface{}{}
53 var timestamp string
54 if date, ok := result["date"]; ok && date != "" {
55 timestamp = date
56 }
57 if time, ok := result["time"]; ok && time != "" {
58 if timestamp != "" {
59 timestamp += " "
60 }
61 timestamp += time
62 }
63 if timestamp != "" {
64 keyvals = append(keyvals, w.timestampKey, timestamp)
65 }
66 if file, ok := result["file"]; ok && file != "" {
67 keyvals = append(keyvals, w.fileKey, file)
68 }
69 if msg, ok := result["msg"]; ok {
70 keyvals = append(keyvals, w.messageKey, msg)
71 }
72 if err := w.Logger.Log(keyvals...); err != nil {
73 return 0, err
74 }
75 return len(p), nil
76 }
77
78 const (
79 logRegexpDate = `(?P<date>[0-9]{4}/[0-9]{2}/[0-9]{2})?[ ]?`
80 logRegexpTime = `(?P<time>[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]+)?)?[ ]?`
81 logRegexpFile = `(?P<file>[^:]+:[0-9]+)?`
82 logRegexpMsg = `(: )?(?P<msg>.*)`
83 )
84
85 var (
86 logRegexp = regexp.MustCompile(logRegexpDate + logRegexpTime + logRegexpFile + logRegexpMsg)
87 )
88
89 func subexps(line []byte) map[string]string {
90 m := logRegexp.FindSubmatch(line)
91 if len(m) < len(logRegexp.SubexpNames()) {
92 return map[string]string{}
93 }
94 result := map[string]string{}
95 for i, name := range logRegexp.SubexpNames() {
96 result[name] = string(m[i])
97 }
98 return result
99 }
+0
-133
log/stdlib_writer_test.go less more
0 package log
1
2 import (
3 "bytes"
4 "fmt"
5 "log"
6 "testing"
7 "time"
8 )
9
10 func TestStdlibWriterUsage(t *testing.T) {
11 buf := &bytes.Buffer{}
12 logger := NewPrefixLogger(buf)
13 writer := NewStdlibWriter(logger)
14 log.SetOutput(writer)
15
16 now := time.Now()
17 date := now.Format("2006/01/02")
18 time := now.Format("15:04:05")
19
20 for flag, want := range map[int]string{
21 0: "msg=hello\n",
22 log.Ldate: "ts=" + date + " msg=hello\n",
23 log.Ltime: "ts=" + time + " msg=hello\n",
24 log.Ldate | log.Ltime: "ts=" + date + " " + time + " msg=hello\n",
25 log.Lshortfile: "file=stdlib_writer_test.go:32 msg=hello\n",
26 log.Lshortfile | log.Ldate: "ts=" + date + " file=stdlib_writer_test.go:32 msg=hello\n",
27 log.Lshortfile | log.Ldate | log.Ltime: "ts=" + date + " " + time + " file=stdlib_writer_test.go:32 msg=hello\n",
28 } {
29 buf.Reset()
30 log.SetFlags(flag)
31 log.Print("hello")
32 if have := buf.String(); want != have {
33 t.Errorf("flag=%d: want %#v, have %#v", flag, want, have)
34 }
35 }
36 }
37
38 func TestStdLibWriterExtraction(t *testing.T) {
39 buf := &bytes.Buffer{}
40 logger := NewPrefixLogger(buf)
41 writer := NewStdlibWriter(logger)
42 for input, want := range map[string]string{
43 "hello": "msg=hello\n",
44 "2009/01/23: hello": "ts=2009/01/23 msg=hello\n",
45 "2009/01/23 01:23:23: hello": "ts=2009/01/23 01:23:23 msg=hello\n",
46 "01:23:23: hello": "ts=01:23:23 msg=hello\n",
47 "2009/01/23 01:23:23.123123: hello": "ts=2009/01/23 01:23:23.123123 msg=hello\n",
48 "2009/01/23 01:23:23.123123 /a/b/c/d.go:23: hello": "ts=2009/01/23 01:23:23.123123 file=/a/b/c/d.go:23 msg=hello\n",
49 "01:23:23.123123 /a/b/c/d.go:23: hello": "ts=01:23:23.123123 file=/a/b/c/d.go:23 msg=hello\n",
50 "2009/01/23 01:23:23 /a/b/c/d.go:23: hello": "ts=2009/01/23 01:23:23 file=/a/b/c/d.go:23 msg=hello\n",
51 "2009/01/23 /a/b/c/d.go:23: hello": "ts=2009/01/23 file=/a/b/c/d.go:23 msg=hello\n",
52 "/a/b/c/d.go:23: hello": "file=/a/b/c/d.go:23 msg=hello\n",
53 } {
54 buf.Reset()
55 fmt.Fprintf(writer, input)
56 if have := buf.String(); want != have {
57 t.Errorf("%q: want %#v, have %#v", input, want, have)
58 }
59 }
60 }
61
62 func TestStdlibWriterSubexps(t *testing.T) {
63 for input, wantMap := range map[string]map[string]string{
64 "hello world": map[string]string{
65 "date": "",
66 "time": "",
67 "file": "",
68 "msg": "hello world",
69 },
70 "2009/01/23: hello world": map[string]string{
71 "date": "2009/01/23",
72 "time": "",
73 "file": "",
74 "msg": "hello world",
75 },
76 "2009/01/23 01:23:23: hello world": map[string]string{
77 "date": "2009/01/23",
78 "time": "01:23:23",
79 "file": "",
80 "msg": "hello world",
81 },
82 "01:23:23: hello world": map[string]string{
83 "date": "",
84 "time": "01:23:23",
85 "file": "",
86 "msg": "hello world",
87 },
88 "2009/01/23 01:23:23.123123: hello world": map[string]string{
89 "date": "2009/01/23",
90 "time": "01:23:23.123123",
91 "file": "",
92 "msg": "hello world",
93 },
94 "2009/01/23 01:23:23.123123 /a/b/c/d.go:23: hello world": map[string]string{
95 "date": "2009/01/23",
96 "time": "01:23:23.123123",
97 "file": "/a/b/c/d.go:23",
98 "msg": "hello world",
99 },
100 "01:23:23.123123 /a/b/c/d.go:23: hello world": map[string]string{
101 "date": "",
102 "time": "01:23:23.123123",
103 "file": "/a/b/c/d.go:23",
104 "msg": "hello world",
105 },
106 "2009/01/23 01:23:23 /a/b/c/d.go:23: hello world": map[string]string{
107 "date": "2009/01/23",
108 "time": "01:23:23",
109 "file": "/a/b/c/d.go:23",
110 "msg": "hello world",
111 },
112 "2009/01/23 /a/b/c/d.go:23: hello world": map[string]string{
113 "date": "2009/01/23",
114 "time": "",
115 "file": "/a/b/c/d.go:23",
116 "msg": "hello world",
117 },
118 "/a/b/c/d.go:23: hello world": map[string]string{
119 "date": "",
120 "time": "",
121 "file": "/a/b/c/d.go:23",
122 "msg": "hello world",
123 },
124 } {
125 haveMap := subexps([]byte(input))
126 for key, want := range wantMap {
127 if have := haveMap[key]; want != have {
128 t.Errorf("%q: %q: want %q, have %q", input, key, want, have)
129 }
130 }
131 }
132 }