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 }