github.com/gondor/docker@v1.9.0-rc1/daemon/logger/jsonfilelog/jsonfilelog.go (about)

     1  // Package jsonfilelog provides the default Logger implementation for
     2  // Docker logging. This logger logs to files on the host server in the
     3  // JSON format.
     4  package jsonfilelog
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/json"
     9  	"fmt"
    10  	"io"
    11  	"os"
    12  	"strconv"
    13  	"sync"
    14  	"time"
    15  
    16  	"gopkg.in/fsnotify.v1"
    17  
    18  	"github.com/Sirupsen/logrus"
    19  	"github.com/docker/docker/daemon/logger"
    20  	"github.com/docker/docker/pkg/ioutils"
    21  	"github.com/docker/docker/pkg/jsonlog"
    22  	"github.com/docker/docker/pkg/pubsub"
    23  	"github.com/docker/docker/pkg/tailfile"
    24  	"github.com/docker/docker/pkg/timeutils"
    25  	"github.com/docker/docker/pkg/units"
    26  )
    27  
    28  const (
    29  	// Name is the name of the file that the jsonlogger logs to.
    30  	Name               = "json-file"
    31  	maxJSONDecodeRetry = 10
    32  )
    33  
    34  // JSONFileLogger is Logger implementation for default Docker logging.
    35  type JSONFileLogger struct {
    36  	buf          *bytes.Buffer
    37  	f            *os.File   // store for closing
    38  	mu           sync.Mutex // protects buffer
    39  	capacity     int64      //maximum size of each file
    40  	n            int        //maximum number of files
    41  	ctx          logger.Context
    42  	readers      map[*logger.LogWatcher]struct{} // stores the active log followers
    43  	notifyRotate *pubsub.Publisher
    44  	extra        []byte // json-encoded extra attributes
    45  }
    46  
    47  func init() {
    48  	if err := logger.RegisterLogDriver(Name, New); err != nil {
    49  		logrus.Fatal(err)
    50  	}
    51  	if err := logger.RegisterLogOptValidator(Name, ValidateLogOpt); err != nil {
    52  		logrus.Fatal(err)
    53  	}
    54  }
    55  
    56  // New creates new JSONFileLogger which writes to filename passed in
    57  // on given context.
    58  func New(ctx logger.Context) (logger.Logger, error) {
    59  	log, err := os.OpenFile(ctx.LogPath, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600)
    60  	if err != nil {
    61  		return nil, err
    62  	}
    63  	var capval int64 = -1
    64  	if capacity, ok := ctx.Config["max-size"]; ok {
    65  		var err error
    66  		capval, err = units.FromHumanSize(capacity)
    67  		if err != nil {
    68  			return nil, err
    69  		}
    70  	}
    71  	var maxFiles = 1
    72  	if maxFileString, ok := ctx.Config["max-file"]; ok {
    73  		maxFiles, err = strconv.Atoi(maxFileString)
    74  		if err != nil {
    75  			return nil, err
    76  		}
    77  		if maxFiles < 1 {
    78  			return nil, fmt.Errorf("max-file cannot be less than 1")
    79  		}
    80  	}
    81  
    82  	var extra []byte
    83  	if attrs := ctx.ExtraAttributes(nil); len(attrs) > 0 {
    84  		var err error
    85  		extra, err = json.Marshal(attrs)
    86  		if err != nil {
    87  			return nil, err
    88  		}
    89  	}
    90  
    91  	return &JSONFileLogger{
    92  		f:            log,
    93  		buf:          bytes.NewBuffer(nil),
    94  		ctx:          ctx,
    95  		capacity:     capval,
    96  		n:            maxFiles,
    97  		readers:      make(map[*logger.LogWatcher]struct{}),
    98  		notifyRotate: pubsub.NewPublisher(0, 1),
    99  		extra:        extra,
   100  	}, nil
   101  }
   102  
   103  // Log converts logger.Message to jsonlog.JSONLog and serializes it to file.
   104  func (l *JSONFileLogger) Log(msg *logger.Message) error {
   105  	l.mu.Lock()
   106  	defer l.mu.Unlock()
   107  
   108  	timestamp, err := timeutils.FastMarshalJSON(msg.Timestamp)
   109  	if err != nil {
   110  		return err
   111  	}
   112  	err = (&jsonlog.JSONLogs{
   113  		Log:      append(msg.Line, '\n'),
   114  		Stream:   msg.Source,
   115  		Created:  timestamp,
   116  		RawAttrs: l.extra,
   117  	}).MarshalJSONBuf(l.buf)
   118  	if err != nil {
   119  		return err
   120  	}
   121  	l.buf.WriteByte('\n')
   122  	_, err = writeLog(l)
   123  	return err
   124  }
   125  
   126  func writeLog(l *JSONFileLogger) (int64, error) {
   127  	if l.capacity == -1 {
   128  		return writeToBuf(l)
   129  	}
   130  	meta, err := l.f.Stat()
   131  	if err != nil {
   132  		return -1, err
   133  	}
   134  	if meta.Size() >= l.capacity {
   135  		name := l.f.Name()
   136  		if err := l.f.Close(); err != nil {
   137  			return -1, err
   138  		}
   139  		if err := rotate(name, l.n); err != nil {
   140  			return -1, err
   141  		}
   142  		file, err := os.OpenFile(name, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666)
   143  		if err != nil {
   144  			return -1, err
   145  		}
   146  		l.f = file
   147  		l.notifyRotate.Publish(struct{}{})
   148  	}
   149  	return writeToBuf(l)
   150  }
   151  
   152  func writeToBuf(l *JSONFileLogger) (int64, error) {
   153  	i, err := l.buf.WriteTo(l.f)
   154  	if err != nil {
   155  		l.buf = bytes.NewBuffer(nil)
   156  	}
   157  	return i, err
   158  }
   159  
   160  func rotate(name string, n int) error {
   161  	if n < 2 {
   162  		return nil
   163  	}
   164  	for i := n - 1; i > 1; i-- {
   165  		oldFile := name + "." + strconv.Itoa(i)
   166  		replacingFile := name + "." + strconv.Itoa(i-1)
   167  		if err := backup(oldFile, replacingFile); err != nil {
   168  			return err
   169  		}
   170  	}
   171  	if err := backup(name+".1", name); err != nil {
   172  		return err
   173  	}
   174  	return nil
   175  }
   176  
   177  // backup renames a file from curr to old, creating an empty file curr if it does not exist.
   178  func backup(old, curr string) error {
   179  	if _, err := os.Stat(old); !os.IsNotExist(err) {
   180  		err := os.Remove(old)
   181  		if err != nil {
   182  			return err
   183  		}
   184  	}
   185  	if _, err := os.Stat(curr); os.IsNotExist(err) {
   186  		f, err := os.Create(curr)
   187  		if err != nil {
   188  			return err
   189  		}
   190  		f.Close()
   191  	}
   192  	return os.Rename(curr, old)
   193  }
   194  
   195  // ValidateLogOpt looks for json specific log options max-file & max-size.
   196  func ValidateLogOpt(cfg map[string]string) error {
   197  	for key := range cfg {
   198  		switch key {
   199  		case "max-file":
   200  		case "max-size":
   201  		case "labels":
   202  		case "env":
   203  		default:
   204  			return fmt.Errorf("unknown log opt '%s' for json-file log driver", key)
   205  		}
   206  	}
   207  	return nil
   208  }
   209  
   210  // LogPath returns the location the given json logger logs to.
   211  func (l *JSONFileLogger) LogPath() string {
   212  	return l.ctx.LogPath
   213  }
   214  
   215  // Close closes underlying file and signals all readers to stop.
   216  func (l *JSONFileLogger) Close() error {
   217  	l.mu.Lock()
   218  	err := l.f.Close()
   219  	for r := range l.readers {
   220  		r.Close()
   221  		delete(l.readers, r)
   222  	}
   223  	l.mu.Unlock()
   224  	return err
   225  }
   226  
   227  // Name returns name of this logger.
   228  func (l *JSONFileLogger) Name() string {
   229  	return Name
   230  }
   231  
   232  func decodeLogLine(dec *json.Decoder, l *jsonlog.JSONLog) (*logger.Message, error) {
   233  	l.Reset()
   234  	if err := dec.Decode(l); err != nil {
   235  		return nil, err
   236  	}
   237  	msg := &logger.Message{
   238  		Source:    l.Stream,
   239  		Timestamp: l.Created,
   240  		Line:      []byte(l.Log),
   241  	}
   242  	return msg, nil
   243  }
   244  
   245  // ReadLogs implements the logger's LogReader interface for the logs
   246  // created by this driver.
   247  func (l *JSONFileLogger) ReadLogs(config logger.ReadConfig) *logger.LogWatcher {
   248  	logWatcher := logger.NewLogWatcher()
   249  
   250  	go l.readLogs(logWatcher, config)
   251  	return logWatcher
   252  }
   253  
   254  func (l *JSONFileLogger) readLogs(logWatcher *logger.LogWatcher, config logger.ReadConfig) {
   255  	defer close(logWatcher.Msg)
   256  
   257  	pth := l.ctx.LogPath
   258  	var files []io.ReadSeeker
   259  	for i := l.n; i > 1; i-- {
   260  		f, err := os.Open(fmt.Sprintf("%s.%d", pth, i-1))
   261  		if err != nil {
   262  			if !os.IsNotExist(err) {
   263  				logWatcher.Err <- err
   264  				break
   265  			}
   266  			continue
   267  		}
   268  		defer f.Close()
   269  		files = append(files, f)
   270  	}
   271  
   272  	latestFile, err := os.Open(pth)
   273  	if err != nil {
   274  		logWatcher.Err <- err
   275  		return
   276  	}
   277  	defer latestFile.Close()
   278  
   279  	files = append(files, latestFile)
   280  	tailer := ioutils.MultiReadSeeker(files...)
   281  
   282  	if config.Tail != 0 {
   283  		tailFile(tailer, logWatcher, config.Tail, config.Since)
   284  	}
   285  
   286  	if !config.Follow {
   287  		return
   288  	}
   289  
   290  	if config.Tail >= 0 {
   291  		latestFile.Seek(0, os.SEEK_END)
   292  	}
   293  
   294  	l.mu.Lock()
   295  	l.readers[logWatcher] = struct{}{}
   296  	l.mu.Unlock()
   297  
   298  	notifyRotate := l.notifyRotate.Subscribe()
   299  	followLogs(latestFile, logWatcher, notifyRotate, config.Since)
   300  
   301  	l.mu.Lock()
   302  	delete(l.readers, logWatcher)
   303  	l.mu.Unlock()
   304  
   305  	l.notifyRotate.Evict(notifyRotate)
   306  }
   307  
   308  func tailFile(f io.ReadSeeker, logWatcher *logger.LogWatcher, tail int, since time.Time) {
   309  	var rdr io.Reader = f
   310  	if tail > 0 {
   311  		ls, err := tailfile.TailFile(f, tail)
   312  		if err != nil {
   313  			logWatcher.Err <- err
   314  			return
   315  		}
   316  		rdr = bytes.NewBuffer(bytes.Join(ls, []byte("\n")))
   317  	}
   318  	dec := json.NewDecoder(rdr)
   319  	l := &jsonlog.JSONLog{}
   320  	for {
   321  		msg, err := decodeLogLine(dec, l)
   322  		if err != nil {
   323  			if err != io.EOF {
   324  				logWatcher.Err <- err
   325  			}
   326  			return
   327  		}
   328  		if !since.IsZero() && msg.Timestamp.Before(since) {
   329  			continue
   330  		}
   331  		logWatcher.Msg <- msg
   332  	}
   333  }
   334  
   335  func followLogs(f *os.File, logWatcher *logger.LogWatcher, notifyRotate chan interface{}, since time.Time) {
   336  	dec := json.NewDecoder(f)
   337  	l := &jsonlog.JSONLog{}
   338  	fileWatcher, err := fsnotify.NewWatcher()
   339  	if err != nil {
   340  		logWatcher.Err <- err
   341  		return
   342  	}
   343  	defer fileWatcher.Close()
   344  	if err := fileWatcher.Add(f.Name()); err != nil {
   345  		logWatcher.Err <- err
   346  		return
   347  	}
   348  
   349  	var retries int
   350  	for {
   351  		msg, err := decodeLogLine(dec, l)
   352  		if err != nil {
   353  			if err != io.EOF {
   354  				// try again because this shouldn't happen
   355  				if _, ok := err.(*json.SyntaxError); ok && retries <= maxJSONDecodeRetry {
   356  					dec = json.NewDecoder(f)
   357  					retries++
   358  					continue
   359  				}
   360  				logWatcher.Err <- err
   361  				return
   362  			}
   363  
   364  			select {
   365  			case <-fileWatcher.Events:
   366  				dec = json.NewDecoder(f)
   367  				continue
   368  			case <-fileWatcher.Errors:
   369  				logWatcher.Err <- err
   370  				return
   371  			case <-logWatcher.WatchClose():
   372  				return
   373  			case <-notifyRotate:
   374  				fileWatcher.Remove(f.Name())
   375  
   376  				f, err = os.Open(f.Name())
   377  				if err != nil {
   378  					logWatcher.Err <- err
   379  					return
   380  				}
   381  				if err := fileWatcher.Add(f.Name()); err != nil {
   382  					logWatcher.Err <- err
   383  				}
   384  				dec = json.NewDecoder(f)
   385  				continue
   386  			}
   387  		}
   388  
   389  		retries = 0 // reset retries since we've succeeded
   390  		if !since.IsZero() && msg.Timestamp.Before(since) {
   391  			continue
   392  		}
   393  		select {
   394  		case logWatcher.Msg <- msg:
   395  		case <-logWatcher.WatchClose():
   396  			logWatcher.Msg <- msg
   397  			for {
   398  				msg, err := decodeLogLine(dec, l)
   399  				if err != nil {
   400  					return
   401  				}
   402  				if !since.IsZero() && msg.Timestamp.Before(since) {
   403  					continue
   404  				}
   405  				logWatcher.Msg <- msg
   406  			}
   407  		}
   408  	}
   409  }