Browse Source

Merge pull request #20121 from solganik/master

syslog format
Brian Goff 9 years ago
parent
commit
f500951598

+ 38 - 0
daemon/logger/syslog/syslog.go

@@ -13,6 +13,7 @@ import (
 	"path"
 	"path"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
+	"time"
 
 
 	syslog "github.com/RackSec/srslog"
 	syslog "github.com/RackSec/srslog"
 
 
@@ -64,6 +65,17 @@ func init() {
 	}
 	}
 }
 }
 
 
+// rsyslog uses appname part of syslog message to fill in an %syslogtag% template
+// attribute in rsyslog.conf. In order to be backward compatible to rfc3164
+// tag will be also used as an appname
+func rfc5424formatterWithAppNameAsTag(p syslog.Priority, hostname, tag, content string) string {
+	timestamp := time.Now().Format(time.RFC3339)
+	pid := os.Getpid()
+	msg := fmt.Sprintf("<%d>%d %s %s %s %d %s %s",
+		p, 1, timestamp, hostname, tag, pid, tag, content)
+	return msg
+}
+
 // New creates a syslog logger using the configuration passed in on
 // New creates a syslog logger using the configuration passed in on
 // the context. Supported context configuration variables are
 // the context. Supported context configuration variables are
 // syslog-address, syslog-facility, & syslog-tag.
 // syslog-address, syslog-facility, & syslog-tag.
@@ -83,6 +95,11 @@ func New(ctx logger.Context) (logger.Logger, error) {
 		return nil, err
 		return nil, err
 	}
 	}
 
 
+	syslogFormatter, syslogFramer, err := parseLogFormat(ctx.Config["syslog-format"])
+	if err != nil {
+		return nil, err
+	}
+
 	logTag := path.Base(os.Args[0]) + "/" + tag
 	logTag := path.Base(os.Args[0]) + "/" + tag
 
 
 	var log *syslog.Writer
 	var log *syslog.Writer
@@ -100,6 +117,9 @@ func New(ctx logger.Context) (logger.Logger, error) {
 		return nil, err
 		return nil, err
 	}
 	}
 
 
+	log.SetFormatter(syslogFormatter)
+	log.SetFramer(syslogFramer)
+
 	return &syslogger{
 	return &syslogger{
 		writer: log,
 		writer: log,
 	}, nil
 	}, nil
@@ -165,6 +185,7 @@ func ValidateLogOpt(cfg map[string]string) error {
 		case "syslog-tls-key":
 		case "syslog-tls-key":
 		case "syslog-tls-skip-verify":
 		case "syslog-tls-skip-verify":
 		case "tag":
 		case "tag":
+		case "syslog-format":
 		default:
 		default:
 			return fmt.Errorf("unknown log opt '%s' for syslog log driver", key)
 			return fmt.Errorf("unknown log opt '%s' for syslog log driver", key)
 		}
 		}
@@ -175,6 +196,9 @@ func ValidateLogOpt(cfg map[string]string) error {
 	if _, err := parseFacility(cfg["syslog-facility"]); err != nil {
 	if _, err := parseFacility(cfg["syslog-facility"]); err != nil {
 		return err
 		return err
 	}
 	}
+	if _, _, err := parseLogFormat(cfg["syslog-format"]); err != nil {
+		return err
+	}
 	return nil
 	return nil
 }
 }
 
 
@@ -207,3 +231,17 @@ func parseTLSConfig(cfg map[string]string) (*tls.Config, error) {
 
 
 	return tlsconfig.Client(opts)
 	return tlsconfig.Client(opts)
 }
 }
+
+func parseLogFormat(logFormat string) (syslog.Formatter, syslog.Framer, error) {
+	switch logFormat {
+	case "":
+		return syslog.UnixFormatter, syslog.DefaultFramer, nil
+	case "rfc3164":
+		return syslog.RFC3164Formatter, syslog.DefaultFramer, nil
+	case "rfc5424":
+		return rfc5424formatterWithAppNameAsTag, syslog.RFC5425MessageLengthFramer, nil
+	default:
+		return nil, nil, errors.New("Invalid syslog format")
+	}
+
+}

