fix: logs error coding

This commit is contained in:
duorameng
2026-04-29 09:06:05 +08:00
parent 32e04d5ea2
commit a1899298d5
2 changed files with 138 additions and 16 deletions
+34 -16
View File
@@ -9,12 +9,18 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"sync" "sync"
"unicode/utf8"
"github.com/engigu/baihu-panel/internal/constant" "github.com/engigu/baihu-panel/internal/constant"
"github.com/engigu/baihu-panel/internal/logger" "github.com/engigu/baihu-panel/internal/logger"
"github.com/engigu/baihu-panel/internal/utils" "github.com/engigu/baihu-panel/internal/utils"
) )
const (
// maxLogBufferLen 定义了没有换行符时的最大缓冲长度 (4KB)
maxLogBufferLen = 4096
)
var ( var (
// globalTinyLogManager 跟踪所有活跃的 TinyLog 实例 // globalTinyLogManager 跟踪所有活跃的 TinyLog 实例
globalTinyLogManager = &TinyLogManager{ globalTinyLogManager = &TinyLogManager{
@@ -98,12 +104,30 @@ func (l *TinyLog) Write(p []byte) (n int, err error) {
l.remainder = nil l.remainder = nil
} }
// 1. 寻找最后一个换行符 // 1. 寻找最后一个换行符 (\n 或 \r)
lastNewline := bytes.LastIndexByte(payload, '\n') lastLineBreak := bytes.LastIndexAny(payload, "\n\r")
if lastNewline == -1 { var completeBytes []byte
// 没有换行符,且如果长度超过 4KB,强制截断并输出,防止内存无限制增长 var remainder []byte
if len(payload) > 4096 {
lastNewline = len(payload) - 1 if lastLineBreak != -1 {
// 2. 提取出完整的行
completeBytes = payload[:lastLineBreak+1]
remainder = payload[lastLineBreak+1:]
} else {
// 3. 没有换行符,且如果长度超过最大缓冲,强制截断并输出,防止内存无限制增长
if len(payload) > maxLogBufferLen {
// 寻找最后一个完整的 UTF-8 字符边界,避免乱码
lastSafe := maxLogBufferLen
for i := maxLogBufferLen; i > 0 && i > maxLogBufferLen-4; i-- {
if utf8.RuneStart(payload[i-1]) {
if !utf8.FullRune(payload[i-1 : maxLogBufferLen]) {
lastSafe = i - 1
}
break
}
}
completeBytes = payload[:lastSafe]
remainder = payload[lastSafe:]
} else { } else {
// 保留当前所有内容到下一轮 // 保留当前所有内容到下一轮
l.remainder = payload l.remainder = payload
@@ -111,20 +135,14 @@ func (l *TinyLog) Write(p []byte) (n int, err error) {
} }
} }
// 2. 提取出完整的行 // 4. 将剩余部分保存
completeBytes := payload[:lastNewline+1] l.remainder = remainder
// 3. 剥离并保存剩余的部分 // 5. 将完整行转换为 UTF-8 并脱敏
if lastNewline+1 < len(payload) {
l.remainder = make([]byte, len(payload)-(lastNewline+1))
copy(l.remainder, payload[lastNewline+1:])
}
// 4. 将完整行转换为 UTF-8 并脱敏
text := utils.MaskSecrets(utils.ToUTF8(completeBytes), l.masks) text := utils.MaskSecrets(utils.ToUTF8(completeBytes), l.masks)
outData := []byte(text) outData := []byte(text)
// 5. 输出安全部分 // 6. 输出安全部分
_, err = l.writer.Write(outData) _, err = l.writer.Write(outData)
if err != nil { if err != nil {
return 0, err return 0, err
+104
View File
@@ -0,0 +1,104 @@
package tasks
import (
"bytes"
"testing"
)
func TestTinyLog_UTF8Splitting(t *testing.T) {
tl, err := NewTinyLog("test-utf8", nil)
if err != nil {
t.Fatalf("Failed to create TinyLog: %v", err)
}
defer tl.Close()
// "你好" in UTF-8: E4 BD A0, E5 a5 bd
part1 := []byte{0xE4, 0xBD} // Partial "你"
part2 := []byte{0xA0, 0xE5, 0xA5} // Rest of "你", partial "好"
part3 := []byte{0xBD, '\n'} // Rest of "好", newline
_, _ = tl.Write(part1)
if len(tl.remainder) != 2 {
t.Errorf("Expected remainder len 2, got %d", len(tl.remainder))
}
_, _ = tl.Write(part2)
// Currently it should collect both parts but still no newline,
// so remainder should be 5 bytes.
if len(tl.remainder) != 5 {
t.Errorf("Expected remainder len 5, got %d", len(tl.remainder))
}
_, _ = tl.Write(part3)
if len(tl.remainder) != 0 {
t.Errorf("Expected remainder len 0 after newline, got %d", len(tl.remainder))
}
// Read and verify
data, err := tl.ReadLastLines(1)
if err != nil {
t.Fatalf("ReadLastLines failed: %v", err)
}
if !bytes.Contains(data, []byte("你好")) {
t.Errorf("Expected output to contain '你好', got %q", data)
}
}
func TestTinyLog_CarriageReturn(t *testing.T) {
tl, err := NewTinyLog("test-cr", nil)
if err != nil {
t.Fatalf("Failed to create TinyLog: %v", err)
}
defer tl.Close()
input := []byte("progress: 50%\rprogress: 100%\r\n")
_, _ = tl.Write(input)
data, err := tl.ReadLastLines(10)
if err != nil {
t.Fatalf("ReadLastLines failed: %v", err)
}
// Should contain both progress lines (or at least be split correctly)
if !bytes.Contains(data, []byte("progress: 50%")) {
t.Errorf("Expected output to contain 'progress: 50%%', got %q", data)
}
if !bytes.Contains(data, []byte("progress: 100%")) {
t.Errorf("Expected output to contain 'progress: 100%%', got %q", data)
}
}
func TestTinyLog_LongLineCut(t *testing.T) {
tl, err := NewTinyLog("test-long", nil)
if err != nil {
t.Fatalf("Failed to create TinyLog: %v", err)
}
defer tl.Close()
// Create a buffer of maxLogBufferLen-1 bytes with a multi-byte character at the maxLogBufferLen boundary
// We want to ensure it doesn't cut in the middle of a 3-byte char.
longData := make([]byte, maxLogBufferLen-1)
for i := range longData {
longData[i] = 'A'
}
// "你" is E4 BD A0
longData = append(longData, 0xE4, 0xBD, 0xA0) // This starts at index maxLogBufferLen-1.
// Index maxLogBufferLen-1: E4
// Index maxLogBufferLen: BD
// Index maxLogBufferLen+1: A0
// If we cut at maxLogBufferLen, we split E4 and BD.
_, _ = tl.Write(longData)
// Since it's > maxLogBufferLen and no newline, it should trigger the cut.
// Our logic finds the last safe boundary before maxLogBufferLen.
// RuneStart(E4) at maxLogBufferLen-1 is true. FullRune(E4 at maxLogBufferLen-1 in payload[:maxLogBufferLen]) is false.
// So lastSafe should be maxLogBufferLen-1.
// The first maxLogBufferLen-1 bytes (all 'A') should be processed.
// The "你" should be in remainder.
if len(tl.remainder) != 3 {
t.Errorf("Expected remainder len 3 (the char '你'), got %d", len(tl.remainder))
}
}