github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/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/worker/v3/catacomb" 12 "gopkg.in/tomb.v2" 13 14 "github.com/juju/juju/api/base" 15 "github.com/juju/juju/logfwd" 16 "github.com/juju/juju/rpc/params" 17 ) 18 19 // logger is here to stop the desire of creating a package level logger. 20 // Don't do this, instead use the one passed as manifold config. 21 type logger interface{} 22 23 var _ logger = struct{}{} 24 25 // LogStream streams log entries from a log source (e.g. the Juju controller). 26 type LogStream interface { 27 // Next returns the next batch of log records from the stream. 28 Next() ([]logfwd.Record, error) 29 } 30 31 // LogStreamFn is a function that opens a log stream. 32 type LogStreamFn func(_ base.APICaller, _ params.LogStreamConfig, controllerUUID string) (LogStream, error) 33 34 // SendCloser is responsible for sending log records to a log sink. 35 type SendCloser interface { 36 sender 37 io.Closer 38 } 39 40 type sender interface { 41 // Send sends the records to its log sink. It is also responsible 42 // for notifying the controller that record was forwarded. 43 Send([]logfwd.Record) error 44 } 45 46 // TODO(ericsnow) It is likely that eventually we will want to support 47 // multiplexing to multiple senders, each in its own goroutine (or worker). 48 49 // LogForwarder is a worker that forwards log records from a source 50 // to a sender. 51 type LogForwarder struct { 52 catacomb catacomb.Catacomb 53 args OpenLogForwarderArgs 54 enabledCh chan bool 55 mu sync.Mutex 56 enabled bool 57 } 58 59 // OpenLogForwarderArgs holds the info needed to open a LogForwarder. 60 type OpenLogForwarderArgs struct { 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 Logger Logger 82 } 83 84 // processNewConfig acts on a new syslog forward config change. 85 func (lf *LogForwarder) processNewConfig(currentSender SendCloser) (SendCloser, error) { 86 lf.mu.Lock() 87 defer lf.mu.Unlock() 88 89 closeExisting := func() error { 90 lf.enabled = false 91 // If we are already sending, close the current sender. 92 if currentSender != nil { 93 return currentSender.Close() 94 } 95 return nil 96 } 97 98 // Get the new config and set up log forwarding if enabled. 99 cfg, ok, err := lf.args.LogForwardConfig.LogForwardConfig() 100 if err != nil { 101 _ = closeExisting() 102 return nil, errors.Trace(err) 103 } 104 if !ok || !cfg.Enabled { 105 lf.args.Logger.Infof("config change - log forwarding not enabled") 106 return nil, closeExisting() 107 } 108 // If the config is not valid, we don't want to exit with an error 109 // and bounce the worker; we'll just log the issue and wait for another 110 // config change to come through. 111 // We'll continue sending using the current sink. 112 if err := cfg.Validate(); err != nil { 113 lf.args.Logger.Errorf("invalid log forward config change: %v", err) 114 return currentSender, nil 115 } 116 117 // Shutdown the existing sink since we need to now create a new one. 118 if err := closeExisting(); err != nil { 119 return nil, errors.Trace(err) 120 } 121 sink, err := OpenTrackingSink(TrackingSinkArgs{ 122 Name: lf.args.Name, 123 Config: cfg, 124 Caller: lf.args.Caller, 125 OpenSink: lf.args.OpenSink, 126 }) 127 if err != nil { 128 return nil, errors.Trace(err) 129 } 130 lf.enabledCh <- true 131 return sink, nil 132 } 133 134 // waitForEnabled returns true if streaming is enabled. 135 // Otherwise if blocks and waits for enabled to be true. 136 func (lf *LogForwarder) waitForEnabled() (bool, error) { 137 lf.mu.Lock() 138 enabled := lf.enabled 139 lf.mu.Unlock() 140 if enabled { 141 return true, nil 142 } 143 144 select { 145 case <-lf.catacomb.Dying(): 146 return false, tomb.ErrDying 147 case enabled = <-lf.enabledCh: 148 } 149 lf.mu.Lock() 150 defer lf.mu.Unlock() 151 152 if !lf.enabled && enabled { 153 lf.args.Logger.Infof("log forward enabled, starting to stream logs to syslog sink") 154 } 155 lf.enabled = enabled 156 return enabled, nil 157 } 158 159 // NewLogForwarder returns a worker that forwards logs received from 160 // the stream to the sender. 161 func NewLogForwarder(args OpenLogForwarderArgs) (*LogForwarder, error) { 162 lf := &LogForwarder{ 163 args: args, 164 enabledCh: make(chan bool, 1), 165 } 166 err := catacomb.Invoke(catacomb.Plan{ 167 Site: &lf.catacomb, 168 Work: func() error { 169 return errors.Trace(lf.loop()) 170 }, 171 }) 172 if err != nil { 173 return nil, errors.Trace(err) 174 } 175 return lf, nil 176 } 177 178 func (lf *LogForwarder) loop() error { 179 configWatcher, err := lf.args.LogForwardConfig.WatchForLogForwardConfigChanges() 180 if err != nil { 181 return errors.Trace(err) 182 } 183 if err := lf.catacomb.Add(configWatcher); err != nil { 184 return errors.Trace(err) 185 } 186 187 records := make(chan []logfwd.Record) 188 var stream LogStream 189 go func() { 190 for { 191 enabled, err := lf.waitForEnabled() 192 if err == tomb.ErrDying { 193 return 194 } 195 if !enabled { 196 continue 197 } 198 // Lazily create log streamer if needed. 199 if stream == nil { 200 streamCfg := params.LogStreamConfig{ 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 }