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  }