fix: enrich error logs
This commit is contained in:
@@ -10,6 +10,9 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
|
||||||
|
"quyun/v2/database/models"
|
||||||
|
"quyun/v2/pkg/consts"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ResponseSender 响应发送器
|
// ResponseSender 响应发送器
|
||||||
@@ -29,14 +32,14 @@ func (s *ResponseSender) SendError(ctx fiber.Ctx, err error) error {
|
|||||||
appErr := s.handler.Handle(err)
|
appErr := s.handler.Handle(err)
|
||||||
|
|
||||||
// 记录错误日志
|
// 记录错误日志
|
||||||
s.logError(appErr)
|
s.logError(appErr, ctx)
|
||||||
|
|
||||||
// 根据 Content-Type 返回不同格式
|
// 根据 Content-Type 返回不同格式
|
||||||
return s.sendResponse(ctx, appErr)
|
return s.sendResponse(ctx, appErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// logError 记录错误日志
|
// logError 记录错误日志
|
||||||
func (s *ResponseSender) logError(appErr *AppError) {
|
func (s *ResponseSender) logError(appErr *AppError, ctx fiber.Ctx) {
|
||||||
// 确保每个错误实例都有唯一ID,便于日志关联
|
// 确保每个错误实例都有唯一ID,便于日志关联
|
||||||
if appErr.ID == "" {
|
if appErr.ID == "" {
|
||||||
appErr.ID = uuid.NewString()
|
appErr.ID = uuid.NewString()
|
||||||
@@ -86,7 +89,18 @@ func (s *ResponseSender) logError(appErr *AppError) {
|
|||||||
|
|
||||||
root := chain[len(chain)-1]["error"]
|
root := chain[len(chain)-1]["error"]
|
||||||
|
|
||||||
logEntry := log.WithFields(log.Fields{
|
requestID := ctx.Get(fiber.HeaderXRequestID)
|
||||||
|
path := ctx.Path()
|
||||||
|
method := ctx.Method()
|
||||||
|
ua := ctx.Get(fiber.HeaderUserAgent)
|
||||||
|
remoteIP := ctx.IP()
|
||||||
|
query := string(ctx.Request().URI().QueryString())
|
||||||
|
fullPath := path
|
||||||
|
if query != "" {
|
||||||
|
fullPath = fmt.Sprintf("%s?%s", path, query)
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := log.Fields{
|
||||||
"id": appErr.ID,
|
"id": appErr.ID,
|
||||||
"code": appErr.Code,
|
"code": appErr.Code,
|
||||||
"statusCode": appErr.StatusCode,
|
"statusCode": appErr.StatusCode,
|
||||||
@@ -95,7 +109,29 @@ func (s *ResponseSender) logError(appErr *AppError) {
|
|||||||
"params": appErr.params,
|
"params": appErr.params,
|
||||||
"error_chain": chain,
|
"error_chain": chain,
|
||||||
"root_error": root,
|
"root_error": root,
|
||||||
})
|
"request_id": requestID,
|
||||||
|
"method": method,
|
||||||
|
"path": path,
|
||||||
|
"full_path": fullPath,
|
||||||
|
"user_agent": ua,
|
||||||
|
"ip": remoteIP,
|
||||||
|
}
|
||||||
|
|
||||||
|
if user := ctx.Locals(consts.CtxKeyUser); user != nil {
|
||||||
|
if model, ok := user.(*models.User); ok {
|
||||||
|
fields["user_id"] = model.ID
|
||||||
|
fields["user_roles"] = model.Roles
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tenant := ctx.Locals(consts.CtxKeyTenant); tenant != nil {
|
||||||
|
if model, ok := tenant.(*models.Tenant); ok {
|
||||||
|
fields["tenant_id"] = model.ID
|
||||||
|
fields["tenant_code"] = model.Code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logEntry := log.WithFields(fields)
|
||||||
|
|
||||||
if appErr.originalErr != nil {
|
if appErr.originalErr != nil {
|
||||||
logEntry = logEntry.WithError(appErr.originalErr)
|
logEntry = logEntry.WithError(appErr.originalErr)
|
||||||
|
|||||||
79
docs/plan.md
79
docs/plan.md
@@ -1,85 +1,64 @@
|
|||||||
# Implementation Plan: Global Auth + Tenant Route Prefix
|
# Implementation Plan: Improve Error Logging
|
||||||
|
|
||||||
**Branch**: `main` | **Date**: 2026-01-26 | **Spec**: N/A
|
**Branch**: `main` | **Date**: 2026-01-26 | **Spec**: N/A
|
||||||
**Input**: 改为全局登录;租户资源访问再鉴权;后端路由从 `/t/:tenantCode/v1` 改为 `/v1/t/:tenantCode`,同步前端路由。
|
**Input**: 完善应用错误记录,输出更精确的错误定位信息。
|
||||||
|
|
||||||
**Note**: 本计划遵循 `docs/templates/plan-template.md`。
|
**Note**: 本计划遵循 `docs/templates/plan-template.md`。
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
实施全局登录(不要求 tenantCode),并将多租户路由前缀统一为 `/v1/t/:tenantCode`。同步更新后端路由与前端路由/请求封装,确保登录与资源鉴权一致。
|
定位当前错误记录入口与结构,补充关键上下文信息(请求 ID、路径、用户/租户 ID、处理器名称、错误链),并统一输出格式,方便快速定位问题。
|
||||||
|
|
||||||
## Technical Context
|
## Technical Context
|
||||||
|
|
||||||
**Language/Version**: Go 1.22 + Vue 3 (Vite)
|
**Language/Version**: Go 1.22
|
||||||
**Primary Dependencies**: Fiber, GORM-Gen, Vue Router
|
**Primary Dependencies**: Fiber, logrus, errorx
|
||||||
**Storage**: PostgreSQL
|
**Storage**: N/A
|
||||||
**Testing**: go test / 前端 build+l int(如需)
|
**Testing**: go test / 运行时日志
|
||||||
**Target Platform**: local/staging
|
**Target Platform**: local/staging
|
||||||
**Project Type**: Web application
|
**Project Type**: Web application
|
||||||
**Performance Goals**: N/A
|
**Performance Goals**: N/A
|
||||||
**Constraints**: 不改生成文件,需通过 `atomctl` 重新生成路由/Swagger
|
**Constraints**: 不修改生成文件
|
||||||
**Scale/Scope**: auth + 路由前缀调整
|
**Scale/Scope**: 日志格式与错误处理
|
||||||
|
|
||||||
## Constitution Check
|
## Constitution Check
|
||||||
|
|
||||||
- 遵循 `backend/llm.txt`(控制器薄、服务层处理、生成文件不手改)
|
- 遵循 `backend/llm.txt`
|
||||||
- 变更后需跑 `atomctl gen route/provider/swag`
|
- 只改非生成文件
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
### Backend
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
backend/app/http/v1/
|
backend/app/errorx/
|
||||||
backend/app/services/
|
backend/providers/http/
|
||||||
backend/app/http/**/routes.*.go
|
backend/app/middlewares/
|
||||||
```
|
|
||||||
|
|
||||||
### Frontend
|
|
||||||
|
|
||||||
```text
|
|
||||||
frontend/portal/src/router/index.js
|
|
||||||
frontend/portal/src/utils/request.js
|
|
||||||
frontend/portal/src/api/*
|
|
||||||
frontend/superadmin/src/router/index.js
|
|
||||||
```
|
|
||||||
|
|
||||||
### Docs
|
|
||||||
|
|
||||||
```text
|
|
||||||
docs/plan.md
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Plan Phases
|
## Plan Phases
|
||||||
|
|
||||||
### Phase 1: Backend route + auth changes
|
### Phase 1: 现状定位
|
||||||
- 调整 `/t/:tenantCode/v1` 为 `/v1/t/:tenantCode`
|
- 追踪错误记录入口与结构(errorx + middleware + http provider)。
|
||||||
- 登录逻辑改为全局登录(租户鉴权移到资源访问)
|
|
||||||
|
|
||||||
### Phase 2: Frontend route + request changes
|
### Phase 2: 日志增强
|
||||||
- 更新 portal 路由基座与 request baseUrl
|
- 补充 request-id、path、method、tenant/user、handler、error chain。
|
||||||
- 更新所有前端 API 路径对应新前缀
|
- 确认错误等级与格式一致。
|
||||||
|
|
||||||
### Phase 3: Regenerate + sanity check
|
### Phase 3: 验证
|
||||||
- 生成 routes/providers/swagger
|
- 使用典型 4xx/5xx 请求验证日志输出。
|
||||||
- 关键页面手动/自动冒烟校验
|
|
||||||
|
|
||||||
## Tasks
|
## Tasks
|
||||||
|
|
||||||
**Format**: `[ID] [P?] [Story] Description`
|
**Format**: `[ID] [P?] [Story] Description`
|
||||||
|
|
||||||
### Phase 1
|
### Phase 1
|
||||||
- [ ] T001 [US0] 修改后端路由前缀与路由注册
|
- [x] T001 [US0] 定位错误记录入口与字段
|
||||||
- [ ] T002 [US0] 调整登录逻辑为全局登录
|
|
||||||
|
|
||||||
### Phase 2
|
### Phase 2
|
||||||
- [ ] T010 [US1] 修改 portal 路由与 request baseUrl
|
- [x] T010 [US1] 增强错误日志上下文
|
||||||
- [ ] T011 [US1] 更新 portal API 路径
|
- [x] T011 [US1] 统一格式与等级
|
||||||
|
|
||||||
### Phase 3
|
### Phase 3
|
||||||
- [ ] T020 [US2] 重新生成路由与 swagger
|
- [x] T020 [US2] 验证输出与文档说明
|
||||||
- [x] T021 [US2] 冒烟验证核心路径
|
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
@@ -87,14 +66,12 @@ docs/plan.md
|
|||||||
|
|
||||||
## Acceptance Criteria
|
## Acceptance Criteria
|
||||||
|
|
||||||
- 登录不依赖 tenantCode。
|
- 日志可直接定位请求来源与错误链。
|
||||||
- 租户路由统一为 `/v1/t/:tenantCode`。
|
- 关键字段完整(request-id、path、tenant/user、handler)。
|
||||||
- Portal 页面正常加载并可登录。
|
|
||||||
|
|
||||||
## Risks
|
## Risks
|
||||||
|
|
||||||
- 大量前端 API 路径需要同步改动。
|
- 日志字段增加影响性能或泄露敏感信息。
|
||||||
- 老路径可能被外部依赖使用。
|
|
||||||
|
|
||||||
## Complexity Tracking
|
## Complexity Tracking
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user