// Copyright 2026 Alibaba Group // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package logging import ( "errors" "os" "path/filepath" "strings" "testing" ) func TestSetupCreatesLogFile(t *testing.T) { t.Parallel() dir := t.TempDir() fl := Setup(dir) defer fl.Close() fl.Logger.Info("test message", "key", "value") fl.Close() logPath := filepath.Join(dir, logSubDir, logFileName) data, err := os.ReadFile(logPath) if err != nil { t.Fatalf("failed to read log file: %v", err) } if !strings.Contains(string(data), "test message") { t.Errorf("log file does not contain 'test message': %s", data) } if !strings.Contains(string(data), `"key":"value"`) { t.Errorf("log file does not contain structured key: %s", data) } } func TestSetupInvalidDirReturnsNopLogger(t *testing.T) { t.Parallel() fl := Setup("/nonexistent/path/that/cannot/be/created") defer fl.Close() // Should not panic fl.Logger.Info("should be discarded") } func TestRotatingWriterRotatesOnSize(t *testing.T) { t.Parallel() dir := t.TempDir() logPath := filepath.Join(dir, "test.log") w := newRotatingWriter(logPath, 100, 2) // 100 bytes max if err := w.open(); err != nil { t.Fatalf("open: %v", err) } // Write enough data to trigger rotation payload := strings.Repeat("x", 60) + "\n" w.Write([]byte(payload)) // 61 bytes w.Write([]byte(payload)) // triggers rotate at 122 bytes // Should have created a backup if _, err := os.Stat(logPath + ".1"); err != nil { t.Errorf("backup .1 not created: %v", err) } // Write more to trigger second rotation w.Write([]byte(payload)) w.Write([]byte(payload)) w.close() if _, err := os.Stat(logPath + ".2"); err != nil { t.Errorf("backup .2 not created: %v", err) } // Active log should be small (last write only) info, _ := os.Stat(logPath) if info.Size() > 100 { t.Errorf("active log too large after rotation: %d bytes", info.Size()) } } func TestRotatingWriterMaxBackups(t *testing.T) { t.Parallel() dir := t.TempDir() logPath := filepath.Join(dir, "test.log") w := newRotatingWriter(logPath, 50, 2) if err := w.open(); err != nil { t.Fatalf("open: %v", err) } payload := strings.Repeat("a", 51) + "\n" // Force multiple rotations for i := 0; i < 5; i++ { w.Write([]byte(payload)) } w.close() // .3 should NOT exist (maxBackups=2) if _, err := os.Stat(logPath + ".3"); err == nil { t.Error("backup .3 should not exist with maxBackups=2") } } func TestCloseOnNilLogger(t *testing.T) { t.Parallel() var fl *FileLogger if err := fl.Close(); err != nil { t.Errorf("Close on nil should not error: %v", err) } } func TestCrossPlatformCoverageFileLoggerCloseIsTerminal(t *testing.T) { t.Parallel() dir := t.TempDir() fl := Setup(dir) logPath := filepath.Join(dir, logSubDir, logFileName) fl.Logger.Info("before close") if err := fl.Close(); err != nil { t.Fatalf("close logger: %v", err) } if err := os.Remove(logPath); err != nil { t.Fatalf("remove closed log file: %v", err) } if err := fl.writer.open(); !errors.Is(err, os.ErrClosed) { t.Fatalf("open after close = %v, want os.ErrClosed", err) } fl.writer.mu.Lock() err := fl.writer.reopenLocked() fl.writer.mu.Unlock() if !errors.Is(err, os.ErrClosed) { t.Fatalf("reopen after close = %v, want os.ErrClosed", err) } n, err := fl.Writer().Write([]byte("late write")) if n != 0 || !errors.Is(err, os.ErrClosed) { t.Fatalf("write after close = (%d, %v), want (0, os.ErrClosed)", n, err) } fl.Logger.Info("late structured write") if _, err := os.Stat(logPath); !os.IsNotExist(err) { t.Fatalf("closed logger recreated log file: %v", err) } if err := fl.Close(); err != nil { t.Fatalf("second close: %v", err) } }