Add syslog support (#574)
* WIP: Sketch out syslogLogger based on colorLogger
* WIP: test syslog writer
* WIP: Add documentation and an example
* WIP: Format and copy
* WIP: handle edge with level key as the last and odd keyval
* WIP: Shuffle code around
Mingan authored 6 years ago
Peter Bourgon committed 6 years ago
0 | package syslog_test | |
1 | ||
2 | import ( | |
3 | "fmt" | |
4 | ||
5 | gosyslog "log/syslog" | |
6 | ||
7 | "github.com/go-kit/kit/log" | |
8 | "github.com/go-kit/kit/log/level" | |
9 | "github.com/go-kit/kit/log/syslog" | |
10 | ) | |
11 | ||
12 | func ExampleNewLogger_defaultPrioritySelector() { | |
13 | // Normal syslog writer | |
14 | w, err := gosyslog.New(gosyslog.LOG_INFO, "experiment") | |
15 | if err != nil { | |
16 | fmt.Println(err) | |
17 | return | |
18 | } | |
19 | ||
20 | // syslog logger with logfmt formatting | |
21 | logger := syslog.NewSyslogLogger(w, log.NewLogfmtLogger) | |
22 | logger.Log("msg", "info because of default") | |
23 | logger.Log(level.Key(), level.DebugValue(), "msg", "debug because of explicit level") | |
24 | } |
0 | package syslog | |
1 | ||
2 | import ( | |
3 | "bytes" | |
4 | "io" | |
5 | "sync" | |
6 | ||
7 | gosyslog "log/syslog" | |
8 | ||
9 | "github.com/go-kit/kit/log" | |
10 | "github.com/go-kit/kit/log/level" | |
11 | ) | |
12 | ||
13 | // SyslogWriter is an interface wrapping stdlib syslog Writer. | |
14 | type SyslogWriter interface { | |
15 | Write([]byte) (int, error) | |
16 | Close() error | |
17 | Emerg(string) error | |
18 | Alert(string) error | |
19 | Crit(string) error | |
20 | Err(string) error | |
21 | Warning(string) error | |
22 | Notice(string) error | |
23 | Info(string) error | |
24 | Debug(string) error | |
25 | } | |
26 | ||
27 | // NewSyslogLogger returns a new Logger which writes to syslog in syslog format. | |
28 | // The body of the log message is the formatted output from the Logger returned | |
29 | // by newLogger. | |
30 | func NewSyslogLogger(w SyslogWriter, newLogger func(io.Writer) log.Logger, options ...Option) log.Logger { | |
31 | l := &syslogLogger{ | |
32 | w: w, | |
33 | newLogger: newLogger, | |
34 | prioritySelector: defaultPrioritySelector, | |
35 | bufPool: sync.Pool{New: func() interface{} { | |
36 | return &loggerBuf{} | |
37 | }}, | |
38 | } | |
39 | ||
40 | for _, option := range options { | |
41 | option(l) | |
42 | } | |
43 | ||
44 | return l | |
45 | } | |
46 | ||
47 | type syslogLogger struct { | |
48 | w SyslogWriter | |
49 | newLogger func(io.Writer) log.Logger | |
50 | prioritySelector PrioritySelector | |
51 | bufPool sync.Pool | |
52 | } | |
53 | ||
54 | func (l *syslogLogger) Log(keyvals ...interface{}) error { | |
55 | level := l.prioritySelector(keyvals...) | |
56 | ||
57 | lb := l.getLoggerBuf() | |
58 | defer l.putLoggerBuf(lb) | |
59 | if err := lb.logger.Log(keyvals...); err != nil { | |
60 | return err | |
61 | } | |
62 | ||
63 | switch level { | |
64 | case gosyslog.LOG_EMERG: | |
65 | return l.w.Emerg(lb.buf.String()) | |
66 | case gosyslog.LOG_ALERT: | |
67 | return l.w.Alert(lb.buf.String()) | |
68 | case gosyslog.LOG_CRIT: | |
69 | return l.w.Crit(lb.buf.String()) | |
70 | case gosyslog.LOG_ERR: | |
71 | return l.w.Err(lb.buf.String()) | |
72 | case gosyslog.LOG_WARNING: | |
73 | return l.w.Warning(lb.buf.String()) | |
74 | case gosyslog.LOG_NOTICE: | |
75 | return l.w.Notice(lb.buf.String()) | |
76 | case gosyslog.LOG_INFO: | |
77 | return l.w.Info(lb.buf.String()) | |
78 | case gosyslog.LOG_DEBUG: | |
79 | return l.w.Debug(lb.buf.String()) | |
80 | default: | |
81 | _, err := l.w.Write(lb.buf.Bytes()) | |
82 | return err | |
83 | } | |
84 | } | |
85 | ||
86 | type loggerBuf struct { | |
87 | buf *bytes.Buffer | |
88 | logger log.Logger | |
89 | } | |
90 | ||
91 | func (l *syslogLogger) getLoggerBuf() *loggerBuf { | |
92 | lb := l.bufPool.Get().(*loggerBuf) | |
93 | if lb.buf == nil { | |
94 | lb.buf = &bytes.Buffer{} | |
95 | lb.logger = l.newLogger(lb.buf) | |
96 | } else { | |
97 | lb.buf.Reset() | |
98 | } | |
99 | return lb | |
100 | } | |
101 | ||
102 | func (l *syslogLogger) putLoggerBuf(lb *loggerBuf) { | |
103 | l.bufPool.Put(lb) | |
104 | } | |
105 | ||
106 | // Option sets a parameter for syslog loggers. | |
107 | type Option func(*syslogLogger) | |
108 | ||
109 | // PrioritySelector inspects the list of keyvals and selects a syslog priority. | |
110 | type PrioritySelector func(keyvals ...interface{}) gosyslog.Priority | |
111 | ||
112 | // PrioritySelectorOption sets priority selector function to choose syslog | |
113 | // priority. | |
114 | func PrioritySelectorOption(selector PrioritySelector) Option { | |
115 | return func(l *syslogLogger) { l.prioritySelector = selector } | |
116 | } | |
117 | ||
118 | func defaultPrioritySelector(keyvals ...interface{}) gosyslog.Priority { | |
119 | l := len(keyvals) | |
120 | for i := 0; i < l; i += 2 { | |
121 | if keyvals[i] == level.Key() { | |
122 | var val interface{} | |
123 | if i+1 < l { | |
124 | val = keyvals[i+1] | |
125 | } | |
126 | if v, ok := val.(level.Value); ok { | |
127 | switch v { | |
128 | case level.DebugValue(): | |
129 | return gosyslog.LOG_DEBUG | |
130 | case level.InfoValue(): | |
131 | return gosyslog.LOG_INFO | |
132 | case level.WarnValue(): | |
133 | return gosyslog.LOG_WARNING | |
134 | case level.ErrorValue(): | |
135 | return gosyslog.LOG_ERR | |
136 | } | |
137 | } | |
138 | } | |
139 | } | |
140 | ||
141 | return gosyslog.LOG_INFO | |
142 | } |
0 | package syslog | |
1 | ||
2 | import ( | |
3 | "fmt" | |
4 | "reflect" | |
5 | "testing" | |
6 | ||
7 | gosyslog "log/syslog" | |
8 | ||
9 | "github.com/go-kit/kit/log" | |
10 | "github.com/go-kit/kit/log/level" | |
11 | ) | |
12 | ||
13 | func TestSyslogLoggerDefaultPrioritySelector(t *testing.T) { | |
14 | w := &testSyslogWriter{} | |
15 | l := NewSyslogLogger(w, log.NewLogfmtLogger) | |
16 | ||
17 | l.Log("level", level.WarnValue(), "msg", "one") | |
18 | l.Log("level", "undefined", "msg", "two") | |
19 | l.Log("level", level.InfoValue(), "msg", "three") | |
20 | l.Log("level", level.ErrorValue(), "msg", "four") | |
21 | l.Log("level", level.DebugValue(), "msg", "five") | |
22 | ||
23 | l.Log("msg", "six", "level", level.ErrorValue()) | |
24 | l.Log("msg", "seven", "level", level.DebugValue()) | |
25 | l.Log("msg", "eight", "level", level.InfoValue()) | |
26 | l.Log("msg", "nine", "level", "undefined") | |
27 | l.Log("msg", "ten", "level", level.WarnValue()) | |
28 | ||
29 | l.Log("level", level.ErrorValue(), "msg") | |
30 | l.Log("msg", "eleven", "level") | |
31 | ||
32 | want := []string{ | |
33 | "warning: level=warn msg=one\n", | |
34 | "info: level=undefined msg=two\n", | |
35 | "info: level=info msg=three\n", | |
36 | "err: level=error msg=four\n", | |
37 | "debug: level=debug msg=five\n", | |
38 | ||
39 | "err: msg=six level=error\n", | |
40 | "debug: msg=seven level=debug\n", | |
41 | "info: msg=eight level=info\n", | |
42 | "info: msg=nine level=undefined\n", | |
43 | "warning: msg=ten level=warn\n", | |
44 | ||
45 | "err: level=error msg=null\n", | |
46 | "info: msg=eleven level=null\n", | |
47 | } | |
48 | have := w.writes | |
49 | if !reflect.DeepEqual(want, have) { | |
50 | t.Errorf("wrong writes: want %s, have %s", want, have) | |
51 | } | |
52 | } | |
53 | ||
54 | func TestSyslogLoggerExhaustivePrioritySelector(t *testing.T) { | |
55 | w := &testSyslogWriter{} | |
56 | selector := func(keyvals ...interface{}) gosyslog.Priority { | |
57 | for i := 0; i < len(keyvals); i += 2 { | |
58 | if keyvals[i] == level.Key() { | |
59 | if v, ok := keyvals[i+1].(string); ok { | |
60 | switch v { | |
61 | case "emergency": | |
62 | return gosyslog.LOG_EMERG | |
63 | case "alert": | |
64 | return gosyslog.LOG_ALERT | |
65 | case "critical": | |
66 | return gosyslog.LOG_CRIT | |
67 | case "error": | |
68 | return gosyslog.LOG_ERR | |
69 | case "warning": | |
70 | return gosyslog.LOG_WARNING | |
71 | case "notice": | |
72 | return gosyslog.LOG_NOTICE | |
73 | case "info": | |
74 | return gosyslog.LOG_INFO | |
75 | case "debug": | |
76 | return gosyslog.LOG_DEBUG | |
77 | } | |
78 | return gosyslog.LOG_LOCAL0 | |
79 | } | |
80 | } | |
81 | } | |
82 | return gosyslog.LOG_LOCAL0 | |
83 | } | |
84 | l := NewSyslogLogger(w, log.NewLogfmtLogger, PrioritySelectorOption(selector)) | |
85 | ||
86 | l.Log("level", "warning", "msg", "one") | |
87 | l.Log("level", "error", "msg", "two") | |
88 | l.Log("level", "critical", "msg", "three") | |
89 | l.Log("level", "debug", "msg", "four") | |
90 | l.Log("level", "info", "msg", "five") | |
91 | l.Log("level", "alert", "msg", "six") | |
92 | l.Log("level", "emergency", "msg", "seven") | |
93 | l.Log("level", "notice", "msg", "eight") | |
94 | l.Log("level", "unknown", "msg", "nine") | |
95 | ||
96 | want := []string{ | |
97 | "warning: level=warning msg=one\n", | |
98 | "err: level=error msg=two\n", | |
99 | "crit: level=critical msg=three\n", | |
100 | "debug: level=debug msg=four\n", | |
101 | "info: level=info msg=five\n", | |
102 | "alert: level=alert msg=six\n", | |
103 | "emerg: level=emergency msg=seven\n", | |
104 | "notice: level=notice msg=eight\n", | |
105 | "write: level=unknown msg=nine\n", | |
106 | } | |
107 | have := w.writes | |
108 | if !reflect.DeepEqual(want, have) { | |
109 | t.Errorf("wrong writes: want %s, have %s", want, have) | |
110 | } | |
111 | } | |
112 | ||
113 | type testSyslogWriter struct { | |
114 | writes []string | |
115 | } | |
116 | ||
117 | func (w *testSyslogWriter) Write(b []byte) (int, error) { | |
118 | msg := string(b) | |
119 | w.writes = append(w.writes, fmt.Sprintf("write: %s", msg)) | |
120 | return len(msg), nil | |
121 | } | |
122 | ||
123 | func (w *testSyslogWriter) Close() error { | |
124 | return nil | |
125 | } | |
126 | ||
127 | func (w *testSyslogWriter) Emerg(msg string) error { | |
128 | w.writes = append(w.writes, fmt.Sprintf("emerg: %s", msg)) | |
129 | return nil | |
130 | } | |
131 | ||
132 | func (w *testSyslogWriter) Alert(msg string) error { | |
133 | w.writes = append(w.writes, fmt.Sprintf("alert: %s", msg)) | |
134 | return nil | |
135 | } | |
136 | ||
137 | func (w *testSyslogWriter) Crit(msg string) error { | |
138 | w.writes = append(w.writes, fmt.Sprintf("crit: %s", msg)) | |
139 | return nil | |
140 | } | |
141 | ||
142 | func (w *testSyslogWriter) Err(msg string) error { | |
143 | w.writes = append(w.writes, fmt.Sprintf("err: %s", msg)) | |
144 | return nil | |
145 | } | |
146 | ||
147 | func (w *testSyslogWriter) Warning(msg string) error { | |
148 | w.writes = append(w.writes, fmt.Sprintf("warning: %s", msg)) | |
149 | return nil | |
150 | } | |
151 | ||
152 | func (w *testSyslogWriter) Notice(msg string) error { | |
153 | w.writes = append(w.writes, fmt.Sprintf("notice: %s", msg)) | |
154 | return nil | |
155 | } | |
156 | ||
157 | func (w *testSyslogWriter) Info(msg string) error { | |
158 | w.writes = append(w.writes, fmt.Sprintf("info: %s", msg)) | |
159 | return nil | |
160 | } | |
161 | ||
162 | func (w *testSyslogWriter) Debug(msg string) error { | |
163 | w.writes = append(w.writes, fmt.Sprintf("debug: %s", msg)) | |
164 | return nil | |
165 | } |