docker logs 二进制数据格式介绍及解析
为什么需要了解docker logs二进制数据格式
docker 以特定格式的二进制数据接口,提供docker logs。从该接口中获取的日志数据包括,输出到标准设备的日志和输出到标准错误输出设备的日志。如果我们需要按照我们想要的方式,来获取我们需要的日志信息,就必须要了解docker logs 二进制数据格式, 才能正确的反序列化docker格式的二进制数据。
docker logs 二进制数据格式是怎样的
先上一张docker logs二进制格式图片
如图所示,日志头部信息,占8个字节。第一个字节表示设备类型(标准输出设备or标准错误输出)。5~8字节(共4字节)表示本条日志长度。中间有3个字节为保留字段。长度字段后紧跟是本条日志内容。然后是第二条日志头部… 这样依次循环,直到最后一条日志内容结尾(读到错误类型是io.EOF即读完了)。
解析函数
官方在"github.com/docker/docker/pkg/stdcopy" 包中提供了函数:
StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error)
该函数能将日志文件分流为输出到标准输出和标准错误输出两类日志,功能非常强大。
但是,在某些场景下,将日志分类输出,反而带了麻烦。比如要在一个页面(界面)按时间顺序同时输出所有日志(包含标准输出/标准错误输出)。在这种场景,就比较尴尬。因为,前面将日志分类分流出来,现在又要合并到一块。并且还要按照日志产生的时间顺序排序,然后统一输出。这在大量日志的情况下,这将是一场灾难!
既然,已经了解了docker logs二进制数据格式,不如自己新造一个更好用的解析函数。废话不多说,直接上代码:
// StdCopyMix is a modified version of io.Copy and it is different from StdCopy.
// StdCopyMix is used for deserializing binary docker logs
// As it reads from `src`, StdCopyMix will write to `dstout` only.
//
// StdCopyMix will read until it hits EOF on `src`. It will then return a nil error.
// In other words: if `err` is non nil, it indicates a real underlying error.
//
// `written` will hold the total number of bytes written to `dstout` only.
func StdCopyMix(dstout io.Writer, src io.Reader) (written int64, err error) {
var (
buf = make([]byte, startingBufLen)
bufLen = len(buf)
nr, nw int
er, ew error
frameSize int
)
for {
// Make sure we have at least a full header
for nr < stdWriterPrefixLen {
var nr2 int
nr2, er = src.Read(buf[nr:])
nr += nr2
if er == io.EOF {
if nr < stdWriterPrefixLen {
return written, nil
}
break
}
if er != nil {
return 0, er
}
}
stream := StdType(buf[stdWriterFdIndex])
// Retrieve the size of the frame
frameSize = int(binary.BigEndian.Uint32(buf[stdWriterSizeIndex : stdWriterSizeIndex+4]))
// Check if the buffer is big enough to read the frame.
// Extend it if necessary.
if frameSize+stdWriterPrefixLen > bufLen {
buf = append(buf, make([]byte, frameSize+stdWriterPrefixLen-bufLen+1)...)
bufLen = len(buf)
}
// While the amount of bytes read is less than the size of the frame + header, we keep reading
for nr < frameSize+stdWriterPrefixLen {
var nr2 int
nr2, er = src.Read(buf[nr:])
nr += nr2
if er == io.EOF {
if nr < frameSize+stdWriterPrefixLen {
return written, nil
}
break
}
if er != nil {
return 0, er
}
}
// we might have an error from the source mixed up in our multiplexed
// stream. if we do, return it.
if stream == Systemerr {
return written, fmt.Errorf("error from daemon in stream: %s", string(buf[stdWriterPrefixLen:frameSize+stdWriterPrefixLen]))
}
// Write the retrieved frame (without header)
nw, ew = dstout.Write(buf[stdWriterPrefixLen : frameSize+stdWriterPrefixLen])
if ew != nil {
return 0, ew
}
// If the frame has not been fully written: error
if nw != frameSize {
return 0, io.ErrShortWrite
}
written += int64(nw)
// Move the rest of the buffer to the beginning
copy(buf, buf[frameSize+stdWriterPrefixLen:])
// Move the index
nr -= frameSize + stdWriterPrefixLen
}
}
这段代码,经过测试,工作正常。
如果你喜欢本文,欢迎转载。转载请注明出处!