制定方案

This commit is contained in:
2025-11-03 20:39:31 +08:00
parent d7c7b56b95
commit b6c2fa58f5
6 changed files with 141 additions and 29 deletions

View File

@@ -0,0 +1,89 @@
# 实现方案:纯 Context 驱动的调用链追踪
本方案旨在提供一个绝对安全、符合 Go 语言习惯且对业务代码侵入性最小的调用链追踪方案。其核心思想是:**调用链信息完全由标准的 `context.Context` 承载,并通过 `logs` 包提供的一系列无状态包级函数进行原子化、安全的操作。**
### 1. 核心设计原则
- **`Context` 是唯一载体**: 所有的调用链信息,包括组件名(对象名)和函数名(方法名),都只存储在标准的 `context.Context` 中。我们不再定义任何自定义的 `Context` 接口,以保证最大的兼容性。
- **纯粹的包级函数**: `logs` 包将提供一系列纯粹的、无状态的包级函数,作为与 `Context` 交互的唯一 API。这些函数负责向 `Context` 中添加信息,或从 `Context` 中生成 `Logger`
- **无状态的 `Logger`**: `Logger` 对象本身不再携带任何调用链信息。它在被 `logs.GetLogger(ctx)` 生成时,才被一次性地赋予包含完整调用链的配置。
### 2. 实现细节
#### a. Context 中存储的数据
`context.Context` 将通过 `context.WithValue` 在幕后存储两种核心信息,这两种信息都使用 `logs` 包内部的私有 `key` 类型,以避免与其他包的键冲突。
- **组件名 (`compNameKey`)**: 用于存储一个字符串,表示当前上下文环境属于哪个组件(例如 `\"组件1\"`)。
- **调用链 (`chainKey`)**: 用于存储一个字符串切片 (`[]string`),记录了从请求开始到当前位置的完整调用路径(例如 `[\"组件2.Create\", \"组件1.Create\"]`)。
#### b. `logs` 包提供的核心 API
`logs` 包需要对外提供以下四个核心的包级函数,以提供不同粒度的灵活性:
1. **`logs.AddCompName(ctx, compName) context.Context`**
- **职责**: 将一个组件名(对象名)存入 `Context`,并返回一个包含该信息的新 `Context`。这通常在依赖注入时完成,用于创建组件的“身份名牌” `selfCtx`
- **实现细节**: 该函数接收一个 `context.Context` 和一个 `compName` 字符串。它内部使用 `context.WithValue`,以私有的 `compNameKey` 为键,将 `compName` 字符串存入 `Context`,然后返回这个全新的 `Context`
2. **`logs.AddFuncName(upstreamCtx, selfCtx, funcName) context.Context`**
- **职责**: 这是构建调用链的核心原子操作。它智能地合并上游的调用链和当前组件的信息,生成并返回一个包含更新后调用链和当前组件名的 **新 `Context`**。此函数用于只需要传递调用链而不需要立即打印日志的场景。
- **实现细节**:
1. 函数接收 `upstreamCtx`, `selfCtx`, `funcName`
2. **获取上游调用链**: 从 `upstreamCtx` 中,通过 `chainKey` 读取出已经存在的调用链(`oldChain []string`)。
3. **获取当前组件名**: 从 `selfCtx` 中,通过 `compNameKey` 读取出当前组件的名称(`compName string`)。
4. **构建新节点**: 将 `compName``funcName` 拼接成一个新节点(例如 `\"组件2.Create\"`)。
5. **生成新调用链**: 将这个新节点追加到 `oldChain` 的末尾,形成 `newChain []string`
6. **创建新的 Context**:
- 使用 `context.WithValue`,以 `chainKey` 为键,将 `newChain` 存入一个新的 `Context`,我们称之为 `tmpCtx`
- 接着,**(关键修正)** 基于 `tmpCtx`,再次调用 `context.WithValue`,以 `compNameKey` 为键,将从 `selfCtx` 中获取的 `compName` 存入,得到最终的 `newCtx`。这确保了传向下游的 `Context` 正确地标识了当前组件。
7. **返回**: 返回 `newCtx`
3. **`logs.GetLogger(ctx) *Logger`**
- **职责**: 从 `Context` 中生成最终的、可用于打印的 `Logger` 实例。
- **实现细节**:
1. 函数接收一个 `context.Context`
2. 它从 `ctx` 中,通过 `chainKey` 读取出完整的调用链 `[]string`
3. 如果调用链不存在或为空,它就生成一个不带 `trace` 字段的普通 `Logger` 实例并返回。
4. 如果调用链存在,它就用 `->` 符号将切片中的所有节点拼接成一个完整的 `trace` 字符串。
5. 最后,它创建一个**一次性**的 `Logger` 实例,将这个 `trace` 字符串和底层的 `zap` 配置传给它,然后返回这个准备就绪的 `Logger`
4. **`logs.Trace(upstreamCtx, selfCtx, funcName) (context.Context, *Logger)`**
- **职责**: 作为 `AddFuncName``GetLogger` 的便捷封装,一步到位地完成调用链构建和 `Logger` 生成。用于需要立即打印日志的场景。
- **实现细节**:
1. 内部调用 `newCtx := logs.AddFuncName(upstreamCtx, selfCtx, funcName)`
2. 内部调用 `logger := logs.GetLogger(newCtx)`
3. 返回 `newCtx``logger`
### 3. 最终使用模式
#### a. 依赖注入阶段
(保持不变)在应用启动和组装依赖时,我们不再注入 `Logger` 对象,而是为每个组件创建一个包含其自身名称的专属 `Context`,并将这个 `Context` 注入到组件实例中。
- **流程**:
1. 在依赖注入的根源处,创建一个全局的、初始的 `context.Background()`
2. 对于需要被追踪的组件(例如 `组件1`),调用 `logs.AddCompName(ctx, \"组件1\")` 来创建一个 `ctxForC1`
3. 在创建 `组件1` 的实例时,将这个 `ctxForC1` 作为其成员变量(例如 `selfCtx`)保存起来。这个 `selfCtx` 就成了 `组件1` 的“身份名牌”。
#### b. 请求处理阶段
开发者可以根据需求灵活选择 API。
- **场景一:需要立即打印日志 (推荐)**:
1. 在方法入口处,立即调用 `ctx, logger := logs.Trace(upstreamCtx, z.selfCtx, \"Create\")`
2. 使用这个 `logger` 打印日志:`logger.Info(\"创建组件2\")`
3. 当需要调用下游方法时,将返回的 **新 `Context` (`ctx`)** 传递下去。
- **场景二:只需要传递调用链**:
1. 在方法入口处,调用 `ctx := logs.AddFuncName(upstreamCtx, z.selfCtx, \"Create\")`
2. 这个方法本身不打印日志,但在调用下游方法时,将这个包含了更新后调用链的 **新 `Context` (`ctx`)** 传递下去。
- **场景三:在方法中间打印日志**:
1. 一个方法可能在执行了一部分逻辑后才需要打印日志。
2. `ctx := logs.AddFuncName(upstreamCtx, z.selfCtx, \"Create\")` // 先在入口更新调用链
3. // ... 执行一些业务逻辑 ...
4. `logger := logs.GetLogger(ctx)` // 在需要时,基于更新后的 ctx 获取 logger
5. `logger.Info(\"业务逻辑执行到一半\")`
6. // ... 继续执行并传递 ctx ...

View File

@@ -0,0 +1,21 @@
# 需求
为日志系统提供一种机制,能够记录和追踪跨越不同对象和方法的调用链,以增强日志的可读性和可观测性。
## issue
http://git.huangwc.com/pig/pig-farm-controller/issues/56
## 描述
### 初始问题
在复杂的业务流程中,一个请求可能会流经多个服务或控制器。为了追踪一个完整的操作流程,开发者不得不在每个方法的日志中手动添加上下文信息(如 `actionType`),这非常繁琐且容易出错。
### 期望的演进
1. **初步设想**: 提供一个 `logger.With()` 方法,调用后返回一个携带预设信息的日志记录器副本,后续日志自动附带此信息。
2. **解决上下文覆盖**: 简单的 `With("action", "some_action")` 会导致同名字段被覆盖。因此,期望能将调用链信息存储在数组中,并在打印时拼接,如 `action: "action1·action2"`
3. **最终目标 (可观测性)**: 进一步区分“对象”和“方法”,构建出更具表现力的调用链。例如,当 `userController``Create` 方法调用 `userService``Create` 方法时,日志应能清晰地展示 `trace: "userController.Create->userService.Create"` 这样的调用关系。
## 实现方案
[实现方案](./implementation.md)

View File

@@ -998,6 +998,7 @@ const docTemplate = `{
},
{
"enum": [
7,
-1,
0,
1,
@@ -1007,12 +1008,12 @@ const docTemplate = `{
5,
-1,
5,
6,
7
6
],
"type": "integer",
"format": "int32",
"x-enum-varnames": [
"_numLevels",
"DebugLevel",
"InfoLevel",
"WarnLevel",
@@ -1022,8 +1023,7 @@ const docTemplate = `{
"FatalLevel",
"_minLevel",
"_maxLevel",
"InvalidLevel",
"_numLevels"
"InvalidLevel"
],
"name": "level",
"in": "query"
@@ -6859,6 +6859,7 @@ const docTemplate = `{
"type": "integer",
"format": "int32",
"enum": [
7,
-1,
0,
1,
@@ -6868,10 +6869,10 @@ const docTemplate = `{
5,
-1,
5,
6,
7
6
],
"x-enum-varnames": [
"_numLevels",
"DebugLevel",
"InfoLevel",
"WarnLevel",
@@ -6881,8 +6882,7 @@ const docTemplate = `{
"FatalLevel",
"_minLevel",
"_maxLevel",
"InvalidLevel",
"_numLevels"
"InvalidLevel"
]
}
},

View File

@@ -990,6 +990,7 @@
},
{
"enum": [
7,
-1,
0,
1,
@@ -999,12 +1000,12 @@
5,
-1,
5,
6,
7
6
],
"type": "integer",
"format": "int32",
"x-enum-varnames": [
"_numLevels",
"DebugLevel",
"InfoLevel",
"WarnLevel",
@@ -1014,8 +1015,7 @@
"FatalLevel",
"_minLevel",
"_maxLevel",
"InvalidLevel",
"_numLevels"
"InvalidLevel"
],
"name": "level",
"in": "query"
@@ -6851,6 +6851,7 @@
"type": "integer",
"format": "int32",
"enum": [
7,
-1,
0,
1,
@@ -6860,10 +6861,10 @@
5,
-1,
5,
6,
7
6
],
"x-enum-varnames": [
"_numLevels",
"DebugLevel",
"InfoLevel",
"WarnLevel",
@@ -6873,8 +6874,7 @@
"FatalLevel",
"_minLevel",
"_maxLevel",
"InvalidLevel",
"_numLevels"
"InvalidLevel"
]
}
},

View File

@@ -1950,6 +1950,7 @@ definitions:
- PlanTypeFilterSystem
zapcore.Level:
enum:
- 7
- -1
- 0
- 1
@@ -1960,10 +1961,10 @@ definitions:
- -1
- 5
- 6
- 7
format: int32
type: integer
x-enum-varnames:
- _numLevels
- DebugLevel
- InfoLevel
- WarnLevel
@@ -1974,7 +1975,6 @@ definitions:
- _minLevel
- _maxLevel
- InvalidLevel
- _numLevels
info:
contact:
email: divano@example.com
@@ -2546,6 +2546,7 @@ paths:
name: end_time
type: string
- enum:
- 7
- -1
- 0
- 1
@@ -2556,12 +2557,12 @@ paths:
- -1
- 5
- 6
- 7
format: int32
in: query
name: level
type: integer
x-enum-varnames:
- _numLevels
- DebugLevel
- InfoLevel
- WarnLevel
@@ -2572,7 +2573,6 @@ paths:
- _minLevel
- _maxLevel
- InvalidLevel
- _numLevels
- enum:
- 邮件
- 企业微信

View File

@@ -10,15 +10,17 @@ RELAY_API.md
TODO-List.txt
config.example.yml
config.yml
design/verification-before-device-deletion/add_get_device_id_configs_to_task.md
design/verification-before-device-deletion/check_before_device_deletion.md
design/verification-before-device-deletion/device_task_association_maintenance.md
design/verification-before-device-deletion/device_task_many_to_many_design.md
design/verification-before-device-deletion/index.md
design/verification-before-device-deletion/plan_service_refactor.md
design/verification-before-device-deletion/plan_service_refactor_to_domain.md
design/verification-before-device-deletion/refactor_deletion_check.md
design/verification-before-device-deletion/refactor_id_conversion.md
design/archive/2025-11-3-verification-before-device-deletion/add_get_device_id_configs_to_task.md
design/archive/2025-11-3-verification-before-device-deletion/check_before_device_deletion.md
design/archive/2025-11-3-verification-before-device-deletion/device_task_association_maintenance.md
design/archive/2025-11-3-verification-before-device-deletion/device_task_many_to_many_design.md
design/archive/2025-11-3-verification-before-device-deletion/index.md
design/archive/2025-11-3-verification-before-device-deletion/plan_service_refactor.md
design/archive/2025-11-3-verification-before-device-deletion/plan_service_refactor_to_domain.md
design/archive/2025-11-3-verification-before-device-deletion/refactor_deletion_check.md
design/archive/2025-11-3-verification-before-device-deletion/refactor_id_conversion.md
design/provide-logger-with-mothed/implementation.md
design/provide-logger-with-mothed/index.md
docs/docs.go
docs/swagger.json
docs/swagger.yaml