github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/clients/pkg/promtail/targets/file/filetarget.go (about)

     1  package file
     2  
     3  import (
     4  	"flag"
     5  	"os"
     6  	"path/filepath"
     7  	"time"
     8  
     9  	"github.com/bmatcuk/doublestar"
    10  	"github.com/go-kit/log"
    11  	"github.com/go-kit/log/level"
    12  	"github.com/pkg/errors"
    13  	"github.com/prometheus/client_golang/prometheus"
    14  	"github.com/prometheus/common/model"
    15  	fsnotify "gopkg.in/fsnotify.v1"
    16  
    17  	"github.com/grafana/loki/clients/pkg/promtail/api"
    18  	"github.com/grafana/loki/clients/pkg/promtail/client"
    19  	"github.com/grafana/loki/clients/pkg/promtail/positions"
    20  	"github.com/grafana/loki/clients/pkg/promtail/targets/target"
    21  )
    22  
    23  const (
    24  	FilenameLabel = "filename"
    25  )
    26  
    27  // Config describes behavior for Target
    28  type Config struct {
    29  	SyncPeriod time.Duration `yaml:"sync_period"`
    30  	Stdin      bool          `yaml:"stdin"`
    31  }
    32  
    33  // RegisterFlags with prefix registers flags where every name is prefixed by
    34  // prefix. If prefix is a non-empty string, prefix should end with a period.
    35  func (cfg *Config) RegisterFlagsWithPrefix(prefix string, f *flag.FlagSet) {
    36  	f.DurationVar(&cfg.SyncPeriod, prefix+"target.sync-period", 10*time.Second, "Period to resync directories being watched and files being tailed.")
    37  	f.BoolVar(&cfg.Stdin, prefix+"stdin", false, "Set to true to pipe logs to promtail.")
    38  }
    39  
    40  // RegisterFlags register flags.
    41  func (cfg *Config) RegisterFlags(flags *flag.FlagSet) {
    42  	cfg.RegisterFlagsWithPrefix("", flags)
    43  }
    44  
    45  type fileTargetEventType string
    46  
    47  const (
    48  	fileTargetEventWatchStart fileTargetEventType = "WATCH_START"
    49  	fileTargetEventWatchStop  fileTargetEventType = "WATCH_STOP"
    50  )
    51  
    52  type fileTargetEvent struct {
    53  	path      string
    54  	eventType fileTargetEventType
    55  }
    56  
    57  // FileTarget describes a particular set of logs.
    58  // nolint:revive
    59  type FileTarget struct {
    60  	metrics *Metrics
    61  	logger  log.Logger
    62  
    63  	handler          api.EntryHandler
    64  	positions        positions.Positions
    65  	labels           model.LabelSet
    66  	discoveredLabels model.LabelSet
    67  
    68  	fileEventWatcher   chan fsnotify.Event
    69  	targetEventHandler chan fileTargetEvent
    70  	watches            map[string]struct{}
    71  	path               string
    72  	pathExclude        string
    73  	quit               chan struct{}
    74  	done               chan struct{}
    75  
    76  	tails map[string]*tailer
    77  
    78  	targetConfig *Config
    79  
    80  	encoding string
    81  }
    82  
    83  // NewFileTarget create a new FileTarget.
    84  func NewFileTarget(
    85  	metrics *Metrics,
    86  	logger log.Logger,
    87  	handler api.EntryHandler,
    88  	positions positions.Positions,
    89  	path string,
    90  	pathExclude string,
    91  	labels model.LabelSet,
    92  	discoveredLabels model.LabelSet,
    93  	targetConfig *Config,
    94  	fileEventWatcher chan fsnotify.Event,
    95  	targetEventHandler chan fileTargetEvent,
    96  	encoding string,
    97  ) (*FileTarget, error) {
    98  	t := &FileTarget{
    99  		logger:             logger,
   100  		metrics:            metrics,
   101  		path:               path,
   102  		pathExclude:        pathExclude,
   103  		labels:             labels,
   104  		discoveredLabels:   discoveredLabels,
   105  		handler:            api.AddLabelsMiddleware(labels).Wrap(handler),
   106  		positions:          positions,
   107  		quit:               make(chan struct{}),
   108  		done:               make(chan struct{}),
   109  		tails:              map[string]*tailer{},
   110  		targetConfig:       targetConfig,
   111  		fileEventWatcher:   fileEventWatcher,
   112  		targetEventHandler: targetEventHandler,
   113  		encoding:           encoding,
   114  	}
   115  
   116  	go t.run()
   117  	return t, nil
   118  }
   119  
   120  // Ready if at least one file is being tailed
   121  func (t *FileTarget) Ready() bool {
   122  	return len(t.tails) > 0
   123  }
   124  
   125  // Stop the target.
   126  func (t *FileTarget) Stop() {
   127  	close(t.quit)
   128  	<-t.done
   129  	t.handler.Stop()
   130  }
   131  
   132  // Type implements a Target
   133  func (t *FileTarget) Type() target.TargetType {
   134  	return target.FileTargetType
   135  }
   136  
   137  // DiscoveredLabels implements a Target
   138  func (t *FileTarget) DiscoveredLabels() model.LabelSet {
   139  	return t.discoveredLabels
   140  }
   141  
   142  // Labels implements a Target
   143  func (t *FileTarget) Labels() model.LabelSet {
   144  	return t.labels
   145  }
   146  
   147  // Details implements a Target
   148  func (t *FileTarget) Details() interface{} {
   149  	files := map[string]int64{}
   150  	for fileName := range t.tails {
   151  		files[fileName], _ = t.positions.Get(fileName)
   152  	}
   153  	return files
   154  }
   155  
   156  func (t *FileTarget) run() {
   157  	defer func() {
   158  		for _, v := range t.tails {
   159  			v.stop()
   160  		}
   161  		level.Info(t.logger).Log("msg", "filetarget: watcher closed, tailer stopped, positions saved", "path", t.path)
   162  		close(t.done)
   163  	}()
   164  
   165  	err := t.sync()
   166  	if err != nil {
   167  		level.Error(t.logger).Log("msg", "error running sync function", "error", err)
   168  	}
   169  
   170  	ticker := time.NewTicker(t.targetConfig.SyncPeriod)
   171  	defer ticker.Stop()
   172  
   173  	for {
   174  		select {
   175  		case event, ok := <-t.fileEventWatcher:
   176  			if !ok {
   177  				// fileEventWatcher has been closed
   178  				return
   179  			}
   180  			switch event.Op {
   181  			case fsnotify.Create:
   182  				t.startTailing([]string{event.Name})
   183  			default:
   184  				// No-op we only care about Create events
   185  			}
   186  		case <-ticker.C:
   187  			err := t.sync()
   188  			if err != nil {
   189  				level.Error(t.logger).Log("msg", "error running sync function", "error", err)
   190  			}
   191  		case <-t.quit:
   192  			return
   193  		}
   194  	}
   195  }
   196  
   197  func (t *FileTarget) sync() error {
   198  	var matches, matchesExcluded []string
   199  	if fi, err := os.Stat(t.path); err == nil && !fi.IsDir() {
   200  		// if the path points to a file that exists, then it we can skip the Glob search
   201  		matches = []string{t.path}
   202  	} else {
   203  		// Gets current list of files to tail.
   204  		matches, err = doublestar.Glob(t.path)
   205  		if err != nil {
   206  			return errors.Wrap(err, "filetarget.sync.filepath.Glob")
   207  		}
   208  	}
   209  
   210  	if fi, err := os.Stat(t.pathExclude); err == nil && !fi.IsDir() {
   211  		matchesExcluded = []string{t.pathExclude}
   212  	} else {
   213  		matchesExcluded, err = doublestar.Glob(t.pathExclude)
   214  		if err != nil {
   215  			return errors.Wrap(err, "filetarget.sync.filepathexclude.Glob")
   216  		}
   217  	}
   218  
   219  	for i := 0; i < len(matchesExcluded); i++ {
   220  		for j := 0; j < len(matches); j++ {
   221  			if matchesExcluded[i] == matches[j] {
   222  				// exclude this specific match
   223  				matches = append(matches[:j], matches[j+1:]...)
   224  			}
   225  		}
   226  	}
   227  
   228  	if len(matches) == 0 {
   229  		level.Debug(t.logger).Log("msg", "no files matched requested path, nothing will be tailed", "path", t.path, "pathExclude", t.pathExclude)
   230  	}
   231  
   232  	// Gets absolute path for each pattern.
   233  	for i := 0; i < len(matches); i++ {
   234  		if !filepath.IsAbs(matches[i]) {
   235  			path, err := filepath.Abs(matches[i])
   236  			if err != nil {
   237  				return errors.Wrap(err, "filetarget.sync.filepath.Abs")
   238  			}
   239  			matches[i] = path
   240  		}
   241  	}
   242  
   243  	// Record the size of all the files matched by the Glob pattern.
   244  	t.reportSize(matches)
   245  
   246  	// Get the current unique set of dirs to watch.
   247  	dirs := map[string]struct{}{}
   248  	for _, p := range matches {
   249  		dirs[filepath.Dir(p)] = struct{}{}
   250  	}
   251  
   252  	// Add any directories which are not already being watched.
   253  	toStartWatching := missing(t.watches, dirs)
   254  	t.startWatching(toStartWatching)
   255  
   256  	// Remove any directories which no longer need watching.
   257  	toStopWatching := missing(dirs, t.watches)
   258  	t.stopWatching(toStopWatching)
   259  
   260  	// fsnotify.Watcher doesn't allow us to see what is currently being watched so we have to track it ourselves.
   261  	t.watches = dirs
   262  
   263  	// Check if any running tailers have stopped because of errors and remove them from the running list
   264  	// (They will be restarted in startTailing)
   265  	t.pruneStoppedTailers()
   266  
   267  	// Start tailing all of the matched files if not already doing so.
   268  	t.startTailing(matches)
   269  
   270  	// Stop tailing any files which no longer exist
   271  	toStopTailing := toStopTailing(matches, t.tails)
   272  	t.stopTailingAndRemovePosition(toStopTailing)
   273  
   274  	return nil
   275  }
   276  
   277  func (t *FileTarget) startWatching(dirs map[string]struct{}) {
   278  	for dir := range dirs {
   279  		if _, ok := t.watches[dir]; ok {
   280  			continue
   281  		}
   282  		level.Info(t.logger).Log("msg", "watching new directory", "directory", dir)
   283  		t.targetEventHandler <- fileTargetEvent{
   284  			path:      dir,
   285  			eventType: fileTargetEventWatchStart,
   286  		}
   287  	}
   288  }
   289  
   290  func (t *FileTarget) stopWatching(dirs map[string]struct{}) {
   291  	for dir := range dirs {
   292  		if _, ok := t.watches[dir]; !ok {
   293  			continue
   294  		}
   295  		level.Info(t.logger).Log("msg", "removing directory from watcher", "directory", dir)
   296  		t.targetEventHandler <- fileTargetEvent{
   297  			path:      dir,
   298  			eventType: fileTargetEventWatchStop,
   299  		}
   300  	}
   301  }
   302  
   303  func (t *FileTarget) startTailing(ps []string) {
   304  	for _, p := range ps {
   305  		if _, ok := t.tails[p]; ok {
   306  			continue
   307  		}
   308  
   309  		fi, err := os.Stat(p)
   310  		if err != nil {
   311  			level.Error(t.logger).Log("msg", "failed to tail file, stat failed", "error", err, "filename", p)
   312  			t.metrics.totalBytes.DeleteLabelValues(p)
   313  			continue
   314  		}
   315  
   316  		if fi.IsDir() {
   317  			level.Info(t.logger).Log("msg", "failed to tail file", "error", "file is a directory", "filename", p)
   318  			t.metrics.totalBytes.DeleteLabelValues(p)
   319  			continue
   320  		}
   321  
   322  		level.Debug(t.logger).Log("msg", "tailing new file", "filename", p)
   323  		tailer, err := newTailer(t.metrics, t.logger, t.handler, t.positions, p, t.encoding)
   324  		if err != nil {
   325  			level.Error(t.logger).Log("msg", "failed to start tailer", "error", err, "filename", p)
   326  			continue
   327  		}
   328  		t.tails[p] = tailer
   329  	}
   330  }
   331  
   332  // stopTailingAndRemovePosition will stop the tailer and remove the positions entry.
   333  // Call this when a file no longer exists and you want to remove all traces of it.
   334  func (t *FileTarget) stopTailingAndRemovePosition(ps []string) {
   335  	for _, p := range ps {
   336  		if tailer, ok := t.tails[p]; ok {
   337  			tailer.stop()
   338  			t.positions.Remove(tailer.path)
   339  			delete(t.tails, p)
   340  		}
   341  		if h, ok := t.handler.(api.InstrumentedEntryHandler); ok {
   342  			h.UnregisterLatencyMetric(prometheus.Labels{client.LatencyLabel: p})
   343  		}
   344  	}
   345  }
   346  
   347  // pruneStoppedTailers removes any tailers which have stopped running from
   348  // the list of active tailers. This allows them to be restarted if there were errors.
   349  func (t *FileTarget) pruneStoppedTailers() {
   350  	toRemove := make([]string, 0, len(t.tails))
   351  	for k, t := range t.tails {
   352  		if !t.isRunning() {
   353  			toRemove = append(toRemove, k)
   354  		}
   355  	}
   356  	for _, tr := range toRemove {
   357  		delete(t.tails, tr)
   358  	}
   359  }
   360  
   361  func toStopTailing(nt []string, et map[string]*tailer) []string {
   362  	// Make a set of all existing tails
   363  	existingTails := make(map[string]struct{}, len(et))
   364  	for file := range et {
   365  		existingTails[file] = struct{}{}
   366  	}
   367  	// Make a set of what we are about to start tailing
   368  	newTails := make(map[string]struct{}, len(nt))
   369  	for _, p := range nt {
   370  		newTails[p] = struct{}{}
   371  	}
   372  	// Find the tails in our existing which are not in the new, these need to be stopped!
   373  	ts := missing(newTails, existingTails)
   374  	ta := make([]string, len(ts))
   375  	i := 0
   376  	for t := range ts {
   377  		ta[i] = t
   378  		i++
   379  	}
   380  	return ta
   381  }
   382  
   383  func (t *FileTarget) reportSize(ms []string) {
   384  	for _, m := range ms {
   385  		// Ask the tailer to update the size if a tailer exists, this keeps position and size metrics in sync
   386  		if tailer, ok := t.tails[m]; ok {
   387  			err := tailer.markPositionAndSize()
   388  			if err != nil {
   389  				level.Warn(t.logger).Log("msg", "failed to get file size from tailer, ", "file", m, "error", err)
   390  				return
   391  			}
   392  		} else {
   393  			// Must be a new file, just directly read the size of it
   394  			fi, err := os.Stat(m)
   395  			if err != nil {
   396  				// If the file was deleted between when the glob match and here,
   397  				// we just ignore recording a size for it,
   398  				// the tail code will also check if the file exists before creating a tailer.
   399  				return
   400  			}
   401  			t.metrics.totalBytes.WithLabelValues(m).Set(float64(fi.Size()))
   402  		}
   403  
   404  	}
   405  }
   406  
   407  // Returns the elements from set b which are missing from set a
   408  func missing(as map[string]struct{}, bs map[string]struct{}) map[string]struct{} {
   409  	c := map[string]struct{}{}
   410  	for a := range bs {
   411  		if _, ok := as[a]; !ok {
   412  			c[a] = struct{}{}
   413  		}
   414  	}
   415  	return c
   416  }