+ 45 - 0
daemon/logger/syslog/syslog_test.go

@@ -0,0 +1,45 @@
+// +build linux
+
+package syslog
+
+import (
+	syslog "github.com/RackSec/srslog"
+	"reflect"
+	"testing"
+)
+
+func functionMatches(expectedFun interface{}, actualFun interface{}) bool {
+	return reflect.ValueOf(expectedFun).Pointer() == reflect.ValueOf(actualFun).Pointer()
+}
+
+func TestParseLogFormat(t *testing.T) {
+	formatter, framer, err := parseLogFormat("rfc5424")
+	if err != nil || !functionMatches(rfc5424formatterWithAppNameAsTag, formatter) ||
+		!functionMatches(syslog.RFC5425MessageLengthFramer, framer) {
+		t.Fatal("Failed to parse rfc5424 format", err, formatter, framer)
+	}
+
+	formatter, framer, err = parseLogFormat("rfc3164")
+	if err != nil || !functionMatches(syslog.RFC3164Formatter, formatter) ||
+		!functionMatches(syslog.DefaultFramer, framer) {
+		t.Fatal("Failed to parse rfc3164 format", err, formatter, framer)
+	}
+
+	formatter, framer, err = parseLogFormat("")
+	if err != nil || !functionMatches(syslog.UnixFormatter, formatter) ||
+		!functionMatches(syslog.DefaultFramer, framer) {
+		t.Fatal("Failed to parse empty format", err, formatter, framer)
+	}
+
+	formatter, framer, err = parseLogFormat("invalid")
+	if err == nil {
+		t.Fatal("Failed to parse invalid format", err, formatter, framer)
+	}
+}
+
+func TestValidateLogOptEmpty(t *testing.T) {
+	emptyConfig := make(map[string]string)
+	if err := ValidateLogOpt(emptyConfig); err != nil {
+		t.Fatal("Failed to parse empty config", err)
+	}
+}

+ 6 - 0
docs/admin/logging/overview.md

@@ -80,6 +80,7 @@ The following logging options are supported for the `syslog` logging driver:
     --log-opt syslog-tls-key=/etc/ca-certificates/custom/key.pem
     --log-opt syslog-tls-key=/etc/ca-certificates/custom/key.pem
     --log-opt syslog-tls-skip-verify=true
     --log-opt syslog-tls-skip-verify=true
     --log-opt tag="mailer"
     --log-opt tag="mailer"
+    --log-opt syslog-format=[rfc5424|rfc3164] 
 
 
 `syslog-address` specifies the remote syslog server address where the driver connects to.
 `syslog-address` specifies the remote syslog server address where the driver connects to.
 If not specified it defaults to the local unix socket of the running system.
 If not specified it defaults to the local unix socket of the running system.
@@ -131,6 +132,11 @@ By default, Docker uses the first 12 characters of the container ID to tag log m
 Refer to the [log tag option documentation](log_tags.md) for customizing
 Refer to the [log tag option documentation](log_tags.md) for customizing
 the log tag format.
 the log tag format.
 
 
+`syslog-format` specifies syslog message format to use when logging.
+If not specified it defaults to the local unix syslog format without hostname specification.
+Specify rfc3164 to perform logging in RFC-3164 compatible format. Specify rfc5424 to perform 
+logging in RFC-5424 compatible format
+
 
 
 ## journald options
 ## journald options
 
 

+ 1 - 1
hack/vendor.sh

@@ -25,7 +25,7 @@ clone git golang.org/x/sys eb2c74142fd19a79b3f237334c7384d5167b1b46 https://gith
 clone git github.com/docker/go-units 651fc226e7441360384da338d0fd37f2440ffbe3
 clone git github.com/docker/go-units 651fc226e7441360384da338d0fd37f2440ffbe3
 clone git github.com/docker/go-connections v0.2.0
 clone git github.com/docker/go-connections v0.2.0
 clone git github.com/docker/engine-api 9bab0d5b73872e53dfadfa055dcc519e57b09439
 clone git github.com/docker/engine-api 9bab0d5b73872e53dfadfa055dcc519e57b09439
