更新
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
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")
|
||||
Reference in New Issue
Block a user