package logging import ( "encoding/json" "os" "path/filepath" "strings" "testing" "time" ) func TestNew_TextFormat(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "text.log") file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600) //nolint:gosec // path is from t.TempDir() if err != nil { t.Fatalf("unexpected error opening file: %v", err) } defer func() { _ = file.Close() }() logger := New(InfoLevel, file) logger.Info("hello world") data, err := os.ReadFile(path) //nolint:gosec // path is from t.TempDir() if err != nil { t.Fatalf("failed to read log file: %v", err) } out := string(data) if !strings.Contains(out, "hello world") { t.Fatalf("expected output to contain message, got: %q", out) } if !strings.Contains(out, "level=INFO") { t.Fatalf("expected text output to contain level=INFO, got: %q", out) } if json.Valid(data) { t.Fatalf("expected non-JSON text output, got JSON: %q", out) } } func TestNewFromConfig_JSONOutput(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "json.log") logger, err := NewFromConfig("info", path) if err != nil { t.Fatalf("unexpected error: %v", err) } logger.Info("structured message") data, err := os.ReadFile(path) //nolint:gosec // path is from t.TempDir() if err != nil { t.Fatalf("failed to read log file: %v", err) } var payload map[string]interface{} if err := json.Unmarshal(data, &payload); err != nil { t.Fatalf("expected valid JSON output, got error %v; output: %q", err, string(data)) } if payload["msg"] != "structured message" { t.Fatalf("expected msg field to equal message, got: %v", payload["msg"]) } if payload["level"] != "INFO" { t.Fatalf("expected level field INFO, got: %v", payload["level"]) } timeVal, ok := payload["time"].(string) if !ok { t.Fatalf("expected time field to be a string, got: %v", payload["time"]) } if _, err := time.Parse(time.RFC3339, timeVal); err != nil { t.Fatalf("expected time field to be RFC3339, got %q: %v", timeVal, err) } } func TestLevelFiltering_DebugSuppressedAtInfo(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "level.log") file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600) //nolint:gosec // path is from t.TempDir() if err != nil { t.Fatalf("unexpected error opening file: %v", err) } defer func() { _ = file.Close() }() logger := New(InfoLevel, file) logger.Debug("should not appear") logger.Info("should appear") data, err := os.ReadFile(path) //nolint:gosec // path is from t.TempDir() if err != nil { t.Fatalf("failed to read log file: %v", err) } out := string(data) if strings.Contains(out, "should not appear") { t.Fatalf("expected debug message to be suppressed, got: %q", out) } if !strings.Contains(out, "should appear") { t.Fatalf("expected info message to be present, got: %q", out) } } func TestNewFromOptions_FileOutput(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "out.log") logger, err := NewFromOptions("info", "json", path) if err != nil { t.Fatalf("unexpected error: %v", err) } logger.Info("file message") data, err := os.ReadFile(path) //nolint:gosec // path is from t.TempDir() if err != nil { t.Fatalf("failed to read log file: %v", err) } var payload map[string]interface{} if err := json.Unmarshal(data, &payload); err != nil { t.Fatalf("expected valid JSON in file, got error %v; contents: %q", err, string(data)) } if payload["msg"] != "file message" { t.Fatalf("expected msg field, got: %v", payload["msg"]) } } func TestNewFromOptions_FormatSelection(t *testing.T) { dir := t.TempDir() jsonPath := filepath.Join(dir, "json.log") jsonLogger, err := NewFromOptions("info", "json", jsonPath) if err != nil { t.Fatalf("unexpected error: %v", err) } jsonLogger.Info("json message") jsonData, err := os.ReadFile(jsonPath) //nolint:gosec // path is from t.TempDir() if err != nil { t.Fatalf("failed to read json log file: %v", err) } if !json.Valid(jsonData) { t.Fatalf("expected valid JSON, got: %q", string(jsonData)) } textPath := filepath.Join(dir, "text.log") textLogger, err := NewFromOptions("info", "text", textPath) if err != nil { t.Fatalf("unexpected error: %v", err) } textLogger.Info("text message") textData, err := os.ReadFile(textPath) //nolint:gosec // path is from t.TempDir() if err != nil { t.Fatalf("failed to read text log file: %v", err) } if json.Valid(textData) { t.Fatalf("expected non-JSON text output, got: %q", string(textData)) } if !strings.Contains(string(textData), `msg="text message"`) { t.Fatalf("expected text output to contain msg attribute, got: %q", string(textData)) } // Default format ("" -> json) via NewFromOptions. defaultPath := filepath.Join(dir, "default.log") defaultLogger, err := NewFromOptions("info", "", defaultPath) if err != nil { t.Fatalf("unexpected error: %v", err) } defaultLogger.Info("default message") defaultData, err := os.ReadFile(defaultPath) //nolint:gosec // path is from t.TempDir() if err != nil { t.Fatalf("failed to read default log file: %v", err) } if !json.Valid(defaultData) { t.Fatalf("expected empty format to default to JSON, got: %q", string(defaultData)) } // NewFromConfig should also default to JSON. configPath := filepath.Join(dir, "config.log") configLogger, err := NewFromConfig("info", configPath) if err != nil { t.Fatalf("unexpected error: %v", err) } configLogger.Info("config message") configData, err := os.ReadFile(configPath) //nolint:gosec // path is from t.TempDir() if err != nil { t.Fatalf("failed to read config log file: %v", err) } if !json.Valid(configData) { t.Fatalf("expected NewFromConfig to default to JSON format, got: %q", string(configData)) } } func TestSlogAccessor(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "slog.log") file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600) //nolint:gosec // path is from t.TempDir() if err != nil { t.Fatalf("unexpected error opening file: %v", err) } defer func() { _ = file.Close() }() logger := New(InfoLevel, file) if logger.Slog() == nil { t.Fatal("expected Slog() to return a non-nil *slog.Logger") } logger.Slog().Info("via slog accessor") data, err := os.ReadFile(path) //nolint:gosec // path is from t.TempDir() if err != nil { t.Fatalf("failed to read log file: %v", err) } if !strings.Contains(string(data), "via slog accessor") { t.Fatalf("expected message logged via Slog() accessor, got: %q", string(data)) } }