-clone git github.com/RackSec/srslog 6eb773f331e46fbba8eecb8e794e635e75fc04de
+clone git github.com/RackSec/srslog 259aed10dfa74ea2961eddd1d9847619f6e98837
 clone git github.com/imdario/mergo 0.2.1
 clone git github.com/imdario/mergo 0.2.1
 
 
 #get libnetwork packages
 #get libnetwork packages

+ 6 - 1
vendor/src/github.com/RackSec/srslog/.travis.yml

@@ -4,10 +4,15 @@ group: edge
 language: go
 language: go
 go:
 go:
 - 1.5
 - 1.5
+before_install:
+  - pip install --user codecov
 script:
 script:
 - |
 - |
   go get ./...
   go get ./...
-  go test -v ./...
+  go test -v -coverprofile=coverage.txt -covermode=atomic
+  go vet
+after_success:
+  - codecov
 notifications:
 notifications:
   slack:
   slack:
     secure: dtDue9gP6CRR1jYjEf6raXXFak3QKGcCFvCf5mfvv5XScdpmc3udwgqc5TdyjC0goaC9OK/4jTcCD30dYZm/u6ux3E9mo3xwMl2xRLHx76p5r9rSQtloH19BDwA2+A+bpDfFQVz05k2YXuTiGSvNMMdwzx+Dr294Sl/z43RFB4+b9/R/6LlFpRW89IwftvpLAFnBy4K/ZcspQzKM+rQfQTL5Kk+iZ/KBsuR/VziDq6MoJ8t43i4ee8vwS06vFBKDbUiZ4FIZpLgc2RAL5qso5aWRKYXL6waXfoKHZWKPe0w4+9IY1rDJxG1jEb7YGgcbLaF9xzPRRs2b2yO/c87FKpkh6PDgYHfLjpgXotCoojZrL4p1x6MI1ldJr3NhARGPxS9r4liB9n6Y5nD+ErXi1IMf55fuUHcPY27Jc0ySeLFeM6cIWJ8OhFejCgGw6a5DnnmJo0PqopsaBDHhadpLejT1+K6bL2iGkT4SLcVNuRGLs+VyuNf1+5XpkWZvy32vquO7SZOngLLBv+GIem+t3fWm0Z9s/0i1uRCQei1iUutlYjoV/LBd35H2rhob4B5phIuJin9kb0zbHf6HnaoN0CtN8r0d8G5CZiInVlG5Xcid5Byb4dddf5U2EJTDuCMVyyiM7tcnfjqw9UbVYNxtYM9SzcqIq+uVqM8pYL9xSec=
     secure: dtDue9gP6CRR1jYjEf6raXXFak3QKGcCFvCf5mfvv5XScdpmc3udwgqc5TdyjC0goaC9OK/4jTcCD30dYZm/u6ux3E9mo3xwMl2xRLHx76p5r9rSQtloH19BDwA2+A+bpDfFQVz05k2YXuTiGSvNMMdwzx+Dr294Sl/z43RFB4+b9/R/6LlFpRW89IwftvpLAFnBy4K/ZcspQzKM+rQfQTL5Kk+iZ/KBsuR/VziDq6MoJ8t43i4ee8vwS06vFBKDbUiZ4FIZpLgc2RAL5qso5aWRKYXL6waXfoKHZWKPe0w4+9IY1rDJxG1jEb7YGgcbLaF9xzPRRs2b2yO/c87FKpkh6PDgYHfLjpgXotCoojZrL4p1x6MI1ldJr3NhARGPxS9r4liB9n6Y5nD+ErXi1IMf55fuUHcPY27Jc0ySeLFeM6cIWJ8OhFejCgGw6a5DnnmJo0PqopsaBDHhadpLejT1+K6bL2iGkT4SLcVNuRGLs+VyuNf1+5XpkWZvy32vquO7SZOngLLBv+GIem+t3fWm0Z9s/0i1uRCQei1iUutlYjoV/LBd35H2rhob4B5phIuJin9kb0zbHf6HnaoN0CtN8r0d8G5CZiInVlG5Xcid5Byb4dddf5U2EJTDuCMVyyiM7tcnfjqw9UbVYNxtYM9SzcqIq+uVqM8pYL9xSec=

+ 42 - 8
vendor/src/github.com/RackSec/srslog/dialer.go

@@ -5,19 +5,49 @@ import (
 	"net"
 	"net"
 )
 )
 
 
-func (w Writer) getDialer() func() (serverConn, string, error) {
-	dialers := map[string]func() (serverConn, string, error){
-		"":        w.unixDialer,
-		"tcp+tls": w.tlsDialer,
+// dialerFunctionWrapper is a simple object that consists of a dialer function
+// and its name. This is primarily for testing, so we can make sure that the
+// getDialer method returns the correct dialer function. However, if you ever
+// find that you need to check which dialer function you have, this would also
+// be useful for you without having to use reflection.
+type dialerFunctionWrapper struct {
+	Name   string
+	Dialer func() (serverConn, string, error)
+}
+
+// Call the wrapped dialer function and return its return values.
+func (df dialerFunctionWrapper) Call() (serverConn, string, error) {
+	return df.Dialer()
+}
+
+// getDialer returns a "dialer" function that can be called to connect to a
+// syslog server.
+//
+// Each dialer function is responsible for dialing the remote host and returns
+// a serverConn, the hostname (or a default if the Writer has not specified a
+// hostname), and an error in case dialing fails.
+//
+// The reason for separate dialers is that different network types may need
+// to dial their connection differently, yet still provide a net.Conn interface
+// that you can use once they have dialed. Rather than an increasingly long
+// conditional, we have a map of network -> dialer function (with a sane default
+// value), and adding a new network type is as easy as writing the dialer
+// function and adding it to the map.
+func (w *Writer) getDialer() dialerFunctionWrapper {
+	dialers := map[string]dialerFunctionWrapper{
+		"":        dialerFunctionWrapper{"unixDialer", w.unixDialer},
+		"tcp+tls": dialerFunctionWrapper{"tlsDialer", w.tlsDialer},
 	}
 	}
 	dialer, ok := dialers[w.network]
 	dialer, ok := dialers[w.network]
 	if !ok {
 	if !ok {
-		dialer = w.basicDialer
+		dialer = dialerFunctionWrapper{"basicDialer", w.basicDialer}
 	}
 	}
 	return dialer
 	return dialer
 }
 }
 
 
