lolly/internal/middleware/accesslog/accesslog_test.go
xfy 047e033af5 feat(accesslog): add deterministic sampling with sample_rate config
Add logging.access.sample_rate config (0.0-1.0) for deterministic
request sampling. 5xx errors are always logged; 2xx/3xx/4xx follow
the configured rate. Uses atomic.Uint64 counter for lock-free,
zero-allocation sampling decisions.

Includes test updates to verify:
- sample_rate=1.0 logs all requests
- sample_rate=0.0 logs only 5xx
- 5xx are always logged regardless of rate
2026-06-11 14:42:55 +08:00

140 lines
3.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package accesslog 提供访问日志功能的测试。
//
// 该文件测试访问日志模块的各项功能,包括:
// - 访问日志中间件创建
// - 请求处理和响应记录
// - 请求持续时间记录
// - 日志格式化
//
// 作者xfy
package accesslog
import (
"bytes"
"testing"
"time"
"github.com/valyala/fasthttp"
"rua.plus/lolly/internal/config"
)
func TestAccessLog_Name(t *testing.T) {
al := New(&config.LoggingConfig{})
if al.Name() != "accesslog" {
t.Errorf("expected name 'accesslog', got '%s'", al.Name())
}
}
func TestAccessLog_Process(t *testing.T) {
al := New(&config.LoggingConfig{
Access: config.AccessLogConfig{Format: "json"},
})
// 创建一个简单的 handler
handler := func(ctx *fasthttp.RequestCtx) {
ctx.SetStatusCode(200)
ctx.SetBodyString("hello")
}
// 包装 handler
wrapped := al.Process(handler)
// 创建模拟请求上下文
var ctx fasthttp.RequestCtx
ctx.Init(&fasthttp.Request{}, nil, nil)
// 执行
wrapped(&ctx)
// 验证响应未被修改
if ctx.Response.StatusCode() != 200 {
t.Errorf("expected status 200, got %d", ctx.Response.StatusCode())
}
if !bytes.Equal(ctx.Response.Body(), []byte("hello")) {
t.Errorf("expected body 'hello', got '%s'", ctx.Response.Body())
}
// 清理
_ = al.Close()
}
func TestAccessLog_ProcessWithDuration(t *testing.T) {
al := New(&config.LoggingConfig{
Access: config.AccessLogConfig{Format: "json"},
})
// 创建一个有延迟的 handler
handler := func(ctx *fasthttp.RequestCtx) {
time.Sleep(10 * time.Millisecond)
ctx.SetStatusCode(201)
ctx.SetBodyString("created")
}
wrapped := al.Process(handler)
var ctx fasthttp.RequestCtx
ctx.Init(&fasthttp.Request{}, nil, nil)
start := time.Now()
wrapped(&ctx)
elapsed := time.Since(start)
// 验证延迟被记录(至少 10ms
if elapsed < 10*time.Millisecond {
t.Errorf("expected duration >= 10ms, got %v", elapsed)
}
_ = al.Close()
}
func TestAccessLog_SampleRateAlwaysRecordErrors(t *testing.T) {
al := New(&config.LoggingConfig{
Access: config.AccessLogConfig{
Format: "json",
SampleRate: 0.0, // 理论上不采样成功请求,但 5xx 始终记录
},
})
// 5xx 请求应始终记录
for _, status := range []int{500, 502, 503, 504} {
if !al.shouldLog(status) {
t.Errorf("status %d should always be logged regardless of sample rate", status)
}
}
// 2xx/3xx/4xx 请求按采样率0% 不记录)
for _, status := range []int{200, 301, 404} {
if al.shouldLog(status) {
t.Errorf("status %d should not be logged with sample_rate=0", status)
}
}
_ = al.Close()
}
func TestAccessLog_SampleRateDistribution(t *testing.T) {
al := New(&config.LoggingConfig{
Access: config.AccessLogConfig{
Format: "json",
SampleRate: 0.1, // 10% 采样
},
})
// 重置计数器以便测试
al.sampleCounter.Store(0)
logged := 0
for i := 0; i < 1000; i++ {
if al.shouldLog(200) {
logged++
}
}
// 1000 个请求10% 采样,应记录约 100 个(允许 20% 误差)
if logged < 80 || logged > 120 {
t.Errorf("expected ~100 logged requests with 10%% sample rate, got %d", logged)
}
_ = al.Close()
}