浏览代码

add labels/env log option for jsonfile

this allows jsonfile logger to collect extra metadata from containers with
`--log-opt labels=label1,label2 --log-opt env=env1,env2`.

Extra attributes are saved into `attrs` attributes for each log data.

Signed-off-by: Daniel Dao <dqminh@cloudflare.com>
Daniel Dao 9 年之前
父节点
当前提交
0083f6e984

+ 20 - 1
daemon/logger/jsonfilelog/jsonfilelog.go

@@ -41,6 +41,7 @@ type JSONFileLogger struct {
 	ctx          logger.Context
 	ctx          logger.Context
 	readers      map[*logger.LogWatcher]struct{} // stores the active log followers
 	readers      map[*logger.LogWatcher]struct{} // stores the active log followers
 	notifyRotate *pubsub.Publisher
 	notifyRotate *pubsub.Publisher
+	extra        []byte // json-encoded extra attributes
 }
 }
 
 
 func init() {
 func init() {
@@ -77,6 +78,16 @@ func New(ctx logger.Context) (logger.Logger, error) {
 			return nil, fmt.Errorf("max-file cannot be less than 1")
 			return nil, fmt.Errorf("max-file cannot be less than 1")
 		}
 		}
 	}
 	}
+
+	var extra []byte
+	if attrs := ctx.ExtraAttributes(nil); len(attrs) > 0 {
+		var err error
+		extra, err = json.Marshal(attrs)
+		if err != nil {
+			return nil, err
+		}
+	}
+
 	return &JSONFileLogger{
 	return &JSONFileLogger{
 		f:            log,
 		f:            log,
 		buf:          bytes.NewBuffer(nil),
 		buf:          bytes.NewBuffer(nil),
@@ -85,6 +96,7 @@ func New(ctx logger.Context) (logger.Logger, error) {
 		n:            maxFiles,
 		n:            maxFiles,
 		readers:      make(map[*logger.LogWatcher]struct{}),
 		readers:      make(map[*logger.LogWatcher]struct{}),
 		notifyRotate: pubsub.NewPublisher(0, 1),
 		notifyRotate: pubsub.NewPublisher(0, 1),
+		extra:        extra,
 	}, nil
 	}, nil
 }
 }
 
 
