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 }