Collector implementation
This commit is contained in:
178
cmd/collector/tailer_test.go
Normal file
178
cmd/collector/tailer_test.go
Normal file
@@ -0,0 +1,178 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func writeLine(t *testing.T, f *os.File, website string) {
|
||||
t.Helper()
|
||||
_, err := fmt.Fprintf(f, "%s\t1.2.3.4\t0\tGET\t/path\t200\t0\t0.001\n", website)
|
||||
if err != nil {
|
||||
t.Fatalf("writeLine: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiTailerReadsLines(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "access.log")
|
||||
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
ch := make(chan LogRecord, 100)
|
||||
mt := NewMultiTailer([]string{path}, 24, 48, ch)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
go mt.Run(ctx)
|
||||
|
||||
// Give the tailer time to open and seek to EOF.
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
writeLine(t, f, "www.example.com")
|
||||
writeLine(t, f, "api.example.com")
|
||||
|
||||
received := collectN(t, ch, 2, 2*time.Second)
|
||||
websites := map[string]bool{}
|
||||
for _, r := range received {
|
||||
websites[r.Website] = true
|
||||
}
|
||||
if !websites["www.example.com"] || !websites["api.example.com"] {
|
||||
t.Errorf("unexpected records: %v", received)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiTailerMultipleFiles(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
const numFiles = 5
|
||||
files := make([]*os.File, numFiles)
|
||||
paths := make([]string, numFiles)
|
||||
for i := range files {
|
||||
p := filepath.Join(dir, fmt.Sprintf("access%d.log", i))
|
||||
paths[i] = p
|
||||
f, err := os.Create(p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
files[i] = f
|
||||
}
|
||||
|
||||
ch := make(chan LogRecord, 200)
|
||||
mt := NewMultiTailer(paths, 24, 48, ch)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
go mt.Run(ctx)
|
||||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
// Write one line per file
|
||||
for i, f := range files {
|
||||
writeLine(t, f, fmt.Sprintf("site%d.com", i))
|
||||
}
|
||||
|
||||
received := collectN(t, ch, numFiles, 2*time.Second)
|
||||
if len(received) != numFiles {
|
||||
t.Errorf("got %d records, want %d", len(received), numFiles)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiTailerLogRotation(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "access.log")
|
||||
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ch := make(chan LogRecord, 100)
|
||||
mt := NewMultiTailer([]string{path}, 24, 48, ch)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
go mt.Run(ctx)
|
||||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
// Write a line to the original file
|
||||
writeLine(t, f, "before.rotation.com")
|
||||
collectN(t, ch, 1, 2*time.Second)
|
||||
|
||||
// Simulate logrotate: rename the old file, create a new one
|
||||
rotated := filepath.Join(dir, "access.log.1")
|
||||
f.Close()
|
||||
if err := os.Rename(path, rotated); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Give the tailer a moment to detect the rename and start retrying
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
// Create the new log file (as nginx would after logrotate)
|
||||
newF, err := os.Create(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer newF.Close()
|
||||
|
||||
// Allow retry goroutine to pick it up
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
|
||||
writeLine(t, newF, "after.rotation.com")
|
||||
received := collectN(t, ch, 1, 3*time.Second)
|
||||
if len(received) == 0 || received[0].Website != "after.rotation.com" {
|
||||
t.Errorf("expected after.rotation.com, got %v", received)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExpandGlobs(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
for _, name := range []string{"a.log", "b.log", "other.txt"} {
|
||||
f, _ := os.Create(filepath.Join(dir, name))
|
||||
f.Close()
|
||||
}
|
||||
|
||||
pattern := filepath.Join(dir, "*.log")
|
||||
paths := expandGlobs([]string{pattern})
|
||||
if len(paths) != 2 {
|
||||
t.Errorf("glob expanded to %d paths, want 2: %v", len(paths), paths)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExpandGlobsDeduplication(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
p := filepath.Join(dir, "access.log")
|
||||
f, _ := os.Create(p)
|
||||
f.Close()
|
||||
|
||||
// Same file listed twice via explicit path and glob
|
||||
paths := expandGlobs([]string{p, filepath.Join(dir, "*.log")})
|
||||
if len(paths) != 1 {
|
||||
t.Errorf("expected 1 deduplicated path, got %d: %v", len(paths), paths)
|
||||
}
|
||||
}
|
||||
|
||||
// collectN reads exactly n records from ch within timeout, or returns what it got.
|
||||
func collectN(t *testing.T, ch <-chan LogRecord, n int, timeout time.Duration) []LogRecord {
|
||||
t.Helper()
|
||||
var records []LogRecord
|
||||
deadline := time.After(timeout)
|
||||
for len(records) < n {
|
||||
select {
|
||||
case r := <-ch:
|
||||
records = append(records, r)
|
||||
case <-deadline:
|
||||
t.Logf("collectN: timeout waiting for record %d/%d", len(records)+1, n)
|
||||
return records
|
||||
}
|
||||
}
|
||||
return records
|
||||
}
|
||||
Reference in New Issue
Block a user