github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/engine/daemon/logger/local/local_test.go (about) 1 package local 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/binary" 7 "fmt" 8 "io" 9 "os" 10 "path/filepath" 11 "strings" 12 "testing" 13 "time" 14 15 "github.com/docker/docker/api/types/backend" 16 "github.com/docker/docker/api/types/plugins/logdriver" 17 "github.com/docker/docker/daemon/logger" 18 protoio "github.com/gogo/protobuf/io" 19 "gotest.tools/v3/assert" 20 is "gotest.tools/v3/assert/cmp" 21 ) 22 23 func TestWriteLog(t *testing.T) { 24 t.Parallel() 25 26 dir, err := os.MkdirTemp("", t.Name()) 27 assert.NilError(t, err) 28 defer os.RemoveAll(dir) 29 30 logPath := filepath.Join(dir, "test.log") 31 32 l, err := New(logger.Info{LogPath: logPath}) 33 assert.NilError(t, err) 34 defer l.Close() 35 36 m1 := logger.Message{Source: "stdout", Timestamp: time.Now().Add(-1 * 30 * time.Minute), Line: []byte("message 1")} 37 m2 := logger.Message{Source: "stdout", Timestamp: time.Now().Add(-1 * 20 * time.Minute), Line: []byte("message 2"), PLogMetaData: &backend.PartialLogMetaData{Last: true, ID: "0001", Ordinal: 1}} 38 m3 := logger.Message{Source: "stderr", Timestamp: time.Now().Add(-1 * 10 * time.Minute), Line: []byte("message 3")} 39 40 // copy the log message because the underying log writer resets the log message and returns it to a buffer pool 41 err = l.Log(copyLogMessage(&m1)) 42 assert.NilError(t, err) 43 err = l.Log(copyLogMessage(&m2)) 44 assert.NilError(t, err) 45 err = l.Log(copyLogMessage(&m3)) 46 assert.NilError(t, err) 47 48 f, err := os.Open(logPath) 49 assert.NilError(t, err) 50 defer f.Close() 51 dec := protoio.NewUint32DelimitedReader(f, binary.BigEndian, 1e6) 52 53 var ( 54 proto logdriver.LogEntry 55 testProto logdriver.LogEntry 56 partial logdriver.PartialLogEntryMetadata 57 ) 58 59 lenBuf := make([]byte, encodeBinaryLen) 60 seekMsgLen := func() { 61 io.ReadFull(f, lenBuf) 62 } 63 64 err = dec.ReadMsg(&proto) 65 assert.NilError(t, err) 66 messageToProto(&m1, &testProto, &partial) 67 assert.Check(t, is.DeepEqual(testProto, proto), "expected:\n%+v\ngot:\n%+v", testProto, proto) 68 seekMsgLen() 69 70 err = dec.ReadMsg(&proto) 71 assert.NilError(t, err) 72 messageToProto(&m2, &testProto, &partial) 73 assert.Check(t, is.DeepEqual(testProto, proto)) 74 seekMsgLen() 75 76 err = dec.ReadMsg(&proto) 77 assert.NilError(t, err) 78 messageToProto(&m3, &testProto, &partial) 79 assert.Check(t, is.DeepEqual(testProto, proto), "expected:\n%+v\ngot:\n%+v", testProto, proto) 80 } 81 82 func TestReadLog(t *testing.T) { 83 t.Parallel() 84 85 dir, err := os.MkdirTemp("", t.Name()) 86 assert.NilError(t, err) 87 defer os.RemoveAll(dir) 88 89 logPath := filepath.Join(dir, "test.log") 90 l, err := New(logger.Info{LogPath: logPath}) 91 assert.NilError(t, err) 92 defer l.Close() 93 94 m1 := logger.Message{Source: "stdout", Timestamp: time.Now().Add(-1 * 30 * time.Minute), Line: []byte("a message")} 95 m2 := logger.Message{Source: "stdout", Timestamp: time.Now().Add(-1 * 20 * time.Minute), Line: []byte("another message"), PLogMetaData: &backend.PartialLogMetaData{Ordinal: 1, Last: true}} 96 longMessage := []byte("a really long message " + strings.Repeat("a", initialBufSize*2)) 97 m3 := logger.Message{Source: "stderr", Timestamp: time.Now().Add(-1 * 10 * time.Minute), Line: longMessage} 98 m4 := logger.Message{Source: "stderr", Timestamp: time.Now().Add(-1 * 10 * time.Minute), Line: []byte("just one more message")} 99 100 // copy the log message because the underlying log writer resets the log message and returns it to a buffer pool 101 err = l.Log(copyLogMessage(&m1)) 102 assert.NilError(t, err) 103 err = l.Log(copyLogMessage(&m2)) 104 assert.NilError(t, err) 105 err = l.Log(copyLogMessage(&m3)) 106 assert.NilError(t, err) 107 err = l.Log(copyLogMessage(&m4)) 108 assert.NilError(t, err) 109 110 lr := l.(logger.LogReader) 111 112 testMessage := func(t *testing.T, lw *logger.LogWatcher, m *logger.Message) { 113 t.Helper() 114 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 115 defer cancel() 116 select { 117 case <-ctx.Done(): 118 assert.Assert(t, ctx.Err()) 119 case err := <-lw.Err: 120 assert.NilError(t, err) 121 case msg, open := <-lw.Msg: 122 if !open { 123 select { 124 case err := <-lw.Err: 125 assert.NilError(t, err) 126 default: 127 assert.Assert(t, m == nil) 128 return 129 } 130 } 131 assert.Assert(t, m != nil) 132 if m.PLogMetaData == nil { 133 // a `\n` is appended on read to make this work with the existing API's when the message is not a partial. 134 // make sure it's the last entry in the line, and then truncate it for the deep equal below. 135 assert.Check(t, msg.Line[len(msg.Line)-1] == '\n') 136 msg.Line = msg.Line[:len(msg.Line)-1] 137 } 138 assert.Check(t, is.DeepEqual(m, msg), fmt.Sprintf("\n%+v\n%+v", m, msg)) 139 } 140 } 141 142 t.Run("tail exact", func(t *testing.T) { 143 lw := lr.ReadLogs(logger.ReadConfig{Tail: 4}) 144 145 testMessage(t, lw, &m1) 146 testMessage(t, lw, &m2) 147 testMessage(t, lw, &m3) 148 testMessage(t, lw, &m4) 149 testMessage(t, lw, nil) // no more messages 150 }) 151 152 t.Run("tail less than available", func(t *testing.T) { 153 lw := lr.ReadLogs(logger.ReadConfig{Tail: 2}) 154 155 testMessage(t, lw, &m3) 156 testMessage(t, lw, &m4) 157 testMessage(t, lw, nil) // no more messages 158 }) 159 160 t.Run("tail more than available", func(t *testing.T) { 161 lw := lr.ReadLogs(logger.ReadConfig{Tail: 100}) 162 163 testMessage(t, lw, &m1) 164 testMessage(t, lw, &m2) 165 testMessage(t, lw, &m3) 166 testMessage(t, lw, &m4) 167 testMessage(t, lw, nil) // no more messages 168 }) 169 } 170 171 func BenchmarkLogWrite(b *testing.B) { 172 f, err := os.CreateTemp("", b.Name()) 173 assert.Assert(b, err) 174 defer os.Remove(f.Name()) 175 f.Close() 176 177 local, err := New(logger.Info{LogPath: f.Name()}) 178 assert.Assert(b, err) 179 defer local.Close() 180 181 t := time.Now().UTC() 182 for _, data := range [][]byte{ 183 []byte(""), 184 []byte("a short string"), 185 bytes.Repeat([]byte("a long string"), 100), 186 bytes.Repeat([]byte("a really long string"), 10000), 187 } { 188 b.Run(fmt.Sprintf("%d", len(data)), func(b *testing.B) { 189 entry := &logdriver.LogEntry{Line: data, Source: "stdout", TimeNano: t.UnixNano()} 190 b.SetBytes(int64(entry.Size() + encodeBinaryLen + encodeBinaryLen)) 191 b.ResetTimer() 192 for i := 0; i < b.N; i++ { 193 msg := logger.NewMessage() 194 msg.Line = data 195 msg.Timestamp = t 196 msg.Source = "stdout" 197 if err := local.Log(msg); err != nil { 198 b.Fatal(err) 199 } 200 } 201 }) 202 } 203 } 204 205 func copyLogMessage(src *logger.Message) *logger.Message { 206 dst := logger.NewMessage() 207 dst.Source = src.Source 208 dst.Timestamp = src.Timestamp 209 dst.Attrs = src.Attrs 210 dst.Err = src.Err 211 dst.Line = append(dst.Line, src.Line...) 212 if src.PLogMetaData != nil { 213 lmd := *src.PLogMetaData 214 dst.PLogMetaData = &lmd 215 } 216 return dst 217 }