log: support writing to stdlib logger
Peter Bourgon
9 years ago
0 | 0 | package log |
1 | 1 | |
2 | 2 | import ( |
3 | "bytes" | |
3 | 4 | "fmt" |
4 | 5 | "io" |
5 | 6 | ) |
18 | 19 | if len(keyvals)%2 == 1 { |
19 | 20 | panic("odd number of keyvals") |
20 | 21 | } |
22 | buf := &bytes.Buffer{} | |
21 | 23 | for i := 0; i < len(keyvals); i += 2 { |
22 | 24 | if i != 0 { |
23 | if _, err := fmt.Fprint(l.Writer, " "); err != nil { | |
25 | if _, err := fmt.Fprint(buf, " "); err != nil { | |
24 | 26 | return err |
25 | 27 | } |
26 | 28 | } |
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 { | |
28 | 30 | return err |
29 | 31 | } |
30 | 32 | } |
31 | if _, err := fmt.Fprintln(l.Writer); err != nil { | |
33 | if _, err := fmt.Fprintln(l.Writer, buf.String()); err != nil { | |
32 | 34 | return err |
33 | 35 | } |
34 | 36 | 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 | 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 | 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 | } |