Browse code

Fixes #18712. Add rfc5424 log format for syslog.

Previously docker used obsolete rfc3164 syslog format for syslog. rfc3164 explicitly
uses semicolon as a separator between 'TAG' and 'Content' section of the log message.
Docker uses semicolon as a separator between image name and version tag.
When {{.ImageName}} was used as a tag expression and contained ":" syslog parser mistreated
"tag" part of the image name as syslog message body, which resulted in incorrect "syslogtag" been reported by syslog
daemon.
Use of rfc5424 log format partually fixes the issue as it does not use semicolon as a separator.
However using default rfc5424 syslog format itroduces backward incompatability because rsyslog template keyword %syslogtag%
is parsed differently. In rfc3164 it uses the "TAG" part reported before the "pid" part. In rfc5424 it uses "appname" part reported
before the pid part, while tag part is introduced by %msgid% part.
For more information on rsyslog configuration properties see: http://www.rsyslog.com/doc/master/configuration/properties.html

Added two options to specify logging in either rfc5424, rfc3164 format or unix format omitting hostname in order to keep backwards compatability with
previous versions.

Signed-off-by: Solganik Alexander <solganik@gmail.com>

Solganik Alexander authored on 2016/02/04 05:59:27
Showing 3 changed files
... ...
@@ -13,6 +13,7 @@ import (
13 13
 	"path"
14 14
 	"strconv"
15 15
 	"strings"
16
+	"time"
16 17
 
17 18
 	syslog "github.com/RackSec/srslog"
18 19
 
... ...
@@ -64,6 +65,17 @@ func init() {
64 64
 	}
65 65
 }
66 66
 
67
+// rsyslog uses appname part of syslog message to fill in an %syslogtag% template
68
+// attribute in rsyslog.conf. In order to be backward compatible to rfc3164
69
+// tag will be also used as an appname
70
+func rfc5424formatterWithAppNameAsTag(p syslog.Priority, hostname, tag, content string) string {
71
+	timestamp := time.Now().Format(time.RFC3339)
72
+	pid := os.Getpid()
73
+	msg := fmt.Sprintf("<%d>%d %s %s %s %d %s %s",
74
+		p, 1, timestamp, hostname, tag, pid, tag, content)
75
+	return msg
76
+}
77
+
67 78
 // New creates a syslog logger using the configuration passed in on
68 79
 // the context. Supported context configuration variables are
69 80
 // syslog-address, syslog-facility, & syslog-tag.
... ...
@@ -83,6 +95,11 @@ func New(ctx logger.Context) (logger.Logger, error) {
83 83
 		return nil, err
84 84
 	}
85 85
 
86
+	syslogFormatter, syslogFramer, err := parseLogFormat(ctx.Config["syslog-format"])
87
+	if err != nil {
88
+		return nil, err
89
+	}
90
+
86 91
 	logTag := path.Base(os.Args[0]) + "/" + tag
87 92
 
88 93
 	var log *syslog.Writer
... ...
@@ -100,6 +117,9 @@ func New(ctx logger.Context) (logger.Logger, error) {
100 100
 		return nil, err
101 101
 	}
102 102
 
103
+	log.SetFormatter(syslogFormatter)
104
+	log.SetFramer(syslogFramer)
105
+
103 106
 	return &syslogger{
104 107
 		writer: log,
105 108
 	}, nil
... ...
@@ -165,6 +185,7 @@ func ValidateLogOpt(cfg map[string]string) error {
165 165
 		case "syslog-tls-key":
166 166
 		case "syslog-tls-skip-verify":
167 167
 		case "tag":
168
+		case "syslog-format":
168 169
 		default:
169 170
 			return fmt.Errorf("unknown log opt '%s' for syslog log driver", key)
170 171
 		}
... ...
@@ -175,6 +196,9 @@ func ValidateLogOpt(cfg map[string]string) error {
175 175
 	if _, err := parseFacility(cfg["syslog-facility"]); err != nil {
176 176
 		return err
177 177
 	}
178
+	if _, _, err := parseLogFormat(cfg["syslog-format"]); err != nil {
179
+		return err
180
+	}
178 181
 	return nil
179 182
 }
180 183
 
... ...
@@ -207,3 +231,17 @@ func parseTLSConfig(cfg map[string]string) (*tls.Config, error) {
207 207
 
208 208
 	return tlsconfig.Client(opts)
209 209
 }
210
+
211
+func parseLogFormat(logFormat string) (syslog.Formatter, syslog.Framer, error) {
212
+	switch logFormat {
213
+	case "":
214
+		return syslog.UnixFormatter, syslog.DefaultFramer, nil
215
+	case "rfc3164":
216
+		return syslog.RFC3164Formatter, syslog.DefaultFramer, nil
217
+	case "rfc5424":
218
+		return rfc5424formatterWithAppNameAsTag, syslog.RFC5425MessageLengthFramer, nil
219
+	default:
220
+		return nil, nil, errors.New("Invalid syslog format")
221
+	}
222
+
223
+}
210 224
new file mode 100644
... ...
@@ -0,0 +1,45 @@
0
+// +build linux
1
+
2
+package syslog
3
+
4
+import (
5
+	syslog "github.com/RackSec/srslog"
6
+	"reflect"
7
+	"testing"
8
+)
9
+
10
+func functionMatches(expectedFun interface{}, actualFun interface{}) bool {
11
+	return reflect.ValueOf(expectedFun).Pointer() == reflect.ValueOf(actualFun).Pointer()
12
+}
13
+
14
+func TestParseLogFormat(t *testing.T) {
15
+	formatter, framer, err := parseLogFormat("rfc5424")
16
+	if err != nil || !functionMatches(rfc5424formatterWithAppNameAsTag, formatter) ||
17
+		!functionMatches(syslog.RFC5425MessageLengthFramer, framer) {
18
+		t.Fatal("Failed to parse rfc5424 format", err, formatter, framer)
19
+	}
20
+
21
+	formatter, framer, err = parseLogFormat("rfc3164")
22
+	if err != nil || !functionMatches(syslog.RFC3164Formatter, formatter) ||
23
+		!functionMatches(syslog.DefaultFramer, framer) {
24
+		t.Fatal("Failed to parse rfc3164 format", err, formatter, framer)
25
+	}
26
+
27
+	formatter, framer, err = parseLogFormat("")
28
+	if err != nil || !functionMatches(syslog.UnixFormatter, formatter) ||
29
+		!functionMatches(syslog.DefaultFramer, framer) {
30
+		t.Fatal("Failed to parse empty format", err, formatter, framer)
31
+	}
32
+
33
+	formatter, framer, err = parseLogFormat("invalid")
34
+	if err == nil {
35
+		t.Fatal("Failed to parse invalid format", err, formatter, framer)
36
+	}
37
+}
38
+
39
+func TestValidateLogOptEmpty(t *testing.T) {
40
+	emptyConfig := make(map[string]string)
41
+	if err := ValidateLogOpt(emptyConfig); err != nil {
42
+		t.Fatal("Failed to parse empty config", err)
43
+	}
44
+}
... ...
@@ -80,6 +80,7 @@ The following logging options are supported for the `syslog` logging driver:
80 80
     --log-opt syslog-tls-key=/etc/ca-certificates/custom/key.pem
81 81
     --log-opt syslog-tls-skip-verify=true
82 82
     --log-opt tag="mailer"
83
+    --log-opt syslog-format=[rfc5424|rfc3164] 
83 84
 
84 85
 `syslog-address` specifies the remote syslog server address where the driver connects to.
85 86
 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
131 131
 Refer to the [log tag option documentation](log_tags.md) for customizing
132 132
 the log tag format.
133 133
 
134
+`syslog-format` specifies syslog message format to use when logging.
135
+If not specified it defaults to the local unix syslog format without hostname specification.
136
+Specify rfc3164 to perform logging in RFC-3164 compatible format. Specify rfc5424 to perform 
137
+logging in RFC-5424 compatible format
138
+
134 139
 
135 140
 ## journald options
136 141