|
@@ -382,6 +382,47 @@ func fillEnvIfAbsent(env []string, key, value string) []string {
|
|
|
return append(env, prefix+value)
|
|
return append(env, prefix+value)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// envHasKey 判断 env("KEY=VALUE" 形式的切片)中是否已存在指定 key。
|
|
|
|
|
+func envHasKey(env []string, key string) bool {
|
|
|
|
|
+ prefix := key + "="
|
|
|
|
|
+ for _, e := range env {
|
|
|
|
|
+ if strings.HasPrefix(e, prefix) {
|
|
|
|
|
+ return true
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return false
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ensureDirWritableByRunAsUser 确保日志目录可被降权用户写入。
|
|
|
|
|
+// 主服务常以 root 运行,创建的目录属主为 root;子进程降权到 runAsUser 后会写不进去,
|
|
|
|
|
+// 导致 kiro-cli 日志被静默丢弃。这里将目录(含其直接父目录)chown 到 runAsUser 的 uid/gid。
|
|
|
|
|
+// runAsUser 为空表示不降权(与主进程同用户),无需调整。所有失败仅记录告警,不致命。
|
|
|
|
|
+func ensureDirWritableByRunAsUser(dir string, runAsUser string) {
|
|
|
|
|
+ runAsUser = strings.TrimSpace(runAsUser)
|
|
|
|
|
+ if runAsUser == "" {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ u, err := user.Lookup(runAsUser)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ zap.L().Warn("[ACP] 查找降权用户失败,日志目录属主未调整", zap.String("user", runAsUser), zap.Error(err))
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ uid, err1 := strconv.Atoi(u.Uid)
|
|
|
|
|
+ gid, err2 := strconv.Atoi(u.Gid)
|
|
|
|
|
+ if err1 != nil || err2 != nil {
|
|
|
|
|
+ zap.L().Warn("[ACP] 解析降权用户 uid/gid 失败,日志目录属主未调整",
|
|
|
|
|
+ zap.String("user", runAsUser), zap.String("uid", u.Uid), zap.String("gid", u.Gid))
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ // 同时调整父目录(如 /tmp/kiro-log)与目标目录(如 /tmp/kiro-log/logs)。
|
|
|
|
|
+ for _, d := range []string{filepath.Dir(dir), dir} {
|
|
|
|
|
+ if err := os.Chown(d, uid, gid); err != nil {
|
|
|
|
|
+ zap.L().Warn("[ACP] 调整日志目录属主失败(降权用户可能无法写入日志)",
|
|
|
|
|
+ zap.String("dir", d), zap.String("user", runAsUser), zap.Error(err))
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// overrideEnv 用 kv 覆盖 env 中已存在的同名变量,不存在则追加。
|
|
// overrideEnv 用 kv 覆盖 env 中已存在的同名变量,不存在则追加。
|
|
|
func overrideEnv(env []string, kv map[string]string) []string {
|
|
func overrideEnv(env []string, kv map[string]string) []string {
|
|
|
out := make([]string, 0, len(env)+len(kv))
|
|
out := make([]string, 0, len(env)+len(kv))
|
|
@@ -420,6 +461,30 @@ func NewClient(cfg ClientConfig) (*Client, error) {
|
|
|
// 设置环境变量
|
|
// 设置环境变量
|
|
|
cmd.Env = append(os.Environ(), cfg.Env...)
|
|
cmd.Env = append(os.Environ(), cfg.Env...)
|
|
|
|
|
|
|
|
|
|
+ // A 方案:为每次启动的子进程分配带启动时间戳的独立日志文件,避免覆盖历史日志。
|
|
|
|
|
+ // 仅在开启了 kiro-cli debug 日志(env 含 KIRO_LOG_LEVEL)、且调用方未显式指定
|
|
|
|
|
+ // KIRO_CHAT_LOG_FILE 时生效。KIRO_CHAT_LOG_FILE 是 kiro-cli acp 官方支持的日志路径变量。
|
|
|
|
|
+ if envHasKey(cmd.Env, "KIRO_LOG_LEVEL") && !envHasKey(cmd.Env, "KIRO_CHAT_LOG_FILE") {
|
|
|
|
|
+ logDir := "/tmp/kiro-log/logs"
|
|
|
|
|
+ if err := os.MkdirAll(logDir, 0o777); err != nil {
|
|
|
|
|
+ zap.L().Warn("[ACP] 创建日志目录失败,沿用 kiro-cli 默认日志路径",
|
|
|
|
|
+ zap.String("dir", logDir), zap.Error(err))
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 主服务常以 root 运行,创建出的目录默认属主为 root;而子进程会被
|
|
|
|
|
+ // ApplyRunAsUser 降权到 cfg.RunAsUser,若目录不可写日志将被静默丢弃。
|
|
|
|
|
+ // 因此这里把目录属主/权限调整到降权用户可写。
|
|
|
|
|
+ ensureDirWritableByRunAsUser(logDir, cfg.RunAsUser)
|
|
|
|
|
+
|
|
|
|
|
+ logPath := filepath.Join(logDir, fmt.Sprintf("kiro-chat-%s.log", time.Now().Format("20060102T150405.000")))
|
|
|
|
|
+ cmd.Env = append(cmd.Env, "KIRO_CHAT_LOG_FILE="+logPath)
|
|
|
|
|
+ // 顺带关闭 ANSI 颜色码,便于后续直接阅读/grep 日志。
|
|
|
|
|
+ if !envHasKey(cmd.Env, "KIRO_LOG_NO_COLOR") {
|
|
|
|
|
+ cmd.Env = append(cmd.Env, "KIRO_LOG_NO_COLOR=1")
|
|
|
|
|
+ }
|
|
|
|
|
+ zap.L().Info("[ACP] 本次子进程日志文件", zap.String("logFile", logPath))
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// 若配置了降权用户,则以该用户身份拉起子进程
|
|
// 若配置了降权用户,则以该用户身份拉起子进程
|
|
|
if err := ApplyRunAsUser(cmd, cfg.RunAsUser); err != nil {
|
|
if err := ApplyRunAsUser(cmd, cfg.RunAsUser); err != nil {
|
|
|
return nil, err
|
|
return nil, err
|