瀏覽代碼

Merge pull request #25786 from splunk/splunk-logging-driver-raw-format-using-json

Splunk Logging Driver: formats and verifyconnection
Alexander Morozov 8 年之前
父節點
當前提交
78771b6225
共有 4 個文件被更改,包括 220 次插入29 次删除
  1. 6 2
      contrib/completion/bash/docker
  2. 1 1
      contrib/completion/zsh/_docker
  3. 147 26
      daemon/logger/splunk/splunk.go
  4. 66 0
      docs/admin/logging/splunk.md

+ 6 - 2
contrib/completion/bash/docker

@@ -520,7 +520,7 @@ __docker_complete_log_options() {
 	local journald_options="env labels tag"
 	local journald_options="env labels tag"
 	local json_file_options="env labels max-file max-size"
 	local json_file_options="env labels max-file max-size"
 	local syslog_options="env labels syslog-address syslog-facility syslog-format syslog-tls-ca-cert syslog-tls-cert syslog-tls-key syslog-tls-skip-verify tag"
 	local syslog_options="env labels syslog-address syslog-facility syslog-format syslog-tls-ca-cert syslog-tls-cert syslog-tls-key syslog-tls-skip-verify tag"
-	local splunk_options="env labels splunk-caname splunk-capath splunk-index splunk-insecureskipverify splunk-source splunk-sourcetype splunk-token splunk-url tag"
+	local splunk_options="env labels splunk-caname splunk-capath splunk-format splunk-index splunk-insecureskipverify splunk-source splunk-sourcetype splunk-token splunk-url splunk-verify-connection tag"
 
 
 	local all_options="$fluentd_options $gcplogs_options $gelf_options $journald_options $json_file_options $syslog_options $splunk_options"
 	local all_options="$fluentd_options $gcplogs_options $gelf_options $journald_options $json_file_options $syslog_options $splunk_options"
 
 
@@ -629,10 +629,14 @@ __docker_complete_log_driver_options() {
 			__ltrim_colon_completions "${cur}"
 			__ltrim_colon_completions "${cur}"
 			return
 			return
 			;;
 			;;
-		splunk-insecureskipverify)
+		splunk-insecureskipverify|splunk-verify-connection)
 			COMPREPLY=( $( compgen -W "false true" -- "${cur##*=}" ) )
 			COMPREPLY=( $( compgen -W "false true" -- "${cur##*=}" ) )
 			return
 			return
 			;;
 			;;
+		splunk-format)
+			COMPREPLY=( $( compgen -W "inline json raw" -- "${cur##*=}" ) )
+			return
+			;;
 	esac
 	esac
 	return 1
 	return 1
 }
 }

+ 1 - 1
contrib/completion/zsh/_docker

