mirror of
				https://github.com/moby/moby.git
				synced 2022-11-09 12:21:53 -05:00 
			
		
		
		
	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
					
				
					 4 changed files with 85 additions and 1 deletions
				
			
		| 
						 | 
					@ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue