github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/ingester/tailer.go (about) 1 package ingester 2 3 import ( 4 "encoding/binary" 5 "hash/fnv" 6 "sync" 7 "time" 8 9 "github.com/go-kit/log/level" 10 "github.com/prometheus/prometheus/model/labels" 11 "golang.org/x/net/context" 12 13 "github.com/grafana/loki/pkg/logproto" 14 "github.com/grafana/loki/pkg/logql/log" 15 "github.com/grafana/loki/pkg/logql/syntax" 16 "github.com/grafana/loki/pkg/util" 17 util_log "github.com/grafana/loki/pkg/util/log" 18 ) 19 20 const bufferSizeForTailResponse = 5 21 22 type TailServer interface { 23 Send(*logproto.TailResponse) error 24 Context() context.Context 25 } 26 27 type tailer struct { 28 id uint32 29 orgID string 30 matchers []*labels.Matcher 31 expr syntax.LogSelectorExpr 32 pipelineMtx sync.Mutex 33 34 sendChan chan *logproto.Stream 35 36 // Signaling channel used to notify once the tailer gets closed 37 // and the loop and senders should stop 38 closeChan chan struct{} 39 closeOnce sync.Once 40 41 blockedAt *time.Time 42 blockedMtx sync.RWMutex 43 droppedStreams []*logproto.DroppedStream 44 maxDroppedStreams int 45 46 conn TailServer 47 } 48 49 func newTailer(orgID, query string, conn TailServer, maxDroppedStreams int) (*tailer, error) { 50 expr, err := syntax.ParseLogSelector(query, true) 51 if err != nil { 52 return nil, err 53 } 54 // Make sure we can build a pipeline. The stream processing code doesn't have a place to handle 55 // this error so make sure we handle it here. 56 _, err = expr.Pipeline() 57 if err != nil { 58 return nil, err 59 } 60 matchers := expr.Matchers() 61 62 return &tailer{ 63 orgID: orgID, 64 matchers: matchers, 65 sendChan: make(chan *logproto.Stream, bufferSizeForTailResponse), 66 conn: conn, 67 droppedStreams: make([]*logproto.DroppedStream, 0, maxDroppedStreams), 68 maxDroppedStreams: maxDroppedStreams, 69 id: generateUniqueID(orgID, query), 70 closeChan: make(chan struct{}), 71 expr: expr, 72 }, nil 73 } 74 75 func (t *tailer) loop() { 76 var stream *logproto.Stream 77 var err error 78 var ok bool 79 80 for { 81 select { 82 case <-t.conn.Context().Done(): 83 t.close() 84 return 85 case <-t.closeChan: 86 return 87 case stream, ok = <-t.sendChan: 88 if !ok { 89 return 90 } else if stream == nil { 91 continue 92 } 93 94 // while sending new stream pop lined up dropped streams metadata for sending to querier 95 tailResponse := logproto.TailResponse{Stream: stream, DroppedStreams: t.popDroppedStreams()} 96 err = t.conn.Send(&tailResponse) 97 if err != nil { 98 // Don't log any error due to tail client closing the connection 99 if !util.IsConnCanceled(err) { 100 level.Error(util_log.WithContext(t.conn.Context(), util_log.Logger)).Log("msg", "Error writing to tail client", "err", err) 101 } 102 t.close() 103 return 104 } 105 } 106 } 107 } 108 109 func (t *tailer) send(stream logproto.Stream, lbs labels.Labels) { 110 if t.isClosed() { 111 return 112 } 113 114 // if we are already dropping streams due to blocked connection, drop new streams directly to save some effort 115 if blockedSince := t.blockedSince(); blockedSince != nil { 116 if blockedSince.Before(time.Now().Add(-time.Second * 15)) { 117 t.close() 118 return 119 } 120 t.dropStream(stream) 121 return 122 } 123 124 streams := t.processStream(stream, lbs) 125 if len(streams) == 0 { 126 return 127 } 128 for _, s := range streams { 129 select { 130 case t.sendChan <- s: 131 default: 132 t.dropStream(*s) 133 } 134 } 135 } 136 137 func (t *tailer) processStream(stream logproto.Stream, lbs labels.Labels) []*logproto.Stream { 138 // Build a new pipeline for each call because the pipeline builds a cache of labels 139 // and if we don't start with a new pipeline that cache will grow unbounded. 140 // The error is ignored because it would be handled in the constructor of the tailer. 141 pipeline, _ := t.expr.Pipeline() 142 143 // Optimization: skip filtering entirely, if no filter is set 144 if log.IsNoopPipeline(pipeline) { 145 return []*logproto.Stream{&stream} 146 } 147 // pipeline are not thread safe and tailer can process multiple stream at once. 148 t.pipelineMtx.Lock() 149 defer t.pipelineMtx.Unlock() 150 151 streams := map[uint64]*logproto.Stream{} 152 153 sp := pipeline.ForStream(lbs) 154 for _, e := range stream.Entries { 155 newLine, parsedLbs, ok := sp.ProcessString(e.Timestamp.UnixNano(), e.Line) 156 if !ok { 157 continue 158 } 159 var stream *logproto.Stream 160 if stream, ok = streams[parsedLbs.Hash()]; !ok { 161 stream = &logproto.Stream{ 162 Labels: parsedLbs.String(), 163 } 164 streams[parsedLbs.Hash()] = stream 165 } 166 stream.Entries = append(stream.Entries, logproto.Entry{ 167 Timestamp: e.Timestamp, 168 Line: newLine, 169 }) 170 } 171 streamsResult := make([]*logproto.Stream, 0, len(streams)) 172 for _, stream := range streams { 173 streamsResult = append(streamsResult, stream) 174 } 175 return streamsResult 176 } 177 178 // isMatching returns true if lbs matches all matchers. 179 func isMatching(lbs labels.Labels, matchers []*labels.Matcher) bool { 180 for _, matcher := range matchers { 181 if !matcher.Matches(lbs.Get(matcher.Name)) { 182 return false 183 } 184 } 185 return true 186 } 187 188 func (t *tailer) isClosed() bool { 189 select { 190 case <-t.closeChan: 191 return true 192 default: 193 return false 194 } 195 } 196 197 func (t *tailer) close() { 198 t.closeOnce.Do(func() { 199 // Signal the close channel 200 close(t.closeChan) 201 202 // We intentionally do not close sendChan in order to avoid a panic on 203 // send to a just-closed channel. It's OK not to close a channel, since 204 // it will be eventually garbage collected as soon as no goroutine 205 // references it anymore, whether it has been closed or not. 206 }) 207 } 208 209 func (t *tailer) blockedSince() *time.Time { 210 t.blockedMtx.RLock() 211 defer t.blockedMtx.RUnlock() 212 213 return t.blockedAt 214 } 215 216 func (t *tailer) dropStream(stream logproto.Stream) { 217 if len(stream.Entries) == 0 { 218 return 219 } 220 221 t.blockedMtx.Lock() 222 defer t.blockedMtx.Unlock() 223 224 if t.blockedAt == nil { 225 blockedAt := time.Now() 226 t.blockedAt = &blockedAt 227 } 228 229 if len(t.droppedStreams) >= t.maxDroppedStreams { 230 level.Info(util_log.Logger).Log("msg", "tailer dropped streams is reset", "length", len(t.droppedStreams)) 231 t.droppedStreams = nil 232 } 233 234 t.droppedStreams = append(t.droppedStreams, &logproto.DroppedStream{ 235 From: stream.Entries[0].Timestamp, 236 To: stream.Entries[len(stream.Entries)-1].Timestamp, 237 Labels: stream.Labels, 238 }) 239 } 240 241 func (t *tailer) popDroppedStreams() []*logproto.DroppedStream { 242 t.blockedMtx.Lock() 243 defer t.blockedMtx.Unlock() 244 245 if t.blockedAt == nil { 246 return nil 247 } 248 249 droppedStreams := t.droppedStreams 250 t.droppedStreams = []*logproto.DroppedStream{} 251 t.blockedAt = nil 252 253 return droppedStreams 254 } 255 256 func (t *tailer) getID() uint32 { 257 return t.id 258 } 259 260 // An id is useful in managing tailer instances 261 func generateUniqueID(orgID, query string) uint32 { 262 uniqueID := fnv.New32() 263 _, _ = uniqueID.Write([]byte(orgID)) 264 _, _ = uniqueID.Write([]byte(query)) 265 266 timeNow := make([]byte, 8) 267 binary.LittleEndian.PutUint64(timeNow, uint64(time.Now().UnixNano())) 268 _, _ = uniqueID.Write(timeNow) 269 270 return uniqueID.Sum32() 271 }