github.com/qubitproducts/logspray@v0.2.14/sources/docker/dockersource.go (about)

     1  // Copyright 2016 Qubit Digital Ltd.
     2  // Licensed under the Apache License, Version 2.0 (the "License");
     3  // you may not use this file except in compliance with the License.
     4  // You may obtain a copy of the License at
     5  //
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  // Package logspray is a collection of tools for streaming and indexing
    14  // large volumes of dynamic logs.
    15  
    16  package docker
    17  
    18  import (
    19  	"context"
    20  	"encoding/json"
    21  	"fmt"
    22  	"io"
    23  	"path/filepath"
    24  	"strings"
    25  	"time"
    26  
    27  	"github.com/QubitProducts/logspray/proto/logspray"
    28  	"github.com/QubitProducts/logspray/sources"
    29  	"github.com/golang/glog"
    30  	"github.com/golang/protobuf/ptypes"
    31  	"github.com/hpcloud/tail"
    32  
    33  	"github.com/docker/engine-api/client"
    34  )
    35  
    36  var dockerTimeFmt = time.RFC3339Nano
    37  
    38  // MessageReader is a reder log source that reads docker logs.
    39  type MessageReader struct {
    40  	id        string
    41  	cli       *client.Client
    42  	fromStart bool
    43  	poll      bool
    44  
    45  	path string
    46  
    47  	lines chan *logspray.Message
    48  }
    49  
    50  // ReadTarget creates a new docker log source
    51  func (w *Watcher) ReadTarget(ctx context.Context, id string, fromStart bool) (sources.MessageReader, error) {
    52  	path := filepath.Join(w.root, "containers", id, id+"-json.log")
    53  	dls := &MessageReader{
    54  		id:    id,
    55  		lines: make(chan *logspray.Message),
    56  		cli:   w.dcli,
    57  		poll:  w.poll,
    58  		path:  path,
    59  	}
    60  
    61  	go dls.dockerReadLogs(ctx, fromStart)
    62  
    63  	return dls, nil
    64  }
    65  
    66  // MessageRead implements the LogSourcer interface
    67  func (dls *MessageReader) MessageRead(ctx context.Context) (*logspray.Message, error) {
    68  	select {
    69  	case m := <-dls.lines:
    70  		if m == nil {
    71  			return nil, io.EOF
    72  		}
    73  		return m, nil
    74  	}
    75  }
    76  
    77  func (dls *MessageReader) dockerReadLogs(ctx context.Context, fromStart bool) {
    78  	defer func() {
    79  		dls.logExit()
    80  		close(dls.lines)
    81  	}()
    82  
    83  	whence := io.SeekEnd
    84  	if fromStart {
    85  		whence = io.SeekStart
    86  	}
    87  	ft, err := tail.TailFile(dls.path, tail.Config{
    88  		Location:  &tail.SeekInfo{Whence: whence, Offset: 0},
    89  		MustExist: false,
    90  		Follow:    true,
    91  		ReOpen:    true,
    92  		Poll:      dls.poll,
    93  		//		Logger:    logto,
    94  	})
    95  	if err != nil {
    96  		return
    97  	}
    98  
    99  	base := &logspray.Message{}
   100  	base.Labels = map[string]string{}
   101  
   102  	jsonline := struct {
   103  		Log    string
   104  		Stream string
   105  		Time   string
   106  	}{}
   107  
   108  	for {
   109  		select {
   110  		case line := <-ft.Lines:
   111  			if line == nil || line.Err != nil {
   112  				return
   113  			}
   114  
   115  			nbase := base.Copy()
   116  
   117  			err := json.Unmarshal([]byte(line.Text), &jsonline)
   118  			if err != nil {
   119  				if glog.V(2) {
   120  					glog.Error("failed unmarshaling line, err = ", err)
   121  				}
   122  			}
   123  
   124  			if t, err := time.Parse(dockerTimeFmt, jsonline.Time); err == nil {
   125  				nbase.Time, _ = ptypes.TimestampProto(t)
   126  			} else {
   127  				if glog.V(2) {
   128  					glog.Errorf("error parsing docker log time, %v", err)
   129  				}
   130  				nbase.Time, _ = ptypes.TimestampProto(line.Time)
   131  			}
   132  
   133  			nbase.Text = strings.TrimSuffix(jsonline.Log, "\n")
   134  			nbase.Labels["source"] = jsonline.Stream
   135  
   136  			dls.lines <- nbase
   137  		case <-ctx.Done():
   138  			go ft.Stop()
   139  		}
   140  	}
   141  }
   142  
   143  func (dls *MessageReader) logExit() {
   144  	cinfo, _, err := dls.cli.ContainerInspectWithRaw(context.Background(), dls.id, false)
   145  	if err != nil {
   146  		glog.Errorf("error retrieving container exit info,  %v", err)
   147  		return
   148  	}
   149  
   150  	pt, _ := ptypes.TimestampProto(time.Now())
   151  	dls.lines <- &logspray.Message{
   152  		Time:   pt,
   153  		Text:   fmt.Sprintf("Container exitted: error = %#v, exitcode = %d", cinfo.State.Error, cinfo.State.ExitCode),
   154  		Labels: map[string]string{},
   155  	}
   156  
   157  	switch {
   158  	case cinfo.State.OOMKilled:
   159  		pt, _ := ptypes.TimestampProto(time.Now())
   160  		dls.lines <- &logspray.Message{
   161  			Time:   pt,
   162  			Text:   "Container died due to OOM",
   163  			Labels: map[string]string{},
   164  		}
   165  	}
   166  	return
   167  }