github.com/qubitproducts/logspray@v0.2.14/sources/reader.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 sources 17 18 import ( 19 "context" 20 "io" 21 "math/rand" 22 "sync" 23 "time" 24 25 "github.com/QubitProducts/logspray/sinks" 26 "github.com/cloudflare/backoff" 27 "github.com/golang/glog" 28 "github.com/oklog/ulid" 29 "github.com/prometheus/client_golang/prometheus" 30 ) 31 32 var ( 33 lineCount = prometheus.NewCounterVec(prometheus.CounterOpts{ 34 Name: "logspray_reader_read_lines_total", 35 Help: "Counter of total lines read since process start.", 36 }, []string{"logspray_job"}) 37 bytesCount = prometheus.NewCounterVec(prometheus.CounterOpts{ 38 Name: "logspray_reader_read_bytes_total", 39 Help: "Counter of total bytes read since process start.", 40 }, []string{"logspray_job"}) 41 sourcesActive = prometheus.NewGauge(prometheus.GaugeOpts{ 42 Name: "logspray_reader_active_sources", 43 Help: "Gauge of number of active sources.", 44 }) 45 sourcesOpened = prometheus.NewCounter(prometheus.CounterOpts{ 46 Name: "logspray_reader_opened_sources_total", 47 Help: "Counter of number of sources since process start.", 48 }) 49 ) 50 51 func init() { 52 prometheus.MustRegister(lineCount) 53 prometheus.MustRegister(bytesCount) 54 prometheus.MustRegister(sourcesActive) 55 prometheus.MustRegister(sourcesOpened) 56 } 57 58 // ReadAllTargets drains a source of log data 59 func ReadAllTargets(ctx context.Context, snk sinks.Sinker, src Sourcer) error { 60 var err error 61 62 targets := &targetSet{ 63 Sourcer: src, 64 entropy: rand.New(rand.NewSource(time.Now().UnixNano())), 65 } 66 67 defer targets.cancelAll() 68 69 existing, err := src.Next(ctx) 70 if err != nil { 71 return err 72 } 73 74 for _, u := range existing { 75 if glog.V(2) { 76 glog.Infof("Found pre-existing job: %#v", *u) 77 } 78 79 go targets.addSource(ctx, snk, u, false) 80 } 81 82 for us, err := src.Next(ctx); err == nil; us, err = src.Next(ctx) { 83 if err != nil { 84 return err 85 } 86 for _, u := range us { 87 switch u.Action { 88 case Remove: 89 if glog.V(2) { 90 glog.Infof("Removing job: %#v", *u) 91 } 92 targets.cancelTarget(u.Target) 93 case Add: 94 if glog.V(2) { 95 glog.Infof("Found new job: %#v", *u) 96 } 97 go targets.addSource(ctx, snk, u, true) 98 } 99 } 100 } 101 return err 102 } 103 104 func (ts *targetSet) addSource(ctx context.Context, snk sinks.Sinker, u *Update, fromStart bool) { 105 b := backoff.New(10*time.Second, 1*time.Second) 106 for { 107 select { 108 case <-ctx.Done(): 109 return 110 default: 111 streamID := ulid.MustNew(ulid.Now(), ts.entropy) 112 113 w, err := snk.AddSource(streamID.String(), u.Labels) 114 if err != nil { 115 if glog.V(2) { 116 glog.Errorf("addsource error: %#v , %v", *u, err) 117 } 118 <-time.After(b.Duration()) 119 continue 120 } 121 b.Reset() 122 123 err = ts.readAllFromTarget(ctx, w, u, fromStart) 124 switch err { 125 case nil, io.EOF, context.Canceled: 126 return 127 } 128 } 129 } 130 } 131 132 func (ts *targetSet) readAllFromTarget(ctx context.Context, w sinks.MessageWriter, u *Update, fromStart bool) error { 133 sourcesOpened.Inc() 134 sourcesActive.Inc() 135 defer sourcesActive.Dec() 136 137 fctx := ts.setTarget(ctx, u.Target) 138 defer w.Close() 139 // Start at 1, control messages like setheader then all get 0s 140 msgid := uint64(1) 141 142 r, err := ts.ReadTarget(fctx, u.Target, fromStart) 143 if err != nil { 144 return err 145 } 146 147 for { 148 msg, err := r.MessageRead(fctx) 149 if err == io.EOF { 150 if glog.V(2) { 151 glog.Infof("Stream ended %v\n", u.Target) 152 } 153 return err 154 } 155 156 if err != nil { 157 if glog.V(1) { 158 glog.Errorf("read message error: %#v , %v", *u, err) 159 } 160 continue 161 } 162 lineCount.WithLabelValues(u.Labels["job"]).Inc() 163 bytesCount.WithLabelValues(u.Labels["job"]).Add(float64(len(msg.Text))) 164 msg.Index = msgid 165 msgid++ 166 if err := w.WriteMessage(ctx, msg); err != nil { 167 if glog.V(1) { 168 glog.Errorf("write message error: %#v , %v", *u, err) 169 } 170 } 171 } 172 } 173 174 type targetSet struct { 175 Sourcer 176 sinks.Sinker 177 entropy io.Reader 178 179 sync.Mutex 180 cfs map[string]context.CancelFunc 181 } 182 183 func (ts *targetSet) setTarget(ctx context.Context, id string) context.Context { 184 fctx, cf := context.WithCancel(ctx) 185 186 ts.Lock() 187 defer ts.Unlock() 188 if ts.cfs == nil { 189 ts.cfs = map[string]context.CancelFunc{} 190 } 191 192 ts.cfs[id] = cf 193 return fctx 194 } 195 196 func (ts *targetSet) cancelTarget(id string) { 197 ts.Lock() 198 defer ts.Unlock() 199 200 if cf, ok := ts.cfs[id]; ok { 201 delete(ts.cfs, id) 202 cf() 203 } 204 } 205 206 func (ts *targetSet) cancelAll() { 207 ts.Lock() 208 defer ts.Unlock() 209 for _, cf := range ts.cfs { 210 cf() 211 } 212 }