github.com/chenchun/docker@v1.3.2-0.20150629222414-20467faf132b/daemon/logs.go (about)

     1  package daemon
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"net"
     9  	"os"
    10  	"strconv"
    11  	"syscall"
    12  	"time"
    13  
    14  	"github.com/Sirupsen/logrus"
    15  	"github.com/docker/docker/daemon/logger/jsonfilelog"
    16  	"github.com/docker/docker/pkg/jsonlog"
    17  	"github.com/docker/docker/pkg/stdcopy"
    18  	"github.com/docker/docker/pkg/tailfile"
    19  	"github.com/docker/docker/pkg/timeutils"
    20  )
    21  
    22  type ContainerLogsConfig struct {
    23  	Follow, Timestamps   bool
    24  	Tail                 string
    25  	Since                time.Time
    26  	UseStdout, UseStderr bool
    27  	OutStream            io.Writer
    28  	Stop                 <-chan bool
    29  }
    30  
    31  func (daemon *Daemon) ContainerLogs(name string, config *ContainerLogsConfig) error {
    32  	var (
    33  		lines  = -1
    34  		format string
    35  	)
    36  	if !(config.UseStdout || config.UseStderr) {
    37  		return fmt.Errorf("You must choose at least one stream")
    38  	}
    39  	if config.Timestamps {
    40  		format = timeutils.RFC3339NanoFixed
    41  	}
    42  	if config.Tail == "" {
    43  		config.Tail = "all"
    44  	}
    45  
    46  	container, err := daemon.Get(name)
    47  	if err != nil {
    48  		return err
    49  	}
    50  
    51  	var (
    52  		outStream = config.OutStream
    53  		errStream io.Writer
    54  	)
    55  	if !container.Config.Tty {
    56  		errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr)
    57  		outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout)
    58  	} else {
    59  		errStream = outStream
    60  	}
    61  
    62  	if container.LogDriverType() != jsonfilelog.Name {
    63  		return fmt.Errorf("\"logs\" endpoint is supported only for \"json-file\" logging driver")
    64  	}
    65  	logDriver, err := container.getLogger()
    66  	cLog, err := logDriver.GetReader()
    67  	if err != nil {
    68  		logrus.Errorf("Error reading logs: %s", err)
    69  	} else {
    70  		// json-file driver
    71  		if config.Tail != "all" {
    72  			var err error
    73  			lines, err = strconv.Atoi(config.Tail)
    74  			if err != nil {
    75  				logrus.Errorf("Failed to parse tail %s, error: %v, show all logs", config.Tail, err)
    76  				lines = -1
    77  			}
    78  		}
    79  
    80  		if lines != 0 {
    81  			if lines > 0 {
    82  				f := cLog.(*os.File)
    83  				ls, err := tailfile.TailFile(f, lines)
    84  				if err != nil {
    85  					return err
    86  				}
    87  				tmp := bytes.NewBuffer([]byte{})
    88  				for _, l := range ls {
    89  					fmt.Fprintf(tmp, "%s\n", l)
    90  				}
    91  				cLog = tmp
    92  			}
    93  
    94  			dec := json.NewDecoder(cLog)
    95  			l := &jsonlog.JSONLog{}
    96  			for {
    97  				l.Reset()
    98  				if err := dec.Decode(l); err == io.EOF {
    99  					break
   100  				} else if err != nil {
   101  					logrus.Errorf("Error streaming logs: %s", err)
   102  					break
   103  				}
   104  				logLine := l.Log
   105  				if !config.Since.IsZero() && l.Created.Before(config.Since) {
   106  					continue
   107  				}
   108  				if config.Timestamps {
   109  					// format can be "" or time format, so here can't be error
   110  					logLine, _ = l.Format(format)
   111  				}
   112  				if l.Stream == "stdout" && config.UseStdout {
   113  					io.WriteString(outStream, logLine)
   114  				}
   115  				if l.Stream == "stderr" && config.UseStderr {
   116  					io.WriteString(errStream, logLine)
   117  				}
   118  			}
   119  		}
   120  	}
   121  
   122  	if config.Follow && container.IsRunning() {
   123  		chErrStderr := make(chan error)
   124  		chErrStdout := make(chan error)
   125  		var stdoutPipe, stderrPipe io.ReadCloser
   126  
   127  		// write an empty chunk of data (this is to ensure that the
   128  		// HTTP Response is sent immediatly, even if the container has
   129  		// not yet produced any data)
   130  		outStream.Write(nil)
   131  
   132  		if config.UseStdout {
   133  			stdoutPipe = container.StdoutLogPipe()
   134  			go func() {
   135  				logrus.Debug("logs: stdout stream begin")
   136  				chErrStdout <- jsonlog.WriteLog(stdoutPipe, outStream, format, config.Since)
   137  				logrus.Debug("logs: stdout stream end")
   138  			}()
   139  		}
   140  		if config.UseStderr {
   141  			stderrPipe = container.StderrLogPipe()
   142  			go func() {
   143  				logrus.Debug("logs: stderr stream begin")
   144  				chErrStderr <- jsonlog.WriteLog(stderrPipe, errStream, format, config.Since)
   145  				logrus.Debug("logs: stderr stream end")
   146  			}()
   147  		}
   148  
   149  		select {
   150  		case err = <-chErrStderr:
   151  			if stdoutPipe != nil {
   152  				stdoutPipe.Close()
   153  				<-chErrStdout
   154  			}
   155  		case err = <-chErrStdout:
   156  			if stderrPipe != nil {
   157  				stderrPipe.Close()
   158  				<-chErrStderr
   159  			}
   160  		case <-config.Stop:
   161  			if stdoutPipe != nil {
   162  				stdoutPipe.Close()
   163  				<-chErrStdout
   164  			}
   165  			if stderrPipe != nil {
   166  				stderrPipe.Close()
   167  				<-chErrStderr
   168  			}
   169  			return nil
   170  		}
   171  
   172  		if err != nil && err != io.EOF && err != io.ErrClosedPipe {
   173  			if e, ok := err.(*net.OpError); ok && e.Err != syscall.EPIPE {
   174  				logrus.Errorf("error streaming logs: %v", err)
   175  			}
   176  		}
   177  	}
   178  	return nil
   179  }