线程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
相关产品推荐
相关产品推荐