-func (w Writer) unixDialer() (serverConn, string, error) {
+// unixDialer uses the unixSyslog method to open a connection to the syslog
+// daemon running on the local machine.
+func (w *Writer) unixDialer() (serverConn, string, error) {
 	sc, err := unixSyslog()
 	sc, err := unixSyslog()
 	hostname := w.hostname
 	hostname := w.hostname
 	if hostname == "" {
 	if hostname == "" {
@@ -26,7 +56,9 @@ func (w Writer) unixDialer() (serverConn, string, error) {
 	return sc, hostname, err
 	return sc, hostname, err
 }
 }
 
 
-func (w Writer) tlsDialer() (serverConn, string, error) {
+// tlsDialer connects to TLS over TCP, and is used for the "tcp+tls" network
+// type.
+func (w *Writer) tlsDialer() (serverConn, string, error) {
 	c, err := tls.Dial("tcp", w.raddr, w.tlsConfig)
 	c, err := tls.Dial("tcp", w.raddr, w.tlsConfig)
 	var sc serverConn
 	var sc serverConn
 	hostname := w.hostname
 	hostname := w.hostname
@@ -39,7 +71,9 @@ func (w Writer) tlsDialer() (serverConn, string, error) {
 	return sc, hostname, err
 	return sc, hostname, err
 }
 }
 
 
-func (w Writer) basicDialer() (serverConn, string, error) {
+// basicDialer is the most common dialer for syslog, and supports both TCP and
+// UDP connections.
+func (w *Writer) basicDialer() (serverConn, string, error) {
 	c, err := net.Dial(w.network, w.raddr)
 	c, err := net.Dial(w.network, w.raddr)
 	var sc serverConn
 	var sc serverConn
 	hostname := w.hostname
 	hostname := w.hostname

+ 48 - 0
vendor/src/github.com/RackSec/srslog/formatter.go

@@ -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
+}

+ 24 - 0
vendor/src/github.com/RackSec/srslog/framer.go

@@ -0,0 +1,24 @@
+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.
+func RFC5425MessageLengthFramer(in string) string {
+	return fmt.Sprintf("%d %s", len(in), in)
+}

+ 14 - 8
vendor/src/github.com/RackSec/srslog/net_conn.go

@@ -1,24 +1,30 @@
 package srslog
 package srslog
 
 
 import (
 import (
-	"fmt"
 	"net"
 	"net"
-	"os"
-	"time"
 )
 )
 
 
+// netConn has an internal net.Conn and adheres to the serverConn interface,
+// allowing us to send syslog messages over the network.
 type netConn struct {
 type netConn struct {
 	conn net.Conn
 	conn net.Conn
 }
 }
 
 
-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)
+// writeString formats syslog messages using time.RFC3339 and includes the
+// hostname, and sends the message to the connection.
+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
 	return err
 }
 }
 
 
+// close the network connection
 func (n *netConn) close() error {
 func (n *netConn) close() error {
 	return n.conn.Close()
 	return n.conn.Close()
 }
 }

+ 12 - 8
vendor/src/github.com/RackSec/srslog/srslog.go

@@ -8,14 +8,10 @@ import (
 	"os"
 	"os"
 )
 )
 
 
-// This interface and the separate syslog_unix.go file exist for
-// Solaris support as implemented by gccgo.  On Solaris you can not
-// simply open a TCP connection to the syslog daemon.  The gccgo
-// sources have a syslog_solaris.go file that implements unixSyslog to
-// return a type that satisfies this interface and simply calls the C
-// library syslog function.
+// This interface allows us to work with both local and network connections,
+// and enables Solaris support (see syslog_unix.go).
 type serverConn interface {
 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
 	close() error
 }
 }
 
 
