package logstream
import (
"context"
"io"
"net/http"
"time"
"github.com/moby/moby/v2/daemon/server/backend"
"github.com/moby/moby/v2/daemon/server/httputils"
"github.com/moby/moby/v2/pkg/ioutils"
)
// WriteJSON writes a JSON stream of log messages from the messages channel.
func WriteJSON(ctx context.Context, w http.ResponseWriter, msgs <-chan *backend.LogMessage, config *backend.ContainerLogsOptions) {
// 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()
jsonWriter := newJSONLogWriter(wf, w.Header().Get("Content-Type"), config)
for {
select {
case <-ctx.Done():
return
case msg, ok := <-msgs:
if !ok {
return
}
if msg.Err != nil {
// message contains an error; write the error and continue
jsonWriter.write(msg)
continue
}
switch msg.Source {
case "stdout":
if config.ShowStdout {
jsonWriter.write(msg)
}
case "stderr":
if config.ShowStderr {
jsonWriter.write(msg)
}
default:
// unknown source
}
}
}
}
type jsonLogWriter struct {
encode httputils.EncoderFn
details bool
}
func newJSONLogWriter(w io.Writer, contentType string, opts *backend.ContainerLogsOptions) *jsonLogWriter {
encode := httputils.NewJSONStreamEncoder(w, contentType)
return &jsonLogWriter{
encode: encode,
details: opts != nil && opts.Details,
}
}
// jsonLogMessage represents a single log entry in JSON log streaming format.
//
// Each message is serialized as a standalone JSON object and emitted as
// part of a stream (one object per line) in container log responses when
// a JSON-formatted output is requested.
//
// TODO(thaJeztah): move to the api module and generate from swagger.
type jsonLogMessage struct {
// Line contains the log payload as UTF-8 text when text encoding is used.
// When an alternate encoding is requested, this field is omitted.
Line string `json:"Line,omitempty"`
// Source identifies the originating stream ("stdout" or "stderr").
Source string `json:"Source,omitempty"`
// Timestamp is the time at which the log record was produced,
// encoded in RFC3339Nano format.
Timestamp time.Time `json:"Timestamp,omitempty"`
// Attrs contains optional structured attributes when "details" is
// enabled, and if supported by the logging driver in use.
Attrs []backend.LogAttr `json:"Attrs,omitempty"`
// MetaData contains metadata for partial log records that must be
// reassembled by the client.
MetaData *backend.PartialLogMetaData `json:"MetaData,omitempty"`
// Error contains an associated error encountered while processing the
// log message, if any.
Error string `json:"Error,omitempty"`
}
func (w *jsonLogWriter) write(msg *backend.LogMessage) {
var errMsg string
if msg.Err != nil {
errMsg = msg.Err.Error()
}
var attrs []backend.LogAttr
if w.details {
attrs = msg.Attrs
}
_ = w.encode(jsonLogMessage{
Line: string(msg.Line),
Source: msg.Source,
Timestamp: msg.Timestamp,
Attrs: attrs,
MetaData: msg.PLogMetaData,
Error: errMsg,
})
}