1// Package termlog provides facilities for logging to a terminal geared towards 2// interactive use. 3package termlog 4 5import ( 6 "fmt" 7 "io" 8 "os" 9 "sync" 10 "time" 11 12 "github.com/fatih/color" 13 "golang.org/x/crypto/ssh/terminal" 14) 15 16const ( 17 say = iota 18 notice 19 warn 20 shout 21 header 22) 23 24const defaultTimeFmt = "15:04:05: " 25const indent = " " 26 27// A single global output Mutex, because fatih/color has a single global output 28// writer 29var outputMutex = sync.Mutex{} 30 31// Palette defines the colour of output 32type Palette struct { 33 Timestamp *color.Color 34 Say *color.Color 35 Notice *color.Color 36 Warn *color.Color 37 Shout *color.Color 38 Header *color.Color 39} 40 41// DefaultPalette is a sensbile default palette, with the following foreground 42// colours: 43// 44// Say: Terminal default 45// Notice: Blue 46// Warn: Yellow 47// Shout: Red 48// Timestamp: Cyan 49var DefaultPalette = Palette{ 50 Say: color.New(), 51 Notice: color.New(color.FgBlue), 52 Warn: color.New(color.FgYellow), 53 Shout: color.New(color.FgRed), 54 Timestamp: color.New(color.FgCyan), 55 Header: color.New(color.FgBlue), 56} 57 58// Logger logs things 59type Logger interface { 60 Say(format string, args ...interface{}) 61 Notice(format string, args ...interface{}) 62 Warn(format string, args ...interface{}) 63 Shout(format string, args ...interface{}) 64 65 SayAs(name string, format string, args ...interface{}) 66 NoticeAs(name string, format string, args ...interface{}) 67 WarnAs(name string, format string, args ...interface{}) 68 ShoutAs(name string, format string, args ...interface{}) 69} 70 71// Group is a collected group of log entries. Logs are only displayed once the 72// Done method is called. 73type Group interface { 74 Logger 75 Done() 76 Quiet() 77} 78 79// Stream is a stream of log entries with a header 80type Stream interface { 81 Logger 82 Quiet() 83 Header() 84} 85 86// TermLog is the top-level termlog interface 87type TermLog interface { 88 Logger 89 Group() Group 90 Stream(header string) Stream 91 Quiet() 92} 93 94type linesource interface { 95 getID() string 96 getHeader() string 97} 98 99type line struct { 100 name string 101 str string 102 source linesource 103} 104 105// Log is the top-level log structure 106type Log struct { 107 Palette *Palette 108 TimeFmt string 109 enabled map[string]bool 110 quiet bool 111 lastid string 112} 113 114// NewLog creates a new Log instance and initialises it with a set of defaults. 115func NewLog() *Log { 116 l := &Log{ 117 Palette: &DefaultPalette, 118 enabled: make(map[string]bool), 119 TimeFmt: defaultTimeFmt, 120 } 121 l.enabled[""] = true 122 if !terminal.IsTerminal(int(os.Stdout.Fd())) || os.Getenv("TERM") == "dumb" { 123 l.Color(false) 124 } 125 return l 126} 127 128func (l *Log) format(timestamp bool, level int, format string, args []interface{}) string { 129 ts := "" 130 if timestamp { 131 f := l.Palette.Timestamp.SprintfFunc() 132 ts = f( 133 "%s", time.Now().Format(l.TimeFmt), 134 ) 135 } 136 var p *color.Color 137 switch level { 138 case say: 139 p = l.Palette.Say 140 case notice: 141 p = l.Palette.Notice 142 case warn: 143 p = l.Palette.Warn 144 case shout: 145 p = l.Palette.Shout 146 case header: 147 p = l.Palette.Header 148 default: 149 panic("unknown log level") 150 } 151 return ts + p.SprintfFunc()(format, args...) 152} 153 154// Color sets the state of colour output - true to turn on, false to disable. 155func (*Log) Color(state bool) { 156 color.NoColor = !state 157} 158 159// Enable logging for a specified name 160func (l *Log) Enable(name string) { 161 l.enabled[name] = true 162} 163 164// Quiet disables all output 165func (l *Log) Quiet() { 166 l.quiet = true 167} 168 169func (l *Log) header(source linesource) { 170 l.lastid = source.getID() 171 hdr := l.format(true, header, source.getHeader(), nil) 172 fmt.Fprintf(color.Output, hdr+"\n") 173} 174 175func (l *Log) output(quiet bool, lines ...*line) { 176 if quiet { 177 return 178 } 179 outputMutex.Lock() 180 defer outputMutex.Unlock() 181 for _, line := range lines { 182 if _, ok := l.enabled[line.name]; !ok { 183 continue 184 } 185 id := line.source.getID() 186 if id != "" && id != l.lastid { 187 l.header(line.source) 188 } 189 fmt.Fprintf(color.Output, "%s\n", line.str) 190 } 191} 192 193// Say logs a line 194func (l *Log) Say(format string, args ...interface{}) { 195 l.output(l.quiet, &line{"", l.format(true, say, format, args), l}) 196} 197 198// Notice logs a line with the Notice color 199func (l *Log) Notice(format string, args ...interface{}) { 200 l.output(l.quiet, &line{"", l.format(true, notice, format, args), l}) 201} 202 203// Warn logs a line with the Warn color 204func (l *Log) Warn(format string, args ...interface{}) { 205 l.output(l.quiet, &line{"", l.format(true, warn, format, args), l}) 206} 207 208// Shout logs a line with the Shout color 209func (l *Log) Shout(format string, args ...interface{}) { 210 l.output(l.quiet, &line{"", l.format(true, shout, format, args), l}) 211} 212 213// SayAs logs a line 214func (l *Log) SayAs(name string, format string, args ...interface{}) { 215 l.output(l.quiet, &line{name, l.format(true, say, format, args), l}) 216} 217 218// NoticeAs logs a line with the Notice color 219func (l *Log) NoticeAs(name string, format string, args ...interface{}) { 220 l.output(l.quiet, &line{name, l.format(true, notice, format, args), l}) 221} 222 223// WarnAs logs a line with the Warn color 224func (l *Log) WarnAs(name string, format string, args ...interface{}) { 225 l.output(l.quiet, &line{name, l.format(true, warn, format, args), l}) 226} 227 228// ShoutAs logs a line with the Shout color 229func (l *Log) ShoutAs(name string, format string, args ...interface{}) { 230 l.output(l.quiet, &line{name, l.format(true, shout, format, args), l}) 231} 232 233// Group creates a new log group 234func (l *Log) Group() Group { 235 return &group{ 236 lines: make([]*line, 0), 237 log: l, 238 quiet: l.quiet, 239 } 240} 241 242// Stream creates a new log group 243func (l *Log) Stream(header string) Stream { 244 return &stream{ 245 header: header, 246 log: l, 247 quiet: l.quiet, 248 } 249} 250 251func (l *Log) getID() string { 252 return "" 253} 254 255func (l *Log) getHeader() string { 256 return "" 257} 258 259// SetOutput sets the output writer for termlog (stdout by default). 260func SetOutput(w io.Writer) { 261 color.Output = w 262} 263