Files
SRCC/docs/command-log-capture.md
T
2026-06-22 11:33:18 +08:00

308 lines
6.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 命令执行日志获取
> 实时捕获命令输出,写入日志文件
---
## 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()
```