parent
cfc6166135
commit
8fb99b53d7
@ -0,0 +1,158 @@ |
||||
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...)) |
||||
} |
@ -0,0 +1,49 @@ |
||||
// Package pmuxlib implements the process management aspects of the pmux
|
||||
// process.
|
||||
package pmuxlib |
||||
|
||||
import ( |
||||
"context" |
||||
"os" |
||||
"sync" |
||||
) |
||||
|
||||
type Config struct { |
||||
TimeFormat string `yaml:"timeFormat"` |
||||
Processes []ProcessConfig `yaml:"processes"` |
||||
} |
||||
|
||||
// Run runs the given configuration as if this was a real pmux process.
|
||||
func Run(ctx context.Context, cfg Config) { |
||||
|
||||
stdoutLogger := newLogger(os.Stdout, logSepStdout, cfg.TimeFormat) |
||||
defer stdoutLogger.Close() |
||||
|
||||
stderrLogger := newLogger(os.Stderr, logSepStderr, cfg.TimeFormat) |
||||
defer stderrLogger.Close() |
||||
|
||||
sysLogger := stderrLogger.withSep(logSepSys) |
||||
defer sysLogger.Println("exited gracefully, ciao!") |
||||
|
||||
var wg sync.WaitGroup |
||||
defer wg.Wait() |
||||
|
||||
for _, cfgProc := range cfg.Processes { |
||||
wg.Add(1) |
||||
go func(procCfg ProcessConfig) { |
||||
defer wg.Done() |
||||
|
||||
stdoutLogger := stdoutLogger.withPName(procCfg.Name) |
||||
stderrLogger := stderrLogger.withPName(procCfg.Name) |
||||
sysLogger := sysLogger.withPName(procCfg.Name) |
||||
|
||||
sysLogger.Println("starting process") |
||||
defer sysLogger.Println("stopped process handler") |
||||
|
||||
RunProcess( |
||||
ctx, stdoutLogger, stderrLogger, sysLogger, procCfg, |
||||
) |
||||
|
||||
}(cfgProc) |
||||
} |
||||
} |
@ -1,33 +0,0 @@ |
||||
package pmuxproc |
||||
|
||||
import ( |
||||
"fmt" |
||||
"io" |
||||
) |
||||
|
||||
// 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...) |
||||
} |
Loading…
Reference in new issue