github.com/stevenmatthewt/agent@v3.5.4+incompatible/bootstrap/shell/logger.go (about)

     1  package shell
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"os"
     9  	"regexp"
    10  	"runtime"
    11  	"testing"
    12  )
    13  
    14  // Logger represents a logger that outputs to a buildkite shell.
    15  type Logger interface {
    16  	io.Writer
    17  
    18  	// Printf prints a line of output
    19  	Printf(format string, v ...interface{})
    20  
    21  	// Headerf prints a Buildkite formatted header
    22  	Headerf(format string, v ...interface{})
    23  
    24  	// Commentf prints a comment line, e.g `# my comment goes here`
    25  	Commentf(format string, v ...interface{})
    26  
    27  	// Errorf shows a Buildkite formatted error expands the previous group
    28  	Errorf(format string, v ...interface{})
    29  
    30  	// Warningf shows a buildkite bootstrap warning
    31  	Warningf(format string, v ...interface{})
    32  
    33  	// Promptf prints a shell prompt
    34  	Promptf(format string, v ...interface{})
    35  }
    36  
    37  // StderrLogger is a Logger that writes to Stderr
    38  var StderrLogger = &WriterLogger{
    39  	Writer: os.Stderr,
    40  	Ansi:   true,
    41  }
    42  
    43  // DiscardLogger discards all log messages
    44  var DiscardLogger = &WriterLogger{
    45  	Writer: ioutil.Discard,
    46  }
    47  
    48  // WriterLogger provides a logger that writes to an io.Writer
    49  type WriterLogger struct {
    50  	Writer io.Writer
    51  	Ansi   bool
    52  }
    53  
    54  func (wl *WriterLogger) Write(b []byte) (int, error) {
    55  	wl.Printf("%s", b)
    56  	return len(b), nil
    57  }
    58  
    59  func (wl *WriterLogger) Printf(format string, v ...interface{}) {
    60  	fmt.Fprintf(wl.Writer, "%s", fmt.Sprintf(format, v...))
    61  	fmt.Fprintln(wl.Writer)
    62  }
    63  
    64  func (wl *WriterLogger) Headerf(format string, v ...interface{}) {
    65  	fmt.Fprintf(wl.Writer, "~~~ %s", fmt.Sprintf(format, v...))
    66  	fmt.Fprintln(wl.Writer)
    67  }
    68  
    69  func (wl *WriterLogger) Commentf(format string, v ...interface{}) {
    70  	if wl.Ansi {
    71  		wl.Printf(ansiColor("# %s", "90"), fmt.Sprintf(format, v...))
    72  	} else {
    73  		wl.Printf("# %s", fmt.Sprintf(format, v...))
    74  	}
    75  }
    76  
    77  func (wl *WriterLogger) Errorf(format string, v ...interface{}) {
    78  	if wl.Ansi {
    79  		wl.Printf(ansiColor("🚨 Error: %s", "31"), fmt.Sprintf(format, v...))
    80  	} else {
    81  		wl.Printf("🚨 Error: %s", fmt.Sprintf(format, v...))
    82  	}
    83  	wl.Printf("^^^ +++")
    84  }
    85  
    86  func (wl *WriterLogger) Warningf(format string, v ...interface{}) {
    87  	if wl.Ansi {
    88  		wl.Printf(ansiColor("⚠️ Warning: %s", "33"), fmt.Sprintf(format, v...))
    89  	} else {
    90  		wl.Printf("⚠️ Warning: %s", fmt.Sprintf(format, v...))
    91  	}
    92  	wl.Printf("^^^ +++")
    93  }
    94  
    95  func (wl *WriterLogger) Promptf(format string, v ...interface{}) {
    96  	prompt := "$"
    97  	if runtime.GOOS == "windows" {
    98  		prompt = ">"
    99  	}
   100  	if wl.Ansi {
   101  		wl.Printf(ansiColor(prompt, "90")+" %s", fmt.Sprintf(format, v...))
   102  	} else {
   103  		wl.Printf(prompt+" %s", fmt.Sprintf(format, v...))
   104  	}
   105  }
   106  
   107  func ansiColor(s, attributes string) string {
   108  	return fmt.Sprintf("\033[%sm%s\033[0m", attributes, s)
   109  }
   110  
   111  type TestingLogger struct {
   112  	*testing.T
   113  }
   114  
   115  func (tl TestingLogger) Write(b []byte) (int, error) {
   116  	tl.Logf("%s", b)
   117  	return len(b), nil
   118  }
   119  
   120  func (tl TestingLogger) Printf(format string, v ...interface{}) {
   121  	tl.Logf(format, v...)
   122  }
   123  
   124  func (tl TestingLogger) Headerf(format string, v ...interface{}) {
   125  	tl.Logf("~~~ "+format, v...)
   126  }
   127  
   128  func (tl TestingLogger) Commentf(format string, v ...interface{}) {
   129  	tl.Logf("# %s", fmt.Sprintf(format, v...))
   130  }
   131  
   132  func (tl TestingLogger) Errorf(format string, v ...interface{}) {
   133  	tl.Logf("🚨 Error: %s", fmt.Sprintf(format, v...))
   134  }
   135  
   136  func (tl TestingLogger) Warningf(format string, v ...interface{}) {
   137  	tl.Logf("⚠️ Warning: %s", fmt.Sprintf(format, v...))
   138  }
   139  
   140  func (tl TestingLogger) Promptf(format string, v ...interface{}) {
   141  	prompt := "$"
   142  	if runtime.GOOS == "windows" {
   143  		prompt = ">"
   144  	}
   145  	tl.Logf(prompt+" %s", fmt.Sprintf(format, v...))
   146  }
   147  
   148  type LoggerStreamer struct {
   149  	Logger  Logger
   150  	Prefix  string
   151  	started bool
   152  	buf     *bytes.Buffer
   153  	offset  int
   154  }
   155  
   156  var lineRegexp = regexp.MustCompile(`(?m:^(.*)\r?\n)`)
   157  
   158  func NewLoggerStreamer(logger Logger) *LoggerStreamer {
   159  	return &LoggerStreamer{
   160  		Logger: logger,
   161  		buf:    bytes.NewBuffer([]byte("")),
   162  	}
   163  }
   164  
   165  func (l *LoggerStreamer) Write(p []byte) (n int, err error) {
   166  	if bytes.ContainsRune(p, '\n') {
   167  		l.started = true
   168  	}
   169  
   170  	if n, err = l.buf.Write(p); err != nil {
   171  		return
   172  	}
   173  
   174  	err = l.Output()
   175  	return
   176  }
   177  
   178  func (l *LoggerStreamer) Close() error {
   179  	if remaining := l.buf.String()[l.offset:]; len(remaining) > 0 {
   180  		l.Logger.Printf("%s%s", l.Prefix, remaining)
   181  	}
   182  	l.buf = bytes.NewBuffer([]byte(""))
   183  	return nil
   184  }
   185  
   186  func (l *LoggerStreamer) Output() error {
   187  	if !l.started {
   188  		return nil
   189  	}
   190  
   191  	matches := lineRegexp.FindAllStringSubmatch(l.buf.String()[l.offset:], -1)
   192  
   193  	for _, match := range matches {
   194  		l.Logger.Printf("%s%s", l.Prefix, match[1])
   195  		l.offset += len(match[0])
   196  	}
   197  
   198  	return nil
   199  }