Add syslog CEE support (#262)

This commit is contained in:
mathew 2020-10-12 08:16:04 -05:00 committed by GitHub
parent 72acd6cfe8
commit e11d470c08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 70 additions and 10 deletions

View File

@ -7,6 +7,10 @@ import (
"io" "io"
) )
// See http://cee.mitre.org/language/1.0-beta1/clt.html#syslog
// or https://www.rsyslog.com/json-elasticsearch/
const ceePrefix = "@cee:"
// SyslogWriter is an interface matching a syslog.Writer struct. // SyslogWriter is an interface matching a syslog.Writer struct.
type SyslogWriter interface { type SyslogWriter interface {
io.Writer io.Writer
@ -20,16 +24,33 @@ type SyslogWriter interface {
type syslogWriter struct { type syslogWriter struct {
w SyslogWriter w SyslogWriter
prefix string
} }
// SyslogLevelWriter wraps a SyslogWriter and call the right syslog level // SyslogLevelWriter wraps a SyslogWriter and call the right syslog level
// method matching the zerolog level. // method matching the zerolog level.
func SyslogLevelWriter(w SyslogWriter) LevelWriter { func SyslogLevelWriter(w SyslogWriter) LevelWriter {
return syslogWriter{w} return syslogWriter{w, ""}
}
// SyslogCEEWriter wraps a SyslogWriter with a SyslogLevelWriter that adds a
// MITRE CEE prefix for JSON syslog entries, compatible with rsyslog
// and syslog-ng JSON logging support.
// See https://www.rsyslog.com/json-elasticsearch/
func SyslogCEEWriter(w SyslogWriter) LevelWriter {
return syslogWriter{w, ceePrefix}
} }
func (sw syslogWriter) Write(p []byte) (n int, err error) { func (sw syslogWriter) Write(p []byte) (n int, err error) {
return sw.w.Write(p) var pn int
if sw.prefix != "" {
pn, err = sw.w.Write([]byte(sw.prefix))
if err != nil {
return pn, err
}
}
n, err = sw.w.Write(p)
return pn + n, err
} }
// WriteLevel implements LevelWriter interface. // WriteLevel implements LevelWriter interface.
@ -37,22 +58,23 @@ func (sw syslogWriter) WriteLevel(level Level, p []byte) (n int, err error) {
switch level { switch level {
case TraceLevel: case TraceLevel:
case DebugLevel: case DebugLevel:
err = sw.w.Debug(string(p)) err = sw.w.Debug(sw.prefix + string(p))
case InfoLevel: case InfoLevel:
err = sw.w.Info(string(p)) err = sw.w.Info(sw.prefix + string(p))
case WarnLevel: case WarnLevel:
err = sw.w.Warning(string(p)) err = sw.w.Warning(sw.prefix + string(p))
case ErrorLevel: case ErrorLevel:
err = sw.w.Err(string(p)) err = sw.w.Err(sw.prefix + string(p))
case FatalLevel: case FatalLevel:
err = sw.w.Emerg(string(p)) err = sw.w.Emerg(sw.prefix + string(p))
case PanicLevel: case PanicLevel:
err = sw.w.Crit(string(p)) err = sw.w.Crit(sw.prefix + string(p))
case NoLevel: case NoLevel:
err = sw.w.Info(string(p)) err = sw.w.Info(sw.prefix + string(p))
default: default:
panic("invalid level") panic("invalid level")
} }
// Any CEE prefix is not part of the message, so we don't include its length
n = len(p) n = len(p)
return return
} }

View File

@ -4,7 +4,9 @@
package zerolog package zerolog
import ( import (
"bytes"
"reflect" "reflect"
"strings"
"testing" "testing"
) )
@ -68,3 +70,39 @@ func TestSyslogWriter(t *testing.T) {
t.Errorf("Invalid syslog message routing: want %v, got %v", want, got) t.Errorf("Invalid syslog message routing: want %v, got %v", want, got)
} }
} }
type testCEEwriter struct {
buf *bytes.Buffer
}
// Only implement one method as we're just testing the prefixing
func (c testCEEwriter) Debug(m string) error { return nil }
func (c testCEEwriter) Info(m string) error {
_, err := c.buf.Write([]byte(m))
return err
}
func (c testCEEwriter) Warning(m string) error { return nil }
func (c testCEEwriter) Err(m string) error { return nil }
func (c testCEEwriter) Emerg(m string) error { return nil }
func (c testCEEwriter) Crit(m string) error { return nil }
func (c testCEEwriter) Write(b []byte) (int, error) {
return c.buf.Write(b)
}
func TestSyslogWriter_WithCEE(t *testing.T) {
var buf bytes.Buffer
sw := testCEEwriter{&buf}
log := New(SyslogCEEWriter(sw))
log.Info().Str("key", "value").Msg("message string")
got := string(buf.Bytes())
want := "@cee:{"
if !strings.HasPrefix(got, want) {
t.Errorf("Bad CEE message start: want %v, got %v", want, got)
}
}