Collector implementation

This commit is contained in:
2026-03-14 20:07:22 +01:00
parent 4393ae2726
commit 6ca296b2e8
16 changed files with 3052 additions and 0 deletions

View 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
}