diff --git a/log/syslog/example_test.go b/log/syslog/example_test.go new file mode 100644 index 0000000..ff2220d --- /dev/null +++ b/log/syslog/example_test.go @@ -0,0 +1,25 @@ +package syslog_test + +import ( + "fmt" + + gosyslog "log/syslog" + + "github.com/go-kit/kit/log" + "github.com/go-kit/kit/log/level" + "github.com/go-kit/kit/log/syslog" +) + +func ExampleNewLogger_defaultPrioritySelector() { + // Normal syslog writer + w, err := gosyslog.New(gosyslog.LOG_INFO, "experiment") + if err != nil { + fmt.Println(err) + return + } + + // syslog logger with logfmt formatting + logger := syslog.NewSyslogLogger(w, log.NewLogfmtLogger) + logger.Log("msg", "info because of default") + logger.Log(level.Key(), level.DebugValue(), "msg", "debug because of explicit level") +} diff --git a/log/syslog/syslog.go b/log/syslog/syslog.go new file mode 100644 index 0000000..91c3f18 --- /dev/null +++ b/log/syslog/syslog.go @@ -0,0 +1,143 @@ +package syslog + +import ( + "bytes" + "io" + "sync" + + gosyslog "log/syslog" + + "github.com/go-kit/kit/log" + "github.com/go-kit/kit/log/level" +) + +// SyslogWriter is an interface wrapping stdlib syslog Writer. +type SyslogWriter interface { + Write([]byte) (int, error) + Close() error + Emerg(string) error + Alert(string) error + Crit(string) error + Err(string) error + Warning(string) error + Notice(string) error + Info(string) error + Debug(string) error +} + +// NewSyslogLogger returns a new Logger which writes to syslog in syslog format. +// The body of the log message is the formatted output from the Logger returned +// by newLogger. +func NewSyslogLogger(w SyslogWriter, newLogger func(io.Writer) log.Logger, options ...Option) log.Logger { + l := &syslogLogger{ + w: w, + newLogger: newLogger, + prioritySelector: defaultPrioritySelector, + bufPool: sync.Pool{New: func() interface{} { + return &loggerBuf{} + }}, + } + + for _, option := range options { + option(l) + } + + return l +} + +type syslogLogger struct { + w SyslogWriter + newLogger func(io.Writer) log.Logger + prioritySelector PrioritySelector + bufPool sync.Pool +} + +func (l *syslogLogger) Log(keyvals ...interface{}) error { + level := l.prioritySelector(keyvals...) + + lb := l.getLoggerBuf() + defer l.putLoggerBuf(lb) + if err := lb.logger.Log(keyvals...); err != nil { + return err + } + + switch level { + case gosyslog.LOG_EMERG: + return l.w.Emerg(lb.buf.String()) + case gosyslog.LOG_ALERT: + return l.w.Alert(lb.buf.String()) + case gosyslog.LOG_CRIT: + return l.w.Crit(lb.buf.String()) + case gosyslog.LOG_ERR: + return l.w.Err(lb.buf.String()) + case gosyslog.LOG_WARNING: + return l.w.Warning(lb.buf.String()) + case gosyslog.LOG_NOTICE: + return l.w.Notice(lb.buf.String()) + case gosyslog.LOG_INFO: + return l.w.Info(lb.buf.String()) + case gosyslog.LOG_DEBUG: + return l.w.Debug(lb.buf.String()) + default: + _, err := l.w.Write(lb.buf.Bytes()) + return err + } +} + +type loggerBuf struct { + buf *bytes.Buffer + logger log.Logger +} + +func (l *syslogLogger) 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 *syslogLogger) putLoggerBuf(lb *loggerBuf) { + l.bufPool.Put(lb) +} + +// Option sets a parameter for syslog loggers. +type Option func(*syslogLogger) + +// PrioritySelector inspects the list of keyvals and selects a syslog priority. +type PrioritySelector func(keyvals ...interface{}) gosyslog.Priority + +// PrioritySelectorOption sets priority selector function to choose syslog +// priority. +func PrioritySelectorOption(selector PrioritySelector) Option { + return func(l *syslogLogger) { l.prioritySelector = selector } +} + +func defaultPrioritySelector(keyvals ...interface{}) gosyslog.Priority { + l := len(keyvals) + for i := 0; i < l; i += 2 { + if keyvals[i] == level.Key() { + var val interface{} + if i+1 < l { + val = keyvals[i+1] + } + if v, ok := val.(level.Value); ok { + switch v { + case level.DebugValue(): + return gosyslog.LOG_DEBUG + case level.InfoValue(): + return gosyslog.LOG_INFO + case level.WarnValue(): + return gosyslog.LOG_WARNING + case level.ErrorValue(): + return gosyslog.LOG_ERR + } + } + } + } + + return gosyslog.LOG_INFO +} diff --git a/log/syslog/syslog_test.go b/log/syslog/syslog_test.go new file mode 100644 index 0000000..aa9c2cb --- /dev/null +++ b/log/syslog/syslog_test.go @@ -0,0 +1,166 @@ +package syslog + +import ( + "fmt" + "reflect" + "testing" + + gosyslog "log/syslog" + + "github.com/go-kit/kit/log" + "github.com/go-kit/kit/log/level" +) + +func TestSyslogLoggerDefaultPrioritySelector(t *testing.T) { + w := &testSyslogWriter{} + l := NewSyslogLogger(w, log.NewLogfmtLogger) + + l.Log("level", level.WarnValue(), "msg", "one") + l.Log("level", "undefined", "msg", "two") + l.Log("level", level.InfoValue(), "msg", "three") + l.Log("level", level.ErrorValue(), "msg", "four") + l.Log("level", level.DebugValue(), "msg", "five") + + l.Log("msg", "six", "level", level.ErrorValue()) + l.Log("msg", "seven", "level", level.DebugValue()) + l.Log("msg", "eight", "level", level.InfoValue()) + l.Log("msg", "nine", "level", "undefined") + l.Log("msg", "ten", "level", level.WarnValue()) + + l.Log("level", level.ErrorValue(), "msg") + l.Log("msg", "eleven", "level") + + want := []string{ + "warning: level=warn msg=one\n", + "info: level=undefined msg=two\n", + "info: level=info msg=three\n", + "err: level=error msg=four\n", + "debug: level=debug msg=five\n", + + "err: msg=six level=error\n", + "debug: msg=seven level=debug\n", + "info: msg=eight level=info\n", + "info: msg=nine level=undefined\n", + "warning: msg=ten level=warn\n", + + "err: level=error msg=null\n", + "info: msg=eleven level=null\n", + } + have := w.writes + if !reflect.DeepEqual(want, have) { + t.Errorf("wrong writes: want %s, have %s", want, have) + } +} + +func TestSyslogLoggerExhaustivePrioritySelector(t *testing.T) { + w := &testSyslogWriter{} + selector := func(keyvals ...interface{}) gosyslog.Priority { + for i := 0; i < len(keyvals); i += 2 { + if keyvals[i] == level.Key() { + if v, ok := keyvals[i+1].(string); ok { + switch v { + case "emergency": + return gosyslog.LOG_EMERG + case "alert": + return gosyslog.LOG_ALERT + case "critical": + return gosyslog.LOG_CRIT + case "error": + return gosyslog.LOG_ERR + case "warning": + return gosyslog.LOG_WARNING + case "notice": + return gosyslog.LOG_NOTICE + case "info": + return gosyslog.LOG_INFO + case "debug": + return gosyslog.LOG_DEBUG + } + return gosyslog.LOG_LOCAL0 + } + } + } + return gosyslog.LOG_LOCAL0 + } + l := NewSyslogLogger(w, log.NewLogfmtLogger, PrioritySelectorOption(selector)) + + l.Log("level", "warning", "msg", "one") + l.Log("level", "error", "msg", "two") + l.Log("level", "critical", "msg", "three") + l.Log("level", "debug", "msg", "four") + l.Log("level", "info", "msg", "five") + l.Log("level", "alert", "msg", "six") + l.Log("level", "emergency", "msg", "seven") + l.Log("level", "notice", "msg", "eight") + l.Log("level", "unknown", "msg", "nine") + + want := []string{ + "warning: level=warning msg=one\n", + "err: level=error msg=two\n", + "crit: level=critical msg=three\n", + "debug: level=debug msg=four\n", + "info: level=info msg=five\n", + "alert: level=alert msg=six\n", + "emerg: level=emergency msg=seven\n", + "notice: level=notice msg=eight\n", + "write: level=unknown msg=nine\n", + } + have := w.writes + if !reflect.DeepEqual(want, have) { + t.Errorf("wrong writes: want %s, have %s", want, have) + } +} + +type testSyslogWriter struct { + writes []string +} + +func (w *testSyslogWriter) Write(b []byte) (int, error) { + msg := string(b) + w.writes = append(w.writes, fmt.Sprintf("write: %s", msg)) + return len(msg), nil +} + +func (w *testSyslogWriter) Close() error { + return nil +} + +func (w *testSyslogWriter) Emerg(msg string) error { + w.writes = append(w.writes, fmt.Sprintf("emerg: %s", msg)) + return nil +} + +func (w *testSyslogWriter) Alert(msg string) error { + w.writes = append(w.writes, fmt.Sprintf("alert: %s", msg)) + return nil +} + +func (w *testSyslogWriter) Crit(msg string) error { + w.writes = append(w.writes, fmt.Sprintf("crit: %s", msg)) + return nil +} + +func (w *testSyslogWriter) Err(msg string) error { + w.writes = append(w.writes, fmt.Sprintf("err: %s", msg)) + return nil +} + +func (w *testSyslogWriter) Warning(msg string) error { + w.writes = append(w.writes, fmt.Sprintf("warning: %s", msg)) + return nil +} + +func (w *testSyslogWriter) Notice(msg string) error { + w.writes = append(w.writes, fmt.Sprintf("notice: %s", msg)) + return nil +} + +func (w *testSyslogWriter) Info(msg string) error { + w.writes = append(w.writes, fmt.Sprintf("info: %s", msg)) + return nil +} + +func (w *testSyslogWriter) Debug(msg string) error { + w.writes = append(w.writes, fmt.Sprintf("debug: %s", msg)) + return nil +}