feat(middleware,server): 实现访问日志中间件
- 新增 accesslog 中间件,记录请求方法、路径、状态码、响应大小和处理时间 - 集成到 Server 的 single 和 vhost 模式 - 支持 graceful shutdown 时关闭日志文件 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
9d24263918
commit
6f3ecb0a9f
42
internal/middleware/accesslog/accesslog.go
Normal file
42
internal/middleware/accesslog/accesslog.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
// Package accesslog 提供访问日志中间件,记录每个请求的详细信息。
|
||||||
|
package accesslog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/valyala/fasthttp"
|
||||||
|
"rua.plus/lolly/internal/config"
|
||||||
|
"rua.plus/lolly/internal/logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AccessLog 访问日志中间件,记录请求方法、路径、状态码、响应大小和处理时间。
|
||||||
|
type AccessLog struct {
|
||||||
|
logger *logging.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// New 创建访问日志中间件。
|
||||||
|
func New(cfg *config.LoggingConfig) *AccessLog {
|
||||||
|
return &AccessLog{
|
||||||
|
logger: logging.New(cfg),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name 返回中间件名称。
|
||||||
|
func (a *AccessLog) Name() string {
|
||||||
|
return "accesslog"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process 包装 handler,在请求处理后记录访问日志。
|
||||||
|
func (a *AccessLog) Process(next fasthttp.RequestHandler) fasthttp.RequestHandler {
|
||||||
|
return func(ctx *fasthttp.RequestCtx) {
|
||||||
|
start := time.Now()
|
||||||
|
next(ctx)
|
||||||
|
duration := time.Since(start)
|
||||||
|
a.logger.LogAccess(ctx, ctx.Response.StatusCode(), int64(len(ctx.Response.Body())), duration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close 关闭日志文件。
|
||||||
|
func (a *AccessLog) Close() error {
|
||||||
|
return a.logger.Close()
|
||||||
|
}
|
||||||
79
internal/middleware/accesslog/accesslog_test.go
Normal file
79
internal/middleware/accesslog/accesslog_test.go
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
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()
|
||||||
|
}
|
||||||
@ -10,6 +10,7 @@ import (
|
|||||||
"rua.plus/lolly/internal/loadbalance"
|
"rua.plus/lolly/internal/loadbalance"
|
||||||
"rua.plus/lolly/internal/logging"
|
"rua.plus/lolly/internal/logging"
|
||||||
"rua.plus/lolly/internal/middleware"
|
"rua.plus/lolly/internal/middleware"
|
||||||
|
"rua.plus/lolly/internal/middleware/accesslog"
|
||||||
"rua.plus/lolly/internal/proxy"
|
"rua.plus/lolly/internal/proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -19,7 +20,8 @@ type Server struct {
|
|||||||
fastServer *fasthttp.Server
|
fastServer *fasthttp.Server
|
||||||
handler fasthttp.RequestHandler
|
handler fasthttp.RequestHandler
|
||||||
running bool
|
running bool
|
||||||
healthCheckers []*proxy.HealthChecker // 新增
|
healthCheckers []*proxy.HealthChecker
|
||||||
|
accessLogMiddleware *accesslog.AccessLog
|
||||||
}
|
}
|
||||||
|
|
||||||
// New 创建服务器
|
// New 创建服务器
|
||||||
@ -52,7 +54,10 @@ func (s *Server) startSingleMode() error {
|
|||||||
router.GET("/{filepath:*}", staticHandler.Handle)
|
router.GET("/{filepath:*}", staticHandler.Handle)
|
||||||
router.HEAD("/{filepath:*}", staticHandler.Handle)
|
router.HEAD("/{filepath:*}", staticHandler.Handle)
|
||||||
|
|
||||||
chain := middleware.NewChain()
|
// 创建访问日志中间件
|
||||||
|
s.accessLogMiddleware = accesslog.New(&s.config.Logging)
|
||||||
|
|
||||||
|
chain := middleware.NewChain(s.accessLogMiddleware)
|
||||||
s.handler = chain.Apply(router.Handler())
|
s.handler = chain.Apply(router.Handler())
|
||||||
|
|
||||||
s.fastServer = &fasthttp.Server{
|
s.fastServer = &fasthttp.Server{
|
||||||
@ -73,6 +78,10 @@ func (s *Server) startSingleMode() error {
|
|||||||
func (s *Server) startVHostMode() error {
|
func (s *Server) startVHostMode() error {
|
||||||
vhostMgr := NewVHostManager()
|
vhostMgr := NewVHostManager()
|
||||||
|
|
||||||
|
// 创建访问日志中间件(共享给所有虚拟主机)
|
||||||
|
s.accessLogMiddleware = accesslog.New(&s.config.Logging)
|
||||||
|
chain := middleware.NewChain(s.accessLogMiddleware)
|
||||||
|
|
||||||
for i := range s.config.Servers {
|
for i := range s.config.Servers {
|
||||||
router := handler.NewRouter()
|
router := handler.NewRouter()
|
||||||
s.registerProxyRoutes(router, &s.config.Servers[i])
|
s.registerProxyRoutes(router, &s.config.Servers[i])
|
||||||
@ -85,7 +94,7 @@ func (s *Server) startVHostMode() error {
|
|||||||
router.GET("/{filepath:*}", staticHandler.Handle)
|
router.GET("/{filepath:*}", staticHandler.Handle)
|
||||||
router.HEAD("/{filepath:*}", staticHandler.Handle)
|
router.HEAD("/{filepath:*}", staticHandler.Handle)
|
||||||
|
|
||||||
vhostMgr.AddHost(s.config.Servers[i].Name, router.Handler())
|
vhostMgr.AddHost(s.config.Servers[i].Name, chain.Apply(router.Handler()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 默认主机
|
// 默认主机
|
||||||
@ -97,7 +106,7 @@ func (s *Server) startVHostMode() error {
|
|||||||
s.config.Server.Static.Index,
|
s.config.Server.Static.Index,
|
||||||
)
|
)
|
||||||
router.GET("/{filepath:*}", staticHandler.Handle)
|
router.GET("/{filepath:*}", staticHandler.Handle)
|
||||||
vhostMgr.SetDefault(router.Handler())
|
vhostMgr.SetDefault(chain.Apply(router.Handler()))
|
||||||
}
|
}
|
||||||
|
|
||||||
s.handler = vhostMgr.Handler()
|
s.handler = vhostMgr.Handler()
|
||||||
@ -161,6 +170,11 @@ func (s *Server) Stop() error {
|
|||||||
hc.Stop()
|
hc.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 关闭访问日志
|
||||||
|
if s.accessLogMiddleware != nil {
|
||||||
|
s.accessLogMiddleware.Close()
|
||||||
|
}
|
||||||
|
|
||||||
if s.fastServer != nil {
|
if s.fastServer != nil {
|
||||||
return s.fastServer.Shutdown()
|
return s.fastServer.Shutdown()
|
||||||
}
|
}
|
||||||
@ -176,6 +190,11 @@ func (s *Server) GracefulStop(timeout time.Duration) error {
|
|||||||
hc.Stop()
|
hc.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 关闭访问日志
|
||||||
|
if s.accessLogMiddleware != nil {
|
||||||
|
s.accessLogMiddleware.Close()
|
||||||
|
}
|
||||||
|
|
||||||
if s.fastServer != nil {
|
if s.fastServer != nil {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user