308 lines
6.8 KiB
Markdown
308 lines
6.8 KiB
Markdown
|
|
# 命令执行日志获取
|
|||
|
|
|
|||
|
|
> 实时捕获命令输出,写入日志文件
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 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()
|
|||
|
|
```
|