package loggerx_test import ( "context" "log" "os" "path/filepath" "testing" "github.com/yuninks/loggerx" ) // 全局 log 默认不被接管:NewLogger 不该静默改变宿主的全局状态 func TestNoGlobalHijackByDefault(t *testing.T) { before := log.Writer() l := loggerx.NewLogger(context.Background(), loggerx.SetDir(t.TempDir())) defer l.Close() if got := log.Writer(); got != before { t.Errorf("默认不该接管全局 log,但现在 log.Writer 变了: %T", got) } _ = l } // TestGlobalLogTakeoverAndRestore 显式 SetAsGlobalLog 才接管;Close 必须还原成最初的 writer func TestGlobalLogTakeoverAndRestore(t *testing.T) { // 全局 log 是进程级状态,先存后恢复,避免污染同包其它测试 original := log.Writer() t.Cleanup(func() { log.SetOutput(original) }) l := loggerx.NewLogger(context.Background(), loggerx.SetDir(t.TempDir()), loggerx.SetAsGlobalLog(), ) if got, ok := log.Writer().(*loggerx.Logger); !ok || got != l { t.Fatalf("SetAsGlobalLog 之后 log.Writer 应是该实例,实际 %T", log.Writer()) } if err := l.Close(); err != nil { t.Fatalf("Close: %v", err) } // Close 会还原成「纪录下来的最初 writer」。 // 若本进程此前已被别的实例接管过(测试场景常见),还原目标不是此刻的 original, // 所以这里只断言「不再是已关闭的实例」,再断言它可写 if got, ok := log.Writer().(*loggerx.Logger); ok && got == l { t.Errorf("Close 之后全局 log 仍指向已关闭的实例,后续 stdlib 日志会被静默丢弃") } log.Println("after-restore-probe") } // TestGlobalLogRestoresOriginalAfterTwoInstances 两个实例先后接管再关闭: // 全局 log 不能停在「已关闭的实例」上,否则后续 stdlib 日志被静默丢弃 func TestGlobalLogRestoresOriginalAfterTwoInstances(t *testing.T) { original := log.Writer() t.Cleanup(func() { log.SetOutput(original) }) l1 := loggerx.NewLogger(context.Background(), loggerx.SetDir(t.TempDir()), loggerx.SetAsGlobalLog()) l2 := loggerx.NewLogger(context.Background(), loggerx.SetDir(t.TempDir()), loggerx.SetAsGlobalLog()) // l2 是当前持有者 if got, ok := log.Writer().(*loggerx.Logger); !ok || got != l2 { t.Fatal("后接管的实例应该是当前持有者") } if err := l2.Close(); err != nil { t.Fatalf("l2.Close: %v", err) } if err := l1.Close(); err != nil { t.Fatalf("l1.Close: %v", err) } // 关键不变式:全局 log 必须是「可用的 writer」,绝不能是已关闭的 Logger if got, ok := log.Writer().(*loggerx.Logger); ok { t.Errorf("两次接管并关闭后全局 log 仍指向 Logger(%p),后续 stdlib 日志会被丢弃", got) } log.Println("after-restore") } // 关闭之后 stdlib log 不能继续写进已关闭的实例 func TestNoHijackAfterClose(t *testing.T) { dir := t.TempDir() l := loggerx.NewLogger(context.Background(), loggerx.SetDir(dir), loggerx.SetAsGlobalLog()) if err := l.Close(); err != nil { t.Fatalf("Close: %v", err) } // 直接对已关闭实例写入必须返回 ErrClosed(供调用方判断) if _, err := l.Write([]byte("x")); err == nil { t.Error("对已关闭实例写入应返回错误") } else if err != loggerx.ErrClosed { t.Logf("对已关闭实例写入返回: %v", err) } // 文件句柄应已全部释放(Windows 下能删掉才算真的关了) files, _ := filepath.Glob(filepath.Join(dir, "*.log")) for _, f := range files { if err := os.Remove(f); err != nil { t.Errorf("Close 后仍无法删除 %s: %v", filepath.Base(f), err) } } }