mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-10-31 13:50:01 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			163 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			163 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2016 Google Inc. All Rights Reserved.
 | |
| //
 | |
| // Licensed under the Apache License, Version 2.0 (the "License");
 | |
| // you may not use this file except in compliance with the License.
 | |
| // You may obtain a copy of the License at
 | |
| //
 | |
| //     http://www.apache.org/licenses/LICENSE-2.0
 | |
| //
 | |
| // Unless required by applicable law or agreed to in writing, software
 | |
| // distributed under the License is distributed on an "AS IS" BASIS,
 | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| // See the License for the specific language governing permissions and
 | |
| // limitations under the License.
 | |
| 
 | |
| // Package tail implements "tail -F" functionality following rotated logs
 | |
| package tail
 | |
| 
 | |
| import (
 | |
| 	"bufio"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"sync"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/golang/glog"
 | |
| 	"golang.org/x/exp/inotify"
 | |
| )
 | |
| 
 | |
| type Tail struct {
 | |
| 	reader     *bufio.Reader
 | |
| 	readerErr  error
 | |
| 	readerLock sync.RWMutex
 | |
| 	filename   string
 | |
| 	file       *os.File
 | |
| 	stop       chan bool
 | |
| 	watcher    *inotify.Watcher
 | |
| }
 | |
| 
 | |
| const (
 | |
| 	defaultRetryInterval = 100 * time.Millisecond
 | |
| 	maxRetryInterval     = 30 * time.Second
 | |
| )
 | |
| 
 | |
| // NewTail starts opens the given file and watches it for deletion/rotation
 | |
| func NewTail(filename string) (*Tail, error) {
 | |
| 	t, err := newTail(filename)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	go t.watchLoop()
 | |
| 	return t, nil
 | |
| }
 | |
| 
 | |
| // newTail creates a Tail object.
 | |
| func newTail(filename string) (*Tail, error) {
 | |
| 	t := &Tail{
 | |
| 		filename: filename,
 | |
| 	}
 | |
| 	var err error
 | |
| 	t.stop = make(chan bool)
 | |
| 	t.watcher, err = inotify.NewWatcher()
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("inotify init failed on %s: %v", t.filename, err)
 | |
| 	}
 | |
| 	// Initialize readerErr as io.EOF, so that the reader can work properly
 | |
| 	// during initialization.
 | |
| 	t.readerErr = io.EOF
 | |
| 	return t, nil
 | |
| }
 | |
| 
 | |
| // Read implements the io.Reader interface for Tail
 | |
| func (t *Tail) Read(p []byte) (int, error) {
 | |
| 	t.readerLock.RLock()
 | |
| 	defer t.readerLock.RUnlock()
 | |
| 	if t.readerErr != nil {
 | |
| 		return 0, t.readerErr
 | |
| 	}
 | |
| 	return t.reader.Read(p)
 | |
| }
 | |
| 
 | |
| var _ io.ReadCloser = &Tail{}
 | |
| 
 | |
| // Close stops watching and closes the file
 | |
| func (t *Tail) Close() error {
 | |
| 	close(t.stop)
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (t *Tail) attemptOpen() error {
 | |
| 	t.readerLock.Lock()
 | |
| 	defer t.readerLock.Unlock()
 | |
| 	t.readerErr = nil
 | |
| 	attempt := 0
 | |
| 	var lastErr error
 | |
| 	for interval := defaultRetryInterval; ; interval *= 2 {
 | |
| 		attempt++
 | |
| 		glog.V(4).Infof("Opening %s (attempt %d)", t.filename, attempt)
 | |
| 		var err error
 | |
| 		t.file, err = os.Open(t.filename)
 | |
| 		if err == nil {
 | |
| 			// TODO: not interested in old events?
 | |
| 			// t.file.Seek(0, os.SEEK_END)
 | |
| 			t.reader = bufio.NewReader(t.file)
 | |
| 			return nil
 | |
| 		}
 | |
| 		lastErr = err
 | |
| 		glog.V(4).Infof("open log file %s error: %v", t.filename, err)
 | |
| 
 | |
| 		if interval >= maxRetryInterval {
 | |
| 			break
 | |
| 		}
 | |
| 		select {
 | |
| 		case <-time.After(interval):
 | |
| 		case <-t.stop:
 | |
| 			t.readerErr = io.EOF
 | |
| 			return fmt.Errorf("watch was cancelled")
 | |
| 		}
 | |
| 	}
 | |
| 	err := fmt.Errorf("can't open log file %s: %v", t.filename, lastErr)
 | |
| 	t.readerErr = err
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| func (t *Tail) watchLoop() {
 | |
| 	for {
 | |
| 		err := t.watchFile()
 | |
| 		if err != nil {
 | |
| 			glog.Errorf("Tail failed on %s: %v", t.filename, err)
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (t *Tail) watchFile() error {
 | |
| 	err := t.attemptOpen()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	defer t.file.Close()
 | |
| 
 | |
| 	watchDir := filepath.Dir(t.filename)
 | |
| 	err = t.watcher.AddWatch(watchDir, inotify.IN_MOVED_FROM|inotify.IN_DELETE)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("Failed to add watch to directory %s: %v", watchDir, err)
 | |
| 	}
 | |
| 	defer t.watcher.RemoveWatch(watchDir)
 | |
| 
 | |
| 	for {
 | |
| 		select {
 | |
| 		case event := <-t.watcher.Event:
 | |
| 			eventPath := filepath.Clean(event.Name) // Directory events have an extra '/'
 | |
| 			if eventPath == t.filename {
 | |
| 				glog.V(4).Infof("Log file %s moved/deleted", t.filename)
 | |
| 				return nil
 | |
| 			}
 | |
| 		case <-t.stop:
 | |
| 			return fmt.Errorf("watch was cancelled")
 | |
| 		}
 | |
| 	}
 | |
| }
 |