11package debugger
22
33import (
4+ "bufio"
45 "context"
56 "fmt"
7+ "io"
68 "net"
79 "os"
810 "os/exec"
911 "path/filepath"
1012 "time"
1113
1214 "github.com/go-delve/delve/pkg/logflags"
15+ "github.com/go-delve/delve/pkg/proc"
1316 "github.com/go-delve/delve/service"
1417 "github.com/go-delve/delve/service/api"
1518 "github.com/go-delve/delve/service/debugger"
@@ -20,16 +23,28 @@ import (
2023
2124// Client encapsulates the Delve debug client functionality
2225type Client struct {
23- client * rpc2.RPCClient
24- target string
25- pid int
26- server * rpccommon.ServerImpl
27- tempDir string
26+ client * rpc2.RPCClient
27+ target string
28+ pid int
29+ server * rpccommon.ServerImpl
30+ tempDir string
31+ outputChan chan OutputMessage // Channel for captured output
32+ stopOutput chan struct {} // Channel to signal stopping output capture
33+ }
34+
35+ // OutputMessage represents a captured output message
36+ type OutputMessage struct {
37+ Source string `json:"source"` // "stdout" or "stderr"
38+ Content string `json:"content"`
39+ Timestamp time.Time `json:"timestamp"`
2840}
2941
3042// NewClient creates a new Delve client wrapper
3143func NewClient () * Client {
32- return & Client {}
44+ return & Client {
45+ outputChan : make (chan OutputMessage , 100 ), // Buffer for output messages
46+ stopOutput : make (chan struct {}),
47+ }
3348}
3449
3550// LaunchProgram starts a new program with debugging enabled
@@ -68,6 +83,18 @@ func (c *Client) LaunchProgram(program string, args []string) error {
6883 return fmt .Errorf ("couldn't start listener: %s" , err )
6984 }
7085
86+ // Create pipes for stdout and stderr using the proc.Redirector function
87+ stdoutReader , stdoutRedirect , err := proc .Redirector ()
88+ if err != nil {
89+ return fmt .Errorf ("failed to create stdout redirector: %v" , err )
90+ }
91+
92+ stderrReader , stderrRedirect , err := proc .Redirector ()
93+ if err != nil {
94+ stdoutRedirect .File .Close ()
95+ return fmt .Errorf ("failed to create stderr redirector: %v" , err )
96+ }
97+
7198 logger .Printf ("DEBUG: Creating Delve config" )
7299 // Create Delve config
73100 config := & service.Config {
@@ -80,9 +107,15 @@ func (c *Client) LaunchProgram(program string, args []string) error {
80107 Backend : "default" ,
81108 CheckGoVersion : true ,
82109 DisableASLR : true ,
110+ Stdout : stdoutRedirect ,
111+ Stderr : stderrRedirect ,
83112 },
84113 }
85114
115+ // Start goroutines to capture output
116+ go c .captureOutput (stdoutReader , "stdout" )
117+ go c .captureOutput (stderrReader , "stderr" )
118+
86119 logger .Printf ("DEBUG: Creating debug server" )
87120 // Create and start the debugging server
88121 server := rpccommon .NewServer (config )
@@ -172,6 +205,10 @@ func (c *Client) AttachToProcess(pid int) error {
172205 return fmt .Errorf ("couldn't start listener: %s" , err )
173206 }
174207
208+ // Note: When attaching to an existing process, we can't easily redirect its stdout/stderr
209+ // as those file descriptors are already connected. Output capture is limited for attach mode.
210+ logger .Printf ("DEBUG: Note: Output redirection is limited when attaching to an existing process" )
211+
175212 logger .Printf ("DEBUG: Creating Delve config for attach" )
176213 // Create Delve config for attaching to process
177214 config := & service.Config {
@@ -310,6 +347,9 @@ func (c *Client) Close() error {
310347 return nil
311348 }
312349
350+ // Signal to stop output capturing goroutines
351+ close (c .stopOutput )
352+
313353 // Create a context with timeout to prevent indefinite hanging
314354 ctx , cancel := context .WithTimeout (context .Background (), 5 * time .Second )
315355 defer cancel ()
@@ -410,12 +450,38 @@ func (c *Client) DebugSourceFile(sourceFile string, args []string) error {
410450
411451 logger .Printf ("DEBUG: Compiling source file %s to %s" , absPath , outputBinary )
412452
453+ // Create pipes for build output
454+ buildStdoutReader , buildStdoutWriter , err := os .Pipe ()
455+ if err != nil {
456+ os .RemoveAll (tempDir )
457+ return fmt .Errorf ("failed to create stdout pipe: %v" , err )
458+ }
459+ defer buildStdoutReader .Close ()
460+ defer buildStdoutWriter .Close ()
461+
413462 // Run go build with optimizations disabled
414463 buildCmd := exec .Command ("go" , "build" , "-gcflags" , "all=-N -l" , "-o" , outputBinary , absPath )
415- buildOutput , err := buildCmd .CombinedOutput ()
464+ buildCmd .Stdout = buildStdoutWriter
465+ buildCmd .Stderr = buildStdoutWriter
466+
467+ err = buildCmd .Start ()
416468 if err != nil {
417- os .RemoveAll (tempDir ) // Clean up temp directory on error
418- return fmt .Errorf ("failed to compile source file: %v\n Output: %s" , err , buildOutput )
469+ os .RemoveAll (tempDir )
470+ return fmt .Errorf ("failed to start compilation: %v" , err )
471+ }
472+
473+ // Capture build output
474+ go func () {
475+ scanner := bufio .NewScanner (buildStdoutReader )
476+ for scanner .Scan () {
477+ logger .Printf ("Build output: %s" , scanner .Text ())
478+ }
479+ }()
480+
481+ err = buildCmd .Wait ()
482+ if err != nil {
483+ os .RemoveAll (tempDir )
484+ return fmt .Errorf ("failed to compile source file: %v" , err )
419485 }
420486
421487 // Launch the compiled binary with the debugger
@@ -975,3 +1041,47 @@ func (c *Client) GetExecutionPosition() (*ExecutionPosition, error) {
9751041
9761042 return result , nil
9771043}
1044+
1045+ // captureOutput reads from a reader and sends the output to the output channel
1046+ func (c * Client ) captureOutput (reader io.ReadCloser , source string ) {
1047+ defer reader .Close ()
1048+
1049+ scanner := bufio .NewScanner (reader )
1050+ for scanner .Scan () {
1051+ select {
1052+ case <- c .stopOutput :
1053+ return
1054+ case c .outputChan <- OutputMessage {
1055+ Source : source ,
1056+ Content : scanner .Text (),
1057+ Timestamp : time .Now (),
1058+ }:
1059+ }
1060+ }
1061+ }
1062+
1063+ // GetCapturedOutput returns the next captured output message
1064+ // Returns nil when there are no more messages
1065+ func (c * Client ) GetCapturedOutput () * OutputMessage {
1066+ select {
1067+ case msg := <- c .outputChan :
1068+ return & msg
1069+ default :
1070+ return nil
1071+ }
1072+ }
1073+
1074+ // GetAllCapturedOutput returns all currently available captured output messages
1075+ func (c * Client ) GetAllCapturedOutput () []OutputMessage {
1076+ var messages []OutputMessage
1077+
1078+ // Collect all available messages without blocking
1079+ for {
1080+ select {
1081+ case msg := <- c .outputChan :
1082+ messages = append (messages , msg )
1083+ default :
1084+ return messages
1085+ }
1086+ }
1087+ }
0 commit comments