github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/worker/logforwarder/logforwarder.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package logforwarder 5 6 import ( 7 "io" 8 "sync" 9 10 "github.com/juju/errors" 11 "github.com/juju/loggo" 12 "gopkg.in/tomb.v1" 13 14 "github.com/juju/juju/api/base" 15 "github.com/juju/juju/apiserver/params" 16 "github.com/juju/juju/logfwd" 17 "github.com/juju/juju/worker/catacomb" 18 ) 19 20 var logger = loggo.GetLogger("juju.worker.logforwarder") 21 22 // LogStream streams log entries from a log source (e.g. the Juju controller). 23 type LogStream interface { 24 // Next returns the next batch of log records from the stream. 25 Next() ([]logfwd.Record, error) 26 } 27 28 // LogStreamFn is a function that opens a log stream. 29 type LogStreamFn func(_ base.APICaller, _ params.LogStreamConfig, controllerUUID string) (LogStream, error) 30 31 // SendCloser is responsible for sending log records to a log sink. 32 type SendCloser interface { 33 sender 34 io.Closer 35 } 36 37 type sender interface { 38 // Send sends the records to its log sink. It is also responsible 39 // for notifying the controller that record was forwarded. 40 Send([]logfwd.Record) error 41 } 42 43 // TODO(ericsnow) It is likely that eventually we will want to support 44 // multiplexing to multiple senders, each in its own goroutine (or worker). 45 46 // LogForwarder is a worker that forwards log records from a source 47 // to a sender. 48 type LogForwarder struct { 49 catacomb catacomb.Catacomb 50 args OpenLogForwarderArgs 51 enabledCh chan bool 52 mu sync.Mutex 53 enabled bool 54 } 55 56 // OpenLogForwarderArgs holds the info needed to open a LogForwarder. 57 type OpenLogForwarderArgs struct { 58 // AllModels indicates that the tracker is handling all models. 59 AllModels bool 60 61 // ControllerUUID identifies the controller. 62 ControllerUUID string 63 64 // LogForwardConfig is the API used to access log forwarding config. 65 LogForwardConfig LogForwardConfig 66 67 // Caller is the API caller that will be used. 68 Caller base.APICaller 69 70 // Name is the name given to the log sink. 71 Name string 72 73 // OpenSink is the function that opens the underlying log sink that 74 // will be wrapped. 75 OpenSink LogSinkFn 76 77 // OpenLogStream is the function that will be used to for the 78 // log stream. 79 OpenLogStream LogStreamFn 80 } 81 82 // processNewConfig acts on a new syslog forward config change. 83 func (lf *LogForwarder) processNewConfig(currentSender SendCloser) (SendCloser, error) { 84 lf.mu.Lock() 85 defer lf.mu.Unlock() 86 87 closeExisting := func() error { 88 lf.enabled = false 89 // If we are already sending, close the current sender. 90 if currentSender != nil { 91 return currentSender.Close() 92 } 93 return nil 94 } 95 96 // Get the new config and set up log forwarding if enabled. 97 cfg, ok, err := lf.args.LogForwardConfig.LogForwardConfig() 98 if err != nil { 99 closeExisting() 100 return nil, errors.Trace(err) 101 } 102 if !ok || !cfg.Enabled { 103 logger.Infof("config change - log forwarding not enabled") 104 return nil, closeExisting() 105 } 106 // If the config is not valid, we don't want to exit with an error 107 // and bounce the worker; we'll just log the issue and wait for another 108 // config change to come through. 109 // We'll continue sending using the current sink. 110 if err := cfg.Validate(); err != nil { 111 logger.Errorf("invalid log forward config change: %v", err) 112 return currentSender, nil 113 } 114 115 // Shutdown the existing sink since we need to now create a new one. 116 if err := closeExisting(); err != nil { 117 return nil, errors.Trace(err) 118 } 119 sink, err := OpenTrackingSink(TrackingSinkArgs{ 120 Name: lf.args.Name, 121 AllModels: lf.args.AllModels, 122 Config: cfg, 123 Caller: lf.args.Caller, 124 OpenSink: lf.args.OpenSink, 125 }) 126 if err != nil { 127 return nil, errors.Trace(err) 128 } 129 lf.enabledCh <- true 130 return sink, nil 131 } 132 133 // waitForEnabled returns true if streaming is enabled. 134 // Otherwise if blocks and waits for enabled to be true. 135 func (lf *LogForwarder) waitForEnabled() (bool, error) { 136 lf.mu.Lock() 137 enabled := lf.enabled 138 lf.mu.Unlock() 139 if enabled { 140 return true, nil 141 } 142 143 select { 144 case <-lf.catacomb.Dying(): 145 return false, tomb.ErrDying 146 case enabled = <-lf.enabledCh: 147 } 148 lf.mu.Lock() 149 defer lf.mu.Unlock() 150 151 if !lf.enabled && enabled { 152 logger.Infof("log forward enabled, starting to stream logs to syslog sink") 153 } 154 lf.enabled = enabled 155 return enabled, nil 156 } 157 158 // NewLogForwarder returns a worker that forwards logs received from 159 // the stream to the sender. 160 func NewLogForwarder(args OpenLogForwarderArgs) (*LogForwarder, error) { 161 lf := &LogForwarder{ 162 args: args, 163 enabledCh: make(chan bool, 1), 164 } 165 err := catacomb.Invoke(catacomb.Plan{ 166 Site: &lf.catacomb, 167 Work: func() error { 168 return errors.Trace(lf.loop()) 169 }, 170 }) 171 if err != nil { 172 return nil, errors.Trace(err) 173 } 174 return lf, nil 175 } 176 177 func (lf *LogForwarder) loop() error { 178 configWatcher, err := lf.args.LogForwardConfig.WatchForLogForwardConfigChanges() 179 if err != nil { 180 return errors.Trace(err) 181 } 182 if err := lf.catacomb.Add(configWatcher); err != nil { 183 return errors.Trace(err) 184 } 185 186 records := make(chan []logfwd.Record) 187 var stream LogStream 188 go func() { 189 for { 190 enabled, err := lf.waitForEnabled() 191 if err == tomb.ErrDying { 192 return 193 } 194 if !enabled { 195 continue 196 } 197 // Lazily create log streamer if needed. 198 if stream == nil { 199 streamCfg := params.LogStreamConfig{ 200 AllModels: lf.args.AllModels, 201 Sink: lf.args.Name, 202 // TODO(wallyworld) - this should be configurable via lf.args.LogForwardConfig 203 MaxLookbackRecords: 100, 204 } 205 stream, err = lf.args.OpenLogStream(lf.args.Caller, streamCfg, lf.args.ControllerUUID) 206 if err != nil { 207 lf.catacomb.Kill(errors.Annotate(err, "creating log stream")) 208 break 209 } 210 211 } 212 rec, err := stream.Next() 213 if err != nil { 214 lf.catacomb.Kill(errors.Annotate(err, "getting next log record")) 215 break 216 } 217 select { 218 case <-lf.catacomb.Dying(): 219 return 220 case records <- rec: // Wait until the last one is sent. 221 } 222 } 223 }() 224 225 var sender SendCloser 226 defer func() { 227 if sender != nil { 228 sender.Close() 229 } 230 }() 231 232 for { 233 select { 234 case <-lf.catacomb.Dying(): 235 return lf.catacomb.ErrDying() 236 case _, ok := <-configWatcher.Changes(): 237 if !ok { 238 return errors.New("syslog configuration watcher closed") 239 } 240 if sender, err = lf.processNewConfig(sender); err != nil { 241 return errors.Trace(err) 242 } 243 case rec := <-records: 244 if sender == nil { 245 continue 246 } 247 if err := sender.Send(rec); err != nil { 248 return errors.Trace(err) 249 } 250 } 251 } 252 } 253 254 // Kill implements Worker.Kill() 255 func (lf *LogForwarder) Kill() { 256 lf.catacomb.Kill(nil) 257 } 258 259 // Wait implements Worker.Wait() 260 func (lf *LogForwarder) Wait() error { 261 return lf.catacomb.Wait() 262 }