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