制定方案
This commit is contained in:
89
design/provide-logger-with-mothed/implementation.md
Normal file
89
design/provide-logger-with-mothed/implementation.md
Normal 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 ...
|
||||
21
design/provide-logger-with-mothed/index.md
Normal file
21
design/provide-logger-with-mothed/index.md
Normal 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)
|
||||
16
docs/docs.go
16
docs/docs.go
@@ -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"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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:
|
||||
- 邮件
|
||||
- 企业微信
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user