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