github.com/anchore/syft@v1.38.2/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() func() {
    21  	return capture(&os.Stdout, newLogWriter(), defaultStdoutLogBufferSize)
    22  }
    23  
    24  func capture(target **os.File, writer io.Writer, bufSize int) 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 original != nil {
    40  			n, err := r.Read(buf)
    41  			if n > 0 {
    42  				_, _ = writer.Write(buf[0:n])
    43  			}
    44  
    45  			if err != nil {
    46  				break
    47  			}
    48  		}
    49  	}()
    50  
    51  	return func() {
    52  		if original != nil {
    53  			_ = w.Close()
    54  			select {
    55  			case <-done:
    56  			case <-time.After(1 * time.Second):
    57  				log.Debugf("stdout buffer timed out after 1 second")
    58  			}
    59  			*target = original
    60  			original = nil
    61  		}
    62  	}
    63  }