github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/clients/cmd/docker-driver/driver.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"encoding/binary"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"path/filepath"
    10  	"strings"
    11  	"sync"
    12  	"syscall"
    13  	"time"
    14  
    15  	"github.com/docker/docker/api/types/backend"
    16  	"github.com/docker/docker/api/types/plugins/logdriver"
    17  	"github.com/docker/docker/daemon/logger"
    18  	"github.com/docker/docker/daemon/logger/jsonfilelog"
    19  	"github.com/go-kit/log"
    20  	"github.com/go-kit/log/level"
    21  	protoio "github.com/gogo/protobuf/io"
    22  	"github.com/pkg/errors"
    23  	"github.com/tonistiigi/fifo"
    24  )
    25  
    26  type driver struct {
    27  	mu     sync.Mutex
    28  	logs   map[string]*logPair
    29  	idx    map[string]*logPair
    30  	logger log.Logger
    31  }
    32  
    33  type logPair struct {
    34  	jsonl  logger.Logger
    35  	lokil  logger.Logger
    36  	stream io.ReadCloser
    37  	info   logger.Info
    38  	logger log.Logger
    39  	// folder where json log files will be created.
    40  	folder string
    41  	// keep created files after stopping the container.
    42  	keepFile bool
    43  }
    44  
    45  func (l *logPair) Close() {
    46  	if err := l.stream.Close(); err != nil {
    47  		level.Error(l.logger).Log("msg", "error while closing fifo stream", "err", err)
    48  	}
    49  	if err := l.lokil.Close(); err != nil {
    50  		level.Error(l.logger).Log("msg", "error while closing loki logger", "err", err)
    51  	}
    52  	if l.jsonl == nil {
    53  		return
    54  	}
    55  	if err := l.jsonl.Close(); err != nil {
    56  		level.Error(l.logger).Log("msg", "error while closing json logger", "err", err)
    57  	}
    58  }
    59  
    60  func newDriver(logger log.Logger) *driver {
    61  	return &driver{
    62  		logs:   make(map[string]*logPair),
    63  		idx:    make(map[string]*logPair),
    64  		logger: logger,
    65  	}
    66  }
    67  
    68  func (d *driver) StartLogging(file string, logCtx logger.Info) error {
    69  	d.mu.Lock()
    70  	if _, exists := d.logs[file]; exists {
    71  		d.mu.Unlock()
    72  		return fmt.Errorf("logger for %q already exists", file)
    73  	}
    74  	d.mu.Unlock()
    75  	folder := fmt.Sprintf("/var/log/docker/%s/", logCtx.ContainerID)
    76  	logCtx.LogPath = filepath.Join(folder, "json.log")
    77  	level.Info(d.logger).Log("msg", "starting logging driver for container", "id", logCtx.ContainerID, "config", fmt.Sprintf("%+v", logCtx.Config), "file", file, "logpath", logCtx.LogPath)
    78  
    79  	noFile, err := parseBoolean(cfgNofile, logCtx, false)
    80  	if err != nil {
    81  		return err
    82  	}
    83  
    84  	keepFile, err := parseBoolean(cfgKeepFile, logCtx, false)
    85  	if err != nil {
    86  		return err
    87  	}
    88  
    89  	var jsonl logger.Logger
    90  	if !noFile {
    91  		if err := os.MkdirAll(folder, 0755); err != nil {
    92  			return errors.Wrap(err, "error setting up logger dir")
    93  		}
    94  
    95  		jsonl, err = jsonfilelog.New(logCtx)
    96  		if err != nil {
    97  			return errors.Wrap(err, "error creating jsonfile logger")
    98  		}
    99  	}
   100  
   101  	lokil, err := New(logCtx, d.logger)
   102  	if err != nil {
   103  		return errors.Wrap(err, "error creating loki logger")
   104  	}
   105  	f, err := fifo.OpenFifo(context.Background(), file, syscall.O_RDONLY, 0700)
   106  	if err != nil {
   107  		return errors.Wrapf(err, "error opening logger fifo: %q", file)
   108  	}
   109  
   110  	d.mu.Lock()
   111  	lf := &logPair{jsonl, lokil, f, logCtx, d.logger, folder, keepFile}
   112  	d.logs[file] = lf
   113  	d.idx[logCtx.ContainerID] = lf
   114  	d.mu.Unlock()
   115  
   116  	go consumeLog(lf)
   117  	return nil
   118  }
   119  
   120  func (d *driver) StopLogging(file string) {
   121  	level.Debug(d.logger).Log("msg", "Stop logging", "file", file)
   122  	d.mu.Lock()
   123  	defer d.mu.Unlock()
   124  	lf, ok := d.logs[file]
   125  	if !ok {
   126  		return
   127  	}
   128  	lf.Close()
   129  	delete(d.logs, file)
   130  	if !lf.keepFile && lf.jsonl != nil {
   131  		// delete the folder where all log files were created.
   132  		if err := os.RemoveAll(lf.folder); err != nil {
   133  			level.Debug(d.logger).Log("msg", "error deleting folder", "folder", lf.folder)
   134  		}
   135  	}
   136  }
   137  
   138  func consumeLog(lf *logPair) {
   139  	dec := protoio.NewUint32DelimitedReader(lf.stream, binary.BigEndian, 1e6)
   140  	defer dec.Close()
   141  	defer lf.Close()
   142  	var buf logdriver.LogEntry
   143  	for {
   144  		if err := dec.ReadMsg(&buf); err != nil {
   145  			if err == io.EOF || err == os.ErrClosed || strings.Contains(err.Error(), "file already closed") {
   146  				level.Debug(lf.logger).Log("msg", "shutting down log logger", "id", lf.info.ContainerID, "err", err)
   147  				return
   148  			}
   149  			dec = protoio.NewUint32DelimitedReader(lf.stream, binary.BigEndian, 1e6)
   150  		}
   151  		var msg logger.Message
   152  		msg.Line = buf.Line
   153  		msg.Source = buf.Source
   154  		if buf.PartialLogMetadata != nil {
   155  			if msg.PLogMetaData == nil {
   156  				msg.PLogMetaData = &backend.PartialLogMetaData{}
   157  			}
   158  			msg.PLogMetaData.ID = buf.PartialLogMetadata.Id
   159  			msg.PLogMetaData.Last = buf.PartialLogMetadata.Last
   160  			msg.PLogMetaData.Ordinal = int(buf.PartialLogMetadata.Ordinal)
   161  		}
   162  		msg.Timestamp = time.Unix(0, buf.TimeNano)
   163  
   164  		// loki goes first as the json logger reset the message on completion.
   165  		if err := lf.lokil.Log(&msg); err != nil {
   166  			level.Error(lf.logger).Log("msg", "error pushing message to loki", "id", lf.info.ContainerID, "err", err, "message", msg)
   167  		}
   168  		if lf.jsonl != nil {
   169  			if err := lf.jsonl.Log(&msg); err != nil {
   170  				level.Error(lf.logger).Log("msg", "error writing log message", "id", lf.info.ContainerID, "err", err, "message", msg)
   171  				continue
   172  			}
   173  		}
   174  
   175  		buf.Reset()
   176  	}
   177  }
   178  
   179  func (d *driver) ReadLogs(info logger.Info, config logger.ReadConfig) (io.ReadCloser, error) {
   180  	d.mu.Lock()
   181  	lf, exists := d.idx[info.ContainerID]
   182  	d.mu.Unlock()
   183  	if !exists {
   184  		return nil, fmt.Errorf("logger does not exist for %s", info.ContainerID)
   185  	}
   186  
   187  	if lf.jsonl == nil {
   188  		return nil, fmt.Errorf("%s option set to true, no reading capability", cfgNofile)
   189  	}
   190  
   191  	r, w := io.Pipe()
   192  	lr, ok := lf.jsonl.(logger.LogReader)
   193  	if !ok {
   194  		return nil, errors.New("logger does not support reading")
   195  	}
   196  
   197  	go func() {
   198  		watcher := lr.ReadLogs(config)
   199  
   200  		enc := protoio.NewUint32DelimitedWriter(w, binary.BigEndian)
   201  		defer enc.Close()
   202  		defer watcher.ConsumerGone()
   203  
   204  		var buf logdriver.LogEntry
   205  		for {
   206  			select {
   207  			case msg, ok := <-watcher.Msg:
   208  				if !ok {
   209  					w.Close()
   210  					return
   211  				}
   212  
   213  				buf.Line = msg.Line
   214  				buf.Partial = msg.PLogMetaData != nil
   215  				buf.TimeNano = msg.Timestamp.UnixNano()
   216  				buf.Source = msg.Source
   217  
   218  				if err := enc.WriteMsg(&buf); err != nil {
   219  					_ = w.CloseWithError(err)
   220  					return
   221  				}
   222  			case err := <-watcher.Err:
   223  				_ = w.CloseWithError(err)
   224  				return
   225  			}
   226  
   227  			buf.Reset()
   228  		}
   229  	}()
   230  
   231  	return r, nil
   232  }