feat(log): 访问日志携带 ua,挂 Seq 后控制台放宽到 Info+

- WebConnectLog 访问日志新增 ua 字段(User-Agent 按 rune 截断 200 字符),便于按设备归因
- SetSeqWriter 挂 Seq 后业务控制台由 Warn+ 放宽为 Info+,Debug/SQL 仍只进 Seq 与文件
- 新增 Logger.SetConsoleMinLevel,访问日志控制台单独保持 Warn+

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-24 01:35:45 +08:00
parent 853940c795
commit 332c2414bc
6 changed files with 180 additions and 11 deletions
+15 -6
View File
@@ -209,6 +209,8 @@ func TestLevelFilterWriter_WarnOnly(t *testing.T) {
}
}
// TestSetSeqWriter_QuietsConsoleKeepsSeqFull 验证挂 Seq 后控制台放宽到 Info+:
// Debug 不上控制台,Info/Warn 均可见,Seq 侧全量收到(含 Debug)。
func TestSetSeqWriter_QuietsConsoleKeepsSeqFull(t *testing.T) {
var mu sync.Mutex
var gotBody string
@@ -223,13 +225,14 @@ func TestSetSeqWriter_QuietsConsoleKeepsSeqFull(t *testing.T) {
consoleBuf := &captureBuf{}
l.console.inner = consoleBuf
l.SetSeqWriter(srv.URL, "", "test:quiet")
l.Info().Msg("info-to-seq-only")
l.Debug().Msg("debug-to-seq-only")
l.Info().Msg("info-both")
l.Warn().Msg("warn-both")
deadline := time.Now().Add(3 * time.Second)
for time.Now().Before(deadline) {
mu.Lock()
ok := strings.Contains(gotBody, "info-to-seq-only") && strings.Contains(gotBody, "warn-both")
ok := strings.Contains(gotBody, "debug-to-seq-only") && strings.Contains(gotBody, "info-both") && strings.Contains(gotBody, "warn-both")
mu.Unlock()
if ok {
break
@@ -239,22 +242,28 @@ func TestSetSeqWriter_QuietsConsoleKeepsSeqFull(t *testing.T) {
mu.Lock()
body := gotBody
mu.Unlock()
if !strings.Contains(body, "info-to-seq-only") || !strings.Contains(body, "warn-both") {
if !strings.Contains(body, "debug-to-seq-only") || !strings.Contains(body, "info-both") || !strings.Contains(body, "warn-both") {
t.Fatalf("Seq incomplete: %q", body)
}
consoleBuf.mu.Lock()
defer consoleBuf.mu.Unlock()
for _, line := range consoleBuf.lines {
if strings.Contains(line, "info-to-seq-only") {
t.Fatalf("Info leaked to console: %v", consoleBuf.lines)
if strings.Contains(line, "debug-to-seq-only") {
t.Fatalf("Debug leaked to console: %v", consoleBuf.lines)
}
}
foundWarn := false
foundInfo, foundWarn := false, false
for _, line := range consoleBuf.lines {
if strings.Contains(line, "info-both") {
foundInfo = true
}
if strings.Contains(line, "warn-both") {
foundWarn = true
}
}
if !foundInfo {
t.Fatalf("Info missing on console: %v", consoleBuf.lines)
}
if !foundWarn {
t.Fatalf("Warn missing on console: %v", consoleBuf.lines)
}
+87
View File
@@ -0,0 +1,87 @@
package log
import (
"bytes"
"testing"
"github.com/rs/zerolog"
)
// TestSetSeqWriter_ConsoleMinLevelBecomesInfo 验证挂 Seq 后控制台门槛降到 Info(而非 Warn)
func TestSetSeqWriter_ConsoleMinLevelBecomesInfo(t *testing.T) {
l := NewLogger(1, "", 0)
if got := zerolog.Level(l.console.minLevel.Load()); got != zerolog.DebugLevel {
t.Fatalf("初始 console minLevel=%v want Debug", got)
}
// seqUrl 不必可达:SeqWriter 异步 channel 推送,Write 不阻塞
l.SetSeqWriter("http://127.0.0.1:1", "", "test:level")
defer l.CloseSeq()
if got := zerolog.Level(l.console.minLevel.Load()); got != zerolog.InfoLevel {
t.Fatalf("挂 Seq 后 console minLevel=%v want Info", got)
}
}
// TestSetConsoleMinLevel_OverridesToWarn 验证导出方法可将 console 门槛单独收紧到 Warn
// 用于访问日志等高噪声 Logger 在挂 Seq 后仍保持 Warn+。
func TestSetConsoleMinLevel_OverridesToWarn(t *testing.T) {
l := NewLogger(1, "", 0)
l.SetSeqWriter("http://127.0.0.1:1", "", "test:override")
defer l.CloseSeq()
l.SetConsoleMinLevel(zerolog.WarnLevel)
if got := zerolog.Level(l.console.minLevel.Load()); got != zerolog.WarnLevel {
t.Fatalf("SetConsoleMinLevel 后 minLevel=%v want Warn", got)
}
var buf bytes.Buffer
l.console.inner = &buf
if _, err := l.console.WriteLevel(zerolog.InfoLevel, []byte("info-line\n")); err != nil {
t.Fatal(err)
}
if buf.Len() != 0 {
t.Fatalf("Info 应被 Warn 门槛丢弃,实际写入: %q", buf.String())
}
if _, err := l.console.WriteLevel(zerolog.WarnLevel, []byte("warn-line\n")); err != nil {
t.Fatal(err)
}
if !bytes.Contains(buf.Bytes(), []byte("warn-line")) {
t.Fatalf("Warn 应放行,实际: %q", buf.String())
}
}
// TestLevelFilterWriter_WriteLevel_DropAndPass 直接验证 levelFilterWriter.WriteLevel
// 对低于门槛的级别丢弃(但仍返回成功),达到门槛的放行。
func TestLevelFilterWriter_WriteLevel_DropAndPass(t *testing.T) {
var buf bytes.Buffer
w := newLevelFilterWriter(&buf, zerolog.WarnLevel)
n, err := w.WriteLevel(zerolog.DebugLevel, []byte("debug-msg"))
if err != nil || n != len("debug-msg") {
t.Fatalf("Debug 丢弃返回值异常: n=%d err=%v", n, err)
}
if buf.Len() != 0 {
t.Fatalf("Debug 不应写入 inner: %q", buf.String())
}
n, err = w.WriteLevel(zerolog.InfoLevel, []byte("info-msg"))
if err != nil || n != len("info-msg") {
t.Fatalf("Info 丢弃返回值异常: n=%d err=%v", n, err)
}
if buf.Len() != 0 {
t.Fatalf("Info 不应写入 inner: %q", buf.String())
}
if _, err := w.WriteLevel(zerolog.WarnLevel, []byte("warn-msg")); err != nil {
t.Fatal(err)
}
if !bytes.Contains(buf.Bytes(), []byte("warn-msg")) {
t.Fatalf("Warn 应放行: %q", buf.String())
}
if _, err := w.WriteLevel(zerolog.ErrorLevel, []byte("error-msg")); err != nil {
t.Fatal(err)
}
if !bytes.Contains(buf.Bytes(), []byte("error-msg")) {
t.Fatalf("Error 应放行: %q", buf.String())
}
}
+12 -2
View File
@@ -826,8 +826,18 @@ func (l *Logger) SetSeqWriter(seqUrl, apiKey, instance string) {
l.seqsMu.Lock()
l.seqs = append(l.seqs, sw)
l.seqsMu.Unlock()
// 挂上 Seq 后控制台只打 Warn+Info/Debug/SQL/访问日志仍进 Seq 与文件
l.console.setMinLevel(zerolog.WarnLevel)
// 挂上 Seq 后控制台放宽到 Info+Debug/SQL 不上控制台),仍全量进 Seq 与文件
// 访问日志等高噪声 Logger 可再调用 SetConsoleMinLevel 单独收紧。
l.console.setMinLevel(zerolog.InfoLevel)
}
// SetConsoleMinLevel 单独设置本 Logger 控制台出口的最低级别,不影响 Seq/文件等其他出口。
// 用于访问日志等高噪声场景在挂 Seq 后仍保持更高的控制台门槛(如 Warn+)。
func (l *Logger) SetConsoleMinLevel(level zerolog.Level) {
if l == nil {
return
}
l.console.setMinLevel(level)
}
// CloseSeq 冲刷并关闭本 Logger 挂载的全部 SeqWriter(幂等、nil 安全)