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>
This commit is contained in:
Daniel Dao 2015-10-04 21:07:09 +00:00 committed by Vincent Demeester
parent 11a24f19c2
commit 0083f6e984
4 changed files with 85 additions and 1 deletions

View File

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

View File

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

View File

@ -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 {

View File

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