github.com/2dev2/tail@v1.4.5/tail.go (about) 1 // Copyright (c) 2015 HPE Software Inc. All rights reserved. 2 // Copyright (c) 2013 ActiveState Software Inc. All rights reserved. 3 4 package tail 5 6 import ( 7 "bufio" 8 "errors" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "log" 13 "os" 14 "strings" 15 "sync" 16 "time" 17 18 "github.com/nxadm/tail/ratelimiter" 19 "github.com/nxadm/tail/util" 20 "github.com/nxadm/tail/watch" 21 "gopkg.in/tomb.v1" 22 ) 23 24 var ( 25 ErrStop = errors.New("tail should now stop") 26 ) 27 28 type Line struct { 29 Text string 30 Num int 31 SeekInfo SeekInfo 32 Time time.Time 33 Err error // Error from tail 34 } 35 36 // NewLine returns a Line with present time. 37 func NewLine(text string, lineNum int) *Line { 38 return &Line{text, lineNum, SeekInfo{}, time.Now(), nil} 39 } 40 41 // SeekInfo represents arguments to `io.Seek` 42 type SeekInfo struct { 43 Offset int64 44 Whence int // io.Seek* 45 } 46 47 type logger interface { 48 Fatal(v ...interface{}) 49 Fatalf(format string, v ...interface{}) 50 Fatalln(v ...interface{}) 51 Panic(v ...interface{}) 52 Panicf(format string, v ...interface{}) 53 Panicln(v ...interface{}) 54 Print(v ...interface{}) 55 Printf(format string, v ...interface{}) 56 Println(v ...interface{}) 57 } 58 59 // Config is used to specify how a file must be tailed. 60 type Config struct { 61 // File-specifc 62 Location *SeekInfo // Seek to this location before tailing 63 ReOpen bool // Reopen recreated files (tail -F) 64 MustExist bool // Fail early if the file does not exist 65 Poll bool // Poll for file changes instead of using inotify 66 Pipe bool // Is a named pipe (mkfifo) 67 RateLimiter *ratelimiter.LeakyBucket 68 69 // Generic IO 70 Follow bool // Continue looking for new lines (tail -f) 71 MaxLineSize int // If non-zero, split longer lines into multiple lines 72 73 // Logger, when nil, is set to tail.DefaultLogger 74 // To disable logging: set field to tail.DiscardingLogger 75 Logger logger 76 } 77 78 type Tail struct { 79 Filename string 80 Lines chan *Line 81 Config 82 83 file *os.File 84 reader *bufio.Reader 85 lineNum int 86 87 watcher watch.FileWatcher 88 changes *watch.FileChanges 89 90 tomb.Tomb // provides: Done, Kill, Dying 91 92 lk sync.Mutex 93 } 94 95 var ( 96 // DefaultLogger is used when Config.Logger == nil 97 DefaultLogger = log.New(os.Stderr, "", log.LstdFlags) 98 // DiscardingLogger can be used to disable logging output 99 DiscardingLogger = log.New(ioutil.Discard, "", 0) 100 ) 101 102 // TailFile begins tailing the file. Output stream is made available 103 // via the `Tail.Lines` channel. To handle errors during tailing, 104 // invoke the `Wait` or `Err` method after finishing reading from the 105 // `Lines` channel. 106 func TailFile(filename string, config Config) (*Tail, error) { 107 if config.ReOpen && !config.Follow { 108 util.Fatal("cannot set ReOpen without Follow.") 109 } 110 111 t := &Tail{ 112 Filename: filename, 113 Lines: make(chan *Line), 114 Config: config, 115 } 116 117 // when Logger was not specified in config, use default logger 118 if t.Logger == nil { 119 t.Logger = DefaultLogger 120 } 121 122 if t.Poll { 123 t.watcher = watch.NewPollingFileWatcher(filename) 124 } else { 125 t.watcher = watch.NewInotifyFileWatcher(filename) 126 } 127 128 if t.MustExist { 129 var err error 130 t.file, err = OpenFile(t.Filename) 131 if err != nil { 132 return nil, err 133 } 134 } 135 136 go t.tailFileSync() 137 138 return t, nil 139 } 140 141 // Tell returns the file's current position, like stdio's ftell(). 142 // But this value is not very accurate. 143 // One line from the chan(tail.Lines) may have been read, 144 // so it may have lost one line. 145 func (tail *Tail) Tell() (offset int64, err error) { 146 if tail.file == nil { 147 return 148 } 149 offset, err = tail.file.Seek(0, io.SeekCurrent) 150 if err != nil { 151 return 152 } 153 154 tail.lk.Lock() 155 defer tail.lk.Unlock() 156 if tail.reader == nil { 157 return 158 } 159 160 offset -= int64(tail.reader.Buffered()) 161 return 162 } 163 164 // Stop stops the tailing activity. 165 func (tail *Tail) Stop() error { 166 tail.Kill(nil) 167 return tail.Wait() 168 } 169 170 // StopAtEOF stops tailing as soon as the end of the file is reached. 171 func (tail *Tail) StopAtEOF() error { 172 tail.Kill(errStopAtEOF) 173 return tail.Wait() 174 } 175 176 var errStopAtEOF = errors.New("tail: stop at eof") 177 178 func (tail *Tail) close() { 179 close(tail.Lines) 180 tail.closeFile() 181 } 182 183 func (tail *Tail) closeFile() { 184 if tail.file != nil { 185 tail.file.Close() 186 tail.file = nil 187 } 188 } 189 190 func (tail *Tail) reopen() error { 191 tail.closeFile() 192 tail.lineNum = 0 193 for { 194 var err error 195 tail.file, err = OpenFile(tail.Filename) 196 if err != nil { 197 if os.IsNotExist(err) { 198 tail.Logger.Printf("Waiting for %s to appear...", tail.Filename) 199 if err := tail.watcher.BlockUntilExists(&tail.Tomb); err != nil { 200 if err == tomb.ErrDying { 201 return err 202 } 203 return fmt.Errorf("Failed to detect creation of %s: %s", tail.Filename, err) 204 } 205 continue 206 } 207 return fmt.Errorf("Unable to open file %s: %s", tail.Filename, err) 208 } 209 break 210 } 211 return nil 212 } 213 214 func (tail *Tail) readLine() (string, error) { 215 tail.lk.Lock() 216 line, err := tail.reader.ReadString('\n') 217 tail.lk.Unlock() 218 if err != nil { 219 // Note ReadString "returns the data read before the error" in 220 // case of an error, including EOF, so we return it as is. The 221 // caller is expected to process it if err is EOF. 222 return line, err 223 } 224 225 line = strings.TrimRight(line, "\n") 226 227 return line, err 228 } 229 230 func (tail *Tail) tailFileSync() { 231 defer tail.Done() 232 defer tail.close() 233 234 if !tail.MustExist { 235 // deferred first open. 236 err := tail.reopen() 237 if err != nil { 238 if err != tomb.ErrDying { 239 tail.Kill(err) 240 } 241 return 242 } 243 } 244 245 // Seek to requested location on first open of the file. 246 if tail.Location != nil { 247 _, err := tail.file.Seek(tail.Location.Offset, tail.Location.Whence) 248 if err != nil { 249 tail.Killf("Seek error on %s: %s", tail.Filename, err) 250 return 251 } 252 } 253 254 tail.openReader() 255 256 // Read line by line. 257 for { 258 // do not seek in named pipes 259 if !tail.Pipe { 260 // grab the position in case we need to back up in the event of a half-line 261 if _, err := tail.Tell(); err != nil { 262 tail.Kill(err) 263 return 264 } 265 } 266 267 line, err := tail.readLine() 268 269 // Process `line` even if err is EOF. 270 if err == nil { 271 cooloff := !tail.sendLine(line) 272 if cooloff { 273 // Wait a second before seeking till the end of 274 // file when rate limit is reached. 275 msg := ("Too much log activity; waiting a second before resuming tailing") 276 offset, _ := tail.Tell() 277 tail.Lines <- &Line{msg, tail.lineNum, SeekInfo{Offset: offset}, time.Now(), errors.New(msg)} 278 select { 279 case <-time.After(time.Second): 280 case <-tail.Dying(): 281 return 282 } 283 if err := tail.seekEnd(); err != nil { 284 tail.Kill(err) 285 return 286 } 287 } 288 } else if err == io.EOF { 289 if !tail.Follow { 290 if line != "" { 291 tail.sendLine(line) 292 } 293 return 294 } 295 296 if tail.Follow && line != "" { 297 tail.sendLine(line) 298 if err := tail.seekEnd(); err != nil { 299 tail.Kill(err) 300 return 301 } 302 } 303 304 // When EOF is reached, wait for more data to become 305 // available. Wait strategy is based on the `tail.watcher` 306 // implementation (inotify or polling). 307 err := tail.waitForChanges() 308 if err != nil { 309 if err != ErrStop { 310 tail.Kill(err) 311 } 312 return 313 } 314 } else { 315 // non-EOF error 316 tail.Killf("Error reading %s: %s", tail.Filename, err) 317 return 318 } 319 320 select { 321 case <-tail.Dying(): 322 if tail.Err() == errStopAtEOF { 323 continue 324 } 325 return 326 default: 327 } 328 } 329 } 330 331 // waitForChanges waits until the file has been appended, deleted, 332 // moved or truncated. When moved or deleted - the file will be 333 // reopened if ReOpen is true. Truncated files are always reopened. 334 func (tail *Tail) waitForChanges() error { 335 if tail.changes == nil { 336 pos, err := tail.file.Seek(0, io.SeekCurrent) 337 if err != nil { 338 return err 339 } 340 tail.changes, err = tail.watcher.ChangeEvents(&tail.Tomb, pos) 341 if err != nil { 342 return err 343 } 344 } 345 346 select { 347 case <-tail.changes.Modified: 348 return nil 349 case <-tail.changes.Deleted: 350 tail.changes = nil 351 if tail.ReOpen { 352 // XXX: we must not log from a library. 353 tail.Logger.Printf("Re-opening moved/deleted file %s ...", tail.Filename) 354 if err := tail.reopen(); err != nil { 355 return err 356 } 357 tail.Logger.Printf("Successfully reopened %s", tail.Filename) 358 tail.openReader() 359 return nil 360 } 361 tail.Logger.Printf("Stopping tail as file no longer exists: %s", tail.Filename) 362 return ErrStop 363 case <-tail.changes.Truncated: 364 // Always reopen truncated files (Follow is true) 365 tail.Logger.Printf("Re-opening truncated file %s ...", tail.Filename) 366 if err := tail.reopen(); err != nil { 367 return err 368 } 369 tail.Logger.Printf("Successfully reopened truncated %s", tail.Filename) 370 tail.openReader() 371 return nil 372 case <-tail.Dying(): 373 return ErrStop 374 } 375 } 376 377 func (tail *Tail) openReader() { 378 tail.lk.Lock() 379 if tail.MaxLineSize > 0 { 380 // add 2 to account for newline characters 381 tail.reader = bufio.NewReaderSize(tail.file, tail.MaxLineSize+2) 382 } else { 383 tail.reader = bufio.NewReader(tail.file) 384 } 385 tail.lk.Unlock() 386 } 387 388 func (tail *Tail) seekEnd() error { 389 return tail.seekTo(SeekInfo{Offset: 0, Whence: io.SeekEnd}) 390 } 391 392 func (tail *Tail) seekTo(pos SeekInfo) error { 393 _, err := tail.file.Seek(pos.Offset, pos.Whence) 394 if err != nil { 395 return fmt.Errorf("Seek error on %s: %s", tail.Filename, err) 396 } 397 // Reset the read buffer whenever the file is re-seek'ed 398 tail.reader.Reset(tail.file) 399 return nil 400 } 401 402 // sendLine sends the line(s) to Lines channel, splitting longer lines 403 // if necessary. Return false if rate limit is reached. 404 func (tail *Tail) sendLine(line string) bool { 405 now := time.Now() 406 lines := []string{line} 407 408 // Split longer lines 409 if tail.MaxLineSize > 0 && len(line) > tail.MaxLineSize { 410 lines = util.PartitionString(line, tail.MaxLineSize) 411 } 412 413 for _, line := range lines { 414 tail.lineNum++ 415 offset, _ := tail.Tell() 416 select { 417 case tail.Lines <- &Line{line, tail.lineNum, SeekInfo{Offset: offset}, now, nil}: 418 case <-tail.Dying(): 419 return true 420 } 421 } 422 423 if tail.Config.RateLimiter != nil { 424 ok := tail.Config.RateLimiter.Pour(uint16(len(lines))) 425 if !ok { 426 tail.Logger.Printf("Leaky bucket full (%v); entering 1s cooloff period.", 427 tail.Filename) 428 return false 429 } 430 } 431 432 return true 433 } 434 435 // Cleanup removes inotify watches added by the tail package. This function is 436 // meant to be invoked from a process's exit handler. Linux kernel may not 437 // automatically remove inotify watches after the process exits. 438 func (tail *Tail) Cleanup() { 439 watch.Cleanup(tail.Filename) 440 }