github.com/qubitproducts/logspray@v0.2.14/sources/filesystem/filewatcher.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 filesystem
    17  
    18  import (
    19  	"context"
    20  	"fmt"
    21  	"os"
    22  	"path/filepath"
    23  	"regexp"
    24  
    25  	"github.com/QubitProducts/logspray/sources"
    26  	"github.com/golang/glog"
    27  	"github.com/rjeczalik/notify"
    28  )
    29  
    30  // New creates a new filesystem log source
    31  func New(Path string, NameRegexp *regexp.Regexp, Recur bool, Poll bool) *Watcher {
    32  	return &Watcher{
    33  		Path:       Path,
    34  		Recur:      Recur,
    35  		NameRegexp: NameRegexp,
    36  		Poll:       Poll,
    37  	}
    38  }
    39  
    40  // Watcher watches for files being added and removed from a filesystem
    41  type Watcher struct {
    42  	Path       string
    43  	NameRegexp *regexp.Regexp
    44  	Recur      bool //recur into directories
    45  	Poll       bool
    46  
    47  	ups chan []*sources.Update
    48  }
    49  
    50  func (fs *Watcher) String() string {
    51  	str := fs.Path
    52  	if fs.Recur {
    53  		str += "/..."
    54  	}
    55  	if fs.NameRegexp != nil {
    56  		str += fmt.Sprintf("(%s)", fs.NameRegexp)
    57  	}
    58  	return str
    59  }
    60  
    61  // Next should be called each time you wish to watch for an update.
    62  func (fs *Watcher) Next(ctx context.Context) ([]*sources.Update, error) {
    63  	if fs.ups == nil {
    64  		fs.ups = make(chan []*sources.Update, 1)
    65  		initFiles := []*sources.Update{}
    66  		filepath.Walk(fs.Path, func(path string, info os.FileInfo, err error) error {
    67  			if info == nil {
    68  				return nil
    69  			}
    70  			if info.IsDir() && fs.Path != path && !fs.Recur {
    71  				return filepath.SkipDir
    72  			}
    73  			if info.IsDir() {
    74  				return nil
    75  			}
    76  			if fs.NameRegexp != nil {
    77  				if !fs.NameRegexp.MatchString(info.Name()) {
    78  					return nil
    79  				}
    80  			}
    81  			initFiles = append(initFiles, &sources.Update{Action: sources.Add, Target: path})
    82  			return nil
    83  		})
    84  
    85  		fs.ups <- initFiles
    86  		if err := fs.watch(ctx); err != nil {
    87  			return nil, err
    88  		}
    89  	}
    90  
    91  	select {
    92  	case <-ctx.Done():
    93  		return nil, ctx.Err()
    94  	case up := <-fs.ups:
    95  		return up, nil
    96  	}
    97  }
    98  
    99  const fsevs = notify.Create | notify.Remove | notify.Rename | notify.InDeleteSelf | notify.InMoveSelf
   100  
   101  func (fs *Watcher) watch(ctx context.Context) error {
   102  	// Watcher doesn't wait, but also doesn't inform us if we miss
   103  	// events
   104  	ec := make(chan notify.EventInfo, 100)
   105  
   106  	path := fs.Path
   107  	if fs.Recur {
   108  		path = filepath.Join(path, "...")
   109  	}
   110  
   111  	err := notify.Watch(path, ec, fsevs)
   112  	if err != nil {
   113  		return err
   114  	}
   115  
   116  	go func() {
   117  		defer func() {
   118  			notify.Stop(ec)
   119  		}()
   120  
   121  		for {
   122  			select {
   123  			case <-ctx.Done():
   124  				return
   125  			case e := <-ec:
   126  				go func(e notify.EventInfo) {
   127  					afn, err := filepath.Abs(e.Path())
   128  					if err != nil {
   129  						// probbaly need to log here
   130  						return
   131  					}
   132  
   133  					fn := filepath.Base(afn)
   134  					if fs.NameRegexp != nil {
   135  						if !fs.NameRegexp.MatchString(fn) {
   136  							return
   137  						}
   138  					}
   139  
   140  					if e.Path() == afn {
   141  						labels := map[string]string{
   142  							"filename": afn,
   143  						}
   144  						switch e.Event() {
   145  
   146  						case notify.Create:
   147  							fs.ups <- []*sources.Update{
   148  								{
   149  									Action: sources.Add,
   150  									Target: afn,
   151  									Labels: labels,
   152  								},
   153  							}
   154  						case notify.Remove:
   155  							fs.ups <- []*sources.Update{
   156  								{
   157  									Action: sources.Remove,
   158  									Target: afn,
   159  									Labels: labels,
   160  								},
   161  							}
   162  						case notify.InDeleteSelf:
   163  							fs.ups <- []*sources.Update{
   164  								{
   165  									Action: sources.Remove,
   166  									Target: afn,
   167  									Labels: labels,
   168  								},
   169  							}
   170  						case notify.Rename, notify.InMoveSelf:
   171  							fs.ups <- []*sources.Update{
   172  								{
   173  									Action: sources.Remove,
   174  									Target: afn,
   175  									Labels: labels,
   176  								},
   177  							}
   178  						default:
   179  							glog.Infof("Ignoring $s event on %s", e.Event(), e.Path())
   180  						}
   181  					}
   182  				}(e)
   183  			}
   184  		}
   185  	}()
   186  	return nil
   187  }