github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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  }