212 lines
6.2 KiB
Go
212 lines
6.2 KiB
Go
// FILE: lixenwraith/log/processor_test.go
|
|
package log
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestLoggerHeartbeat(t *testing.T) {
|
|
logger, tmpDir := createTestLogger(t)
|
|
defer logger.Shutdown()
|
|
|
|
cfg := logger.GetConfig()
|
|
cfg.HeartbeatLevel = 3 // All heartbeats
|
|
cfg.HeartbeatIntervalS = 1
|
|
err := logger.ApplyConfig(cfg)
|
|
require.NoError(t, err)
|
|
|
|
// Wait for heartbeats
|
|
time.Sleep(1500 * time.Millisecond)
|
|
logger.Flush(time.Second)
|
|
|
|
content, err := os.ReadFile(filepath.Join(tmpDir, "log.log"))
|
|
require.NoError(t, err)
|
|
|
|
// Check for heartbeat content
|
|
assert.Contains(t, string(content), "PROC")
|
|
assert.Contains(t, string(content), "DISK")
|
|
assert.Contains(t, string(content), "SYS")
|
|
assert.Contains(t, string(content), "uptime_hours")
|
|
assert.Contains(t, string(content), "processed_logs")
|
|
assert.Contains(t, string(content), "num_goroutine")
|
|
}
|
|
|
|
func TestDroppedLogs(t *testing.T) {
|
|
logger := NewLogger()
|
|
|
|
cfg := DefaultConfig()
|
|
cfg.Directory = t.TempDir()
|
|
cfg.BufferSize = 1 // Very small buffer
|
|
cfg.FlushIntervalMs = 10 // Fast processing
|
|
cfg.HeartbeatLevel = 1 // Enable proc heartbeat
|
|
cfg.HeartbeatIntervalS = 1 // Fast heartbeat
|
|
|
|
err := logger.ApplyConfig(cfg)
|
|
require.NoError(t, err)
|
|
|
|
err = logger.Start()
|
|
require.NoError(t, err)
|
|
defer logger.Shutdown()
|
|
|
|
// Flood to guarantee drops
|
|
for i := 0; i < 100; i++ {
|
|
logger.Info("flood", i)
|
|
}
|
|
|
|
// Wait for first heartbeat
|
|
time.Sleep(1500 * time.Millisecond)
|
|
|
|
// Flood again
|
|
for i := 0; i < 50; i++ {
|
|
logger.Info("flood2", i)
|
|
}
|
|
|
|
// Wait for second heartbeat
|
|
time.Sleep(1000 * time.Millisecond)
|
|
logger.Flush(time.Second)
|
|
|
|
// Read log file and verify heartbeats
|
|
content, err := os.ReadFile(filepath.Join(cfg.Directory, "log.log"))
|
|
require.NoError(t, err)
|
|
|
|
lines := strings.Split(string(content), "\n")
|
|
foundTotal := false
|
|
foundInterval := false
|
|
|
|
for _, line := range lines {
|
|
if strings.Contains(line, "PROC") {
|
|
if strings.Contains(line, "total_dropped_logs") {
|
|
foundTotal = true
|
|
}
|
|
if strings.Contains(line, "dropped_since_last") {
|
|
foundInterval = true
|
|
}
|
|
}
|
|
}
|
|
|
|
assert.True(t, foundTotal, "Expected PROC heartbeat with total_dropped_logs")
|
|
assert.True(t, foundInterval, "Expected PROC heartbeat with dropped_since_last")
|
|
}
|
|
|
|
func TestAdaptiveDiskCheck(t *testing.T) {
|
|
logger, _ := createTestLogger(t)
|
|
defer logger.Shutdown()
|
|
|
|
cfg := logger.GetConfig()
|
|
cfg.EnableAdaptiveInterval = true
|
|
cfg.DiskCheckIntervalMs = 100
|
|
cfg.MinCheckIntervalMs = 50
|
|
cfg.MaxCheckIntervalMs = 500
|
|
err := logger.ApplyConfig(cfg)
|
|
require.NoError(t, err)
|
|
|
|
// Generate varying log rates and verify no panic
|
|
for i := 0; i < 10; i++ {
|
|
logger.Info("adaptive test", i)
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
|
|
// Burst
|
|
for i := 0; i < 100; i++ {
|
|
logger.Info("burst", i)
|
|
}
|
|
|
|
logger.Flush(time.Second)
|
|
}
|
|
|
|
func TestDroppedLogRecoveryOnDroppedHeartbeat(t *testing.T) {
|
|
logger := NewLogger()
|
|
|
|
cfg := DefaultConfig()
|
|
cfg.Directory = t.TempDir()
|
|
cfg.BufferSize = 10 // Small buffer
|
|
cfg.HeartbeatLevel = 1 // Enable proc heartbeat
|
|
cfg.HeartbeatIntervalS = 1 // Fast heartbeat
|
|
cfg.Format = "json" // Use JSON for easy parsing
|
|
|
|
err := logger.ApplyConfig(cfg)
|
|
require.NoError(t, err)
|
|
|
|
err = logger.Start()
|
|
require.NoError(t, err)
|
|
defer logger.Shutdown()
|
|
|
|
// 1. Flood the logger to guarantee drops. Let's aim to drop exactly 50 logs.
|
|
const floodCount = 50
|
|
for i := 0; i < int(cfg.BufferSize)+floodCount; i++ {
|
|
logger.Info("flood", i)
|
|
}
|
|
|
|
// Wait for the first heartbeat to be generated. It will carry the count of ~50 drops.
|
|
time.Sleep(1100 * time.Millisecond)
|
|
|
|
// 2. Immediately put the logger into a "disk full" state.
|
|
// This will cause the processor to drop the first heartbeat record.
|
|
diskFullCfg := logger.GetConfig()
|
|
diskFullCfg.MinDiskFreeKB = 9999999999
|
|
err = logger.ApplyConfig(diskFullCfg)
|
|
require.NoError(t, err)
|
|
// Force a disk check to ensure the state is updated to not OK.
|
|
logger.performDiskCheck(true)
|
|
assert.False(t, logger.state.DiskStatusOK.Load(), "Disk status should be not OK")
|
|
|
|
// 3. Now, "fix" the disk so the next heartbeat can be written successfully.
|
|
diskOKCfg := logger.GetConfig()
|
|
diskOKCfg.MinDiskFreeKB = 0
|
|
err = logger.ApplyConfig(diskOKCfg)
|
|
require.NoError(t, err)
|
|
logger.performDiskCheck(true) // Ensure state is updated back to OK.
|
|
assert.True(t, logger.state.DiskStatusOK.Load(), "Disk status should be OK")
|
|
|
|
// 4. Wait for the second heartbeat to be generated and written to the file.
|
|
time.Sleep(1100 * time.Millisecond)
|
|
logger.Flush(time.Second)
|
|
|
|
// 5. Verify the log file content.
|
|
content, err := os.ReadFile(filepath.Join(cfg.Directory, "log.log"))
|
|
require.NoError(t, err)
|
|
|
|
var foundHeartbeat bool
|
|
var intervalDropCount, totalDropCount float64
|
|
lines := strings.Split(string(content), "\n")
|
|
|
|
for _, line := range lines {
|
|
// Find the last valid heartbeat with drop stats.
|
|
if strings.Contains(line, `"level":"PROC"`) && strings.Contains(line, "dropped_since_last") {
|
|
foundHeartbeat = true
|
|
var entry map[string]interface{}
|
|
err := json.Unmarshal([]byte(line), &entry)
|
|
require.NoError(t, err, "Failed to parse heartbeat log line: %s", line)
|
|
|
|
fields := entry["fields"].([]interface{})
|
|
for i := 0; i < len(fields)-1; i += 2 {
|
|
if key, ok := fields[i].(string); ok {
|
|
if key == "dropped_since_last" {
|
|
intervalDropCount, _ = fields[i+1].(float64)
|
|
}
|
|
if key == "total_dropped_logs" {
|
|
totalDropCount, _ = fields[i+1].(float64)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
require.True(t, foundHeartbeat, "Did not find the final heartbeat with drop stats")
|
|
|
|
// ASSERT THE CURRENT BEHAVIOR:
|
|
// The 'dropped_since_last' count from the first heartbeat (~50) was lost when that heartbeat was dropped.
|
|
// The only new drop in the next interval was the heartbeat record itself.
|
|
assert.Equal(t, float64(1), intervalDropCount, "The interval drop count should only reflect the single dropped heartbeat from the previous interval.")
|
|
|
|
// The 'total_dropped_logs' counter should be accurate, reflecting the initial flood (~50) + the one dropped heartbeat.
|
|
assert.True(t, totalDropCount >= float64(floodCount), "Total drop count should be at least the number of flooded logs plus the dropped heartbeat.")
|
|
} |