diff --git a/daemon/logger/syslog/syslog.go b/daemon/logger/syslog/syslog.go index f6861947a6..99e03278ce 100644 --- a/daemon/logger/syslog/syslog.go +++ b/daemon/logger/syslog/syslog.go @@ -13,6 +13,7 @@ import ( "path" "strconv" "strings" + "time" 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 // the context. Supported context configuration variables are // syslog-address, syslog-facility, & syslog-tag. @@ -83,6 +95,11 @@ func New(ctx logger.Context) (logger.Logger, error) { return nil, err } + syslogFormatter, syslogFramer, err := parseLogFormat(ctx.Config["syslog-format"]) + if err != nil { + return nil, err + } + logTag := path.Base(os.Args[0]) + "/" + tag var log *syslog.Writer @@ -100,6 +117,9 @@ func New(ctx logger.Context) (logger.Logger, error) { return nil, err } + log.SetFormatter(syslogFormatter) + log.SetFramer(syslogFramer) + return &syslogger{ writer: log, }, nil @@ -165,6 +185,7 @@ func ValidateLogOpt(cfg map[string]string) error { case "syslog-tls-key": case "syslog-tls-skip-verify": case "tag": + case "syslog-format": default: 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 { return err } + if _, _, err := parseLogFormat(cfg["syslog-format"]); err != nil { + return err + } return nil } @@ -207,3 +231,17 @@ func parseTLSConfig(cfg map[string]string) (*tls.Config, error) { 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") + } + +} diff --git a/daemon/logger/syslog/syslog_test.go b/daemon/logger/syslog/syslog_test.go new file mode 100644 index 0000000000..c18494be10 --- /dev/null +++ b/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) + } +} diff --git a/docs/admin/logging/overview.md b/docs/admin/logging/overview.md index e3d3d11256..8338ab3286 100644 --- a/docs/admin/logging/overview.md +++ b/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-skip-verify=true --log-opt tag="mailer" + --log-opt syslog-format=[rfc5424|rfc3164] `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. @@ -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 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