@@ -228,7 +228,7 @@ __docker_get_log_options() {
     journald_options=("env" "labels" "tag")
     journald_options=("env" "labels" "tag")
     json_file_options=("env" "labels" "max-file" "max-size")
     json_file_options=("env" "labels" "max-file" "max-size")
     syslog_options=("env" "labels" "syslog-address" "syslog-facility" "syslog-format" "syslog-tls-ca-cert" "syslog-tls-cert" "syslog-tls-key" "syslog-tls-skip-verify" "tag")
     syslog_options=("env" "labels" "syslog-address" "syslog-facility" "syslog-format" "syslog-tls-ca-cert" "syslog-tls-cert" "syslog-tls-key" "syslog-tls-skip-verify" "tag")
-    splunk_options=("env" "labels" "splunk-caname" "splunk-capath" "splunk-index" "splunk-insecureskipverify" "splunk-source" "splunk-sourcetype" "splunk-token" "splunk-url" "tag")
+    splunk_options=("env" "labels" "splunk-caname" "splunk-capath" "splunk-format" "splunk-index" "splunk-insecureskipverify" "splunk-source" "splunk-sourcetype" "splunk-token" "splunk-url" "splunk-verify-connection" "tag")
 
 
     [[ $log_driver = (awslogs|all) ]] && _describe -t awslogs-options "awslogs options" awslogs_options "$@" && ret=0
     [[ $log_driver = (awslogs|all) ]] && _describe -t awslogs-options "awslogs options" awslogs_options "$@" && ret=0
     [[ $log_driver = (fluentd|all) ]] && _describe -t fluentd-options "fluentd options" fluentd_options "$@" && ret=0
     [[ $log_driver = (fluentd|all) ]] && _describe -t fluentd-options "fluentd options" fluentd_options "$@" && ret=0

+ 147 - 26
daemon/logger/splunk/splunk.go

@@ -13,6 +13,7 @@ import (
 	"net/http"
 	"net/http"
 	"net/url"
 	"net/url"
 	"strconv"
 	"strconv"
+	"time"
 
 
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
 	"github.com/docker/docker/daemon/logger"
 	"github.com/docker/docker/daemon/logger"
@@ -30,6 +31,8 @@ const (
 	splunkCAPathKey             = "splunk-capath"
 	splunkCAPathKey             = "splunk-capath"
 	splunkCANameKey             = "splunk-caname"
 	splunkCANameKey             = "splunk-caname"
 	splunkInsecureSkipVerifyKey = "splunk-insecureskipverify"
 	splunkInsecureSkipVerifyKey = "splunk-insecureskipverify"
+	splunkFormatKey             = "splunk-format"
+	splunkVerifyConnectionKey   = "splunk-verify-connection"
 	envKey                      = "env"
 	envKey                      = "env"
 	labelsKey                   = "labels"
 	labelsKey                   = "labels"
 	tagKey                      = "tag"
 	tagKey                      = "tag"
@@ -44,22 +47,44 @@ type splunkLogger struct {
 	nullMessage *splunkMessage
 	nullMessage *splunkMessage
 }
 }
 
 
+type splunkLoggerInline struct {
+	*splunkLogger
+
+	nullEvent *splunkMessageEvent
+}
+
+type splunkLoggerJSON struct {
+	*splunkLoggerInline
+}
+
+type splunkLoggerRaw struct {
+	*splunkLogger
+
+	prefix []byte
+}
+
 type splunkMessage struct {
 type splunkMessage struct {
-	Event      splunkMessageEvent `json:"event"`
-	Time       string             `json:"time"`
-	Host       string             `json:"host"`
-	Source     string             `json:"source,omitempty"`
-	SourceType string             `json:"sourcetype,omitempty"`
-	Index      string             `json:"index,omitempty"`
+	Event      interface{} `json:"event"`
+	Time       string      `json:"time"`
+	Host       string      `json:"host"`
+	Source     string      `json:"source,omitempty"`
+	SourceType string      `json:"sourcetype,omitempty"`
+	Index      string      `json:"index,omitempty"`
 }
 }
 
 
 type splunkMessageEvent struct {
 type splunkMessageEvent struct {
-	Line   string            `json:"line"`
+	Line   interface{}       `json:"line"`
 	Source string            `json:"source"`
 	Source string            `json:"source"`
 	Tag    string            `json:"tag,omitempty"`
 	Tag    string            `json:"tag,omitempty"`
 	Attrs  map[string]string `json:"attrs,omitempty"`
 	Attrs  map[string]string `json:"attrs,omitempty"`
 }
 }
 
 
+const (
+	splunkFormatRaw    = "raw"
+	splunkFormatJSON   = "json"
+	splunkFormatInline = "inline"
+)
+
 func init() {
 func init() {
 	if err := logger.RegisterLogDriver(driverName, New); err != nil {
 	if err := logger.RegisterLogDriver(driverName, New); err != nil {
 		logrus.Fatal(err)
 		logrus.Fatal(err)
@@ -122,21 +147,23 @@ func New(ctx logger.Context) (logger.Logger, error) {
 		Transport: transport,
 		Transport: transport,
 	}
 	}
 
 
+	source := ctx.Config[splunkSourceKey]
+	sourceType := ctx.Config[splunkSourceTypeKey]
+	index := ctx.Config[splunkIndexKey]
+
 	var nullMessage = &splunkMessage{
 	var nullMessage = &splunkMessage{
-		Host: hostname,
+		Host:       hostname,
+		Source:     source,
+		SourceType: sourceType,
+		Index:      index,
 	}
 	}
 
 
-	// Optional parameters for messages
-	nullMessage.Source = ctx.Config[splunkSourceKey]
-	nullMessage.SourceType = ctx.Config[splunkSourceTypeKey]
-	nullMessage.Index = ctx.Config[splunkIndexKey]
-
 	tag, err := loggerutils.ParseLogTag(ctx, loggerutils.DefaultTemplate)
 	tag, err := loggerutils.ParseLogTag(ctx, loggerutils.DefaultTemplate)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
-	nullMessage.Event.Tag = tag
-	nullMessage.Event.Attrs = ctx.ExtraAttributes(nil)
+
+	attrs := ctx.ExtraAttributes(nil)
 
 
 	logger := &splunkLogger{
 	logger := &splunkLogger{
 		client:      client,
 		client:      client,
@@ -146,22 +173,108 @@ func New(ctx logger.Context) (logger.Logger, error) {
 		nullMessage: nullMessage,
 		nullMessage: nullMessage,
 	}
 	}
 
 
-	err = verifySplunkConnection(logger)
-	if err != nil {
-		return nil, err
+	// By default we verify connection, but we allow use to skip that
+	verifyConnection := true
+	if verifyConnectionStr, ok := ctx.Config[splunkVerifyConnectionKey]; ok {
+		var err error
+		verifyConnection, err = strconv.ParseBool(verifyConnectionStr)
+		if err != nil {
+			return nil, err
+		}
+	}
+	if verifyConnection {
+		err = verifySplunkConnection(logger)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	var splunkFormat string
+	if splunkFormatParsed, ok := ctx.Config[splunkFormatKey]; ok {
+		switch splunkFormatParsed {
+		case splunkFormatInline:
+		case splunkFormatJSON:
+		case splunkFormatRaw:
+		default:
+			return nil, fmt.Errorf("Unknown format specified %s, supported formats are inline, json and raw", splunkFormat)
+		}
+		splunkFormat = splunkFormatParsed
+	} else {
+		splunkFormat = splunkFormatInline
 	}
 	}
 
 
-	return logger, nil
+	switch splunkFormat {
+	case splunkFormatInline:
+		nullEvent := &splunkMessageEvent{
+			Tag:   tag,
+			Attrs: attrs,
+		}
+
+		return &splunkLoggerInline{logger, nullEvent}, nil
+	case splunkFormatJSON:
+		nullEvent := &splunkMessageEvent{
+			Tag:   tag,
+			Attrs: attrs,
+		}
+
+		return &splunkLoggerJSON{&splunkLoggerInline{logger, nullEvent}}, nil
+	case splunkFormatRaw:
+		var prefix bytes.Buffer
+		prefix.WriteString(tag)
+		prefix.WriteString(" ")
+		for key, value := range attrs {
+			prefix.WriteString(key)
+			prefix.WriteString("=")
+			prefix.WriteString(value)
+			prefix.WriteString(" ")
+		}
+
+		return &splunkLoggerRaw{logger, prefix.Bytes()}, nil
+	default:
+		return nil, fmt.Errorf("Unexpected format %s", splunkFormat)
+	}
 }
 }
 
 
-func (l *splunkLogger) Log(msg *logger.Message) error {
-	// Construct message as a copy of nullMessage
-	message := *l.nullMessage
-	message.Time = fmt.Sprintf("%f", float64(msg.Timestamp.UnixNano())/1000000000)
-	message.Event.Line = string(msg.Line)
-	message.Event.Source = msg.Source
+func (l *splunkLoggerInline) Log(msg *logger.Message) error {
+	message := l.createSplunkMessage(msg)
+
+	event := *l.nullEvent
+	event.Line = string(msg.Line)
+	event.Source = msg.Source
+
+	message.Event = &event
+
+	return l.postMessage(message)
+}
+
+func (l *splunkLoggerJSON) Log(msg *logger.Message) error {
+	message := l.createSplunkMessage(msg)
+	event := *l.nullEvent
 
 
-	jsonEvent, err := json.Marshal(&message)
+	var rawJSONMessage json.RawMessage
+	if err := json.Unmarshal(msg.Line, &rawJSONMessage); err == nil {
+		event.Line = &rawJSONMessage
+	} else {
+		event.Line = string(msg.Line)
+	}
+
+	event.Source = msg.Source
+
+	message.Event = &event
+
+	return l.postMessage(message)
+}
+
+func (l *splunkLoggerRaw) Log(msg *logger.Message) error {
+	message := l.createSplunkMessage(msg)
+
+	message.Event = string(append(l.prefix, msg.Line...))
+
+	return l.postMessage(message)
+}
+
+func (l *splunkLogger) postMessage(message *splunkMessage) error {
+	jsonEvent, err := json.Marshal(message)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -196,6 +309,12 @@ func (l *splunkLogger) Name() string {
 	return driverName
 	return driverName
 }
 }
 
 
+func (l *splunkLogger) createSplunkMessage(msg *logger.Message) *splunkMessage {
+	message := *l.nullMessage
+	message.Time = fmt.Sprintf("%f", float64(msg.Timestamp.UnixNano())/float64(time.Second))
+	return &message
+}
+
 // ValidateLogOpt looks for all supported by splunk driver options
 // ValidateLogOpt looks for all supported by splunk driver options
 func ValidateLogOpt(cfg map[string]string) error {
 func ValidateLogOpt(cfg map[string]string) error {
 	for key := range cfg {
 	for key := range cfg {
@@ -208,6 +327,8 @@ func ValidateLogOpt(cfg map[string]string) error {
 		case splunkCAPathKey:
 		case splunkCAPathKey:
 		case splunkCANameKey:
 		case splunkCANameKey:
 		case splunkInsecureSkipVerifyKey:
 		case splunkInsecureSkipVerifyKey:
+		case splunkFormatKey:
+		case splunkVerifyConnectionKey:
 		case envKey:
 		case envKey:
 		case labelsKey:
 		case labelsKey:
 		case tagKey:
 		case tagKey:

+ 66 - 0
docs/admin/logging/splunk.md

@@ -42,6 +42,8 @@ logging driver options:
 | `splunk-capath`             | optional | Path to root certificate.                                                                                                                                                                                          |
 | `splunk-capath`             | optional | Path to root certificate.                                                                                                                                                                                          |
 | `splunk-caname`             | optional | Name to use for validating server certificate; by default the hostname of the `splunk-url` will be used.                                                                                                           |
 | `splunk-caname`             | optional | Name to use for validating server certificate; by default the hostname of the `splunk-url` will be used.                                                                                                           |
 | `splunk-insecureskipverify` | optional | Ignore server certificate validation.                                                                                                                                                                              |
 | `splunk-insecureskipverify` | optional | Ignore server certificate validation.                                                                                                                                                                              |
+| `splunk-format`             | optional | Message format. Can be `inline`, `json` or `raw`. Defaults to `inline`.                                                                                                                                            |
+| `splunk-verify-connection`   | optional | Verify on start, that docker can connect to Splunk server. Defaults to true.                                                                                                                                       |
 | `tag`                       | optional | Specify tag for message, which interpret some markup. Default value is `{{.ID}}` (12 characters of the container ID). Refer to the [log tag option documentation](log_tags.md) for customizing the log tag format. |
 | `tag`                       | optional | Specify tag for message, which interpret some markup. Default value is `{{.ID}}` (12 characters of the container ID). Refer to the [log tag option documentation](log_tags.md) for customizing the log tag format. |
 | `labels`                    | optional | Comma-separated list of keys of labels, which should be included in message, if these labels are specified for container.                                                                                          |
 | `labels`                    | optional | Comma-separated list of keys of labels, which should be included in message, if these labels are specified for container.                                                                                          |
 | `env`                       | optional | Comma-separated list of keys of environment variables, which should be included in message, if these variables are specified for container.                                                                        |
 | `env`                       | optional | Comma-separated list of keys of environment variables, which should be included in message, if these variables are specified for container.                                                                        |
@@ -66,3 +68,67 @@ The `SplunkServerDefaultCert` is automatically generated by Splunk certificates.
         --env "TEST=false"
         --env "TEST=false"
         --label location=west
         --label location=west
         your/application
         your/application
+
+### Message formats
+
+By default Logging Driver sends messages as `inline` format, where each message
+will be embedded as a string, for example
+
+```
+{
+    "attrs": {
+        "env1": "val1",
+        "label1": "label1"
+    },
+    "tag": "MyImage/MyContainer",
+    "source":  "stdout",
+    "line": "my message"
+}
+{
+    "attrs": {
+        "env1": "val1",
+        "label1": "label1"
+    },
+    "tag": "MyImage/MyContainer",
+    "source":  "stdout",
+    "line": "{\"foo\": \"bar\"}"
+}
+```
+
+In case if your messages are JSON objects you may want to embed them in the
+message we send to Splunk. By specifying `--log-opt splunk-format=json` driver
+will try to parse every line as a JSON object and send it as embedded object. In
+case if it cannot parse it - message will be send as `inline`. For example
+
+
+```
+{
+    "attrs": {
+        "env1": "val1",
+        "label1": "label1"
+    },
+    "tag": "MyImage/MyContainer",
+    "source":  "stdout",
+    "line": "my message"
+}
+{
+    "attrs": {
+        "env1": "val1",
+        "label1": "label1"
+    },
+    "tag": "MyImage/MyContainer",
+    "source":  "stdout",
+    "line": {
+        "foo": "bar"
+    }
+}
+```
+
+Third format is a `raw` message. You can specify it by using
+`--log-opt splunk-format=raw`. Attributes (environment variables and labels) and
+tag will be prefixed to the message. For example
+
+```
+MyImage/MyContainer env1=val1 label1=label1 my message
+MyImage/MyContainer env1=val1 label1=label1 {"foo": "bar"}
+```