From b6c2fa58f5ae831be1bfc83862a4bbd2db241a0c Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Mon, 3 Nov 2025 20:39:31 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=B6=E5=AE=9A=E6=96=B9=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../implementation.md | 89 +++++++++++++++++++ design/provide-logger-with-mothed/index.md | 21 +++++ docs/docs.go | 16 ++-- docs/swagger.json | 16 ++-- docs/swagger.yaml | 8 +- project_structure.txt | 20 +++-- 6 files changed, 141 insertions(+), 29 deletions(-) create mode 100644 design/provide-logger-with-mothed/implementation.md create mode 100644 design/provide-logger-with-mothed/index.md diff --git a/design/provide-logger-with-mothed/implementation.md b/design/provide-logger-with-mothed/implementation.md new file mode 100644 index 0000000..9aba8c8 --- /dev/null +++ b/design/provide-logger-with-mothed/implementation.md @@ -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 ... diff --git a/design/provide-logger-with-mothed/index.md b/design/provide-logger-with-mothed/index.md new file mode 100644 index 0000000..8aec52d --- /dev/null +++ b/design/provide-logger-with-mothed/index.md @@ -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) diff --git a/docs/docs.go b/docs/docs.go index 65016db..4b50bd2 100644 --- a/docs/docs.go +++ b/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" ] } }, diff --git a/docs/swagger.json b/docs/swagger.json index 3351636..c6061bd 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -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" ] } }, diff --git a/docs/swagger.yaml b/docs/swagger.yaml index c37a73a..c143ecf 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -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: - 邮件 - 企业微信 diff --git a/project_structure.txt b/project_structure.txt index 767be01..372465c 100644 --- a/project_structure.txt +++ b/project_structure.txt @@ -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