mirror of
				https://github.com/moby/moby.git
				synced 2022-11-09 12:21:53 -05:00 
			
		
		
		
	Implement tail for docker logs
Fixes #4330 Docker-DCO-1.1-Signed-off-by: Alexandr Morozov <lk4d4math@gmail.com> (github: LK4D4)
This commit is contained in:
		
							parent
							
								
									cdc62c778f
								
							
						
					
					
						commit
						1dc0caf9c0
					
				
					 8 changed files with 317 additions and 32 deletions
				
			
		
							
								
								
									
										61
									
								
								pkg/tailfile/tailfile.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								pkg/tailfile/tailfile.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,61 @@
 | 
			
		|||
package tailfile
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"os"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const blockSize = 1024
 | 
			
		||||
 | 
			
		||||
var eol = []byte("\n")
 | 
			
		||||
var ErrNonPositiveLinesNumber = errors.New("Lines number must be positive")
 | 
			
		||||
 | 
			
		||||
//TailFile returns last n lines of file f
 | 
			
		||||
func TailFile(f *os.File, n int) ([][]byte, error) {
 | 
			
		||||
	if n <= 0 {
 | 
			
		||||
		return nil, ErrNonPositiveLinesNumber
 | 
			
		||||
	}
 | 
			
		||||
	size, err := f.Seek(0, os.SEEK_END)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	block := -1
 | 
			
		||||
	var data []byte
 | 
			
		||||
	var cnt int
 | 
			
		||||
	for {
 | 
			
		||||
		var b []byte
 | 
			
		||||
		step := int64(block * blockSize)
 | 
			
		||||
		left := size + step // how many bytes to beginning
 | 
			
		||||
		if left < 0 {
 | 
			
		||||
			if _, err := f.Seek(0, os.SEEK_SET); err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			b = make([]byte, blockSize+left)
 | 
			
		||||
			if _, err := f.Read(b); err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			data = append(b, data...)
 | 
			
		||||
			break
 | 
			
		||||
		} else {
 | 
			
		||||
			b = make([]byte, blockSize)
 | 
			
		||||
			if _, err := f.Seek(step, os.SEEK_END); err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			if _, err := f.Read(b); err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			data = append(b, data...)
 | 
			
		||||
		}
 | 
			
		||||
		cnt += bytes.Count(b, eol)
 | 
			
		||||
		if cnt > n {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		block--
 | 
			
		||||
	}
 | 
			
		||||
	lines := bytes.Split(data, eol)
 | 
			
		||||
	if n < len(lines) {
 | 
			
		||||
		return lines[len(lines)-n-1 : len(lines)-1], nil
 | 
			
		||||
	}
 | 
			
		||||
	return lines[:len(lines)-1], nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										148
									
								
								pkg/tailfile/tailfile_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								pkg/tailfile/tailfile_test.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,148 @@
 | 
			
		|||
package tailfile
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestTailFile(t *testing.T) {
 | 
			
		||||
	f, err := ioutil.TempFile("", "tail-test")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	defer f.Close()
 | 
			
		||||
	defer os.RemoveAll(f.Name())
 | 
			
		||||
	testFile := []byte(`first line
 | 
			
		||||
second line
 | 
			
		||||
third line
 | 
			
		||||
fourth line
 | 
			
		||||
fifth line
 | 
			
		||||
next first line
 | 
			
		||||
next second line
 | 
			
		||||
next third line
 | 
			
		||||
next fourth line
 | 
			
		||||
next fifth line
 | 
			
		||||
last first line
 | 
			
		||||
next first line
 | 
			
		||||
next second line
 | 
			
		||||
next third line
 | 
			
		||||
next fourth line
 | 
			
		||||
next fifth line
 | 
			
		||||
next first line
 | 
			
		||||
next second line
 | 
			
		||||
next third line
 | 
			
		||||
next fourth line
 | 
			
		||||
next fifth line
 | 
			
		||||
last second line
 | 
			
		||||
last third line
 | 
			
		||||
last fourth line
 | 
			
		||||
last fifth line
 | 
			
		||||
truncated line`)
 | 
			
		||||
	if _, err := f.Write(testFile); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := f.Seek(0, os.SEEK_SET); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	expected := []string{"last fourth line", "last fifth line"}
 | 
			
		||||
	res, err := TailFile(f, 2)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	for i, l := range res {
 | 
			
		||||
		t.Logf("%s", l)
 | 
			
		||||
		if expected[i] != string(l) {
 | 
			
		||||
			t.Fatalf("Expected line %s, got %s", expected[i], l)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestTailFileManyLines(t *testing.T) {
 | 
			
		||||
	f, err := ioutil.TempFile("", "tail-test")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	defer f.Close()
 | 
			
		||||
	defer os.RemoveAll(f.Name())
 | 
			
		||||
	testFile := []byte(`first line
 | 
			
		||||
second line
 | 
			
		||||
truncated line`)
 | 
			
		||||
	if _, err := f.Write(testFile); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := f.Seek(0, os.SEEK_SET); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	expected := []string{"first line", "second line"}
 | 
			
		||||
	res, err := TailFile(f, 10000)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	for i, l := range res {
 | 
			
		||||
		t.Logf("%s", l)
 | 
			
		||||
		if expected[i] != string(l) {
 | 
			
		||||
			t.Fatalf("Expected line %s, got %s", expected[i], l)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestTailEmptyFile(t *testing.T) {
 | 
			
		||||
	f, err := ioutil.TempFile("", "tail-test")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	defer f.Close()
 | 
			
		||||
	defer os.RemoveAll(f.Name())
 | 
			
		||||
	res, err := TailFile(f, 10000)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if len(res) != 0 {
 | 
			
		||||
		t.Fatal("Must be empty slice from empty file")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestTailNegativeN(t *testing.T) {
 | 
			
		||||
	f, err := ioutil.TempFile("", "tail-test")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	defer f.Close()
 | 
			
		||||
	defer os.RemoveAll(f.Name())
 | 
			
		||||
	testFile := []byte(`first line
 | 
			
		||||
second line
 | 
			
		||||
truncated line`)
 | 
			
		||||
	if _, err := f.Write(testFile); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := f.Seek(0, os.SEEK_SET); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := TailFile(f, -1); err != ErrNonPositiveLinesNumber {
 | 
			
		||||
		t.Fatalf("Expected ErrNonPositiveLinesNumber, got %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := TailFile(f, 0); err != ErrNonPositiveLinesNumber {
 | 
			
		||||
		t.Fatalf("Expected ErrNonPositiveLinesNumber, got %s", err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func BenchmarkTail(b *testing.B) {
 | 
			
		||||
	f, err := ioutil.TempFile("", "tail-test")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		b.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	defer f.Close()
 | 
			
		||||
	defer os.RemoveAll(f.Name())
 | 
			
		||||
	for i := 0; i < 10000; i++ {
 | 
			
		||||
		if _, err := f.Write([]byte("tailfile pretty interesting line\n")); err != nil {
 | 
			
		||||
			b.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	b.ResetTimer()
 | 
			
		||||
	for i := 0; i < b.N; i++ {
 | 
			
		||||
		if _, err := TailFile(f, 1000); err != nil {
 | 
			
		||||
			b.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue