You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

线程stdout/stderr重定向:CLI工具日志与panic捕获方案问询

解决方案

1. 重定向标准输出/错误到自定义日志缓冲区

先实现一个线程安全的日志缓冲区,替代默认的stdout/stderr,让所有线程的输出都写入这个缓冲区,避免被清屏逻辑覆盖。

import (
    "bufio"
    "sync"
)

// 线程安全的日志缓冲区
type LogBuffer struct {
    mu    sync.Mutex
    lines []string
}

// 实现io.Writer接口,将写入内容按行分割存入缓冲区
func (lb *LogBuffer) Write(p []byte) (n int, err error) {
    lb.mu.Lock()
    defer lb.mu.Unlock()

    scanner := bufio.NewScanner(bufio.NewReader(p))
    for scanner.Scan() {
        lb.lines = append(lb.lines, scanner.Text())
    }
    return len(p), scanner.Err()
}

// 获取最新日志行(可选:限制显示行数,避免内容过多)
func (lb *LogBuffer) GetLatestLines(maxLines int) []string {
    lb.mu.Lock()
    defer lb.mu.Unlock()

    start := 0
    if len(lb.lines) > maxLines {
        start = len(lb.lines) - maxLines
    }
    return lb.lines[start:]
}

启动任务线程前,将其标准输出/错误替换为这个缓冲区:

logBuf := &LogBuffer{}

// 替换当前goroutine的stdout/stderr(新goroutine可在内部替换)
originalStdout := os.Stdout
originalStderr := os.Stderr
os.Stdout = logBuf
os.Stderr = logBuf

// 执行任务逻辑
go func() {
    // 任务代码示例
    fmt.Println("任务初始化完成")
    // 可选:退出时恢复原标准流
    defer func() {
        os.Stdout = originalStdout
        os.Stderr = originalStderr
    }()
}()

2. 捕获Panic及堆栈跟踪

Go中只有当前goroutine内部能捕获panic,所以要给每个任务线程加recover包装,捕获后提取堆栈信息写入日志缓冲区。

import (
    "debug/stack"
    "fmt"
)

// 包装任务函数,自动捕获panic并记录堆栈
func wrapTask(task func()) func() {
    return func() {
        defer func() {
            if r := recover(); r != nil {
                // 获取堆栈跟踪信息
                stackTrace := stack.Stack()
                // 将panic信息和堆栈写入日志缓冲区
                logBuf.mu.Lock()
                logBuf.lines = append(logBuf.lines, fmt.Sprintf("Panic触发: %v", r))
                logBuf.lines = append(logBuf.lines, string(stackTrace))
                logBuf.mu.Unlock()
            }
        }()
        task()
    }
}

// 使用方式
go wrapTask(func() {
    // 可能触发panic的任务代码示例
    panic("任务执行异常")
})()

3. 整合到渲染逻辑

在循环渲染函数中,每次清屏后先渲染应用状态,再渲染日志区域,取出缓冲区最新内容展示:

import "time"

func renderAppState(logBuf *LogBuffer) {
    // ASCII清屏转义码
    fmt.Print("\033[H\033[2J")

    // 渲染应用状态
    fmt.Println("=== 应用实时状态 ===")
    fmt.Println("当前运行任务数: 2")
    fmt.Println("整体任务进度: 60%")

    // 渲染日志区域
    fmt.Println("\n=== 运行日志 ===")
    latestLogs := logBuf.GetLatestLines(10) // 只显示最新10行日志
    for _, line := range latestLogs {
        fmt.Println(line)
    }
}

// 渲染循环
func renderLoop(logBuf *LogBuffer) {
    ticker := time.NewTicker(500 * time.Millisecond)
    defer ticker.Stop()

    for range ticker.C {
        renderAppState(logBuf)
    }
}

内容的提问来源于stack exchange,提问作者Saurabh Goyal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 23:00:08