mirror of https://github.com/docker/docs.git
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:
parent
11a24f19c2
commit
0083f6e984
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue