pmux/pmuxlib/logger.go
2022-06-18 19:52:04 -06:00

159 lines
2.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package pmuxlib
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"strings"
"sync"
"time"
)
// pname used by pmux itself for logging.
const pmuxPName = "pmux"
// characters used to denote different kinds of logs
const (
logSepStdout = ''
logSepStderr = '»'
logSepSys = '~'
)
// Logger is used by RunProcess to log process details in realtime. You can use
// a new(NullLogger) if you don't care.
type Logger interface {
Println(string)
Printf(string, ...interface{})
}
// NullLogger is an implementation of Logger which doesn't do anything.
type NullLogger struct{}
func (*NullLogger) Println(string) {}
func (*NullLogger) Printf(string, ...interface{}) {}
// PlainLogger implements Logger by writing each line directly to the given
// io.Writer as-is.
type PlainLogger struct {
io.Writer
}
func (l PlainLogger) Println(line string) {
fmt.Fprintln(l, line)
}
func (l PlainLogger) Printf(str string, args ...interface{}) {
fmt.Fprintf(l, str, args...)
}
type logger struct {
timeFmt string
l *sync.Mutex
out io.Writer
outBuf *bufio.Writer
// maxPNameLen is a pointer because it changes when WithPrefix is called.
maxPNameLen *uint64
pname string
sep rune
}
func newLogger(
out io.Writer,
sep rune,
timeFmt string,
) *logger {
pname := pmuxPName
maxPNameLen := uint64(len(pname))
l := &logger{
timeFmt: timeFmt,
maxPNameLen: &maxPNameLen,
l: new(sync.Mutex),
out: out,
outBuf: bufio.NewWriter(out),
pname: pname,
sep: sep,
}
return l
}
func (l *logger) withSep(sep rune) *logger {
l2 := *l
l2.sep = sep
return &l2
}
func (l *logger) withPName(pname string) *logger {
l2 := *l
l2.pname = pname
l2.l.Lock()
defer l2.l.Unlock()
if pnameLen := uint64(len(pname)); pnameLen > *l2.maxPNameLen {
*l2.maxPNameLen = pnameLen
}
return &l2
}
func (l *logger) Close() {
l.l.Lock()
defer l.l.Unlock()
l.outBuf.Flush()
if syncer, ok := l.out.(interface{ Sync() error }); ok {
_ = syncer.Sync()
} else if flusher, ok := l.out.(interface{ Flush() error }); ok {
_ = flusher.Flush()
}
// this generally shouldn't be necessary, but we could run into cases (e.g.
// during a force-kill) where further Prints are called after a Close. These
// should just do nothing.
l.out = ioutil.Discard
l.outBuf = bufio.NewWriter(l.out)
}
func (l *logger) println(line string) {
l.l.Lock()
defer l.l.Unlock()
if l.timeFmt != "" {
fmt.Fprintf(
l.outBuf,
"%s %c ",
time.Now().Format(l.timeFmt),
l.sep,
)
}
fmt.Fprintf(
l.outBuf,
"%s%s%c %s\n",
l.pname,
strings.Repeat(" ", int(*l.maxPNameLen+1)-len(l.pname)),
l.sep,
line,
)
l.outBuf.Flush()
}
func (l *logger) Println(line string) {
l.println(line)
}
func (l *logger) Printf(msg string, args ...interface{}) {
l.Println(fmt.Sprintf(msg, args...))
}