测试一下修复一部分问题
This commit is contained in:
@@ -1,42 +1,42 @@
|
|||||||
<!--
|
<!--
|
||||||
SYNC IMPACT REPORT - Constitution Amendment
|
SYNC IMPACT REPORT - Constitution Amendment
|
||||||
============================================
|
============================================
|
||||||
Version Change: 2.1.1 → 2.2.0
|
Version Change: 2.2.0 → 2.3.0
|
||||||
Date: 2025-11-11
|
Date: 2025-11-11
|
||||||
|
|
||||||
NEW PRINCIPLES ADDED:
|
NEW PRINCIPLES ADDED:
|
||||||
- VII. Documentation Standards (文档规范) - NEW principle for feature documentation
|
- VIII. Access Logging Standards (访问日志规范) - NEW principle for comprehensive request/response logging
|
||||||
|
|
||||||
MODIFIED SECTIONS:
|
MODIFIED SECTIONS:
|
||||||
- Added new Principle VII with documentation structure and language requirements
|
- Added new Principle VIII with mandatory access logging requirements
|
||||||
- Rule: Summary docs MUST be placed in docs/{feature-id}/ mirroring specs/{feature-id}/
|
- Rule: ALL requests MUST be logged to access.log without exception
|
||||||
- Rule: Summary doc filenames MUST use Chinese
|
- Rule: Request parameters (query + body) MUST be logged (limited to 50KB)
|
||||||
- Rule: Summary doc content MUST use Chinese
|
- Rule: Response parameters (body) MUST be logged (limited to 50KB)
|
||||||
- Rule: README.md MUST be updated with brief Chinese summary for each feature
|
- Rule: Logging MUST happen via centralized Logger middleware
|
||||||
- Rule: Summary should be concise enough for first-time contributors to understand quickly
|
- Rule: No middleware can bypass access logging (including auth failures)
|
||||||
- Examples of correct documentation structure and naming
|
- Rule: Body truncation MUST indicate "... (truncated)" when over limit
|
||||||
- Rationale for centralized Chinese documentation
|
- Rationale for comprehensive logging: debugging, audit trails, compliance
|
||||||
|
|
||||||
TEMPLATES REQUIRING UPDATES:
|
TEMPLATES REQUIRING UPDATES:
|
||||||
✅ .specify/templates/plan-template.md - Added documentation structure guidance
|
✅ .specify/templates/plan-template.md - Added access logging check in Constitution Check
|
||||||
✅ .specify/templates/spec-template.md - No changes needed
|
✅ .specify/templates/tasks-template.md - Added access logging verification in Quality Gates
|
||||||
✅ .specify/templates/tasks-template.md - Added documentation task template in Polish phase
|
|
||||||
|
|
||||||
FOLLOW-UP ACTIONS:
|
FOLLOW-UP ACTIONS:
|
||||||
- Update plan-template.md with docs/ structure
|
- None required - logging implementation already completed
|
||||||
- Update tasks-template.md with documentation tasks in Polish phase
|
|
||||||
|
|
||||||
RATIONALE:
|
RATIONALE:
|
||||||
MINOR version bump (2.2.0) - New principle added for documentation standards.
|
MINOR version bump (2.3.0) - New principle added for access logging standards.
|
||||||
This is a new governance rule that establishes how feature documentation should be
|
This establishes a mandatory governance rule that ALL HTTP requests must be logged
|
||||||
organized, named, and written. The principle addresses:
|
with complete request and response data, regardless of middleware short-circuiting
|
||||||
1. Documentation location consistency (docs/ mirrors specs/)
|
(auth failures, rate limits, etc.). This ensures:
|
||||||
2. Language requirements (Chinese for accessibility)
|
1. Complete audit trail for all API interactions
|
||||||
3. README.md update requirements (brief summaries)
|
2. Debugging capability for all failure scenarios
|
||||||
4. Target audience (first-time contributors)
|
3. Compliance with logging requirements
|
||||||
|
4. No special cases or exceptions in logging
|
||||||
|
|
||||||
This is a MINOR bump (not PATCH) because it adds a new mandatory principle that
|
This is a MINOR bump (not PATCH) because it adds a new mandatory principle that
|
||||||
affects the development workflow, requiring documentation tasks for all features.
|
affects the development workflow and quality gates, requiring verification that
|
||||||
|
all middleware respects the logging standard.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
# 君鸿卡管系统 Constitution
|
# 君鸿卡管系统 Constitution
|
||||||
@@ -933,6 +933,134 @@ README.md # 项目主文档
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### VIII. Access Logging Standards (访问日志规范)
|
||||||
|
|
||||||
|
**规则 (RULES):**
|
||||||
|
|
||||||
|
- **所有** HTTP 请求 **MUST** 被记录到 `access.log`,无例外
|
||||||
|
- 访问日志 **MUST** 记录完整的请求参数(query 参数 + request body)
|
||||||
|
- 访问日志 **MUST** 记录完整的响应参数(response body)
|
||||||
|
- 请求/响应 body **MUST** 限制大小为 50KB,超过部分截断并标注 `... (truncated)`
|
||||||
|
- 访问日志 **MUST** 通过统一的 Logger 中间件(`pkg/logger/Middleware()`)记录
|
||||||
|
- **任何中间件** 的短路返回(认证失败、限流拒绝、参数验证失败等)**MUST NOT** 绕过访问日志
|
||||||
|
- 访问日志 **MUST** 包含以下字段(最低要求):
|
||||||
|
- `method`: HTTP 方法
|
||||||
|
- `path`: 请求路径
|
||||||
|
- `query`: Query 参数字符串
|
||||||
|
- `status`: HTTP 状态码
|
||||||
|
- `duration_ms`: 请求耗时(毫秒)
|
||||||
|
- `request_id`: 请求唯一 ID
|
||||||
|
- `ip`: 客户端 IP
|
||||||
|
- `user_agent`: 用户代理
|
||||||
|
- `user_id`: 用户 ID(认证后有值,否则为空)
|
||||||
|
- `request_body`: 请求体(限制 50KB)
|
||||||
|
- `response_body`: 响应体(限制 50KB)
|
||||||
|
- 访问日志 **SHOULD** 使用 JSON 格式,便于日志分析和监控
|
||||||
|
- 访问日志文件 **MUST** 配置自动轮转(基于大小或时间)
|
||||||
|
|
||||||
|
**正确的访问日志示例:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"level": "info",
|
||||||
|
"timestamp": "2025-11-11T17:45:03.186+0800",
|
||||||
|
"message": "",
|
||||||
|
"method": "POST",
|
||||||
|
"path": "/api/v1/users",
|
||||||
|
"query": "page=1&size=10",
|
||||||
|
"status": 400,
|
||||||
|
"duration_ms": 0.035,
|
||||||
|
"request_id": "f1d8b767-dfb3-4588-9fa0-8a97e5337184",
|
||||||
|
"ip": "127.0.0.1",
|
||||||
|
"user_agent": "curl/8.7.1",
|
||||||
|
"user_id": "",
|
||||||
|
"request_body": "{\"username\":\"testuser\",\"email\":\"test@example.com\"}",
|
||||||
|
"response_body": "{\"code\":1001,\"data\":null,\"msg\":\"缺失认证令牌\",\"timestamp\":\"2025-11-11T17:45:03+08:00\"}"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Logger 中间件实现要求:**
|
||||||
|
|
||||||
|
```go
|
||||||
|
// pkg/logger/middleware.go
|
||||||
|
func Middleware() fiber.Handler {
|
||||||
|
return func(c *fiber.Ctx) error {
|
||||||
|
startTime := time.Now()
|
||||||
|
|
||||||
|
// 在 c.Next() 之前读取请求 body
|
||||||
|
requestBody := truncateBody(c.Body(), MaxBodyLogSize)
|
||||||
|
queryParams := string(c.Request().URI().QueryString())
|
||||||
|
|
||||||
|
// 处理请求(可能被中间件短路返回)
|
||||||
|
err := c.Next()
|
||||||
|
|
||||||
|
// 在 c.Next() 之后读取响应 body(无论是否短路)
|
||||||
|
responseBody := truncateBody(c.Response().Body(), MaxBodyLogSize)
|
||||||
|
|
||||||
|
// 记录完整的访问日志(包含请求和响应参数)
|
||||||
|
accessLogger.Info("",
|
||||||
|
zap.String("method", c.Method()),
|
||||||
|
zap.String("path", c.Path()),
|
||||||
|
zap.String("query", queryParams),
|
||||||
|
zap.Int("status", c.Response().StatusCode()),
|
||||||
|
zap.Float64("duration_ms", time.Since(startTime).Seconds()*1000),
|
||||||
|
zap.String("request_id", getRequestID(c)),
|
||||||
|
zap.String("ip", c.IP()),
|
||||||
|
zap.String("user_agent", c.Get("User-Agent")),
|
||||||
|
zap.String("user_id", getUserID(c)),
|
||||||
|
zap.String("request_body", requestBody),
|
||||||
|
zap.String("response_body", responseBody),
|
||||||
|
)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**禁止的做法:**
|
||||||
|
|
||||||
|
```go
|
||||||
|
// ❌ 在中间件中跳过日志记录
|
||||||
|
func AuthMiddleware() fiber.Handler {
|
||||||
|
return func(c *fiber.Ctx) error {
|
||||||
|
if !isAuthenticated(c) {
|
||||||
|
// ❌ 直接返回,没有记录到 access.log
|
||||||
|
return c.Status(401).JSON(fiber.Map{"error": "unauthorized"})
|
||||||
|
}
|
||||||
|
return c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ 只在部分路由记录日志
|
||||||
|
app.Use("/api/v1/users", logger.Middleware()) // ❌ 其他路由没有日志
|
||||||
|
|
||||||
|
// ❌ 不记录请求/响应 body
|
||||||
|
accessLogger.Info("",
|
||||||
|
zap.String("method", c.Method()),
|
||||||
|
zap.String("path", c.Path()),
|
||||||
|
zap.Int("status", c.Response().StatusCode()),
|
||||||
|
// ❌ 缺少 request_body 和 response_body
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**理由 (RATIONALE):**
|
||||||
|
|
||||||
|
完整的访问日志是系统可观测性的基础,对于以下场景至关重要:
|
||||||
|
|
||||||
|
1. **问题排查**:当用户报告错误时,通过 request_id 可以追溯完整的请求/响应数据,快速定位问题
|
||||||
|
2. **安全审计**:记录所有请求(包括认证失败、参数验证失败等)可以追踪潜在的安全攻击
|
||||||
|
3. **性能分析**:通过 duration_ms 和请求参数可以分析慢查询和性能瓶颈
|
||||||
|
4. **合规要求**:某些行业(金融、医疗等)要求完整的操作审计日志
|
||||||
|
5. **用户行为分析**:通过 user_id 和请求参数可以分析用户行为模式
|
||||||
|
6. **无例外原则**:确保没有请求"逃脱"日志记录,避免日志盲点
|
||||||
|
|
||||||
|
通过强制在 Logger 中间件中统一记录,确保:
|
||||||
|
- 中间件的短路返回(auth 失败、rate limit 等)不会绕过日志
|
||||||
|
- 所有请求的日志格式统一,便于日志分析工具处理
|
||||||
|
- 50KB 限制平衡了日志完整性和存储成本
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Development Workflow (开发工作流程)
|
## Development Workflow (开发工作流程)
|
||||||
|
|
||||||
### 分支管理
|
### 分支管理
|
||||||
@@ -964,6 +1092,7 @@ README.md # 项目主文档
|
|||||||
- 性能影响可接受
|
- 性能影响可接受
|
||||||
- 代码通过 `gofmt`、`go vet`、`golangci-lint` 检查
|
- 代码通过 `gofmt`、`go vet`、`golangci-lint` 检查
|
||||||
- **文档已按规范更新(docs/ 和 README.md)**
|
- **文档已按规范更新(docs/ 和 README.md)**
|
||||||
|
- **访问日志记录符合规范(所有请求都被记录,包含请求/响应 body)**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -990,6 +1119,9 @@ README.md # 项目主文档
|
|||||||
- [ ] **使用 Go 惯用并发模式**(goroutines/channels,无线程池类)
|
- [ ] **使用 Go 惯用并发模式**(goroutines/channels,无线程池类)
|
||||||
- [ ] **功能总结文档已创建**(在 `docs/{feature-id}/` 目录下,中文命名和内容)
|
- [ ] **功能总结文档已创建**(在 `docs/{feature-id}/` 目录下,中文命名和内容)
|
||||||
- [ ] **README.md 已更新**(添加功能简短描述,2-3 句话)
|
- [ ] **README.md 已更新**(添加功能简短描述,2-3 句话)
|
||||||
|
- [ ] **访问日志验证通过**(所有请求被记录到 access.log,包含完整请求/响应参数)
|
||||||
|
- [ ] **访问日志格式正确**(包含所有必需字段:method, path, query, status, duration_ms, request_id, ip, user_agent, user_id, request_body, response_body)
|
||||||
|
- [ ] **中间件不绕过日志**(认证失败、限流等短路返回也被记录)
|
||||||
|
|
||||||
### 上线前检查
|
### 上线前检查
|
||||||
|
|
||||||
@@ -998,6 +1130,7 @@ README.md # 项目主文档
|
|||||||
- [ ] 数据库迁移在 staging 环境验证通过
|
- [ ] 数据库迁移在 staging 环境验证通过
|
||||||
- [ ] 监控和告警配置完成
|
- [ ] 监控和告警配置完成
|
||||||
- [ ] 回滚方案已准备
|
- [ ] 回滚方案已准备
|
||||||
|
- [ ] 访问日志轮转配置正确(防止日志文件过大)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -1025,12 +1158,17 @@ README.md # 项目主文档
|
|||||||
- 特别关注 Go 惯用法原则,拒绝 Java 风格的代码
|
- 特别关注 Go 惯用法原则,拒绝 Java 风格的代码
|
||||||
- 特别关注常量使用规范,拒绝不必要的硬编码
|
- 特别关注常量使用规范,拒绝不必要的硬编码
|
||||||
- 特别关注文档规范,确保每个功能都有完整的中文文档
|
- 特别关注文档规范,确保每个功能都有完整的中文文档
|
||||||
|
- 特别关注访问日志规范,确保所有请求都被完整记录
|
||||||
- 任何复杂性增加(新依赖、新架构层)**MUST** 在设计文档中明确说明必要性
|
- 任何复杂性增加(新依赖、新架构层)**MUST** 在设计文档中明确说明必要性
|
||||||
|
|
||||||
### 运行时开发指导
|
### 运行时开发指导
|
||||||
|
|
||||||
开发时参考本宪章确保一致性。如有疑问,优先遵守原则,再讨论例外情况。记住:**写 Go 代码,不是用 Go 语法写 Java**。记住:**定义常量是为了使用,不是为了装饰**。记住:**写文档是为了团队协作,不是为了应付检查**。
|
开发时参考本宪章确保一致性。如有疑问,优先遵守原则,再讨论例外情况。记住:
|
||||||
|
- **写 Go 代码,不是用 Go 语法写 Java**
|
||||||
|
- **定义常量是为了使用,不是为了装饰**
|
||||||
|
- **写文档是为了团队协作,不是为了应付检查**
|
||||||
|
- **记录日志是为了可观测性,不能有例外**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Version**: 2.2.0 | **Ratified**: 2025-11-10 | **Last Amended**: 2025-11-11
|
**Version**: 2.3.0 | **Ratified**: 2025-11-10 | **Last Amended**: 2025-11-11
|
||||||
|
|||||||
@@ -108,6 +108,15 @@
|
|||||||
- [ ] Uses `context.Context` for timeout control
|
- [ ] Uses `context.Context` for timeout control
|
||||||
- [ ] Uses `sync.Pool` for frequently allocated objects
|
- [ ] Uses `sync.Pool` for frequently allocated objects
|
||||||
|
|
||||||
|
**Access Logging Standards** (Constitution Principle VIII):
|
||||||
|
- [ ] ALL HTTP requests logged to access.log without exception
|
||||||
|
- [ ] Request parameters (query + body) logged (limited to 50KB)
|
||||||
|
- [ ] Response parameters (body) logged (limited to 50KB)
|
||||||
|
- [ ] Logging happens via centralized Logger middleware (pkg/logger/Middleware())
|
||||||
|
- [ ] No middleware bypasses access logging (including auth failures, rate limits)
|
||||||
|
- [ ] Body truncation indicates "... (truncated)" when over 50KB limit
|
||||||
|
- [ ] Access log includes all required fields: method, path, query, status, duration_ms, request_id, ip, user_agent, user_id, request_body, response_body
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
### Documentation (this feature)
|
### Documentation (this feature)
|
||||||
|
|||||||
@@ -201,6 +201,12 @@ Foundational tasks for 君鸿卡管系统 tech stack:
|
|||||||
- [ ] TXXX Quality Gate: Verify feature summary docs created in docs/{feature-id}/ with Chinese filenames
|
- [ ] TXXX Quality Gate: Verify feature summary docs created in docs/{feature-id}/ with Chinese filenames
|
||||||
- [ ] TXXX Quality Gate: Verify summary doc content uses Chinese
|
- [ ] TXXX Quality Gate: Verify summary doc content uses Chinese
|
||||||
- [ ] TXXX Quality Gate: Verify README.md updated with brief feature description (2-3 sentences)
|
- [ ] TXXX Quality Gate: Verify README.md updated with brief feature description (2-3 sentences)
|
||||||
|
- [ ] TXXX Quality Gate: Verify ALL HTTP requests logged to access.log (no exceptions)
|
||||||
|
- [ ] TXXX Quality Gate: Verify access log includes request parameters (query + body, limited to 50KB)
|
||||||
|
- [ ] TXXX Quality Gate: Verify access log includes response parameters (body, limited to 50KB)
|
||||||
|
- [ ] TXXX Quality Gate: Verify logging via centralized Logger middleware (pkg/logger/Middleware())
|
||||||
|
- [ ] TXXX Quality Gate: Verify no middleware bypasses logging (test auth failures, rate limits, etc.)
|
||||||
|
- [ ] TXXX Quality Gate: Verify access log has all required fields (method, path, query, status, duration_ms, request_id, ip, user_agent, user_id, request_body, response_body)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ logging:
|
|||||||
compress: false
|
compress: false
|
||||||
|
|
||||||
middleware:
|
middleware:
|
||||||
enable_auth: false # 开发环境可选禁用认证
|
enable_auth: true # 开发环境可选禁用认证
|
||||||
enable_rate_limiter: false
|
enable_rate_limiter: true
|
||||||
rate_limiter:
|
rate_limiter:
|
||||||
max: 1000
|
max: 1000
|
||||||
expiration: "1m"
|
expiration: "1m"
|
||||||
|
|||||||
@@ -4,12 +4,15 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
"github.com/break/junhong_cmp_fiber/pkg/logger"
|
||||||
"github.com/break/junhong_cmp_fiber/pkg/response"
|
"github.com/break/junhong_cmp_fiber/pkg/response"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HealthCheck 健康检查处理器
|
// HealthCheck 健康检查处理器
|
||||||
func HealthCheck(c *fiber.Ctx) error {
|
func HealthCheck(c *fiber.Ctx) error {
|
||||||
|
logger.GetAppLogger().Info("我还活着!!!!", zap.String("time", time.Now().Format(time.RFC3339)))
|
||||||
return response.Success(c, fiber.Map{
|
return response.Success(c, fiber.Map{
|
||||||
"status": "healthy",
|
"status": "healthy",
|
||||||
"timestamp": time.Now().Format(time.RFC3339),
|
"timestamp": time.Now().Format(time.RFC3339),
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package logger
|
package logger
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"go.uber.org/zap/zapcore"
|
"go.uber.org/zap/zapcore"
|
||||||
"gopkg.in/natefinch/lumberjack.v2"
|
"gopkg.in/natefinch/lumberjack.v2"
|
||||||
@@ -25,17 +27,17 @@ func InitLoggers(
|
|||||||
|
|
||||||
// 创建编码器配置
|
// 创建编码器配置
|
||||||
encoderConfig := zapcore.EncoderConfig{
|
encoderConfig := zapcore.EncoderConfig{
|
||||||
TimeKey: "timestamp",
|
TimeKey: "time",
|
||||||
LevelKey: "level",
|
LevelKey: "level",
|
||||||
NameKey: "logger",
|
NameKey: "logger",
|
||||||
CallerKey: "caller",
|
CallerKey: "caller",
|
||||||
MessageKey: "message",
|
MessageKey: "msg",
|
||||||
StacktraceKey: "stacktrace",
|
StacktraceKey: "stacktrace",
|
||||||
LineEnding: zapcore.DefaultLineEnding,
|
LineEnding: zapcore.DefaultLineEnding,
|
||||||
EncodeLevel: zapcore.LowercaseLevelEncoder,
|
EncodeLevel: zapcore.CapitalColorLevelEncoder, // 使用彩色级别编码器
|
||||||
EncodeTime: zapcore.ISO8601TimeEncoder, // RFC3339 格式
|
EncodeTime: zapcore.ISO8601TimeEncoder, // 2025-11-11T17:50:52.830+0800 格式
|
||||||
EncodeDuration: zapcore.SecondsDurationEncoder,
|
EncodeDuration: zapcore.SecondsDurationEncoder,
|
||||||
EncodeCaller: zapcore.ShortCallerEncoder,
|
EncodeCaller: zapcore.ShortCallerEncoder, // 输出 middleware/trace.go:58 格式
|
||||||
}
|
}
|
||||||
|
|
||||||
// 选择编码器(开发模式使用控制台,生产使用 JSON)
|
// 选择编码器(开发模式使用控制台,生产使用 JSON)
|
||||||
@@ -46,10 +48,21 @@ func InitLoggers(
|
|||||||
encoder = zapcore.NewJSONEncoder(encoderConfig)
|
encoder = zapcore.NewJSONEncoder(encoderConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 创建应用日志写入器
|
||||||
|
appWriter := zapcore.AddSync(newLumberjackLogger(appLogConfig))
|
||||||
|
|
||||||
|
// 开发模式下同时输出到控制台
|
||||||
|
if development {
|
||||||
|
appWriter = zapcore.NewMultiWriteSyncer(
|
||||||
|
appWriter,
|
||||||
|
zapcore.AddSync(os.Stdout),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// 创建应用日志核心
|
// 创建应用日志核心
|
||||||
appCore := zapcore.NewCore(
|
appCore := zapcore.NewCore(
|
||||||
encoder,
|
encoder,
|
||||||
zapcore.AddSync(newLumberjackLogger(appLogConfig)),
|
appWriter,
|
||||||
zapLevel,
|
zapLevel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -8,14 +8,39 @@ import (
|
|||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// MaxBodyLogSize 限制记录的请求/响应 body 大小为 50KB
|
||||||
|
MaxBodyLogSize = 50 * 1024
|
||||||
|
)
|
||||||
|
|
||||||
|
// truncateBody 截断 body 到指定大小
|
||||||
|
func truncateBody(body []byte, maxSize int) string {
|
||||||
|
if len(body) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(body) <= maxSize {
|
||||||
|
return string(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 超过限制,截断并添加提示
|
||||||
|
return string(body[:maxSize]) + "... (truncated)"
|
||||||
|
}
|
||||||
|
|
||||||
// Middleware 创建 Fiber 日志中间件
|
// Middleware 创建 Fiber 日志中间件
|
||||||
// 记录所有 HTTP 请求到访问日志
|
// 记录所有 HTTP 请求到访问日志(包括请求和响应 body)
|
||||||
func Middleware() fiber.Handler {
|
func Middleware() fiber.Handler {
|
||||||
return func(c *fiber.Ctx) error {
|
return func(c *fiber.Ctx) error {
|
||||||
// 记录请求开始时间
|
// 记录请求开始时间
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
c.Locals(constants.ContextKeyStartTime, startTime)
|
c.Locals(constants.ContextKeyStartTime, startTime)
|
||||||
|
|
||||||
|
// 获取请求 body(在 c.Next() 之前读取)
|
||||||
|
requestBody := truncateBody(c.Body(), MaxBodyLogSize)
|
||||||
|
|
||||||
|
// 获取 query 参数
|
||||||
|
queryParams := string(c.Request().URI().QueryString())
|
||||||
|
|
||||||
// 处理请求
|
// 处理请求
|
||||||
err := c.Next()
|
err := c.Next()
|
||||||
|
|
||||||
@@ -34,17 +59,23 @@ func Middleware() fiber.Handler {
|
|||||||
userID = uid.(string)
|
userID = uid.(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取响应 body
|
||||||
|
responseBody := truncateBody(c.Response().Body(), MaxBodyLogSize)
|
||||||
|
|
||||||
// 记录访问日志
|
// 记录访问日志
|
||||||
accessLogger := GetAccessLogger()
|
accessLogger := GetAccessLogger()
|
||||||
accessLogger.Info("",
|
accessLogger.Info("",
|
||||||
zap.String("method", c.Method()),
|
zap.String("method", c.Method()),
|
||||||
zap.String("path", c.Path()),
|
zap.String("path", c.Path()),
|
||||||
|
zap.String("query", queryParams),
|
||||||
zap.Int("status", c.Response().StatusCode()),
|
zap.Int("status", c.Response().StatusCode()),
|
||||||
zap.Float64("duration_ms", float64(duration.Microseconds())/1000.0),
|
zap.Float64("duration_ms", float64(duration.Microseconds())/1000.0),
|
||||||
zap.String("request_id", requestID),
|
zap.String("request_id", requestID),
|
||||||
zap.String("ip", c.IP()),
|
zap.String("ip", c.IP()),
|
||||||
zap.String("user_agent", c.Get("User-Agent")),
|
zap.String("user_agent", c.Get("User-Agent")),
|
||||||
zap.String(constants.ContextKeyUserID, userID),
|
zap.String(constants.ContextKeyUserID, userID),
|
||||||
|
zap.String("request_body", requestBody),
|
||||||
|
zap.String("response_body", responseBody),
|
||||||
)
|
)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
|||||||
Reference in New Issue
Block a user