Skip to content

Commit

Permalink
Add syslog support (#574)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
mingan authored and peterbourgon committed Feb 13, 2018
1 parent 51bc750 commit 328b7e6
Show file tree
Hide file tree
Showing 3 changed files with 334 additions and 0 deletions.
25 changes: 25 additions & 0 deletions log/syslog/example_test.go
Original file line number Diff line number Diff line change
@@ -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")
}
143 changes: 143 additions & 0 deletions log/syslog/syslog.go
Original file line number Diff line number Diff line change
@@ -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
}
166 changes: 166 additions & 0 deletions log/syslog/syslog_test.go
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit 328b7e6

Please sign in to comment.