github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/cmd/syft/internal/ui/capture.go (about)

     1  package ui
     2  
     3  import (
     4  	"io"
     5  	"os"
     6  	"time"
     7  
     8  	"github.com/anchore/syft/internal/log"
     9  )
    10  
    11  const defaultStdoutLogBufferSize = 1024
    12  
    13  // CaptureStdoutToTraceLog replaces stdout and redirects output to the log as trace lines. The return value is a
    14  // function, which is used to stop the current capturing of output and restore the original file.
    15  // Example:
    16  //
    17  //	restore := CaptureStdoutToTraceLog()
    18  //	// here, stdout will be captured and redirected to the provided writer
    19  //	restore() // block until the output has all been sent to the writer and restore the original stdout
    20  func CaptureStdoutToTraceLog() (close func()) {
    21  	return capture(&os.Stdout, newLogWriter(), defaultStdoutLogBufferSize)
    22  }
    23  
    24  func capture(target **os.File, writer io.Writer, bufSize int) (close func()) {
    25  	original := *target
    26  
    27  	r, w, _ := os.Pipe()
    28  
    29  	*target = w
    30  
    31  	done := make(chan struct{}, 1)
    32  
    33  	go func() {
    34  		defer func() {
    35  			done <- struct{}{}
    36  		}()
    37  
    38  		buf := make([]byte, bufSize)
    39  		for {
    40  			if original == nil {
    41  				break
    42  			}
    43  
    44  			n, err := r.Read(buf)
    45  			if n > 0 {
    46  				_, _ = writer.Write(buf[0:n])
    47  			}
    48  
    49  			if err != nil {
    50  				break
    51  			}
    52  		}
    53  	}()
    54  
    55  	return func() {
    56  		if original != nil {
    57  			_ = w.Close()
    58  			select {
    59  			case <-done:
    60  			case <-time.After(1 * time.Second):
    61  				log.Debugf("stdout buffer timed out after 1 second")
    62  			}
    63  			*target = original
    64  			original = nil
    65  		}
    66  	}
    67  }