Code change:
We should make the CRI log parsing code in kubelet to be a library. So that container runtimes and external tools don't need to copy the whole code ( vendor it instead ). Referring
/assign
/sig node
I can help on it.
/assign
GitHub didn't allow me to assign the following users: yanxuean. Note that only can be assigned. <detailsIn response to : /assign Instructions for interacting with me using PR comments are available . If you have questions or suggestions related to my behavior, please file an issue against the repository. </details
Thanks. I have a branch ready for this. Will open a PR later and ping you when it is out.
ok

Related discussion:
text: <ins> /* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package logs import ( "bytes" "testing" "time" "github.com/stretchr/testify/assert" "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func TestLogOptions(t *testing.T) { var ( line         = int64(8) bytes        = int64(64) timestamp    = metav1.Now() sinceseconds = int64(10) ) for c, test := range []struct { apiOpts *v1.PodLogOptions expect  *LogOptions }{ { // empty options apiOpts: &v1.PodLogOptions{}, expect:  &LogOptions{tail: -1, bytes: -1}, }, { // test tail lines apiOpts: &v1.PodLogOptions{TailLines: &line}, expect:  &LogOptions{tail: line, bytes: -1}, }, { // test limit bytes apiOpts: &v1.PodLogOptions{LimitBytes: &bytes}, expect:  &LogOptions{tail: -1, bytes: bytes}, }, { // test since timestamp apiOpts: &v1.PodLogOptions{SinceTime: &timestamp}, expect:  &LogOptions{tail: -1, bytes: -1, since: timestamp.Time}, }, { // test since seconds apiOpts: &v1.PodLogOptions{SinceSeconds: &sinceseconds}, expect:  &LogOptions{tail: -1, bytes: -1, since: timestamp.Add(-10 * time.Second)}, }, } { t.Logf("TestCase #%d: %+v", c, test) opts := NewLogOptions(test.apiOpts, timestamp.Time) assert.Equal(t, test.expect, opts) } } func TestParseLog(t *testing.T) { timestamp, err := time.Parse(timeFormat, "2016-10-20T18:39:20.57606443Z") assert.NoError(t, err) msg := &logMessage{} for c, test := range []struct { line string msg  *logMessage err  bool }{ { // Docker log format stdout line: `{"log":"docker stdout test log","stream":"stdout","time":"2016-10-20T18:39:20.57606443Z"}` + "n", msg: &logMessage{ timestamp: timestamp, stream:    stdoutType, log:       []byte("docker stdout test log"), }, }, { // Docker log format stderr line: `{"log":"docker stderr test log","stream":"stderr","time":"2016-10-20T18:39:20.57606443Z"}` + "n", msg: &logMessage{ timestamp: timestamp, stream:    stderrType, log:       []byte("docker stderr test log"), }, }, { // CRI log format stdout line: "2016-10-20T18:39:20.57606443Z stdout cri stdout test logn", msg: &logMessage{ timestamp: timestamp, stream:    stdoutType, log:       []byte("cri stdout test logn"), }, }, { // CRI log format stderr line: "2016-10-20T18:39:20.57606443Z stderr cri stderr test logn", msg: &logMessage{ timestamp: timestamp, stream:    stderrType, log:       []byte("cri stderr test logn"), }, }, { // Unsupported Log format line: "unsupported log format test logn", msg:  &logMessage{}, err:  true, }, } { t.Logf("TestCase #%d: %+v", c, test) parse, err := getParseFunc([]byte(test.line)) if test.err { assert.Error(t, err) continue } assert.NoError(t, err) err = parse([]byte(test.line), msg) assert.NoError(t, err) assert.Equal(t, test.msg, msg) } } func TestWriteLogs(t *testing.T) { timestamp := time.Unix(1234, 4321) log := "abcdefgn" for c, test := range []struct { stream       streamType since        time.Time timestamp    bool expectStdout string expectStderr string }{ { // stderr log stream:       stderrType, expectStderr: log, }, { // stdout log stream:       stdoutType, expectStdout: log, }, { // since is after timestamp stream: stdoutType, since:  timestamp.Add(1 * time.Second), }, { // timestamp enabled stream:       stderrType, timestamp:    true, expectStderr: timestamp.Format(timeFormat) + " " + log, }, } { t.Logf("TestCase #%d: %+v", c, test) msg := &logMessage{ timestamp: timestamp, stream:    test.stream, log:       []byte(log), } stdoutBuf := bytes.NewBuffer(nil) stderrBuf := bytes.NewBuffer(nil) w := newLogWriter(stdoutBuf, stderrBuf, &LogOptions{since: test.since, timestamp: test.timestamp, bytes: -1}) err := w.write(msg) assert.NoError(t, err) assert.Equal(t, test.expectStdout, stdoutBuf.String()) assert.Equal(t, test.expectStderr, stderrBuf.String()) } } func TestWriteLogsWithBytesLimit(t *testing.T) { timestamp := time.Unix(1234, 4321) timestampStr := timestamp.Format(timeFormat) log := "abcdefgn" for c, test := range []struct { stdoutLines  int stderrLines  int bytes        int timestamp    bool expectStdout string expectStderr string }{ { // limit bytes less than one line stdoutLines:  3, bytes:        3, expectStdout: "abc", }, { // limit bytes across lines stdoutLines:  3, bytes:        len(log) + 3, expectStdout: "abcdefgnabc", }, { // limit bytes more than all lines stdoutLines:  3, bytes:        3 * len(log), expectStdout: "abcdefgnabcdefgnabcdefgn", }, { // limit bytes for stderr stderrLines:  3, bytes:        len(log) + 3, expectStderr: "abcdefgnabc", }, { // limit bytes for both stdout and stderr, stdout first. stdoutLines:  1, stderrLines:  2, bytes:        len(log) + 3, expectStdout: "abcdefgn", expectStderr: "abc", }, { // limit bytes with timestamp stdoutLines:  3, timestamp:    true, bytes:        len(timestampStr) + 1 + len(log) + 2, expectStdout: timestampStr + " " + log + timestampStr[:2], }, } { t.Logf("TestCase #%d: %+v", c, test) msg := &logMessage{ timestamp: timestamp, log:       []byte(log), } stdoutBuf := bytes.NewBuffer(nil) stderrBuf := bytes.NewBuffer(nil) w := newLogWriter(stdoutBuf, stderrBuf, &LogOptions{timestamp: test.timestamp, bytes: int64(test.bytes)}) for i := 0; i < test.stdoutLines; i++ { msg.stream = stdoutType if err := w.write(msg); err != nil { assert.EqualError(t, err, errMaximumWrite.Error()) } } for i := 0; i < test.stderrLines; i++ { msg.stream = stderrType if err := w.write(msg); err != nil { assert.EqualError(t, err, errMaximumWrite.Error()) } } assert.Equal(t, test.expectStdout, stdoutBuf.String()) assert.Equal(t, test.expectStderr, stderrBuf.String()) } } </ins>