Repository: apache/dubbo-go-pixiu

Issue:
[BUG] review result of doubao
### ✅ 验证清单

- [x] 🔍 我已经搜索过 [现有 Issues](https://github.com/apache/dubbo-go-pixiu/issues)，确信这不是重复问题

### 🚀 Go 版本

1.25

### 📦 Dubbo-go-pixiu 版本

v1

### 🖥️ 服务端配置

_No response_

### 💻 客户端配置

_No response_

### 🌐 协议配置

_No response_

### 📋 注册中心配置

_No response_

### 💾 操作系统

🪟 Windows

### 📝 Bug 描述

# dubbo-go-pixiu 代码静态分析报告
本次分析基于提供的 Go、JavaScript、YAML 三类代码文件，从**错误处理、资源管理、代码健壮性、配置合理性、最佳实践**等维度展开，梳理潜在问题与优化点。


## 一、Go 代码分析（核心后端逻辑）
### 1. 错误处理缺陷
#### 1.1 直接 Panic 导致程序崩溃（高危）
- **文件**：`pkg/adapter/dubboregistry/registry/registry.go`
- **问题代码**：
  ```go
  if err != nil {
      panic("Initialize Registry" + name + "failed due to: " + err.Error())
  }
  ```
- **分析**：注册中心初始化失败时直接 `panic`，会导致整个进程崩溃，生产环境中应**返回错误让上层优雅处理**（如重试、降级），而非终止服务。
- **优化建议**：
  ```go
  if err != nil {
      logger.Errorf("Initialize Registry %s failed: %v", name, err)
      return nil, err // 返回错误，由调用方决定后续逻辑
  }
  ```

#### 1.2 忽略错误信息（中危）
- **文件**：`pkg/config/config_load.go`
- **问题代码**：
  ```go
  if configPath != "" && CheckYamlFormat(configPath) {
      RegisterConfigLoadFunc(LoadYAMLConfig)
  }
  ```
- **分析**：`CheckYamlFormat` 仅判断布尔值，忽略了格式检查失败的具体原因（如文件不存在、语法错误），不利于排查问题。
- **优化建议**：修改 `CheckYamlFormat` 返回 `(bool, error)`，显式处理错误：
  ```go
  valid, err := CheckYamlFormat(configPath)
  if configPath != "" && valid {
      RegisterConfigLoadFunc(LoadYAMLConfig)
  } else if err != nil {
      logger.Warnf("YAML config format check failed: %v", err)
  }
  ```

#### 1.3 错误信息缺乏上下文（低危）
- **文件**：`pkg/client/proxy/reflection.go`
- **问题代码**：
  ```go
  return nil, errors.New("method not found upstream")
  ```
- **分析**：错误信息未包含「服务名」和「方法名」，排查时无法定位具体是哪个服务的哪个方法不存在。
- **优化建议**：
  ```go
  return nil, fmt.Errorf("method %s not found in service %s upstream", methodName, serviceName)
  ```


### 2. 资源与数据处理问题
#### 2.1 切片复制逻辑错误（高危）
- **文件**：`pkg/common/router/trie/trie.go`
- **问题代码**：
  ```go
  newList := []string{key}
  copy(newList[1:], pathVariableList) // newList[1:] 是空切片，复制无效果
  ```
- **分析**：`newList` 初始长度为 1，`newList[1:]` 是长度为 0 的空切片，`copy` 无法将 `pathVariableList` 的元素复制进去，导致路径变量丢失。
- **优化建议**：使用 `append` 拼接切片：
  ```go
  newList := append([]string{key}, pathVariableList...)
  ```

#### 2.2 重复代码冗余（低危）
- **文件**：`pkg/client/dubbo/mapper.go`、`pkg/client/http/mapper.go`
- **问题**：两者均包含「读取请求体 → 延迟恢复 Body」的重复逻辑：
  ```go
  rawBody, err := io.ReadAll(c.IngressRequest.Body)
  defer func() {
      c.IngressRequest.Body = io.NopCloser(bytes.NewReader(rawBody))
  }()
  ```
- **优化建议**：抽象为公共函数（如 `pkg/common/http/body.go` 中的 `ReadAndRestoreBody`），减少代码冗余。


### 3. 代码健壮性不足
#### 3.1 未处理默认分支（中危）
- **文件**：`pkg/filter/failinject/filter.go`
- **问题代码**：
  ```go
  switch rule.TriggerType {
  case TriggerTypeAlways: return true
  case TriggerTypePercentage: return percentage(rule.Odds)
  case TriggerTypeRandom: return random()
  default: return false // 无显式默认分支，依赖语言默认行为
  }
  ```
- **分析**：虽逻辑上返回 `false`，但未显式写 `default` 分支，代码可读性差，且若新增 `TriggerType` 易遗漏处理。
- **优化建议**：显式添加 `default` 分支并记录日志：
  ```go
  default:
      logger.Warnf("unknown failinject trigger type: %v", rule.TriggerType)
      return false
  ```

#### 3.2 类型宽泛导致安全隐患（中危）
- **文件**：`pkg/model/trace.go`
- **问题代码**：
  ```go
  type TracerConfig struct {
      // ...
      Config map[string]any `yaml:"config" json:"config" mapstructure:"config"`
  }
  ```
- **分析**：`Config` 字段为 `map[string]any`，类型过于宽泛，使用时需频繁类型断言，易引发 `nil panic` 或类型错误。
- **优化建议**：根据实际配置项定义具体结构体（如 `JaegerConfig`、`ZipkinConfig`），通过 `mapstructure` 动态解析。


### 4. 测试代码问题
#### 4.1 未定义变量导致测试失败（高危）
- **文件**：`pkg/common/http/manager_test.go`
- **问题代码**：
  ```go
  select {
  case event := <-eventCh: // eventCh 未定义，编译失败
      logger.Info("Received event: %s", strings.ReplaceAll(event, "\n", "\\n"))
  // ...
  }
  ```
- **分析**：`eventCh` 未在测试函数中初始化（如 `eventCh := make(chan string)`），导致测试代码无法编译运行。
- **优化建议**：补充 `eventCh` 定义，并确保与上游服务的事件发送逻辑对齐。

#### 4.2 依赖未暴露函数（中危）
- **文件**：`pkg/cluster/loadbalancer/weightrandom/weight_random_test.go`
- **问题**：测试函数依赖 `createWeightedClusterConfig`，但该函数未在代码片段中定义，若函数实现有缺陷（如权重计算错误），会导致测试结果不可靠。
- **优化建议**：在测试文件中显式定义该函数，或从公共包导入，确保测试依赖可追溯。


## 二、JavaScript 代码分析（前端/工具类）
### 1. Socket 客户端逻辑缺陷
#### 1.1 心跳消息 JSON 格式错误（高危）
- **文件**：`admin/web/src/utils/socket.js`
- **问题代码**：
  ```go
  this.send('{"bissnessType":"doSale","chargeWay":"03","data":"{amount:1}"}')
  ```
- **分析**：`data` 字段的值 `{amount:1}` 不是合法 JSON（缺少引号），服务端解析时会报错，导致心跳失效。
- **优化建议**：使用 `JSON.stringify` 构造合法 JSON：
  ```javascript
  const heartMsg = JSON.stringify({
    bissnessType: "doSale",
    chargeWay: "03",
    data: JSON.stringify({ amount: 1 }) // 嵌套 JSON 需二次序列化
  });
  this.send(heartMsg);
  ```

#### 1.2 重连次数未重置（中危）
- **文件**：`admin/web/src/utils/socket.js`
- **问题**：`reconnectTimes` 每次重连后递减，但重连成功后未重置为初始值，下次断开时会继续减少，导致重连次数提前耗尽。
- **优化建议**：在 `onopen` 事件中重置 `reconnectTimes`：
  ```javascript
  this._ws.onopen = () => {
    this._alive = true;
    this.reconnectTimes = this._params.reconnectTimes; // 重置为初始值
    clearInterval(this._reconnect_timer);
  };
  ```

#### 1.3 未处理 `undefined` 类型（低危）
- **文件**：`admin/web/src/utils/socket.js`
- **问题代码**：
  ```javascript
  if (text === 'undefined') return; // 仅判断字符串"undefined"，忽略undefined类型
  ```
- **分析**：若传入 `undefined`（非字符串），判断失效，会执行后续逻辑导致错误。
- **优化建议**：补充 `undefined` 类型判断：
  ```javascript
  if (text === undefined || text === 'undefined') return;
  ```


### 2. 工具函数逻辑问题
#### 2.1 URL 参数未解码（中危）
- **文件**：`admin/web/src/utils/common.js`
- **问题代码**：
  ```javascript
  obj[item[0]] = item[1]; // 未处理 URL 编码（如空格→%20、中文→%E4%B8%AD）
  ```
- **分析**：若参数值包含特殊字符，会导致解析结果错误（如 `name=张三` 解析为 `name=%E5%BC%A0%E4%B8%89`）。
- **优化建议**：使用 `decodeURIComponent` 解码：
  ```javascript
  obj[item[0]] = decodeURIComponent(item[1]);
  ```

#### 2.2 数字处理忽略 `0` 值（低危）
- **文件**：`admin/web/src/utils/common.js`
- **问题代码**：
  ```javascript
  if (val) { // 0 会被当作 false，跳过处理
    // 逗号拼接逻辑
  }
  ```
- **分析**：当 `val` 为 `0` 时，`if (val)` 为 `false`，无法处理 `0` 的逗号格式化（如 `0` → `0`）。
- **优化建议**：调整判断条件，排除 `undefined`/`null`/空字符串：
  ```javascript
  if (val !== undefined && val !== null && val !== '') {
    // 逗号拼接逻辑
  }
  ```


## 三、YAML 配置文件分析（构建/ lint 配置）
### 1. .golangci.yaml 配置优化
#### 1.1 未启用已配置的 linter（低危）
- **问题**：`linters-settings` 中配置了 `gocyclo`、`dupl`、`goconst` 等 linter，但 `linters.enable` 仅启用 5 个基础 linter，浪费配置资源。
- **优化建议**：根据需求启用更多 linter，提升代码质量检查维度：
  ```yaml
  linters:
    disable-all: true
    enable:
      - govet
      - staticcheck
      - ineffassign
      - gofmt
      - misspell
      - gocyclo # 检测循环复杂度
      - dupl # 检测重复代码
      - goconst # 检测重复常量
      - depguard # 控制依赖包引入
  ```

#### 1.2 全局排除 SA1019（中危）
- **问题**：`exclude-rules` 全局排除 `SA1019`（废弃 API 使用），可能隐藏潜在的兼容性问题（如依赖包 API 废弃导致升级风险）。
- **优化建议**：仅针对特定文件/场景排除，而非全局禁用：
  ```yaml
  issues:
    exclude-rules:
      - linters: [staticcheck]
        text: "SA1019:"
        path: "pkg/legacy/.*" # 仅排除遗留代码目录
  ```


### 2. github-actions.yml 配置缺陷
#### 2.1 缺乏多版本 Go 测试（中危）
- **问题**：`lint` 任务仅测试 Go 1.23，未覆盖 1.21、1.22 等常用版本，可能导致版本兼容性问题。
- **优化建议**：扩展 `go_version` 矩阵：
  ```yaml
  matrix:
    go_version:
      - 1.21
      - 1.22
      - 1.23
    os:
      - ubuntu-latest
  ```

#### 2.2 分支合并逻辑风险（低危）
- **问题代码**：
  ```bash
  git checkout -b develop origin/develop # 若本地已有 develop 分支，命令失败
  ```
- **分析**：若 CI 环境中已存在 `develop` 分支，`git checkout -b` 会报错，中断 CI 流程。
- **优化建议**：先拉取远程分支，避免分支创建冲突：
  ```bash
  git fetch origin develop
  git checkout develop
  git pull origin develop
  ```


## 四、共性问题与通用优化建议
### 1. 代码风格一致性
- **Go 代码**：统一错误创建方式（优先用 `fmt.Errorf` 带上下文，`errors.Wrap` 包装底层错误），补充函数注释（遵循 Go Doc 规范，说明功能、参数、返回值）。
- **JavaScript 代码**：统一函数定义风格（事件处理用普通函数避免 `this` 绑定问题），添加 JSDoc 类型注释（如 `@param {string} url - WebSocket 连接地址`）。

### 2. 硬编码常量管理
- 集中管理硬编码字符串（如 HTTP 头 `constant.HeaderKeyContextType`、错误信息、Socket 心跳类型），定义在 `pkg/common/constant` 或前端 `src/constants` 目录，避免散落在业务代码中。

### 3. 类型安全增强
- Go 端：减少 `any` 类型使用，优先定义具体结构体；JavaScript 端：建议迁移到 TypeScript，或通过 JSDoc 强化类型检查（如 `/** @type {Array<{id: string, weight: number}>} */`）。


## 五、风险等级汇总
| 风险等级 | 问题数量 | 核心问题示例 |
|----------|----------|--------------|
| 高危     | 4        | 切片复制错误、Socket 心跳 JSON 错误、测试未定义变量、直接 Panic |
| 中危     | 6        | 重连次数未重置、URL 参数未解码、全局排除 SA1019、类型宽泛 |
| 低危     | 8        | 错误信息缺乏上下文、重复代码冗余、硬编码、多版本测试缺失 |

建议优先修复**高危问题**（如切片复制、Panic 崩溃），其次处理中危问题（如重连逻辑、参数解码），最后优化低危问题（如代码风格、配置调整），以提升代码稳定性与可维护性。

### 🔄 重现步骤

按照上面review结果改进代码

### ✅ 预期行为

改进代码

### ❌ 实际行为

改进代码

### 💡 可能的解决方案

_No response_

Hints from issue comments:
@AlexStocks: # dubbo-go-pixiu 补充静态分析报告
基于新增的 `README.md` 文档及未深入挖掘的代码片段，从**文档完整性、代码逻辑一致性、测试有效性、前端工具函数健壮性、Issue 模板规范性**等维度，补充以下潜在问题与优化点。


## 一、文档类问题（README.md 与示例文档）
### 1. Quick Start 步骤指导性不足（低危）
#### 1.1 绝对路径说明模糊
- **文件**：`dubbo-go-pixiu/README.md`（Quick Start Step 3）
- **问题代码**：
  ```shell
  go run cmd/pixiu/*.go gateway start -c /[absolute-path]/dubbo-go-pixiu-samples/http/simple/pixiu/conf.yaml
  ```
- **分析**：未明确告知用户如何获取「`dubbo-go-pixiu-samples` 的绝对路径」（如 Windows 用 `cd /d 路径 && echo %cd%`，Linux/macOS 用 `pwd`），新手用户易卡壳。
- **优化建议**：补充路径获取命令示例：
  ```shell
  # 1. 先获取 samples 绝对路径（在 samples 目录执行）
  # Windows
  cd /d C:\your-path\dubbo-go-pixiu-samples
  echo %cd%  # 输出绝对路径，如 C:\xxx\dubbo-go-pixiu-samples
  # Linux/macOS
  cd /your-path/dubbo-go-pixiu-samples
  pwd  # 输出绝对路径，如 /xxx/dubbo-go-pixiu-samples

  # 2. 替换路径启动 Pixiu
  go run cmd/pixiu/*.go gateway start -c /[上述输出路径]/http/simple/pixiu/conf.yaml
  ```

#### 1.2 测试步骤缺乏备选方案
- **文件**：`dubbo-go-pixiu/README.md`（Quick Start Step 4）
- **问题**：仅提供「运行测试文件」和「执行 `request.sh`」两种方式，但未说明：
  1. 若 `request.sh` 不存在（如 Windows 无 shell 环境）如何手动测试；
  2. 测试请求的具体 URL、参数格式（如 HTTP 方法、请求体）。
- **优化建议**：补充 curl 手动测试命令：
  ```shell
  # 手动测试示例（假设后端服务提供 /hello 接口）
  curl -X GET http://localhost:8888/hello?name=Pixiu
  # 预期响应：{"message":"Hello Pixiu"}
  ```

#### 1.3 错误文档排查指引缺失
- **文件**：`dubbo-go-pixiu/docs/sample/dubbo/dubbo-error.md`
- **问题**：仅给出「No provider info」的错误日志，但未说明：
  1. 该错误的核心原因（如注册中心无服务、服务未上线、配置的协议不匹配）；
  2. 具体排查步骤（如检查注册中心连接、查看服务列表、验证 Pixiu 集群配置）。
- **优化建议**：补充排查流程：
  ```markdown
  ## No provider info 排查步骤
  1. **检查注册中心连接**：确认 Pixiu 配置的注册中心（Nacos/Zookeeper）地址正确，且网络可通；
  2. **验证服务注册状态**：在注册中心控制台查看目标 Dubbo 服务是否已上线（如 Nacos 服务列表中存在该服务）；
  3. **核对集群配置**：检查 Pixiu 配置文件中 `clusters` 节点的 `protocol` 是否与 Dubbo 服务一致（如 `dubbo`/`triple`）；
  4. **查看 Pixiu 日志**：搜索日志中的「registry discover」关键词，确认是否成功拉取服务列表。
  ```


## 二、Go 代码逻辑补充问题
### 1. 错误信息拼写不一致（中危）
- **文件**：`dubbo-go-pixiu/pkg/client/dubbo/mapper.go`
- **问题代码**：
  ```go
  return nil, errors.New("Target params for dubbo backend must be *dubbogoTarget")
  ```
- **分析**：错误信息中写的是 `*dubbogoTarget`，但函数返回类型是 `*dubboTarget`（结构体名少了「go」），拼写不一致会误导开发者排查问题（如搜索 `dubbogoTarget` 找不到对应类型）。
- **优化建议**：修正错误信息中的类型名：
  ```go
  return nil, errors.New("Target params for dubbo backend must be *dubboTarget")
  ```

### 2. HTTP 响应头常量错误（高危）
- **文件**：`dubbo-go-pixiu/pkg/context/http/context.go`
- **问题代码**：
  ```go
  hc.AddHeader(constant.HeaderKeyContextType, constant.HeaderValueApplicationJson)
  ```
- **分析**：HTTP 标准响应头中表示「内容类型」的是 `Content-Type`，而非 `Context-Type`（推测是常量名拼写错误）。若 `constant.HeaderKeyContextType` 定义为 `Context-Type`，会导致客户端无法正确解析响应格式（如浏览器将 JSON 当作纯文本处理）。
- **优化建议**：
  1. 修正常量定义（如 `constant.go` 中）：
     ```go
     // 原错误定义
     // HeaderKeyContextType = "Context-Type"
     // 修正后
     HeaderKeyContentType = "Content-Type"
     ```
  2. 更新引用处：
     ```go
     hc.AddHeader(constant.HeaderKeyContentType, constant.HeaderValueApplicationJson)
     ```

### 3. 空结构体无实现（低危）
- **文件**：`dubbo-go-pixiu/pkg/logger/triple.go`
- **问题代码**：
  ```go
  type TripleLogger struct {
  }
  ```
- **分析**：`TripleLogger` 结构体无任何字段和方法实现，若用于日志记录（如 `zap.Logger` 或自定义日志接口），会导致调用时 panic（如 `logger.Info()` 无实现），属于未完成的代码。
- **优化建议**：
  1. 若需保留该结构体，补充日志接口实现（如适配 Triple 协议的日志格式）；
  2. 若无需使用，删除该空结构体及相关引用，避免代码冗余。


## 三、测试代码有效性问题
### 1. 测试用例名称重复（中危）
- **文件**：`dubbo-go-pixiu/pkg/cluster/loadbalancer/weightrandom/weight_random_test.go`
- **问题代码**：
  ```go
  tests := []struct {
      {
          name: "some endpoints with default weight", // 重复名称
          endpointsWeights: map[string]int{"x": 2, "y": 1, "z": 3},
          // ...
      },
      {
          name: "some endpoints with default weight", // 相同名称
          endpointsWeights: map[string]int{"x": 2, "y": 0, "z": 3},
          // ...
      },
  }
  ```
- **分析**：两个测试用例名称完全相同，会导致：
  1. 测试报告中无法区分两个用例的结果；
  2. 若其中一个用例失败，难以定位是「权重1」还是「权重0」的场景。
- **优化建议**：修改为唯一名称，明确场景差异：
  ```go
  {
      name: "some endpoints with default weight (y=1)",
      // ...
  },
  {
      name: "some endpoints with zero weight (y=0)",
      // ...
  },
  ```

### 2. 权重为0的端点未显式断言（低危）
- **文件**：`dubbo-go-pixiu/pkg/cluster/loadbalancer/weightrandom/weight_random_test.go`（第二个重复用例）
- **问题**：用例中 `y` 的权重为0，预期概率0，但仅通过「`counts[y]/iterations` 是否接近0」判断，未显式断言「`counts[y]` 必须为0」。若负载均衡逻辑错误（如权重0的端点仍被选中），低迭代次数下可能因概率波动未触发失败。
- **优化建议**：补充显式断言（针对权重0的端点）：
  ```go
  for id, expectedProb := range tt.expectedProbs {
      actualProb := float64(counts[id]) / float64(tt.iterations)
      if diff := abs(actualProb - expectedProb); diff > tt.tolerance {
          t.Errorf("Endpoint %s: expected probability %f, got %f (difference %f > tolerance %f)",
              id, expectedProb, actualProb, diff, tt.tolerance)
      }
      // 对权重0的端点，强制断言计数为0
      if expectedProb == 0 && counts[id] != 0 {
          t.Errorf("Endpoint %s: expected 0 selections (weight=0), got %d", id, counts[id])
      }
  }
  ```


## 四、前端工具函数健壮性问题
### 1. URL 参数解析丢失特殊字符（中危）
- **文件**：`dubbo-go-pixiu/admin/web/src/utils/common.js`（`getLocationSearchObj` 函数）
- **问题代码**：
  ```javascript
  item = arr[i].split('=')
  obj[item[0]] = item[1]
  ```
- **分析**：若 URL 参数值包含 `=`（如 `?query=a=b=c`），`split('=')` 会将其分割为 `["query", "a", "b", "c"]`，导致 `obj.query` 仅赋值为 `a`，丢失后续的 `=b=c`，解析结果错误。
- **优化建议**：使用 `split('=', 2)` 限制分割次数（仅分割第一个 `=`）：
  ```javascript
  item = arr[i].split('=', 2); // 分割为 [key, value]，value 含后续 =
  obj[item[0]] = item[1] || ''; // 处理无值的参数（如 ?key=）
  ```


## 五、Issue 模板规范性问题
### 1. 提案模板字段不完整（低危）
- **文件**：`dubbo-go-pixiu/.github/ISSUE_TEMPLATE/#3_proposal_zh.yml`
- **问题代码**：
  ```yaml
  - type: textarea
    id: proposal-summary
  ```
- **分析**：`textarea` 字段仅定义 `id`，缺少 `label`（显示名称）、`description`（字段说明）和 `validations`（是否必填），用户提交提案时无法明确该字段的填写要求，可能导致内容不规范。
- **优化建议**：补充字段配置：
  ```yaml
  - type: textarea
    id: proposal-summary
    attributes:
      label: "📝 提案摘要"
      description: "简要描述提案的核心目标和解决的问题（不超过200字）"
      placeholder: "例如：为 Pixiu 新增 Redis 缓存插件，解决 LLM 服务响应慢的问题"
    validations:
      required: true # 摘要为必填项
  ```


## 六、版本兼容性文档缺失（低危）
- **文件**：`dubbo-go-pixiu/README.md`（Prerequisites 部分）
- **问题**：仅说明「Go 1.17 或更高」，但未明确：
  1. Go 1.17~1.23 之间的版本是否均经过测试（如是否兼容 1.18 的泛型特性）；
  2. 依赖的第三方库（如 `dubbo-go`、`sentinel-go`）对应的兼容版本。
- **优化建议**：补充版本兼容性表格：
  ```markdown
  ## 版本兼容性说明
  | 组件               | 兼容版本范围       |
  |--------------------|--------------------|
  | Go                 | 1.17 ~ 1.23        |
  | Dubbo-go           | v3.0.5 ~ v3.2.0    |
  | Sentinel-go        | v1.8.0 ~ v1.10.0   |
  | Nacos              | v2.0.0 ~ v2.3.0    |
  | Zookeeper          | v3.6.0 ~ v3.8.0    |
  ```


## 补充风险等级汇总
| 风险等级 | 问题数量 | 核心问题示例 |
|----------|----------|--------------|
| 高危     | 1        | HTTP 响应头常量错误（导致客户端解析失败） |
| 中危     | 3        | 错误信息拼写不一致、URL 参数解析丢失字符、测试用例名称重复 |
| 低危     | 5        | 文档步骤模糊、空结构体无实现、Issue 模板字段不完整、版本兼容性缺失、权重0端点未显式断言 |

建议优先修复**高危的响应头常量错误**（直接影响客户端交互），其次处理中危的参数解析和错误信息问题，最后补充文档和测试的细节优化，进一步提升项目的可用性和可维护性。@Alanxtl: #766

一
1.1: fix
1.2: 忽略
1.3: fix
2.1: 忽略
2.2: 忽略
3.1: fix
3.2: 忽略
4.1: 忽略
4.2: 忽略

二
1.1: 忽略
1.2: 忽略
1.3: fix
2.1: 忽略
2.2: fix

三
1.1: fix
1.2: fix
2.1: 忽略
2.2: 忽略

四
1: 忽略
2: 忽略
3: 忽略

一
1.1: 忽略
1.2: 忽略
1.3: 忽略

二
1: fix
2: fix
3: 忽略

三
1: fix
2: fix

四
1: 忽略

五
1: 忽略

六
1: 忽略


Solution patch:
diff --git a/.golangci.yaml b/.golangci.yaml
index 7417bdde..4570cfc7 100644
--- a/.golangci.yaml
+++ b/.golangci.yaml
@@ -10,6 +10,7 @@ linters:
     - ineffassign
     - misspell
     - staticcheck
+    - depguard
   settings:
     depguard:
       rules:
@@ -51,6 +52,9 @@ linters:
       - linters:
           - staticcheck
         text: 'SA1019:'
+      - linters:
+          - staticcheck
+        text: 'QF1008:'
     paths:
       - test/testdata_etc
       - pkg/golinters/goanalysis/(checker|passes)
diff --git a/admin/web/src/utils/common.js b/admin/web/src/utils/common.js
index 60c5ef98..ddc2c9f3 100644
--- a/admin/web/src/utils/common.js
+++ b/admin/web/src/utils/common.js
@@ -109,7 +109,7 @@ export function getDistance (lat1, lng1, lat2, lng2) {
  * @return 返回已拼接的数值
 */
 export function numJoint (val) {
-  if (val) {
+  if (val !== undefined && val !== null && val !== '') {
     val = val.toString()
     // 是否包含小数
     let idx = val.indexOf('.')
diff --git a/admin/web/src/utils/socket.js b/admin/web/src/utils/socket.js
index ceb54a2d..01e4535e 100644
--- a/admin/web/src/utils/socket.js
+++ b/admin/web/src/utils/socket.js
@@ -111,7 +111,7 @@ class socket {
 
     // 发送消息
     send(text) {
-        if (text === 'undefined') return
+        if (text === undefined || text === 'undefined') return;
         if (this._alive === true) {
             text = typeof text === 'string' ? text : JSON.stringify(text)
             this._ws.send(text)
diff --git a/pkg/adapter/dubboregistry/registry/registry.go b/pkg/adapter/dubboregistry/registry/registry.go
index d8a8d8ac..48b7f3d7 100644
--- a/pkg/adapter/dubboregistry/registry/registry.go
+++ b/pkg/adapter/dubboregistry/registry/registry.go
@@ -34,6 +34,7 @@ import (
 import (
 	common2 "github.com/apache/dubbo-go-pixiu/pkg/adapter/dubboregistry/common"
 	"github.com/apache/dubbo-go-pixiu/pkg/common/constant"
+	"github.com/apache/dubbo-go-pixiu/pkg/logger"
 	"github.com/apache/dubbo-go-pixiu/pkg/model"
 )
 
@@ -85,7 +86,7 @@ func GetRegistry(name string, regConfig model.Registry, listener common2.Registr
 	if registry, ok := registryMap[regConfig.Protocol]; ok {
 		reg, err := registry(regConfig, listener)
 		if err != nil {
-			panic("Initialize Registry" + name + "failed due to: " + err.Error())
+			logger.Warnf("Initialize Registry %s failed due to: %s", name, err.Error())
 		}
 		return reg, nil
 	}
diff --git a/pkg/client/dubbo/mapper.go b/pkg/client/dubbo/mapper.go
index 3ef21ef3..18a60fd6 100644
--- a/pkg/client/dubbo/mapper.go
+++ b/pkg/client/dubbo/mapper.go
@@ -189,7 +189,7 @@ func (um uriMapper) Map(mp config.MappingParam, c *client.Request, target any, o
 func validateTarget(target any) (*dubboTarget, error) {
 	val, ok := target.(*dubboTarget)
 	if !ok {
-		return nil, errors.New("Target params for dubbo backend must be *dubbogoTarget")
+		return nil, errors.New("Target params for dubbo backend must be *dubboTarget")
 	}
 	return val, nil
 }
diff --git a/pkg/client/proxy/reflection.go b/pkg/client/proxy/reflection.go
index 166017a5..cf40f9ae 100644
--- a/pkg/client/proxy/reflection.go
+++ b/pkg/client/proxy/reflection.go
@@ -19,6 +19,7 @@ package proxy
 
 import (
 	"context"
+	"fmt"
 )
 
 import (
@@ -67,7 +68,7 @@ func (r *Reflector) CreateInvocation(ctx context.Context, serviceName, methodNam
 
 	methodDesc := serviceDesc.FindMethodByName(methodName)
 	if methodDesc == nil {
-		return nil, errors.New("method not found upstream")
+		return nil, fmt.Errorf("method %s not found in service %s upstream", methodName, serviceName)
 	}
 	inputMessage := dynamic.NewMessage(methodDesc.GetInputType())
 	err = inputMessage.UnmarshalJSON(input)
diff --git a/pkg/cmd/gateway.go b/pkg/cmd/gateway.go
index 96bb4ae5..84a7a992 100644
--- a/pkg/cmd/gateway.go
+++ b/pkg/cmd/gateway.go
@@ -39,15 +39,6 @@ import (
 )
 
 var (
-	flagToLogLevel = map[string]string{
-		"trace":    "TRACE",
-		"debug":    "DEBUG",
-		"info":     "INFO",
-		"warning":  "WARN",
-		"error":    "ERROR",
-		"critical": "FATAL",
-	}
-
 	configPath    string
 	apiConfigPath string
 	logConfigPath string
@@ -165,7 +156,7 @@ func initDefaultValue() {
 	}
 }
 
-// initLog
+// initLog initializes logger according to log config file and log level
 func initLog() error {
 	err := logger.InitLog(logConfigPath)
 	if err != nil {
@@ -173,13 +164,11 @@ func initLog() error {
 		return err
 	}
 
-	if level, ok := flagToLogLevel[logLevel]; ok {
-		logger.SetLoggerLevel(level)
-	} else {
-		logger.SetLoggerLevel(flagToLogLevel[constant.DefaultLogLevel])
-		return fmt.Errorf("logLevel is invalid, set log level to default: %s", constant.DefaultLogLevel)
+	lvl := logger.ParseLogLevel(logLevel)
+	if ok := logger.SetLoggerLevel(lvl); !ok {
+		err = fmt.Errorf("set logLevel failed")
 	}
-	return nil
+	return err
 }
 
 func initLogWithConfig(boot *model.Bootstrap) {
@@ -189,7 +178,7 @@ func initLogWithConfig(boot *model.Bootstrap) {
 }
 
 // nolint
-// initApiConfig return value of the bool is for the judgment of whether is a api meta data error, a kind of silly (?)
+// initApiConfig return value of the bool is for the judgment of whether is an api metadata error or a kind of silly (?)
 func initApiConfig() (*model.Bootstrap, error) {
 	bootstrap := config.Load(configPath)
 	return bootstrap, nil
diff --git a/pkg/filter/failinject/filter.go b/pkg/filter/failinject/filter.go
index 2c065e07..f7a664b3 100644
--- a/pkg/filter/failinject/filter.go
+++ b/pkg/filter/failinject/filter.go
@@ -117,16 +117,16 @@ func (f Filter) match(rule *Rule) (matched bool) {
 	if rule == nil {
 		return false
 	}
-	if rule.TriggerType == TriggerTypeAlways {
+	switch rule.TriggerType {
+	case TriggerTypeAlways:
 		return true
-	}
-	if rule.TriggerType == TriggerTypePercentage {
+	case TriggerTypePercentage:
 		return percentage(rule.Odds)
-	}
-	if rule.TriggerType == TriggerTypeRandom {
+	case TriggerTypeRandom:
 		return random()
+	default:
+		return false
 	}
-	return false
 }
 
 // random if the current request is matched
diff --git a/pkg/logger/controller.go b/pkg/logger/controller.go
index fb2d9708..309442a9 100644
--- a/pkg/logger/controller.go
+++ b/pkg/logger/controller.go
@@ -18,39 +18,32 @@
 package logger
 
 import (
-	"strings"
 	"sync"
 )
 
 import (
-	"go.uber.org/zap"
 	"go.uber.org/zap/zapcore"
 )
 
 // logController governs the logging output or configuration changes throughout the entire project.
 type logController struct {
-	mu sync.RWMutex
-
-	logger *pixiuLogger
+	mu     sync.RWMutex
+	logger *PixiuLogger
 }
 
-// setLoggerLevel safely changes the log level in a concurrent manner.
-func (c *logController) setLoggerLevel(level string) bool {
+// setLoggerLevel changes the level at runtime without rebuilding the logger.
+func (c *logController) setLoggerLevel(level zapcore.Level) bool {
 	c.mu.Lock()
 	defer c.mu.Unlock()
-	lvl := c.parseLevel(level)
-	if lvl == nil {
+	if c.logger == nil || c.logger.config == nil {
 		return false
 	}
-
-	c.logger.config.Level = *lvl
-	l, _ := c.logger.config.Build(zap.AddCallerSkip(2))
-	c.logger = &pixiuLogger{SugaredLogger: l.Sugar(), config: c.logger.config}
+	c.logger.config.Level.SetLevel(level)
 	return true
 }
 
-// updateLogger safely modifies the log object in a concurrent manner.
-func (c *logController) updateLogger(l *pixiuLogger) {
+// updateLogger swaps the underlying logger atomically.
+func (c *logController) updateLogger(l *PixiuLogger) {
 	c.mu.Lock()
 	defer c.mu.Unlock()
 	c.logger = l
@@ -103,27 +96,3 @@ func (c *logController) errorf(fmt string, args ...any) {
 	defer c.mu.RUnlock()
 	c.logger.Errorf(fmt, args...)
 }
-
-// parseLevel is used to parse the level of the log.
-func (c *logController) parseLevel(level string) *zap.AtomicLevel {
-	var lvl zapcore.Level
-	switch strings.ToLower(level) {
-	case "debug":
-		lvl = zapcore.DebugLevel
-	case "info":
-		lvl = zapcore.InfoLevel
-	case "warn":
-		lvl = zapcore.WarnLevel
-	case "error":
-		lvl = zapcore.ErrorLevel
-	case "panic":
-		lvl = zapcore.PanicLevel
-	case "fatal":
-		lvl = zapcore.FatalLevel
-	default:
-		return nil
-	}
-
-	al := zap.NewAtomicLevelAt(lvl)
-	return &al
-}
diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go
index a1fff168..f9a4362f 100644
--- a/pkg/logger/logger.go
+++ b/pkg/logger/logger.go
@@ -37,20 +37,20 @@ import (
 
 var control *logController
 
-type pixiuLogger struct {
+type PixiuLogger struct {
 	*zap.SugaredLogger
 	config *zap.Config
 }
 
 func init() {
-	// only use in test case, so just load default config
+	// only used in test/bootstrap; keep a sane default
 	if control == nil {
 		control = new(logController)
 		InitLogger(nil)
 	}
 }
 
-// PaddedCallerEncoder is a custom caller encoder that ensures that all file paths are displayed at the same length
+// PaddedCallerEncoder aligns caller path to a fixed width for prettier console output.
 func PaddedCallerEncoder(caller zapcore.EntryCaller, enc zapcore.PrimitiveArrayEncoder) {
 
 	callerPath := caller.TrimmedPath()
@@ -65,7 +65,21 @@ func PaddedCallerEncoder(caller zapcore.EntryCaller, enc zapcore.PrimitiveArrayE
 	enc.AppendString(callerPath)
 }
 
-// InitLog load from config path
+// helper: build with unified pixiu options.
+// - always AddCaller + AddCallerSkip(2)
+// - AddStacktrace(Error+) only when stacktrace is not disabled in cfg
+func buildWithPixiuOptions(cfg *zap.Config) (*zap.Logger, error) {
+	opts := []zap.Option{
+		zap.AddCaller(),
+		zap.AddCallerSkip(2),
+	}
+	if !cfg.DisableStacktrace {
+		opts = append(opts, zap.AddStacktrace(zapcore.ErrorLevel))
+	}
+	return cfg.Build(opts...)
+}
+
+// InitLog loads from YAML file; falls back to development defaults when file is absent/invalid.
 func InitLog(logConfFile string) error {
 	if logConfFile == "" {
 		InitLogger(nil)
@@ -83,8 +97,7 @@ func InitLog(logConfFile string) error {
 	}
 
 	conf := &zap.Config{}
-	err = yaml.UnmarshalYML(confFileStream, conf)
-	if err != nil {
+	if err := yaml.UnmarshalYML(confFileStream, conf); err != nil {
 		InitLogger(nil)
 		return perrors.New(fmt.Sprintf("[Unmarshal]init pixiuLogger error: %v", err))
 	}
@@ -94,10 +107,18 @@ func InitLog(logConfFile string) error {
 	return nil
 }
 
+// InitLogger initializes logger. Default is development-style (console, debug),
+// but we force stacktrace to Error+ only, and enable caller with our custom encoder.
+// If a config is supplied, we respect it and only normalize caller encoder and stacktrace threshold.
 func InitLogger(conf *zap.Config) {
-	var zapLoggerConfig zap.Config
+	var (
+		cfg zap.Config
+	)
+
 	if conf == nil {
-		zapLoggerConfig = zap.NewDevelopmentConfig()
+		// Default: development style
+		cfg = zap.NewDevelopmentConfig()
+
 		zapLoggerEncoderConfig := zapcore.EncoderConfig{
 			TimeKey:        "time",
 			LevelKey:       "level",
@@ -111,28 +132,57 @@ func InitLogger(conf *zap.Config) {
 			EncodeCaller:   PaddedCallerEncoder,
 			// EncodeCaller:   zapcore.ShortCallerEncoder,
 		}
-		zapLoggerConfig.EncoderConfig = zapLoggerEncoderConfig
+
+		cfg.EncoderConfig = zapLoggerEncoderConfig
 	} else {
-		zapLoggerConfig = *conf
-		// Set up a custom encoder directly without checking the original value
-		zapLoggerConfig.EncoderConfig.EncodeCaller = PaddedCallerEncoder
+		cfg = *conf
+		// Unify caller encoder regardless of YAML to keep alignment style
+		cfg.EncoderConfig.EncodeCaller = PaddedCallerEncoder
 	}
-	zapLogger, _ := zapLoggerConfig.Build(zap.AddCallerSkip(2))
-	l := &pixiuLogger{zapLogger.Sugar(), &zapLoggerConfig}
 
+	z, err := buildWithPixiuOptions(&cfg)
+	if err != nil {
+		z = zap.NewNop()
+	}
+	l := &PixiuLogger{z.Sugar(), &cfg}
 	control.updateLogger(l)
 }
 
-// SetLoggerLevel safely changes the log level in a concurrent manner.
-func SetLoggerLevel(level string) bool {
+// SetLoggerLevel changes the level at runtime without rebuilding logger.
+func SetLoggerLevel(level zapcore.Level) bool {
 	return control.setLoggerLevel(level)
 }
 
+// HotReload rebuilds from a new zap.Config (e.g., re-read YAML).
 func HotReload(conf *zap.Config) error {
 	InitLogger(conf)
 	return nil
 }
 
-func GetLogger() *pixiuLogger {
+// GetLogger exposes the current sugared logger.
+func GetLogger() *PixiuLogger {
 	return control.logger
 }
+
+// ParseLogLevel parses textual level to zapcore.Level.
+func ParseLogLevel(level string) zapcore.Level {
+	switch strings.ToLower(strings.TrimSpace(level)) {
+	case "debug", "trace":
+		return zapcore.DebugLevel
+	case "info":
+		return zapcore.InfoLevel
+	case "warn", "warning":
+		return zapcore.WarnLevel
+	case "error":
+		return zapcore.ErrorLevel
+	case "dpanic":
+		return zapcore.DPanicLevel
+	case "panic":
+		return zapcore.PanicLevel
+	case "fatal", "critical":
+		return zapcore.FatalLevel
+	default:
+		Warnf("unknown log level %q, defaulting to info", level)
+		return zapcore.InfoLevel
+	}
+}



Test patch:
diff --git a/pkg/client/dubbo/mapper_test.go b/pkg/client/dubbo/mapper_test.go
index 16f06576..d43f4efe 100644
--- a/pkg/client/dubbo/mapper_test.go
+++ b/pkg/client/dubbo/mapper_test.go
@@ -224,10 +224,10 @@ func TestValidateTarget(t *testing.T) {
 	assert.Nil(t, err)
 	assert.NotNil(t, val)
 	_, err = validateTarget(*target)
-	assert.EqualError(t, err, "Target params for dubbo backend must be *dubbogoTarget")
+	assert.EqualError(t, err, "Target params for dubbo backend must be *dubboTarget")
 	target2 := ""
 	_, err = validateTarget(target2)
-	assert.EqualError(t, err, "Target params for dubbo backend must be *dubbogoTarget")
+	assert.EqualError(t, err, "Target params for dubbo backend must be *dubboTarget")
 }
 
 func TestMapType(t *testing.T) {
diff --git a/pkg/cluster/loadbalancer/weightrandom/weight_random_test.go b/pkg/cluster/loadbalancer/weightrandom/weight_random_test.go
index 8a8c2bd5..4ee8b630 100644
--- a/pkg/cluster/loadbalancer/weightrandom/weight_random_test.go
+++ b/pkg/cluster/loadbalancer/weightrandom/weight_random_test.go
@@ -176,14 +176,14 @@ func TestWeightRandom_Handler_Probabilistic(t *testing.T) {
 			iterations:       20000,
 		},
 		{
-			name:             "some endpoints with default weight",
+			name:             "some endpoints with default weight (y = 1)",
 			endpointsWeights: map[string]int{"x": 2, "y": 1, "z": 3}, // 'y' will get weight 1
 			expectedProbs:    map[string]float64{"x": 0.33, "y": 0.17, "z": 0.5},
 			tolerance:        0.04,
 			iterations:       15000,
 		},
 		{
-			name:             "some endpoints with default weight",
+			name:             "some endpoints with zero weight (y = 0)",
 			endpointsWeights: map[string]int{"x": 2, "y": 0, "z": 3}, // 'y' will get weight 0
 			expectedProbs:    map[string]float64{"x": 0.4, "y": 0, "z": 0.6},
 			tolerance:        0.04,
@@ -212,6 +212,9 @@ func TestWeightRandom_Handler_Probabilistic(t *testing.T) {
 					t.Errorf("Endpoint %s: expected probability %f, got %f (difference %f > tolerance %f)",
 						id, expectedProb, actualProb, diff, tt.tolerance)
 				}
+				if expectedProb == 0 && counts[id] != 0 {
+					t.Errorf("Endpoint %s: expected 0 selections (weight=0), got %d", id, counts[id])
+				}
 			}
 		})
 	}
diff --git a/pkg/configcenter/nacos_load_test.go b/pkg/configcenter/nacos_load_test.go
index cadbf94a..1eca5440 100644
--- a/pkg/configcenter/nacos_load_test.go
+++ b/pkg/configcenter/nacos_load_test.go
@@ -28,6 +28,8 @@ import (
 
 import (
 	. "github.com/smartystreets/goconvey/convey"
+
+	"go.uber.org/zap/zapcore"
 )
 
 import (
@@ -97,14 +99,14 @@ func TestNacosConfig_onChange(t *testing.T) {
 
 		Convey("Test onChange with empty input", func() {
 			// Suppress logs during this test.
-			logger.SetLoggerLevel("fatal")
+			logger.SetLoggerLevel(zapcore.FatalLevel)
 
 			client.remoteConfig = nil
 			client.onChange(Namespace, Group, DataId, "")
 			So(client.remoteConfig, ShouldBeNil)
 
 			// Restore the logger level.
-			logger.SetLoggerLevel("info")
+			logger.SetLoggerLevel(zapcore.InfoLevel)
 		})
 	})
 }
diff --git a/pkg/logger/controller_test.go b/pkg/logger/controller_test.go
new file mode 100644
index 00000000..eaa4cc25
--- /dev/null
+++ b/pkg/logger/controller_test.go
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package logger
+
+import (
+	"testing"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
+
+	"go.uber.org/zap/zapcore"
+)
+
+func TestParseLevelAndSet(t *testing.T) {
+	cfg, _ := newDevConfigToFile(t)
+	InitLogger(cfg)
+
+	tests := []struct {
+		in       string
+		ok       bool
+		zapLevel zapcore.Level
+	}{
+		{"debug", true, zapcore.DebugLevel},
+		{"INFO", true, zapcore.InfoLevel},
+		{"Warn", true, zapcore.WarnLevel},
+		{"error", true, zapcore.ErrorLevel},
+		{"panic", true, zapcore.PanicLevel},
+		{"dpanic", true, zapcore.DPanicLevel},
+		{"fatal", true, zapcore.FatalLevel},
+		{"critical", true, zapcore.FatalLevel},
+		{"unknown", false, zapcore.InfoLevel}, // parseLevel default fallback to info
+		{"trace", true, zapcore.DebugLevel},
+		{"Warning", true, zapcore.WarnLevel},
+	}
+
+	for _, tt := range tests {
+		lvl := ParseLogLevel(tt.in)
+		if lvl != tt.zapLevel {
+			t.Fatalf("SetLoggerLevel(%q) want %v", tt.in, tt.ok)
+		}
+		control.setLoggerLevel(lvl)
+
+		// assert the level is set
+		assert.Equal(t, tt.zapLevel, control.logger.config.Level.Level())
+
+		// assert the cfg is not changed
+		assert.Equal(t, cfg.Development, control.logger.config.Development)
+		assert.Equal(t, cfg.DisableCaller, control.logger.config.DisableCaller)
+		assert.Equal(t, cfg.DisableStacktrace, control.logger.config.DisableStacktrace)
+		assert.Equal(t, cfg.Sampling, control.logger.config.Sampling)
+		assert.Equal(t, cfg.Encoding, control.logger.config.Encoding)
+		assert.Equal(t, cfg.EncoderConfig.MessageKey, control.logger.config.EncoderConfig.MessageKey)
+		assert.Equal(t, cfg.EncoderConfig.LevelKey, control.logger.config.EncoderConfig.LevelKey)
+		assert.Equal(t, cfg.EncoderConfig.TimeKey, control.logger.config.EncoderConfig.TimeKey)
+		assert.Equal(t, cfg.EncoderConfig.NameKey, control.logger.config.EncoderConfig.NameKey)
+		assert.Equal(t, cfg.EncoderConfig.CallerKey, control.logger.config.EncoderConfig.CallerKey)
+		assert.Equal(t, cfg.EncoderConfig.FunctionKey, control.logger.config.EncoderConfig.FunctionKey)
+		assert.Equal(t, cfg.EncoderConfig.StacktraceKey, control.logger.config.EncoderConfig.StacktraceKey)
+		assert.Equal(t, cfg.EncoderConfig.SkipLineEnding, control.logger.config.EncoderConfig.SkipLineEnding)
+		assert.Equal(t, cfg.EncoderConfig.LineEnding, control.logger.config.EncoderConfig.LineEnding)
+		assert.Equal(t, cfg.EncoderConfig.ConsoleSeparator, control.logger.config.EncoderConfig.ConsoleSeparator)
+		assert.Equal(t, cfg.OutputPaths, control.logger.config.OutputPaths)
+		assert.Equal(t, cfg.ErrorOutputPaths, control.logger.config.ErrorOutputPaths)
+		assert.Equal(t, cfg.InitialFields, control.logger.config.InitialFields)
+	}
+
+	// make sure able to write
+	GetLogger().Info("still alive")
+	_ = GetLogger().Sync()
+}
+
+func TestInitLoggerNil(t *testing.T) {
+	InitLogger(nil)
+	if GetLogger() == nil || GetLogger().SugaredLogger == nil {
+		t.Fatalf("GetLogger returned nil")
+	}
+	// retrigger InitLogger(nil)
+	InitLogger(nil)
+	GetLogger().Debug("dev init ok")
+	_ = GetLogger().Sync()
+}
+
+// HotReload(nil) fallback to dev
+func TestHotReloadNil(t *testing.T) {
+	if err := HotReload(nil); err != nil {
+		t.Fatalf("HotReload(nil) unexpected error: %v", err)
+	}
+	GetLogger().Warn("after hot reload nil")
+	_ = GetLogger().Sync()
+}
+
+func TestLoggerBasicUsage(t *testing.T) {
+	cfg, _ := newDevConfigToFile(t)
+	InitLogger(cfg)
+
+	log := GetLogger()
+	log = &PixiuLogger{SugaredLogger: log.With("k", "v"), config: log.config}
+	log.Infow("with fields", "a", 1)
+
+	_ = log.Sync()
+}
diff --git a/pkg/logger/logger_test.go b/pkg/logger/logger_test.go
index 561cc4d7..ce7fb21e 100644
--- a/pkg/logger/logger_test.go
+++ b/pkg/logger/logger_test.go
@@ -18,66 +18,198 @@
 package logger
 
 import (
-	"fmt"
+	"os"
 	"path/filepath"
-	"runtime"
+	"strings"
 	"testing"
+	"time"
 )
 
 import (
-	"github.com/stretchr/testify/assert"
+	"go.uber.org/zap"
+	"go.uber.org/zap/zapcore"
 )
 
-func TestInitLog(t *testing.T) {
-	var (
-		err  error
-		path string
+func newDevConfigToFile(t *testing.T) (*zap.Config, string) {
+	t.Helper()
+	dir := t.TempDir()
+	out := filepath.Join(dir, "zap.log")
+
+	cfg := zap.NewDevelopmentConfig()
+	// output to a temp file
+	cfg.OutputPaths = []string{out}
+	cfg.ErrorOutputPaths = []string{out}
+	cfg.EncoderConfig.StacktraceKey = "stacktrace"
+	cfg.EncoderConfig.CallerKey = "caller"
+
+	return &cfg, out
+}
+
+func readAll(t *testing.T, path string) string {
+	t.Helper()
+	b, err := os.ReadFile(path)
+	if err != nil {
+		t.Fatalf("read %s failed: %v", path, err)
+	}
+	return string(b)
+}
+
+func TestDisableStacktraceTrueNoStackEvenOnError(t *testing.T) {
+	cfg, out := newDevConfigToFile(t)
+	cfg.DisableStacktrace = true // YAML equivalent: disableStacktrace: true
+	InitLogger(cfg)
+
+	log := GetLogger()
+	log.Error("boom")
+	_ = log.Sync()
+
+	got := readAll(t, out)
+	line := ""
+	for _, l := range strings.Split(got, "\n") {
+		if strings.Contains(l, "boom") {
+			line = l
+			break
+		}
+	}
+	if line == "" {
+		t.Fatalf("error line not found")
+	}
+	if strings.Contains(strings.ToLower(line), "stacktrace") {
+		t.Fatalf("disableStacktrace=true: must NOT output stacktrace, got:\n%s", line)
+	}
+}
+
+func TestSetLoggerLevelDoesNotRebuildAndTakesEffect(t *testing.T) {
+	cfg, out := newDevConfigToFile(t)
+	InitLogger(cfg)
+
+	before := GetLogger().SugaredLogger
+
+	// dynamic set to error
+	ok := SetLoggerLevel(zapcore.ErrorLevel)
+	if !ok {
+		t.Fatalf("SetLoggerLevel returned false")
+	}
+
+	after := GetLogger().SugaredLogger
+	if before != after {
+		t.Fatalf("SetLoggerLevel should NOT rebuild logger; pointer changed: %p -> %p", before, after)
+	}
+
+	// write new: info should not appear, error should appear
+	log := GetLogger()
+	log.Info("info should be filtered")
+	log.Error("error should appear")
+	_ = log.Sync()
+
+	got := readAll(t, out)
+	if strings.Contains(got, "info should be filtered") {
+		t.Fatalf("info should NOT appear after level set to error:\n%s", got)
+	}
+	if !strings.Contains(got, "error should appear") {
+		t.Fatalf("error should appear but not found:\n%s", got)
+	}
+}
+
+func TestHotReloadRebuildsAndSwitchesSink(t *testing.T) {
+	// cfg1 -> out1
+	cfg1, out1 := newDevConfigToFile(t)
+	InitLogger(cfg1)
+	l1 := GetLogger().SugaredLogger
+
+	const (
+		H1 = "hello-1"
+		H2 = "hello-2"
 	)
 
-	err = InitLog("")
-	assert.EqualError(t, err, "log configure file name is nil")
-
-	path, err = filepath.Abs("./log.xml")
-	assert.NoError(t, err)
-	err = InitLog(path)
-	assert.EqualError(t, err, "log configure file name "+path+" suffix must be .yml")
-
-	path, err = filepath.Abs("./logger.yml")
-	assert.NoError(t, err)
-	err = InitLog(path)
-	var errMsg string
-	if runtime.GOOS == "windows" {
-		errMsg = fmt.Sprintf("open %s: The system cannot find the file specified.", path)
-	} else {
-		errMsg = fmt.Sprintf("open %s: no such file or directory", path)
+	GetLogger().Info(H1)
+	_ = GetLogger().Sync()
+
+	// cfg2 -> out2（new sink）
+	cfg2, out2 := newDevConfigToFile(t)
+	// to split it, set level to info
+	HotReload(cfg2)
+	l2 := GetLogger().SugaredLogger
+
+	if l1 == l2 {
+		t.Fatalf("HotReload should rebuild logger; got same pointer %p", l1)
+	}
+
+	GetLogger().Info(H2)
+	_ = GetLogger().Sync()
+
+	got1 := readAll(t, out1)
+	got2 := readAll(t, out2)
+
+	if !strings.Contains(got1, H1) {
+		t.Fatalf("out1 should contain hello-1 but not found:\n%s", got1)
+	}
+	if strings.Contains(got1, H2) {
+		t.Fatalf("out1 should NOT contain hello-2 after reload:\n%s", got1)
+	}
+	if !strings.Contains(got2, H2) {
+		t.Fatalf("out2 should contain hello-2 but not found:\n%s", got2)
 	}
-	assert.EqualError(t, err, fmt.Sprintf("os.ReadFile file:%s, error:%s", path, errMsg))
-
-	err = InitLog("./log.yml")
-	assert.NoError(t, err)
-
-	Debug("debug")
-	Info("info")
-	Warn("warn")
-	Error("error")
-	Debugf("%s", "debug")
-	Infof("%s", "info")
-	Warnf("%s", "warn")
-	Errorf("%s", "error")
 }
 
-func TestSetLoggerLevel(t *testing.T) {
-	assert.NotNil(t, control, "control should not be nil")
+func TestPaddedCallerEncoderFixedWidthAtLeast30(t *testing.T) {
+	caller := zapcore.EntryCaller{
+		Defined: true,
+		File:    "a/b/c.go",
+		Line:    7,
+	}
+	collector := &stringCollector{}
+	PaddedCallerEncoder(caller, collector)
+
+	if len(collector.items) == 0 {
+		t.Fatalf("collector got no items")
+	}
+	got := collector.items[0]
+	if len(got) < 30 {
+		t.Fatalf("caller not padded to >=30, got len=%d val=%q", len(got), got)
+	}
+}
 
-	assert.True(t, SetLoggerLevel("info"), "when pass info to SetLoggerLevel, result should be true")
-	assert.True(t, SetLoggerLevel("debug"), "when pass debug to SetLoggerLevel, result should be true")
-	assert.True(t, SetLoggerLevel("error"), "when pass error to SetLoggerLevel, result should be true")
-	assert.True(t, SetLoggerLevel("panic"), "when pass panic to SetLoggerLevel, result should be true")
-	assert.True(t, SetLoggerLevel("INFO"), "when pass INFO to SetLoggerLevel, result should be true")
-	assert.True(t, SetLoggerLevel("DEbug"), "when pass DEbug to SetLoggerLevel, result should be true")
-	assert.True(t, SetLoggerLevel("ErRor"), "when pass ErRor to SetLoggerLevel, result should be true")
-	assert.True(t, SetLoggerLevel("WaRN"), "when pass WaRN to SetLoggerLevel, result should be true")
+// -------------------------- helpers --------------------------
 
-	assert.False(t, SetLoggerLevel("i"), "when pass i to SetLoggerLevel, result should be false")
-	assert.False(t, SetLoggerLevel(""), "when pass nothing to SetLoggerLevel, result should be false")
+type stringCollector struct {
+	items []string
 }
+
+// mock zapcore.PrimitiveArrayEncoder
+func (s *stringCollector) AppendString(v string) { s.items = append(s.items, v) }
+
+func (s *stringCollector) AppendBool(bool)                      { /*mock*/ }
+func (s *stringCollector) AppendByteString([]byte)              { /*mock*/ }
+func (s *stringCollector) AppendComplex128(complex128)          { /*mock*/ }
+func (s *stringCollector) AppendComplex64(complex64)            { /*mock*/ }
+func (s *stringCollector) AppendDuration(time.Duration)         { /*mock*/ }
+func (s *stringCollector) AppendFloat64(float64)                { /*mock*/ }
+func (s *stringCollector) AppendFloat32(float32)                { /*mock*/ }
+func (s *stringCollector) AppendInt(int)                        { /*mock*/ }
+func (s *stringCollector) AppendInt64(int64)                    { /*mock*/ }
+func (s *stringCollector) AppendInt32(int32)                    { /*mock*/ }
+func (s *stringCollector) AppendInt16(int16)                    { /*mock*/ }
+func (s *stringCollector) AppendInt8(int8)                      { /*mock*/ }
+func (s *stringCollector) AppendTime(time.Time)                 { /*mock*/ }
+func (s *stringCollector) AppendUint(uint)                      { /*mock*/ }
+func (s *stringCollector) AppendUint64(uint64)                  { /*mock*/ }
+func (s *stringCollector) AppendUint32(uint32)                  { /*mock*/ }
+func (s *stringCollector) AppendUint16(uint16)                  { /*mock*/ }
+func (s *stringCollector) AppendUint8(uint8)                    { /*mock*/ }
+func (s *stringCollector) AppendUintptr(uintptr)                { /*mock*/ }
+func (s *stringCollector) AppendReflected(any)                  { /*mock*/ }
+func (s *stringCollector) AppendArray(zapcore.ArrayMarshaler)   { /*mock*/ }
+func (s *stringCollector) AppendObject(zapcore.ObjectMarshaler) { /*mock*/ }
+func (s *stringCollector) AppendBinary([]byte)                  { /*mock*/ }
+func (s *stringCollector) AppendComplex(complex128)             { /*mock*/ }
+func (s *stringCollector) AppendDurationRef(time.Duration)      { /*mock*/ }
+func (s *stringCollector) AppendTimeLayout(time.Time, string)   { /*mock*/ }
+func (s *stringCollector) AppendIP(ip any)                      { /*mock*/ }
+func (s *stringCollector) AppendIPNet(net any)                  { /*mock*/ }
+func (s *stringCollector) AppendMAC(mac any)                    { /*mock*/ }
+func (s *stringCollector) AppendHex(any)                        { /*mock*/ }
+func (s *stringCollector) AppendFloat(any)                      { /*mock*/ }
+func (s *stringCollector) Cap() int                             { return 0 }
+func (s *stringCollector) Len() int                             { return len(s.items) }
+func (s *stringCollector) Truncate(int)                         { /*mock*/ }