github.com/jiasir/docker@v1.3.3-0.20170609024000-252e610103e7/api/server/httputils/write_log_stream.go (about)

     1  package httputils
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"net/url"
     7  	"sort"
     8  	"strings"
     9  
    10  	"golang.org/x/net/context"
    11  
    12  	"github.com/docker/docker/api/types"
    13  	"github.com/docker/docker/api/types/backend"
    14  	"github.com/docker/docker/pkg/ioutils"
    15  	"github.com/docker/docker/pkg/jsonlog"
    16  	"github.com/docker/docker/pkg/stdcopy"
    17  )
    18  
    19  // WriteLogStream writes an encoded byte stream of log messages from the
    20  // messages channel, multiplexing them with a stdcopy.Writer if mux is true
    21  func WriteLogStream(ctx context.Context, w io.Writer, msgs <-chan *backend.LogMessage, config *types.ContainerLogsOptions, mux bool) {
    22  	wf := ioutils.NewWriteFlusher(w)
    23  	defer wf.Close()
    24  
    25  	wf.Flush()
    26  
    27  	// this might seem like doing below is clear:
    28  	//   var outStream io.Writer = wf
    29  	// however, this GREATLY DISPLEASES golint, and if you do that, it will
    30  	// fail CI. we need outstream to be type writer because if we mux streams,
    31  	// we will need to reassign all of the streams to be stdwriters, which only
    32  	// conforms to the io.Writer interface.
    33  	var outStream io.Writer
    34  	outStream = wf
    35  	errStream := outStream
    36  	sysErrStream := errStream
    37  	if mux {
    38  		sysErrStream = stdcopy.NewStdWriter(outStream, stdcopy.Systemerr)
    39  		errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr)
    40  		outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout)
    41  	}
    42  
    43  	for {
    44  		msg, ok := <-msgs
    45  		if !ok {
    46  			return
    47  		}
    48  		// check if the message contains an error. if so, write that error
    49  		// and exit
    50  		if msg.Err != nil {
    51  			fmt.Fprintf(sysErrStream, "Error grabbing logs: %v\n", msg.Err)
    52  			continue
    53  		}
    54  		logLine := msg.Line
    55  		if config.Details {
    56  			logLine = append([]byte(stringAttrs(msg.Attrs)+" "), logLine...)
    57  		}
    58  		if config.Timestamps {
    59  			// TODO(dperny) the format is defined in
    60  			// daemon/logger/logger.go as logger.TimeFormat. importing
    61  			// logger is verboten (not part of backend) so idk if just
    62  			// importing the same thing from jsonlog is good enough
    63  			logLine = append([]byte(msg.Timestamp.Format(jsonlog.RFC3339NanoFixed)+" "), logLine...)
    64  		}
    65  		if msg.Source == "stdout" && config.ShowStdout {
    66  			outStream.Write(logLine)
    67  		}
    68  		if msg.Source == "stderr" && config.ShowStderr {
    69  			errStream.Write(logLine)
    70  		}
    71  	}
    72  }
    73  
    74  type byKey []string
    75  
    76  func (s byKey) Len() int { return len(s) }
    77  func (s byKey) Less(i, j int) bool {
    78  	keyI := strings.Split(s[i], "=")
    79  	keyJ := strings.Split(s[j], "=")
    80  	return keyI[0] < keyJ[0]
    81  }
    82  func (s byKey) Swap(i, j int) {
    83  	s[i], s[j] = s[j], s[i]
    84  }
    85  
    86  func stringAttrs(a backend.LogAttributes) string {
    87  	var ss byKey
    88  	for k, v := range a {
    89  		k, v := url.QueryEscape(k), url.QueryEscape(v)
    90  		ss = append(ss, k+"="+v)
    91  	}
    92  	sort.Sort(ss)
    93  	return strings.Join(ss, ",")
    94  }