# 命令执行日志获取 > 实时捕获命令输出,写入日志文件 --- ## 1. 基础日志捕获 ```go package main import ( "bytes" "fmt" "os/exec" ) func runAndLog(command string) string { var stdout, stderr bytes.Buffer cmd := exec.Command("cmd", "/c", command) // Windows // cmd := exec.Command("sh", "-c", command) // Linux cmd.Stdout = &stdout cmd.Stderr = &stderr err := cmd.Run() result := fmt.Sprintf("[命令] %s\n", command) result += fmt.Sprintf("[stdout]\n%s\n", stdout.String()) if stderr.Len() > 0 { result += fmt.Sprintf("[stderr]\n%s\n", stderr.String()) } if err != nil { result += fmt.Sprintf("[错误] %v\n", err) } return result } ``` --- ## 2. 实时逐行日志 + 写入文件 ```go func runWithLiveLog(command string, logPath string) error { cmd := exec.Command("cmd", "/c", command) stdoutPipe, _ := cmd.StdoutPipe() stderrPipe, _ := cmd.StderrPipe() logFile, err := os.Create(logPath) if err != nil { return err } defer logFile.Close() writer := io.MultiWriter(os.Stdout, logFile) // 同时输出到终端和文件 cmd.Start() var wg sync.WaitGroup wg.Add(2) go func() { defer wg.Done() scanner := bufio.NewScanner(stdoutPipe) for scanner.Scan() { writer.Write([]byte("[OUT] " + scanner.Text() + "\n")) } }() go func() { defer wg.Done() scanner := bufio.NewScanner(stderrPipe) for scanner.Scan() { writer.Write([]byte("[ERR] " + scanner.Text() + "\n")) } }() wg.Wait() return cmd.Wait() } ``` --- ## 3. 结构化日志 ```go type CommandLog struct { Timestamp string `json:"timestamp"` Command string `json:"command"` Stdout string `json:"stdout"` Stderr string `json:"stderr"` ExitCode int `json:"exit_code"` Duration string `json:"duration"` Success bool `json:"success"` } func runStructuredLog(command string) *CommandLog { start := time.Now() var stdout, stderr bytes.Buffer cmd := exec.Command("cmd", "/c", command) cmd.Stdout = &stdout cmd.Stderr = &stderr err := cmd.Run() elapsed := time.Since(start) exitCode := 0 if err != nil { if exitErr, ok := err.(*exec.ExitError); ok { exitCode = exitErr.ExitCode() } else { exitCode = -1 } } return &CommandLog{ Timestamp: start.Format(time.RFC3339), Command: command, Stdout: stdout.String(), Stderr: stderr.String(), ExitCode: exitCode, Duration: elapsed.String(), Success: err == nil, } } // 追加写入 JSON 日志文件 func appendLogJSON(logPath string, entry *CommandLog) error { data, _ := json.Marshal(entry) data = append(data, '\n') f, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return err } defer f.Close() _, err = f.Write(data) return err } ``` 输出示例 (`commands.log`): ``` {"timestamp":"2026-06-22T05:30:00+08:00","command":"ipconfig","stdout":"...","stderr":"","exit_code":0,"duration":"1.2s","success":true} {"timestamp":"2026-06-22T05:31:15+08:00","command":"dir X:\\","stdout":"","stderr":"系统找不到指定的路径。\n","exit_code":1,"duration":"0.3s","success":false} ``` --- ## 4. 带时间戳的日志文件 ```go func createLogFile(dir string) (*os.File, error) { os.MkdirAll(dir, 0755) name := fmt.Sprintf("cmd_%s.log", time.Now().Format("20060102_150405")) return os.Create(filepath.Join(dir, name)) } ``` --- ## 5. 滚动日志(限制大小) ```go const maxLogSize = 1 * 1024 * 1024 // 1MB func appendRollingLog(logPath string, line string) error { info, err := os.Stat(logPath) if err == nil && info.Size() > maxLogSize { // 超过 1MB 自动归档 backup := logPath + "." + time.Now().Format("20060102_150405") os.Rename(logPath, backup) } f, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return err } defer f.Close() _, err = f.WriteString(line + "\n") return err } ``` --- ## 6. 超时 + 日志 ```go func runWithTimeoutLog(command string, timeout time.Duration, logPath string) error { ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() cmd := exec.CommandContext(ctx, "cmd", "/c", command) var stdout, stderr bytes.Buffer cmd.Stdout = &stdout cmd.Stderr = &stderr err := cmd.Run() entry := fmt.Sprintf("[%s] 命令: %s | 超时设置: %v | 成功: %v | stdout: %s | stderr: %s", time.Now().Format(time.RFC3339), command, timeout, err == nil, stdout.String(), stderr.String(), ) if ctx.Err() == context.DeadlineExceeded { entry += " | 结果: 超时" } appendRollingLog(logPath, entry) return err } ``` --- ## 7. Fyne GUI 日志查看器 ```go func main() { myApp := app.New() myWindow := myApp.NewWindow("命令日志查看器") myWindow.Resize(fyne.NewSize(700, 500)) logPath := filepath.Join(os.TempDir(), "cmd_history.log") var mu sync.Mutex cmdEntry := widget.NewEntry() cmdEntry.SetPlaceHolder("输入命令...") logView := widget.NewMultiLineEntry() logView.Disable() // 清除按钮 widget.NewButton("清除日志", func() { logView.SetText("") os.Remove(logPath) }) // 执行按钮 widget.NewButton("执行并记录日志", func() { command := cmdEntry.Text if command == "" { return } go func() { mu.Lock() defer mu.Unlock() entry := runAndLog(command) appendRollingLog(logPath, entry) current := logView.Text logView.SetText(current + entry + "\n---\n") }() }) // 加载历史日志 widget.NewButton("加载历史日志", func() { data, err := os.ReadFile(logPath) if err == nil { logView.SetText(string(data)) } }) myWindow.SetContent(container.NewBorder( container.NewVBox(cmdEntry, container.NewHBox(/* 按钮们 */), ), nil, nil, nil, logView, )) myWindow.ShowAndRun() } ``` --- ## 8. 关键类型速查 ```go // 一次性获取全部输出 cmd.Output() // stdout cmd.CombinedOutput() // stdout + stderr // 手动分离获取 cmd.Stdout = &stdout // stdout 写入 buffer cmd.Stderr = &stderr // stderr 写入 buffer // 实时流式获取 cmd.StdoutPipe() // 返回 io.ReadCloser,需在 cmd.Start() 前调用 cmd.StderrPipe() // 同上 // 带超时 exec.CommandContext(ctx, name, args...) // 获取退出码 exitErr, ok := err.(*exec.ExitError) code := exitErr.ExitCode() ```