func getStartLocation(stateFile string, logfile string) *tail.SeekInfo {
	beginning := &tail.SeekInfo{}
	end := &tail.SeekInfo{0, 2}
	fh, err := os.Open(stateFile)
	if err != nil {
		logrus.WithFields(logrus.Fields{
			"starting at": "end", "error": err,
		}).Debug("getStartLocation failed to open the statefile")
		return end
	}
	defer fh.Close()
	content := make([]byte, 1024)
	bytesRead, err := fh.Read(content)
	if err != nil {
		logrus.WithFields(logrus.Fields{
			"starting at": "end", "error": err,
		}).Debug("getStartLocation failed to read the statefile contents")
		return end
	}
	content = content[:bytesRead]
	state := State{}
	if err := json.Unmarshal(content, &state); err != nil {
		logrus.WithFields(logrus.Fields{
			"starting at": "end", "error": err,
		}).Debug("getStartLocation failed to json decode the statefile")
		return end
	}
	logStat := unix.Stat_t{}
	if err := unix.Stat(logfile, &logStat); err != nil {
		logrus.WithFields(logrus.Fields{
			"starting at": "end", "error": err,
		}).Debug("getStartLocation failed to get unix.stat() on the logfile")
		return end
	}
	if state.INode != uint64(logStat.Ino) {
		logrus.WithFields(logrus.Fields{
			"starting at": "beginning", "error": err,
		}).Debug("getStartLocation found a different inode number for the logfile")
		return beginning
	}
	logrus.WithFields(logrus.Fields{
		"starting at": state.Offset,
	}).Debug("getStartLocation seeking to offset in logfile")
	return &tail.SeekInfo{
		Offset: state.Offset,
		Whence: 0,
	}
}
getStartLocation reads the state file and creates an appropriate start
location.  See details at the top of this file on how the loc is chosen.
package tail
import (
	"bufio"
	"context"
	"crypto/sha1"
	"encoding/json"
	"errors"
	"fmt"
	"math/rand"
	"os"
	"path/filepath"
	"strings"
	"time"
	"github.com/hpcloud/tail"
	"github.com/sirupsen/logrus"
	"golang.org/x/sys/unix"
)
type RotateStyle int
const (
	RotateStyleSyslog RotateStyle = iota
	RotateStyleTimestamp
)
type TailOptions struct {
	ReadFrom              string `long:"read_from" description:"Location in the file from which to start reading. Values: beginning, end, last. Last picks up where it left off, if the file has not been rotated, otherwise beginning. When --backfill is set, it will override this option=beginning" default:"last"`
	Stop                  bool   `long:"stop" description:"Stop reading the file after reaching the end rather than continuing to tail. When --backfill is set, it will override this option=true"`
	Poll                  bool   `long:"poll" description:"use poll instead of inotify to tail files"`
	StateFile             string `long:"statefile" description:"File in which to store the last read position. Defaults to a file in /tmp named $logfile.leash.state. If tailing multiple files, default is forced."`
	HashStateFileDirPaths bool   `long:"hash_statefile_paths" description:"Generates a hash of the directory path for each file that is used to uniquely identify each statefile. Prevents re-using the same statefile for tailed files that have the same name."`
}
type Config struct {
	Paths []string
	FilterPaths []string
	Type RotateStyle
	Options TailOptions
}
type State struct {
	INode  uint64  
	Offset int64
}
func GetSampledEntries(ctx context.Context, conf Config, sampleRate uint) ([]chan string, error) {
	unsampledLinesChans, err := GetEntries(ctx, conf)
	if err != nil {
		return nil, err
	}
	if sampleRate == 1 {
		return unsampledLinesChans, nil
	}
	sampledLinesChans := make([]chan string, 0, len(unsampledLinesChans))
	for _, lines := range unsampledLinesChans {
		sampledLines := make(chan string)
		go func(pLines chan string) {
			defer close(sampledLines)
			for line := range pLines {
				if shouldDrop(sampleRate) {
					logrus.WithFields(logrus.Fields{
						"line":       line,
						"samplerate": sampleRate,
					}).Debug("Sampler says skip this line")
				} else {
					sampledLines <- line
				}
			}
		}(lines)
		sampledLinesChans = append(sampledLinesChans, sampledLines)
	}
	return sampledLinesChans, nil
}
func shouldDrop(rate uint) bool {
	return rand.Intn(int(rate)) != 0
}
func GetEntries(ctx context.Context, conf Config) ([]chan string, error) {
	if conf.Type != RotateStyleSyslog {
		return nil, errors.New("Only Syslog style rotation currently supported")
	}
	var filenames []string
	for _, filePath := range conf.Paths {
		if filePath == "-" {
			filenames = append(filenames, filePath)
		} else {
			files, err := filepath.Glob(filePath)
			if err != nil {
				return nil, err
			}
			files = removeStateFiles(files, conf)
			files = removeFilteredPaths(files, conf.FilterPaths)
			filenames = append(filenames, files...)
		}
	}
	if len(filenames) == 0 {
		return nil, errors.New("After removing missing files and state files from the list, there are no files left to tail")
	}
	linesChans := make([]chan string, 0, len(filenames))
	numFiles := len(filenames)
	for _, file := range filenames {
		var lines chan string
		if file == "-" {
			lines = tailStdIn(ctx)
		} else {
			stateFile := getStateFile(conf, file, numFiles)
			tailer, err := getTailer(conf, file, stateFile)
			if err != nil {
				return nil, err
			}
			lines = tailSingleFile(ctx, tailer, file, stateFile)
		}
		linesChans = append(linesChans, lines)
	}
	return linesChans, nil
}
func removeStateFiles(files []string, conf Config) []string {
	newFiles := []string{}
	for _, file := range files {
		if file == conf.Options.StateFile {
			logrus.WithFields(logrus.Fields{
				"file": file,
			}).Debug("skipping tailing file because it is named the same as the statefile flag")
			continue
		}
		if strings.HasSuffix(file, ".leash.state") {
			logrus.WithFields(logrus.Fields{
				"file": file,
			}).Debug("skipping tailing file because the filename ends with .leash.state")
			continue
		}
		newFiles = append(newFiles, file)
	}
	return newFiles
}
func removeFilteredPaths(files []string, filteredPaths []string) []string {
	newFiles := []string{}
	for _, file := range files {
		shouldRemove := false
		for _, filteredPath := range filteredPaths {
			if matched, err := filepath.Match(filteredPath, file); err != nil {
				logrus.WithError(err).WithFields(logrus.Fields{
					"file":   file,
					"filter": filteredPath,
				}).Warn("unable to compare file with filter, will exclude file")
				shouldRemove = true
				continue
			} else if matched {
				logrus.WithFields(logrus.Fields{
					"file":   file,
					"filter": filteredPath,
				}).Info("removing file as it matches filter")
				shouldRemove = true
			}
		}
		if !shouldRemove {
			newFiles = append(newFiles, file)
		}
	}
	return newFiles
}
func tailSingleFile(ctx context.Context, tailer *tail.Tail, file string, stateFile string) chan string {
	lines := make(chan string)
	stateFh, err := os.OpenFile(stateFile, os.O_RDWR|os.O_CREATE, 0644)
	if err != nil {
		logrus.WithFields(logrus.Fields{
			"logfile":   file,
			"statefile": stateFile,
		}).Warn("Failed to open statefile for writing. File location will not be saved.")
	}
	ticker := time.NewTicker(time.Second)
	state := State{}
	go func() {
		for range ticker.C {
			updateStateFile(&state, tailer, file, stateFh)
		}
	}()
	go func() {
	ReadLines:
		for {
			select {
			case line, ok := <-tailer.Lines:
				if !ok {
					break ReadLines
				}
				if line.Err != nil {
					continue
				}
				lines <- line.Text
			case <-ctx.Done():
				break ReadLines
			}
		}
		close(lines)
		ticker.Stop()
		updateStateFile(&state, tailer, file, stateFh)
		stateFh.Close()
	}()
	return lines
}
func tailStdIn(ctx context.Context) chan string {
	lines := make(chan string)
	input := bufio.NewReader(os.Stdin)
	go func() {
		defer close(lines)
		for {
			select {
			case <-ctx.Done():
				return
			default:
			}
			line, partialLine, err := input.ReadLine()
			if err != nil {
				logrus.Debug("stdin is closed")
				return
			}
			var parts []string
			parts = append(parts, string(line))
			for partialLine {
				line, partialLine, _ = input.ReadLine()
				parts = append(parts, string(line))
			}
			lines <- strings.Join(parts, "")
		}
	}()
	return lines
}