Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}