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  }