capture.go RawHistoryFolder
1package logging
2
3import (
4 "bufio"
5 "fmt"
6 "io"
7 "os"
8 "strings"
9 "syscall"
10 "time"
11)
12
13type OutputCapture struct {
14 originalStdout *os.File
15 originalStderr *os.File
16 stdoutRead *os.File
17 stdoutWrite *os.File
18 stderrRead *os.File
19 stderrWrite *os.File
20 logger *Logger
21 stopChan chan struct{}
22 started bool
23}
24
25func NewOutputCapture(logger *Logger) *OutputCapture {
26 return &OutputCapture{
27 originalStdout: os.Stdout,
28 originalStderr: os.Stderr,
29 logger: logger,
30 stopChan: make(chan struct{}),
31 }
32}
33
34func (c *OutputCapture) Start() error {
35 if c.started {
36 return nil
37 }
38
39 // Create pipes for stdout
40 stdoutR, stdoutW, err := os.Pipe()
41 if err != nil {
42 return err
43 }
44
45 // Create pipes for stderr
46 stderrR, stderrW, err := os.Pipe()
47 if err != nil {
48 stdoutR.Close()
49 stdoutW.Close()
50 return err
51 }
52
53 // Store pipe handles
54 c.stdoutRead = stdoutR
55 c.stdoutWrite = stdoutW
56 c.stderrRead = stderrR
57 c.stderrWrite = stderrW
58
59 // Redirect stdout and stderr
60 os.Stdout = stdoutW
61 os.Stderr = stderrW
62
63 // Also redirect file descriptors at syscall level for C code
64 syscall.Dup2(int(stdoutW.Fd()), 1)
65 syscall.Dup2(int(stderrW.Fd()), 2)
66
67 // Start goroutines to read and log
68 go c.pipeToLogger(stdoutR, "STDOUT")
69 go c.pipeToLogger(stderrR, "STDERR")
70
71 c.started = true
72 return nil
73}
74
75func (c *OutputCapture) Stop() {
76 if !c.started {
77 return
78 }
79
80 close(c.stopChan)
81
82 // Restore original stdout and stderr
83 os.Stdout = c.originalStdout
84 os.Stderr = c.originalStderr
85
86 // Restore file descriptors
87 syscall.Dup2(int(c.originalStdout.Fd()), 1)
88 syscall.Dup2(int(c.originalStderr.Fd()), 2)
89
90 // Close pipes
91 if c.stdoutWrite != nil {
92 c.stdoutWrite.Close()
93 }
94 if c.stderrWrite != nil {
95 c.stderrWrite.Close()
96 }
97 if c.stdoutRead != nil {
98 c.stdoutRead.Close()
99 }
100 if c.stderrRead != nil {
101 c.stderrRead.Close()
102 }
103
104 c.started = false
105}
106
107func (c *OutputCapture) pipeToLogger(r io.Reader, prefix string) {
108 scanner := bufio.NewScanner(r)
109 for scanner.Scan() {
110 select {
111 case <-c.stopChan:
112 return
113 default:
114 line := scanner.Text()
115 if line != "" && c.logger != nil {
116 // Avoid feedback loops by not logging our own log messages
117 if !strings.Contains(line, "[STDOUT]") && !strings.Contains(line, "[STDERR]") {
118 // Write directly to original stdout/stderr instead of going through logger
119 // to avoid capture recursion
120 timestamp := time.Now().Format("2006-01-02 15:04:05")
121 logLine := fmt.Sprintf("[%s] INFO [%s] %s\n", timestamp, prefix, line)
122 if prefix == "STDERR" {
123 c.originalStderr.WriteString(logLine)
124 } else {
125 c.originalStdout.WriteString(logLine)
126 }
127 }
128 }
129 }
130 }
131}