blob: e88545bdcaaf01934946278b4999a1f706e38bc3 [file] [edit]
package logstream
import (
"cmp"
"context"
"fmt"
"io"
"net/http"
"net/url"
"slices"
"github.com/moby/moby/api/pkg/stdcopy"
"github.com/moby/moby/v2/daemon/internal/stdcopymux"
"github.com/moby/moby/v2/daemon/server/backend"
"github.com/moby/moby/v2/pkg/ioutils"
)
// rfc3339NanoFixed is time.RFC3339Nano with nanoseconds padded using zeros to
// ensure the formatted time isalways the same number of characters.
const rfc3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00"
// Write writes an encoded byte stream of log messages from the
// messages channel, multiplexing them with a stdcopy.Writer if mux is true
func Write(ctx context.Context, w http.ResponseWriter, msgs <-chan *backend.LogMessage, config *backend.ContainerLogsOptions, mux bool) {
// See https://github.com/moby/moby/issues/47448
// Trigger headers to be written immediately.
w.WriteHeader(http.StatusOK)
wf := ioutils.NewWriteFlusher(w)
defer wf.Close()
wf.Flush()
outStream := io.Writer(wf)
errStream := outStream
sysErrStream := errStream
if mux {
sysErrStream = stdcopymux.NewStdWriter(outStream, stdcopy.Systemerr)
errStream = stdcopymux.NewStdWriter(outStream, stdcopy.Stderr)
outStream = stdcopymux.NewStdWriter(outStream, stdcopy.Stdout)
}
for {
select {
case <-ctx.Done():
return
case msg, ok := <-msgs:
if !ok {
return
}
// check if the message contains an error. if so, write that error
// and exit
if msg.Err != nil {
_, _ = fmt.Fprintf(sysErrStream, "Error grabbing logs: %v\n", msg.Err)
continue
}
var stream io.Writer
switch msg.Source {
case "stdout":
if !config.ShowStdout {
continue
}
stream = outStream
case "stderr":
if !config.ShowStderr {
continue
}
stream = errStream
default:
// unknown source
continue
}
if !config.Timestamps && !config.Details {
// Fast path: avoid allocating and copying the message.
_, _ = stream.Write(msg.Line)
} else {
size := len(msg.Line)
if config.Timestamps {
size += len(rfc3339NanoFixed) + 1
}
if config.Details {
// Reserve some space for attributes to reduce reallocations.
size += len(msg.Attrs) * 16
}
logLine := make([]byte, 0, size)
if config.Timestamps {
logLine = append(logLine, msg.Timestamp.Format(rfc3339NanoFixed)...)
logLine = append(logLine, ' ')
}
if config.Details {
logLine = appendAttrs(logLine, msg.Attrs)
logLine = append(logLine, ' ')
}
logLine = append(logLine, msg.Line...)
_, _ = stream.Write(logLine)
}
}
}
}
func appendAttrs(dst []byte, attrs []backend.LogAttr) []byte {
// Note this sorts attrs in-place. That is fine here - nothing else is
// going to use Attrs or care about the order.
slices.SortFunc(attrs, func(a, b backend.LogAttr) int {
return cmp.Compare(a.Key, b.Key)
})
for i, pair := range attrs {
if i > 0 {
dst = append(dst, ',')
}
dst = append(dst, url.QueryEscape(pair.Key)...)
dst = append(dst, '=')
dst = append(dst, url.QueryEscape(pair.Value)...)
}
return dst
}