528 lines
14 KiB
Go
528 lines
14 KiB
Go
package logger
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// setupTestLogger creates a temporary directory and a new logger instance for testing
|
|
func setupTestLogger(t *testing.T) (*Logger, string) {
|
|
t.Helper()
|
|
|
|
// Create temporary directory for logs
|
|
tempDir, err := os.MkdirTemp("", "logger_test")
|
|
if err != nil {
|
|
t.Fatalf("Failed to create temp directory: %v", err)
|
|
}
|
|
|
|
// Create a new logger
|
|
logger, err := New(tempDir)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create logger: %v", err)
|
|
}
|
|
|
|
return logger, tempDir
|
|
}
|
|
|
|
// cleanupTestLogger removes the temporary directory used for testing
|
|
func cleanupTestLogger(tempDir string) {
|
|
os.RemoveAll(tempDir)
|
|
}
|
|
|
|
// TestLoggerCreation tests the creation of a new logger instance
|
|
func TestLoggerCreation(t *testing.T) {
|
|
// Create temporary directory for logs
|
|
tempDir, err := os.MkdirTemp("", "logger_test")
|
|
if err != nil {
|
|
t.Fatalf("Failed to create temp directory: %v", err)
|
|
}
|
|
defer os.RemoveAll(tempDir)
|
|
|
|
// Test successful logger creation
|
|
logger, err := New(tempDir)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create logger: %v", err)
|
|
}
|
|
|
|
if logger == nil {
|
|
t.Fatal("Logger should not be nil")
|
|
}
|
|
|
|
if logger.Path != tempDir {
|
|
t.Errorf("Expected logger path to be %q, got %q", tempDir, logger.Path)
|
|
}
|
|
|
|
if logger.LastLogTime != 0 {
|
|
t.Errorf("Expected LastLogTime to be 0, got %d", logger.LastLogTime)
|
|
}
|
|
|
|
// Test logger creation with non-existent directory
|
|
nonExistentDir := filepath.Join(tempDir, "non_existent")
|
|
logger, err = New(nonExistentDir)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create logger with non-existent directory: %v", err)
|
|
}
|
|
|
|
// Verify the directory was created
|
|
if _, err := os.Stat(nonExistentDir); os.IsNotExist(err) {
|
|
t.Errorf("Directory was not created: %s", nonExistentDir)
|
|
}
|
|
}
|
|
|
|
// TestBasicLogging tests basic logging functionality
|
|
func TestBasicLogging(t *testing.T) {
|
|
logger, tempDir := setupTestLogger(t)
|
|
defer cleanupTestLogger(tempDir)
|
|
|
|
// Test standard output logging
|
|
err := logger.Log(LogItemArgs{
|
|
Category: "system",
|
|
Message: "System started successfully",
|
|
LogType: LogTypeStdout,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to log: %v", err)
|
|
}
|
|
|
|
// Verify log file exists
|
|
currentTime := time.Now()
|
|
expectedFileName := fmt.Sprintf("%s.log", formatDayHour(currentTime))
|
|
logFilePath := filepath.Join(tempDir, expectedFileName)
|
|
|
|
if _, err := os.Stat(logFilePath); os.IsNotExist(err) {
|
|
t.Fatalf("Log file was not created: %s", logFilePath)
|
|
}
|
|
|
|
// Read log file content
|
|
content, err := os.ReadFile(logFilePath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read log file: %v", err)
|
|
}
|
|
|
|
// Verify log content
|
|
logContent := string(content)
|
|
|
|
if !strings.Contains(logContent, "system - System started successfully") {
|
|
t.Errorf("Log file doesn't contain expected stdout message")
|
|
}
|
|
}
|
|
|
|
// TestMultiLineLogging tests logging of multi-line messages
|
|
func TestMultiLineLogging(t *testing.T) {
|
|
logger, tempDir := setupTestLogger(t)
|
|
defer cleanupTestLogger(tempDir)
|
|
|
|
// Test multi-line logging
|
|
err := logger.Log(LogItemArgs{
|
|
Category: "system",
|
|
Message: "Failed to connect\nRetrying in 5 seconds...",
|
|
LogType: LogTypeError,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to log multi-line: %v", err)
|
|
}
|
|
|
|
// Verify log file exists
|
|
currentTime := time.Now()
|
|
expectedFileName := fmt.Sprintf("%s.log", formatDayHour(currentTime))
|
|
logFilePath := filepath.Join(tempDir, expectedFileName)
|
|
|
|
// Read log file content
|
|
content, err := os.ReadFile(logFilePath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read log file: %v", err)
|
|
}
|
|
|
|
// Verify log content
|
|
logContent := string(content)
|
|
|
|
if !strings.Contains(logContent, "E system - Failed to connect") {
|
|
t.Errorf("Log file doesn't contain expected error message")
|
|
}
|
|
|
|
if !strings.Contains(logContent, "E Retrying in 5 seconds...") {
|
|
t.Errorf("Log file doesn't contain expected multi-line formatting")
|
|
}
|
|
}
|
|
|
|
// TestTimestampFormatting tests that timestamps are properly formatted in log files
|
|
func TestTimestampFormatting(t *testing.T) {
|
|
logger, tempDir := setupTestLogger(t)
|
|
defer cleanupTestLogger(tempDir)
|
|
|
|
// Log a message
|
|
err := logger.Log(LogItemArgs{
|
|
Category: "time",
|
|
Message: "Testing timestamp",
|
|
LogType: LogTypeStdout,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to log: %v", err)
|
|
}
|
|
|
|
// Wait a second and log another message to ensure a new timestamp is written
|
|
time.Sleep(1 * time.Second)
|
|
|
|
err = logger.Log(LogItemArgs{
|
|
Category: "time",
|
|
Message: "Another timestamp",
|
|
LogType: LogTypeStdout,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to log second message: %v", err)
|
|
}
|
|
|
|
// Verify log file exists
|
|
currentTime := time.Now()
|
|
expectedFileName := fmt.Sprintf("%s.log", formatDayHour(currentTime))
|
|
logFilePath := filepath.Join(tempDir, expectedFileName)
|
|
|
|
// Read log file content
|
|
content, err := os.ReadFile(logFilePath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read log file: %v", err)
|
|
}
|
|
|
|
// Verify log content contains timestamps
|
|
logContent := string(content)
|
|
|
|
// Check for timestamp format (HH:MM:SS)
|
|
timestampPattern := time.Now().Format("15:04:")
|
|
if !strings.Contains(logContent, timestampPattern) {
|
|
t.Errorf("Log file doesn't contain expected timestamp format. Content: %s", logContent)
|
|
}
|
|
}
|
|
|
|
// TestCustomTimestamp tests logging with a custom timestamp
|
|
func TestCustomTimestamp(t *testing.T) {
|
|
logger, tempDir := setupTestLogger(t)
|
|
defer cleanupTestLogger(tempDir)
|
|
|
|
// Create a custom timestamp
|
|
customTime := time.Date(2023, 1, 1, 12, 0, 0, 0, time.Local)
|
|
|
|
// Log with custom timestamp
|
|
err := logger.Log(LogItemArgs{
|
|
Timestamp: &customTime,
|
|
Category: "custom",
|
|
Message: "Custom timestamp test",
|
|
LogType: LogTypeStdout,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to log with custom timestamp: %v", err)
|
|
}
|
|
|
|
// Verify log file exists with custom date
|
|
expectedFileName := fmt.Sprintf("%s.log", formatDayHour(customTime))
|
|
logFilePath := filepath.Join(tempDir, expectedFileName)
|
|
|
|
if _, err := os.Stat(logFilePath); os.IsNotExist(err) {
|
|
t.Fatalf("Log file with custom timestamp was not created: %s", logFilePath)
|
|
}
|
|
|
|
// Read log file content
|
|
content, err := os.ReadFile(logFilePath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read log file: %v", err)
|
|
}
|
|
|
|
// Verify log content
|
|
logContent := string(content)
|
|
|
|
expectedTimestamp := customTime.Format("15:04:05")
|
|
if !strings.Contains(logContent, expectedTimestamp) {
|
|
t.Errorf("Log file doesn't contain expected custom timestamp. Expected: %s, Content: %s",
|
|
expectedTimestamp, logContent)
|
|
}
|
|
}
|
|
|
|
// TestCategoryValidation tests category name validation
|
|
func TestCategoryValidation(t *testing.T) {
|
|
logger, tempDir := setupTestLogger(t)
|
|
defer cleanupTestLogger(tempDir)
|
|
|
|
// Test with valid category
|
|
err := logger.Log(LogItemArgs{
|
|
Category: "valid-cat",
|
|
Message: "Valid category test",
|
|
LogType: LogTypeStdout,
|
|
})
|
|
if err != nil {
|
|
t.Errorf("Failed to log with valid category: %v", err)
|
|
}
|
|
|
|
// Test with too long category
|
|
err = logger.Log(LogItemArgs{
|
|
Category: "category-too-long-for-logger",
|
|
Message: "Should fail",
|
|
LogType: LogTypeStdout,
|
|
})
|
|
if err == nil {
|
|
t.Error("Expected error for category longer than 10 chars, but got none")
|
|
}
|
|
|
|
// Test with special characters in category
|
|
err = logger.Log(LogItemArgs{
|
|
Category: "spec!@l",
|
|
Message: "Special chars test",
|
|
LogType: LogTypeStdout,
|
|
})
|
|
if err != nil {
|
|
t.Errorf("Failed to log with special characters in category: %v", err)
|
|
}
|
|
|
|
// Verify log file exists
|
|
currentTime := time.Now()
|
|
expectedFileName := fmt.Sprintf("%s.log", formatDayHour(currentTime))
|
|
logFilePath := filepath.Join(tempDir, expectedFileName)
|
|
|
|
// Read log file content
|
|
content, err := os.ReadFile(logFilePath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read log file: %v", err)
|
|
}
|
|
|
|
// Verify special characters were properly handled
|
|
logContent := string(content)
|
|
|
|
if !strings.Contains(logContent, "spec__l - Special chars test") {
|
|
t.Errorf("Log file doesn't contain properly formatted special characters category")
|
|
}
|
|
}
|
|
|
|
// TestSearchFunctionality tests the search functionality
|
|
func TestSearchFunctionality(t *testing.T) {
|
|
logger, tempDir := setupTestLogger(t)
|
|
defer cleanupTestLogger(tempDir)
|
|
|
|
// Log some test messages
|
|
err := logger.Log(LogItemArgs{
|
|
Category: "system",
|
|
Message: "System started successfully",
|
|
LogType: LogTypeStdout,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to log: %v", err)
|
|
}
|
|
|
|
err = logger.Log(LogItemArgs{
|
|
Category: "system",
|
|
Message: "Failed to connect\nRetrying in 5 seconds...",
|
|
LogType: LogTypeError,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to log multi-line: %v", err)
|
|
}
|
|
|
|
err = logger.Log(LogItemArgs{
|
|
Category: "network",
|
|
Message: "Network connection established",
|
|
LogType: LogTypeStdout,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to log: %v", err)
|
|
}
|
|
|
|
// Ensure logs have been written
|
|
time.Sleep(1 * time.Second)
|
|
|
|
// Test search by category
|
|
results, err := logger.Search(SearchArgs{
|
|
Category: "system",
|
|
MaxItems: 100,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to search logs by category: %v", err)
|
|
}
|
|
|
|
if len(results) != 2 {
|
|
t.Errorf("Expected 2 system logs, got %d", len(results))
|
|
}
|
|
|
|
// Test search by log type
|
|
results, err = logger.Search(SearchArgs{
|
|
LogType: LogTypeError,
|
|
MaxItems: 100,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to search logs by type: %v", err)
|
|
}
|
|
|
|
if len(results) != 1 {
|
|
t.Errorf("Expected 1 error log, got %d", len(results))
|
|
}
|
|
|
|
if len(results) > 0 && !strings.Contains(results[0].Message, "Failed to connect") {
|
|
t.Errorf("Search result doesn't contain expected message")
|
|
}
|
|
|
|
// Test search by message content
|
|
results, err = logger.Search(SearchArgs{
|
|
Message: "connect",
|
|
MaxItems: 100,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to search logs by message content: %v", err)
|
|
}
|
|
|
|
// We expect either the original error log entry containing 'connect' or both that entry
|
|
// and potentially the continuation line, depending on how the search parses multi-line entries
|
|
if len(results) < 1 {
|
|
t.Errorf("Expected at least 1 log containing 'connect', got %d", len(results))
|
|
}
|
|
|
|
// Verify that the first result contains the expected content
|
|
if len(results) > 0 && !strings.Contains(results[0].Message, "Failed to connect") {
|
|
t.Errorf("Expected message to contain 'Failed to connect', got: %s", results[0].Message)
|
|
}
|
|
|
|
// Test search with multiple criteria
|
|
results, err = logger.Search(SearchArgs{
|
|
Category: "network",
|
|
Message: "established",
|
|
LogType: LogTypeStdout,
|
|
MaxItems: 100,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to search logs with multiple criteria: %v", err)
|
|
}
|
|
|
|
if len(results) != 1 {
|
|
t.Errorf("Expected 1 log matching multiple criteria, got %d", len(results))
|
|
}
|
|
|
|
// Test max items limit
|
|
results, err = logger.Search(SearchArgs{
|
|
MaxItems: 1,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to search logs with max items limit: %v", err)
|
|
}
|
|
|
|
if len(results) > 1 {
|
|
t.Errorf("Expected at most 1 log due to MaxItems, got %d", len(results))
|
|
}
|
|
}
|
|
|
|
// TestSearchTimeRange tests searching logs within a specific time range
|
|
func TestSearchTimeRange(t *testing.T) {
|
|
logger, tempDir := setupTestLogger(t)
|
|
defer cleanupTestLogger(tempDir)
|
|
|
|
// Create timestamps for testing
|
|
now := time.Now()
|
|
pastTime := now.Add(-1 * time.Hour)
|
|
futureTime := now.Add(1 * time.Hour)
|
|
|
|
// Log with custom timestamps
|
|
err := logger.Log(LogItemArgs{
|
|
Timestamp: &pastTime,
|
|
Category: "past",
|
|
Message: "Past message",
|
|
LogType: LogTypeStdout,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to log past message: %v", err)
|
|
}
|
|
|
|
err = logger.Log(LogItemArgs{
|
|
Category: "present",
|
|
Message: "Present message",
|
|
LogType: LogTypeStdout,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to log present message: %v", err)
|
|
}
|
|
|
|
// Search with time range from past to now
|
|
from := pastTime.Add(-1 * time.Minute) // Just before past time
|
|
to := now.Add(1 * time.Minute) // Just after now
|
|
|
|
results, err := logger.Search(SearchArgs{
|
|
TimestampFrom: &from,
|
|
TimestampTo: &to,
|
|
MaxItems: 100,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to search logs with time range: %v", err)
|
|
}
|
|
|
|
if len(results) != 2 {
|
|
t.Errorf("Expected 2 logs in time range, got %d", len(results))
|
|
}
|
|
|
|
// Search with future time range
|
|
// When searching with a future start time, we need to ensure the end time is also in the future
|
|
farFutureTime := futureTime.Add(1 * time.Hour) // 2 hours in the future
|
|
results, err = logger.Search(SearchArgs{
|
|
TimestampFrom: &futureTime,
|
|
TimestampTo: &farFutureTime,
|
|
MaxItems: 100,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to search logs with future time range: %v", err)
|
|
}
|
|
|
|
if len(results) != 0 {
|
|
t.Errorf("Expected 0 logs in future time range, got %d", len(results))
|
|
}
|
|
|
|
// Test invalid time range (from after to)
|
|
results, err = logger.Search(SearchArgs{
|
|
TimestampFrom: &futureTime,
|
|
TimestampTo: &pastTime,
|
|
MaxItems: 100,
|
|
})
|
|
if err == nil {
|
|
t.Error("Expected error for invalid time range, but got none")
|
|
}
|
|
}
|
|
|
|
// TestHelperFunctions tests the helper functions in the logger package
|
|
func TestHelperFunctions(t *testing.T) {
|
|
// Test formatName function
|
|
testCases := []struct {
|
|
input string
|
|
expected string
|
|
}{
|
|
{"normal", "normal"},
|
|
{"with-hyphen", "with_hyphen"},
|
|
{"special!@#", "special___"},
|
|
{"mixed-123!@#", "mixed_123___"},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
result := formatName(tc.input)
|
|
if result != tc.expected {
|
|
t.Errorf("formatName(%q) = %q, expected %q", tc.input, result, tc.expected)
|
|
}
|
|
}
|
|
|
|
// Test expandString function
|
|
if expandString("test", 8, ' ') != "test " {
|
|
t.Errorf("expandString failed to pad correctly")
|
|
}
|
|
|
|
if expandString("toolong", 4, ' ') != "toolong" {
|
|
t.Errorf("expandString failed to handle strings longer than length")
|
|
}
|
|
|
|
// Test dedent function
|
|
indentedText := "First line\n Second line\n Third line"
|
|
expected := "First line\nSecond line\n Third line"
|
|
if dedent(indentedText) != expected {
|
|
t.Errorf("dedent failed to remove common indentation")
|
|
}
|
|
|
|
// Test formatDayHour function
|
|
testTime := time.Date(2023, 1, 15, 14, 30, 0, 0, time.Local)
|
|
expected = "2023-01-15-14"
|
|
if formatDayHour(testTime) != expected {
|
|
t.Errorf("formatDayHour(%v) = %q, expected %q", testTime, formatDayHour(testTime), expected)
|
|
}
|
|
}
|