@@ -97,7 +109,12 @@ func (l *JSONFileLogger) Log(msg *logger.Message) error {
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	err = (&jsonlog.JSONLogs{Log: append(msg.Line, '\n'), Stream: msg.Source, Created: timestamp}).MarshalJSONBuf(l.buf)
+	err = (&jsonlog.JSONLogs{
+		Log:      append(msg.Line, '\n'),
+		Stream:   msg.Source,
+		Created:  timestamp,
+		RawAttrs: l.extra,
+	}).MarshalJSONBuf(l.buf)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -181,6 +198,8 @@ func ValidateLogOpt(cfg map[string]string) error {
 		switch key {
 		switch key {
 		case "max-file":
 		case "max-file":
 		case "max-size":
 		case "max-size":
+		case "labels":
+		case "env":
 		default:
 		default:
 			return fmt.Errorf("unknown log opt '%s' for json-file log driver", key)
 			return fmt.Errorf("unknown log opt '%s' for json-file log driver", key)
 		}
 		}

+ 50 - 0
daemon/logger/jsonfilelog/jsonfilelog_test.go

@@ -1,9 +1,11 @@
 package jsonfilelog
 package jsonfilelog
 
 
 import (
 import (
+	"encoding/json"
 	"io/ioutil"
 	"io/ioutil"
 	"os"
 	"os"
 	"path/filepath"
 	"path/filepath"
+	"reflect"
 	"strconv"
 	"strconv"
 	"testing"
 	"testing"
 	"time"
 	"time"
@@ -149,3 +151,51 @@ func TestJSONFileLoggerWithOpts(t *testing.T) {
 	}
 	}
 
 
 }
 }
+
+func TestJSONFileLoggerWithLabelsEnv(t *testing.T) {
+	cid := "a7317399f3f857173c6179d44823594f8294678dea9999662e5c625b5a1c7657"
+	tmp, err := ioutil.TempDir("", "docker-logger-")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(tmp)
+	filename := filepath.Join(tmp, "container.log")
+	config := map[string]string{"labels": "rack,dc", "env": "environ,debug,ssl"}
+	l, err := New(logger.Context{
+		ContainerID:     cid,
+		LogPath:         filename,
+		Config:          config,
+		ContainerLabels: map[string]string{"rack": "101", "dc": "lhr"},
+		ContainerEnv:    []string{"environ=production", "debug=false", "port=10001", "ssl=true"},
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer l.Close()
+	if err := l.Log(&logger.Message{ContainerID: cid, Line: []byte("line"), Source: "src1"}); err != nil {
+		t.Fatal(err)
+	}
+	res, err := ioutil.ReadFile(filename)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	var jsonLog jsonlog.JSONLogs
+	if err := json.Unmarshal(res, &jsonLog); err != nil {
+		t.Fatal(err)
+	}
+	extra := make(map[string]string)
+	if err := json.Unmarshal(jsonLog.RawAttrs, &extra); err != nil {
+		t.Fatal(err)
+	}
+	expected := map[string]string{
+		"rack":    "101",
+		"dc":      "lhr",
+		"environ": "production",
+		"debug":   "false",
+		"ssl":     "true",
+	}
+	if !reflect.DeepEqual(extra, expected) {
+		t.Fatalf("Wrong log attrs: %q, expected %q", extra, expected)
+	}
+}

+ 13 - 0
pkg/jsonlog/jsonlogbytes.go

@@ -2,6 +2,7 @@ package jsonlog
 
 
 import (
 import (
 	"bytes"
 	"bytes"
+	"encoding/json"
 	"unicode/utf8"
 	"unicode/utf8"
 )
 )
 
 
@@ -12,6 +13,9 @@ type JSONLogs struct {
 	Log     []byte `json:"log,omitempty"`
 	Log     []byte `json:"log,omitempty"`
 	Stream  string `json:"stream,omitempty"`
 	Stream  string `json:"stream,omitempty"`
 	Created string `json:"time"`
 	Created string `json:"time"`
+
+	// json-encoded bytes
+	RawAttrs json.RawMessage `json:"attrs,omitempty"`
 }
 }
 
 
 // MarshalJSONBuf is based on the same method from JSONLog
 // MarshalJSONBuf is based on the same method from JSONLog
@@ -34,6 +38,15 @@ func (mj *JSONLogs) MarshalJSONBuf(buf *bytes.Buffer) error {
 		buf.WriteString(`"stream":`)
 		buf.WriteString(`"stream":`)
 		ffjsonWriteJSONString(buf, mj.Stream)
 		ffjsonWriteJSONString(buf, mj.Stream)
 	}
 	}
+	if len(mj.RawAttrs) > 0 {
+		if first == true {
+			first = false
+		} else {
+			buf.WriteString(`,`)
+		}
+		buf.WriteString(`"attrs":`)
+		buf.Write(mj.RawAttrs)
+	}
 	if first == true {
 	if first == true {
 		first = false
 		first = false
 	} else {
 	} else {

+ 2 - 0
pkg/jsonlog/jsonlogbytes_test.go

@@ -21,6 +21,8 @@ func TestJSONLogsMarshalJSONBuf(t *testing.T) {
 		&JSONLogs{Log: []byte("\u2028 \u2029")}: `^{\"log\":\"\\u2028 \\u2029\",\"time\":}$`,
 		&JSONLogs{Log: []byte("\u2028 \u2029")}: `^{\"log\":\"\\u2028 \\u2029\",\"time\":}$`,
 		&JSONLogs{Log: []byte{0xaF}}:            `^{\"log\":\"\\ufffd\",\"time\":}$`,
 		&JSONLogs{Log: []byte{0xaF}}:            `^{\"log\":\"\\ufffd\",\"time\":}$`,
 		&JSONLogs{Log: []byte{0x7F}}:            `^{\"log\":\"\x7f\",\"time\":}$`,
 		&JSONLogs{Log: []byte{0x7F}}:            `^{\"log\":\"\x7f\",\"time\":}$`,
+		// with raw attributes
+		&JSONLogs{Log: []byte("A log line"), RawAttrs: []byte(`{"hello":"world","value":1234}`)}: `^{\"log\":\"A log line\",\"attrs\":{\"hello\":\"world\",\"value\":1234},\"time\":}$`,
 	}
 	}
 	for jsonLog, expression := range logs {
 	for jsonLog, expression := range logs {
 		var buf bytes.Buffer
 		var buf bytes.Buffer