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  }