@@ -39,11 +35,19 @@ func Dial(network, raddr string, priority Priority, tag string) (*Writer, error)
 // address raddr on the specified network. It uses certPath to load TLS certificates and configure
 // address raddr on the specified network. It uses certPath to load TLS certificates and configure
 // the secure connection.
 // the secure connection.
 func DialWithTLSCertPath(network, raddr string, priority Priority, tag, certPath string) (*Writer, error) {
 func DialWithTLSCertPath(network, raddr string, priority Priority, tag, certPath string) (*Writer, error) {
-	pool := x509.NewCertPool()
 	serverCert, err := ioutil.ReadFile(certPath)
 	serverCert, err := ioutil.ReadFile(certPath)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
+
+	return DialWithTLSCert(network, raddr, priority, tag, serverCert)
+}
+
+// DialWIthTLSCert establishes a secure connection to a log daemon by connecting to
+// address raddr on the specified network. It uses serverCert to load a TLS certificate
+// and configure the secure connection.
+func DialWithTLSCert(network, raddr string, priority Priority, tag string, serverCert []byte) (*Writer, error) {
+	pool := x509.NewCertPool()
 	pool.AppendCertsFromPEM(serverCert)
 	pool.AppendCertsFromPEM(serverCert)
 	config := tls.Config{
 	config := tls.Config{
 		RootCAs: pool,
 		RootCAs: pool,

+ 21 - 14
vendor/src/github.com/RackSec/srslog/srslog_unix.go

@@ -2,15 +2,17 @@ package srslog
 
 
 import (
 import (
 	"errors"
 	"errors"
-	"fmt"
+	"io"
 	"net"
 	"net"
-	"os"
-	"time"
 )
 )
 
 
 // unixSyslog opens a connection to the syslog daemon running on the
 // unixSyslog opens a connection to the syslog daemon running on the
-// local machine using a Unix domain socket.
-
+// local machine using a Unix domain socket. This function exists because of
+// Solaris support as implemented by gccgo.  On Solaris you can not
+// simply open a TCP connection to the syslog daemon.  The gccgo
+// sources have a syslog_solaris.go file that implements unixSyslog to
+// return a type that satisfies the serverConn interface and simply calls the C
+// library syslog function.
 func unixSyslog() (conn serverConn, err error) {
 func unixSyslog() (conn serverConn, err error) {
 	logTypes := []string{"unixgram", "unix"}
 	logTypes := []string{"unixgram", "unix"}
 	logPaths := []string{"/dev/log", "/var/run/syslog", "/var/run/log"}
 	logPaths := []string{"/dev/log", "/var/run/syslog", "/var/run/log"}
@@ -27,21 +29,26 @@ func unixSyslog() (conn serverConn, err error) {
 	return nil, errors.New("Unix syslog delivery error")
 	return nil, errors.New("Unix syslog delivery error")
 }
 }
 
 
+// localConn adheres to the serverConn interface, allowing us to send syslog
+// messages to the local syslog daemon over a Unix domain socket.
 type localConn struct {
 type localConn struct {
-	conn net.Conn
+	conn io.WriteCloser
 }
 }
 
 
-func (n *localConn) writeString(p Priority, hostname, tag, msg string) error {
-	// Compared to the network form at srslog.netConn, the changes are:
-	//	1. Use time.Stamp instead of time.RFC3339.
-	//	2. Drop the hostname field from the Fprintf.
-	timestamp := time.Now().Format(time.Stamp)
-	_, err := fmt.Fprintf(n.conn, "<%d>%s %s[%d]: %s",
-		p, timestamp,
-		tag, os.Getpid(), msg)
+// 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(framer Framer, formatter Formatter, p Priority, hostname, tag, msg string) error {
+	if framer == nil {
+		framer = DefaultFramer
+	}
+	if formatter == nil {
+		formatter = UnixFormatter
+	}
+	_, err := n.conn.Write([]byte(framer(formatter(p, hostname, tag, msg))))
 	return err
 	return err
 }
 }
 
 
+// close the (local) network connection
 func (n *localConn) close() error {
 func (n *localConn) close() error {
 	return n.conn.Close()
 	return n.conn.Close()
 }
 }

+ 16 - 4
vendor/src/github.com/RackSec/srslog/writer.go

@@ -16,6 +16,8 @@ type Writer struct {
 	network   string
 	network   string
 	raddr     string
 	raddr     string
 	tlsConfig *tls.Config
 	tlsConfig *tls.Config
+	framer    Framer
+	formatter Formatter
 
 
 	conn serverConn
 	conn serverConn
 }
 }
@@ -32,7 +34,7 @@ func (w *Writer) connect() (err error) {
 	var conn serverConn
 	var conn serverConn
 	var hostname string
 	var hostname string
 	dialer := w.getDialer()
 	dialer := w.getDialer()
-	conn, hostname, err = dialer()
+	conn, hostname, err = dialer.Call()
 	if err == nil {
 	if err == nil {
 		w.conn = conn
 		w.conn = conn
 		w.hostname = hostname
 		w.hostname = hostname
@@ -41,6 +43,16 @@ func (w *Writer) connect() (err error) {
 	return
 	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
 // Write sends a log message to the syslog daemon using the default priority
 // passed into `srslog.New` or the `srslog.Dial*` functions.
 // passed into `srslog.New` or the `srslog.Dial*` functions.
 func (w *Writer) Write(b []byte) (int, error) {
 func (w *Writer) Write(b []byte) (int, error) {
@@ -133,15 +145,15 @@ func (w *Writer) writeAndRetry(p Priority, s string) (int, error) {
 	return w.write(pr, s)
 	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) {
 func (w *Writer) write(p Priority, msg string) (int, error) {
 	// ensure it ends in a \n
 	// ensure it ends in a \n
 	if !strings.HasSuffix(msg, "\n") {
 	if !strings.HasSuffix(msg, "\n") {
 		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 {
 	if err != nil {
 		return 0, err
 		return 0, err
 	}
 	}