github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/common/charmrunner/logger.go (about) 1 // Copyright 2012-2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package charmrunner 5 6 import ( 7 "bufio" 8 "io" 9 "sync" 10 "time" 11 12 "github.com/juju/loggo" 13 ) 14 15 var logger = loggo.GetLogger("juju.worker.common.runner") 16 17 // NewHookLogger creates a new hook logger. 18 func NewHookLogger(logger loggo.Logger, outReader io.ReadCloser) *HookLogger { 19 return &HookLogger{ 20 r: outReader, 21 done: make(chan struct{}), 22 logger: logger, 23 } 24 } 25 26 // HookLogger streams the output from a hook to a logger. 27 type HookLogger struct { 28 r io.ReadCloser 29 done chan struct{} 30 mu sync.Mutex 31 stopped bool 32 logger loggo.Logger 33 } 34 35 // Run starts the hook logger. 36 func (l *HookLogger) Run() { 37 defer close(l.done) 38 defer l.r.Close() 39 br := bufio.NewReaderSize(l.r, 4096) 40 for { 41 line, _, err := br.ReadLine() 42 if err != nil { 43 if err != io.EOF { 44 logger.Errorf("cannot read hook output: %v", err) 45 } 46 break 47 } 48 l.mu.Lock() 49 if l.stopped { 50 l.mu.Unlock() 51 return 52 } 53 l.logger.Debugf("%s", line) 54 l.mu.Unlock() 55 } 56 } 57 58 // Stop stops the hook logger. 59 func (l *HookLogger) Stop() { 60 // We can see the process exit before the logger has processed 61 // all its output, so allow a moment for the data buffered 62 // in the pipe to be processed. We don't wait indefinitely though, 63 // because the hook may have started a background process 64 // that keeps the pipe open. 65 select { 66 case <-l.done: 67 case <-time.After(100 * time.Millisecond): 68 } 69 // We can't close the pipe asynchronously, so just 70 // stifle output instead. 71 l.mu.Lock() 72 l.stopped = true 73 l.mu.Unlock() 74 }