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 }