diff --git a/pkg/jsonlog/jsonlog_marshalling.go b/pkg/jsonlog/jsonlog_marshalling.go index abaa8a73ba..6bfec48261 100644 --- a/pkg/jsonlog/jsonlog_marshalling.go +++ b/pkg/jsonlog/jsonlog_marshalling.go @@ -52,6 +52,17 @@ // buf.WriteString(`}`) // return nil // } +// @@ -81,9 +81,10 @@ func (mj *JSONLog) MarshalJSONBuf(buf *bytes.Buffer) error { +// if len(mj.Log) != 0 { +// - if first == true { +// - first = false +// - } else { +// - buf.WriteString(`,`) +// - } +// + first = false +// buf.WriteString(`"log":`) +// ffjson_WriteJsonString(buf, mj.Log) +// } package jsonlog @@ -79,11 +90,7 @@ func (mj *JSONLog) MarshalJSONBuf(buf *bytes.Buffer) error { ) buf.WriteString(`{`) if len(mj.Log) != 0 { - if first == true { - first = false - } else { - buf.WriteString(`,`) - } + first = false buf.WriteString(`"log":`) ffjson_WriteJsonString(buf, mj.Log) } diff --git a/pkg/jsonlog/jsonlog_marshalling_test.go b/pkg/jsonlog/jsonlog_marshalling_test.go new file mode 100644 index 0000000000..5e455685ab --- /dev/null +++ b/pkg/jsonlog/jsonlog_marshalling_test.go @@ -0,0 +1,34 @@ +package jsonlog + +import ( + "regexp" + "testing" +) + +func TestJSONLogMarshalJSON(t *testing.T) { + logs := map[JSONLog]string{ + JSONLog{Log: `"A log line with \\"`}: `^{\"log\":\"\\\"A log line with \\\\\\\\\\\"\",\"time\":\".{20,}\"}$`, + JSONLog{Log: "A log line"}: `^{\"log\":\"A log line\",\"time\":\".{20,}\"}$`, + JSONLog{Log: "A log line with \r"}: `^{\"log\":\"A log line with \\r\",\"time\":\".{20,}\"}$`, + JSONLog{Log: "A log line with & < >"}: `^{\"log\":\"A log line with \\u0026 \\u003c \\u003e\",\"time\":\".{20,}\"}$`, + JSONLog{Log: "A log line with utf8 : 🚀 ψ ω β"}: `^{\"log\":\"A log line with utf8 : 🚀 ψ ω β\",\"time\":\".{20,}\"}$`, + JSONLog{Stream: "stdout"}: `^{\"stream\":\"stdout\",\"time\":\".{20,}\"}$`, + JSONLog{}: `^{\"time\":\".{20,}\"}$`, + // These ones are a little weird + JSONLog{Log: "\u2028 \u2029"}: `^{\"log\":\"\\u2028 \\u2029\",\"time\":\".{20,}\"}$`, + JSONLog{Log: string([]byte{0xaF})}: `^{\"log\":\"\\ufffd\",\"time\":\".{20,}\"}$`, + JSONLog{Log: string([]byte{0x7F})}: `^{\"log\":\"\x7f\",\"time\":\".{20,}\"}$`, + } + for jsonLog, expression := range logs { + data, err := jsonLog.MarshalJSON() + if err != nil { + t.Fatal(err) + } + res := string(data) + t.Logf("Result of WriteLog: %q", res) + logRe := regexp.MustCompile(expression) + if !logRe.MatchString(res) { + t.Fatalf("Log line not in expected format [%v]: %q", expression, res) + } + } +} diff --git a/pkg/jsonlog/jsonlog_test.go b/pkg/jsonlog/jsonlog_test.go index d4b26fcb43..2b787efc20 100644 --- a/pkg/jsonlog/jsonlog_test.go +++ b/pkg/jsonlog/jsonlog_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "io/ioutil" "regexp" + "strconv" "strings" "testing" "time" @@ -12,29 +13,123 @@ import ( "github.com/docker/docker/pkg/timeutils" ) -func TestWriteLog(t *testing.T) { +// Invalid json should return an error +func TestWriteLogWithInvalidJSON(t *testing.T) { + json := strings.NewReader("Invalid json") + w := bytes.NewBuffer(nil) + if err := WriteLog(json, w, "json", time.Time{}); err == nil { + t.Fatalf("Expected an error, got [%v]", w.String()) + } +} + +// Any format is valid, it will just print it +func TestWriteLogWithInvalidFormat(t *testing.T) { + testLine := "Line that thinks that it is log line from docker\n" var buf bytes.Buffer e := json.NewEncoder(&buf) - testLine := "Line that thinks that it is log line from docker\n" - for i := 0; i < 30; i++ { + for i := 0; i < 35; i++ { e.Encode(JSONLog{Log: testLine, Stream: "stdout", Created: time.Now()}) } w := bytes.NewBuffer(nil) - format := timeutils.RFC3339NanoFixed - if err := WriteLog(&buf, w, format, time.Time{}); err != nil { + if err := WriteLog(&buf, w, "invalid format", time.Time{}); err != nil { t.Fatal(err) } res := w.String() t.Logf("Result of WriteLog: %q", res) lines := strings.Split(strings.TrimSpace(res), "\n") - if len(lines) != 30 { - t.Fatalf("Must be 30 lines but got %d", len(lines)) + expression := "^invalid format Line that thinks that it is log line from docker$" + logRe := regexp.MustCompile(expression) + expectedLines := 35 + if len(lines) != expectedLines { + t.Fatalf("Must be %v lines but got %d", expectedLines, len(lines)) } - // 30+ symbols, five more can come from system timezone - logRe := regexp.MustCompile(`.{30,} Line that thinks that it is log line from docker`) for _, l := range lines { if !logRe.MatchString(l) { - t.Fatalf("Log line not in expected format: %q", l) + t.Fatalf("Log line not in expected format [%v]: %q", expression, l) + } + } +} + +// Having multiple Log/Stream element +func TestWriteLogWithMultipleStreamLog(t *testing.T) { + testLine := "Line that thinks that it is log line from docker\n" + var buf bytes.Buffer + e := json.NewEncoder(&buf) + for i := 0; i < 35; i++ { + e.Encode(JSONLog{Log: testLine, Stream: "stdout", Created: time.Now()}) + } + w := bytes.NewBuffer(nil) + if err := WriteLog(&buf, w, "invalid format", time.Time{}); err != nil { + t.Fatal(err) + } + res := w.String() + t.Logf("Result of WriteLog: %q", res) + lines := strings.Split(strings.TrimSpace(res), "\n") + expression := "^invalid format Line that thinks that it is log line from docker$" + logRe := regexp.MustCompile(expression) + expectedLines := 35 + if len(lines) != expectedLines { + t.Fatalf("Must be %v lines but got %d", expectedLines, len(lines)) + } + for _, l := range lines { + if !logRe.MatchString(l) { + t.Fatalf("Log line not in expected format [%v]: %q", expression, l) + } + } +} + +// Write log with since after created, it won't print anything +func TestWriteLogWithDate(t *testing.T) { + created, _ := time.Parse("YYYY-MM-dd", "2015-01-01") + var buf bytes.Buffer + testLine := "Line that thinks that it is log line from docker\n" + jsonLog := JSONLog{Log: testLine, Stream: "stdout", Created: created} + if err := json.NewEncoder(&buf).Encode(jsonLog); err != nil { + t.Fatal(err) + } + w := bytes.NewBuffer(nil) + if err := WriteLog(&buf, w, "json", time.Now()); err != nil { + t.Fatal(err) + } + res := w.String() + if res != "" { + t.Fatalf("Expected empty log, got [%v]", res) + } +} + +// Happy path :) +func TestWriteLog(t *testing.T) { + testLine := "Line that thinks that it is log line from docker\n" + format := timeutils.RFC3339NanoFixed + logs := map[string][]string{ + "": {"35", "^Line that thinks that it is log line from docker$"}, + "json": {"1", `^{\"log\":\"Line that thinks that it is log line from docker\\n\",\"stream\":\"stdout\",\"time\":.{30,}\"}$`}, + // 30+ symbols, five more can come from system timezone + format: {"35", `.{30,} Line that thinks that it is log line from docker`}, + } + for givenFormat, expressionAndLines := range logs { + expectedLines, _ := strconv.Atoi(expressionAndLines[0]) + expression := expressionAndLines[1] + var buf bytes.Buffer + e := json.NewEncoder(&buf) + for i := 0; i < 35; i++ { + e.Encode(JSONLog{Log: testLine, Stream: "stdout", Created: time.Now()}) + } + w := bytes.NewBuffer(nil) + if err := WriteLog(&buf, w, givenFormat, time.Time{}); err != nil { + t.Fatal(err) + } + res := w.String() + t.Logf("Result of WriteLog: %q", res) + lines := strings.Split(strings.TrimSpace(res), "\n") + if len(lines) != expectedLines { + t.Fatalf("Must be %v lines but got %d", expectedLines, len(lines)) + } + logRe := regexp.MustCompile(expression) + for _, l := range lines { + if !logRe.MatchString(l) { + t.Fatalf("Log line not in expected format [%v]: %q", expression, l) + } } } } diff --git a/pkg/jsonlog/jsonlogbytes.go b/pkg/jsonlog/jsonlogbytes.go index 0d8fd9c824..81a966b9b2 100644 --- a/pkg/jsonlog/jsonlogbytes.go +++ b/pkg/jsonlog/jsonlogbytes.go @@ -21,11 +21,7 @@ func (mj *JSONLogBytes) MarshalJSONBuf(buf *bytes.Buffer) error { buf.WriteString(`{`) if len(mj.Log) != 0 { - if first == true { - first = false - } else { - buf.WriteString(`,`) - } + first = false buf.WriteString(`"log":`) ffjson_WriteJsonBytesAsString(buf, mj.Log) } diff --git a/pkg/jsonlog/jsonlogbytes_test.go b/pkg/jsonlog/jsonlogbytes_test.go new file mode 100644 index 0000000000..46ec52c642 --- /dev/null +++ b/pkg/jsonlog/jsonlogbytes_test.go @@ -0,0 +1,37 @@ +package jsonlog + +import ( + "bytes" + "regexp" + "testing" +) + +func TestJSONLogBytesMarshalJSONBuf(t *testing.T) { + logs := map[*JSONLogBytes]string{ + &JSONLogBytes{Log: []byte(`"A log line with \\"`)}: `^{\"log\":\"\\\"A log line with \\\\\\\\\\\"\",\"time\":}$`, + &JSONLogBytes{Log: []byte("A log line")}: `^{\"log\":\"A log line\",\"time\":}$`, + &JSONLogBytes{Log: []byte("A log line with \r")}: `^{\"log\":\"A log line with \\r\",\"time\":}$`, + &JSONLogBytes{Log: []byte("A log line with & < >")}: `^{\"log\":\"A log line with \\u0026 \\u003c \\u003e\",\"time\":}$`, + &JSONLogBytes{Log: []byte("A log line with utf8 : 🚀 ψ ω β")}: `^{\"log\":\"A log line with utf8 : 🚀 ψ ω β\",\"time\":}$`, + &JSONLogBytes{Stream: "stdout"}: `^{\"stream\":\"stdout\",\"time\":}$`, + &JSONLogBytes{Stream: "stdout", Log: []byte("A log line")}: `^{\"log\":\"A log line\",\"stream\":\"stdout\",\"time\":}$`, + &JSONLogBytes{Created: "time"}: `^{\"time\":time}$`, + &JSONLogBytes{}: `^{\"time\":}$`, + // These ones are a little weird + &JSONLogBytes{Log: []byte("\u2028 \u2029")}: `^{\"log\":\"\\u2028 \\u2029\",\"time\":}$`, + &JSONLogBytes{Log: []byte{0xaF}}: `^{\"log\":\"\\ufffd\",\"time\":}$`, + &JSONLogBytes{Log: []byte{0x7F}}: `^{\"log\":\"\x7f\",\"time\":}$`, + } + for jsonLog, expression := range logs { + var buf bytes.Buffer + if err := jsonLog.MarshalJSONBuf(&buf); err != nil { + t.Fatal(err) + } + res := buf.String() + t.Logf("Result of WriteLog: %q", res) + logRe := regexp.MustCompile(expression) + if !logRe.MatchString(res) { + t.Fatalf("Log line not in expected format [%v]: %q", expression, res) + } + } +}