Skip to content
This repository has been archived by the owner on Mar 18, 2019. It is now read-only.

Commit

Permalink
Added configurable formatters and framers.
Browse files Browse the repository at this point in the history
Default behavior is backwards compatible (ie, it remains non-compliant).
But now we support RFC 3164 and RFC 5424 formats, and RFC 5425 message
framing.
  • Loading branch information
sirsean committed Jan 19, 2016
1 parent 68fa06c commit 0d1b0f5
Show file tree
Hide file tree
Showing 9 changed files with 268 additions and 20 deletions.
48 changes: 48 additions & 0 deletions formatter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package srslog

import (
"fmt"
"os"
"time"
)

// Formatter is a type of function that takes the consituent parts of a
// syslog message and returns a formatted string. A different Formatter is
// defined for each different syslog protocol we support.
type Formatter func(p Priority, hostname, tag, content string) string

// DefaultFormatter is the original format supported by the Go syslog package,
// and is a non-compliant amalgamation of 3164 and 5424 that is intended to
// maximize compatibility.
func DefaultFormatter(p Priority, hostname, tag, content string) string {
timestamp := time.Now().Format(time.RFC3339)
msg := fmt.Sprintf("<%d> %s %s %s[%d]: %s",
p, timestamp, hostname, tag, os.Getpid(), content)
return msg
}

// UnixFormatter omits the hostname, because it is only used locally.
func UnixFormatter(p Priority, hostname, tag, content string) string {
timestamp := time.Now().Format(time.Stamp)
msg := fmt.Sprintf("<%d>%s %s[%d]: %s",
p, timestamp, tag, os.Getpid(), content)
return msg
}

// RFC3164Formatter provides an RFC 3164 compliant message.
func RFC3164Formatter(p Priority, hostname, tag, content string) string {
timestamp := time.Now().Format(time.Stamp)
msg := fmt.Sprintf("<%d> %s %s %s[%d]: %s",
p, timestamp, hostname, tag, os.Getpid(), content)
return msg
}

// RFC5424Formatter provides an RFC 5424 compliant message.
func RFC5424Formatter(p Priority, hostname, tag, content string) string {
timestamp := time.Now().Format(time.RFC3339)
pid := os.Getpid()
appName := os.Args[0]
msg := fmt.Sprintf("<%d>%d %s %s %s %d %s %s",
p, 1, timestamp, hostname, appName, pid, tag, content)
return msg
}
44 changes: 44 additions & 0 deletions formatter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package srslog

import (
"fmt"
"os"
"testing"
"time"
)

func TestDefaultFormatter(t *testing.T) {
out := DefaultFormatter(LOG_ERR, "hostname", "tag", "content")
expected := fmt.Sprintf("<%d> %s %s %s[%d]: %s",
LOG_ERR, time.Now().Format(time.RFC3339), "hostname", "tag", os.Getpid(), "content")
if out != expected {
t.Errorf("expected %v got %v", expected, out)
}
}

func TestUnixFormatter(t *testing.T) {
out := UnixFormatter(LOG_ERR, "hostname", "tag", "content")
expected := fmt.Sprintf("<%d>%s %s[%d]: %s",
LOG_ERR, time.Now().Format(time.Stamp), "tag", os.Getpid(), "content")
if out != expected {
t.Errorf("expected %v got %v", expected, out)
}
}

func TestRFC3164Formatter(t *testing.T) {
out := RFC3164Formatter(LOG_ERR, "hostname", "tag", "content")
expected := fmt.Sprintf("<%d> %s %s %s[%d]: %s",
LOG_ERR, time.Now().Format(time.Stamp), "hostname", "tag", os.Getpid(), "content")
if out != expected {
t.Errorf("expected %v got %v", expected, out)
}
}

func TestRFC5424Formatter(t *testing.T) {
out := RFC5424Formatter(LOG_ERR, "hostname", "tag", "content")
expected := fmt.Sprintf("<%d>%d %s %s %s %d %s %s",
LOG_ERR, 1, time.Now().Format(time.RFC3339), "hostname", os.Args[0], os.Getpid(), "tag", "content")
if out != expected {
t.Errorf("expected %v got %v", expected, out)
}
}
25 changes: 25 additions & 0 deletions framer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package srslog

import (
"fmt"
)

// Framer is a type of function that takes an input string (typically an
// already-formatted syslog message) and applies "message framing" to it. We
// have different framers because different versions of the syslog protocol
// and its transport requirements define different framing behavior.
type Framer func(in string) string

// DefaultFramer does nothing, since there is no framing to apply. This is
// the original behavior of the Go syslog package, and is also typically used
// for UDP syslog.
func DefaultFramer(in string) string {
return in
}

// RFC5425MessageLengthFramer prepends the message length to the front of the
// provided message, as defined in RFC 5425. This is required by syslog-ng,
// and is used with either RFC 3164 or 5424, over TCP+TLS.
func RFC5425MessageLengthFramer(in string) string {
return fmt.Sprintf("%d %s", len(in), in)
}
19 changes: 19 additions & 0 deletions framer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package srslog

import (
"testing"
)

func TestDefaultFramer(t *testing.T) {
out := DefaultFramer("input message")
if out != "input message" {
t.Errorf("should match the input message")
}
}

func TestRFC5425MessageLengthFramer(t *testing.T) {
out := RFC5425MessageLengthFramer("input message")
if out != "13 input message" {
t.Errorf("should prepend the input message length")
}
}
17 changes: 9 additions & 8 deletions net_conn.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package srslog

import (
"fmt"
"net"
"os"
"time"
)

// netConn has an internal net.Conn and adheres to the serverConn interface,
Expand All @@ -15,11 +12,15 @@ type netConn struct {

// writeString formats syslog messages using time.RFC3339 and includes the
// hostname, and sends the message to the connection.
func (n *netConn) writeString(p Priority, hostname, tag, msg string) error {
timestamp := time.Now().Format(time.RFC3339)
_, err := fmt.Fprintf(n.conn, "<%d>%s %s %s[%d]: %s",
p, timestamp, hostname,
tag, os.Getpid(), msg)
func (n *netConn) writeString(framer Framer, formatter Formatter, p Priority, hostname, tag, msg string) error {
if framer == nil {
framer = DefaultFramer
}
if formatter == nil {
formatter = DefaultFormatter
}
formattedMessage := framer(formatter(p, hostname, tag, msg))
_, err := n.conn.Write([]byte(formattedMessage))
return err
}

Expand Down
2 changes: 1 addition & 1 deletion srslog.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
// This interface allows us to work with both local and network connections,
// and enables Solaris support (see syslog_unix.go).
type serverConn interface {
writeString(p Priority, hostname, tag, s string) error
writeString(framer Framer, formatter Formatter, p Priority, hostname, tag, s string) error
close() error
}

Expand Down
10 changes: 2 additions & 8 deletions srslog_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@ package srslog

import (
"errors"
"fmt"
"net"
"os"
"time"
)

// unixSyslog opens a connection to the syslog daemon running on the
Expand Down Expand Up @@ -39,11 +36,8 @@ type localConn struct {

// writeString formats syslog messages using time.Stamp instead of time.RFC3339,
// and omits the hostname (because it is expected to be used locally).
func (n *localConn) writeString(p Priority, hostname, tag, msg string) error {
timestamp := time.Now().Format(time.Stamp)
_, err := fmt.Fprintf(n.conn, "<%d>%s %s[%d]: %s",
p, timestamp,
tag, os.Getpid(), msg)
func (n *localConn) writeString(framer Framer, f Formatter, p Priority, hostname, tag, msg string) error {
_, err := n.conn.Write([]byte(DefaultFramer(UnixFormatter(p, hostname, tag, msg))))
return err
}

Expand Down
18 changes: 15 additions & 3 deletions writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ type Writer struct {
network string
raddr string
tlsConfig *tls.Config
framer Framer
formatter Formatter

conn serverConn
}
Expand All @@ -41,6 +43,16 @@ func (w *Writer) connect() (err error) {
return
}

// SetFormatter changes the formatter function for subsequent messages.
func (w *Writer) SetFormatter(f Formatter) {
w.formatter = f
}

// SetFramer changes the framer function for subsequent messages.
func (w *Writer) SetFramer(f Framer) {
w.framer = f
}

// Write sends a log message to the syslog daemon using the default priority
// passed into `srslog.New` or the `srslog.Dial*` functions.
func (w *Writer) Write(b []byte) (int, error) {
Expand Down Expand Up @@ -133,15 +145,15 @@ func (w *Writer) writeAndRetry(p Priority, s string) (int, error) {
return w.write(pr, s)
}

// write generates and writes a syslog formatted string. The
// format is as follows: <PRI>TIMESTAMP HOSTNAME TAG[PID]: MSG
// write generates and writes a syslog formatted string. It formats the
// message based on the current Formatter and Framer.
func (w *Writer) write(p Priority, msg string) (int, error) {
// ensure it ends in a \n
if !strings.HasSuffix(msg, "\n") {
msg += "\n"
}

err := w.conn.writeString(p, w.hostname, w.tag, msg)
err := w.conn.writeString(w.framer, w.formatter, p, w.hostname, w.tag, msg)
if err != nil {
return 0, err
}
Expand Down
105 changes: 105 additions & 0 deletions writer_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package srslog

import (
"strings"
"testing"
)

Expand All @@ -25,6 +26,110 @@ func TestWriteAndRetryFails(t *testing.T) {
}
}

func TestWriteFormatters(t *testing.T) {
tests := []struct {
f Formatter
}{
{nil},
{UnixFormatter},
{RFC3164Formatter},
{RFC5424Formatter},
{DefaultFormatter},
}

for _, test := range tests {
done := make(chan string)
addr, sock, srvWG := startServer("udp", "", done)
defer sock.Close()
defer srvWG.Wait()

w := Writer{
priority: LOG_ERR,
tag: "tag",
hostname: "hostname",
network: "udp",
raddr: addr,
}

w.Lock()
err := w.connect()
if err != nil {
t.Errorf("failed to connect: %v", err)
w.Unlock()
}
w.Unlock()
defer w.Close()

w.SetFormatter(test.f)

f := test.f
if f == nil {
f = DefaultFormatter
}
expected := strings.TrimSpace(f(LOG_ERR, "hostname", "tag", "this is a test message"))

_, err = w.Write([]byte("this is a test message"))
if err != nil {
t.Errorf("failed to write: %v", err)
}
sent := strings.TrimSpace(<-done)
if sent != expected {
t.Errorf("expected to use the formatter, got %v, expected %v", sent, expected)
}
}
}

func TestWriterFramers(t *testing.T) {
tests := []struct {
f Framer
}{
{nil},
{RFC5425MessageLengthFramer},
{DefaultFramer},
}

for _, test := range tests {
done := make(chan string)
addr, sock, srvWG := startServer("udp", "", done)
defer sock.Close()
defer srvWG.Wait()

w := Writer{
priority: LOG_ERR,
tag: "tag",
hostname: "hostname",
network: "udp",
raddr: addr,
}

w.Lock()
err := w.connect()
if err != nil {
t.Errorf("failed to connect: %v", err)
w.Unlock()
}
w.Unlock()
defer w.Close()

w.SetFramer(test.f)

f := test.f
if f == nil {
f = DefaultFramer
}
expected := strings.TrimSpace(f(DefaultFormatter(LOG_ERR, "hostname", "tag", "this is a test message") + "\n"))

_, err = w.Write([]byte("this is a test message"))
if err != nil {
t.Errorf("failed to write: %v", err)
}
sent := strings.TrimSpace(<-done)
if sent != expected {
t.Errorf("expected to use the framer, got %v, expected %v", sent, expected)
}
}
}

func TestDebug(t *testing.T) {
done := make(chan string)
addr, sock, srvWG := startServer("udp", "", done)
Expand Down

0 comments on commit 0d1b0f5

Please sign in to comment.