110 lines
2.6 KiB
Go
110 lines
2.6 KiB
Go
package loggerx_test
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"log"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/yuninks/loggerx"
|
|
)
|
|
|
|
// F1 验证:异步消费路径里 panic 之后,整个日志系统是否彻底卡死
|
|
func TestPanicInAsyncConsumerWedgesLogger(t *testing.T) {
|
|
dir := t.TempDir()
|
|
|
|
// 一个会 panic 的 driver(模拟用户 writer / gin writer 里出问题)
|
|
var once sync.Once
|
|
boom := writerFunc(func(p []byte) (int, error) {
|
|
once.Do(func() { panic("driver exploded") })
|
|
return len(p), nil
|
|
})
|
|
|
|
l := loggerx.NewLogger(context.Background(),
|
|
loggerx.SetDir(dir),
|
|
loggerx.SetPrintFile(false),
|
|
loggerx.SetExtraDriver(boom),
|
|
loggerx.SetWriteAsync(),
|
|
)
|
|
|
|
// 第一条触发 panic(消费协程挂掉)
|
|
l.Info(context.Background(), "trigger")
|
|
|
|
// 之后继续写,看是否会在队列满之前就卡死
|
|
done := make(chan struct{})
|
|
go func() {
|
|
defer close(done)
|
|
for i := 0; i < 3000; i++ {
|
|
l.Info(context.Background(), "after-panic")
|
|
}
|
|
}()
|
|
|
|
select {
|
|
case <-done:
|
|
t.Log("panic 之后仍能继续写入(未卡死)")
|
|
case <-time.After(5 * time.Second):
|
|
t.Fatal("F1 复现:异步消费协程 panic 后,写入方永久阻塞(队列满后死锁)")
|
|
}
|
|
|
|
// Close 是否会卡死
|
|
closed := make(chan error, 1)
|
|
go func() { closed <- l.Close() }()
|
|
select {
|
|
case err := <-closed:
|
|
t.Logf("Close 返回: %v", err)
|
|
case <-time.After(10 * time.Second):
|
|
t.Fatal("F1 复现:Close 永久阻塞在 wg.Wait()")
|
|
}
|
|
}
|
|
|
|
// F7 验证:经 stdlib log 写入时,异步队列是否保留了 log 包会复用的缓冲
|
|
func TestStdLogBufferReuseInAsync(t *testing.T) {
|
|
dir := t.TempDir()
|
|
l := loggerx.NewLogger(context.Background(), loggerx.SetDir(dir), loggerx.SetWriteAsync())
|
|
defer l.Close()
|
|
|
|
log.SetOutput(l)
|
|
// stdlib log 用 sync.Pool 的缓冲,Write 返回后立即归还并复用
|
|
for i := 0; i < 2000; i++ {
|
|
log.Printf("POOL-%05d-%s", i, string(make([]byte, 200)))
|
|
}
|
|
|
|
if err := l.Close(); err != nil {
|
|
t.Fatalf("Close: %v", err)
|
|
}
|
|
|
|
// 逐条检查:每个 POOL-nnnnn 是否都完整(不再被复用的池缓冲破坏)
|
|
content := readAllLogs(t, dir)
|
|
bad := 0
|
|
for i := 0; i < 2000; i++ {
|
|
if !strings.Contains(content, "POOL-"+pad(i)) {
|
|
bad++
|
|
}
|
|
}
|
|
if bad > 0 {
|
|
t.Errorf("F7 复现:%d / 2000 条日志内容缺失或被复用缓冲破坏", bad)
|
|
}
|
|
}
|
|
|
|
func pad(i int) string {
|
|
s := "00000"
|
|
d := ""
|
|
for i > 0 {
|
|
d = string(rune('0'+i%10)) + d
|
|
i /= 10
|
|
}
|
|
if len(d) >= len(s) {
|
|
return d
|
|
}
|
|
return s[:len(s)-len(d)] + d
|
|
}
|
|
|
|
type writerFunc func([]byte) (int, error)
|
|
|
|
func (f writerFunc) Write(p []byte) (int, error) { return f(p) }
|
|
|
|
var errBoom = errors.New("boom")
|