github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/command/agent/monitor/monitor.go (about)

     1  package monitor
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  	"time"
     7  
     8  	log "github.com/hashicorp/go-hclog"
     9  	"github.com/hashicorp/nomad/helper"
    10  )
    11  
    12  // Monitor provides a mechanism to stream logs using go-hclog
    13  // InterceptLogger and SinkAdapter. It allows streaming of logs
    14  // at a different log level than what is set on the logger.
    15  type Monitor interface {
    16  	// Start returns a channel of log messages which are sent
    17  	// ever time a log message occurs
    18  	Start() <-chan []byte
    19  
    20  	// Stop de-registers the sink from the InterceptLogger
    21  	// and closes the log channels
    22  	Stop()
    23  }
    24  
    25  // monitor implements the Monitor interface
    26  type monitor struct {
    27  	// protects droppedCount and logCh
    28  	sync.Mutex
    29  
    30  	sink log.SinkAdapter
    31  
    32  	// logger is the logger we will be monitoring
    33  	logger log.InterceptLogger
    34  
    35  	// logCh is a buffered chan where we send logs when streaming
    36  	logCh chan []byte
    37  
    38  	// doneCh coordinates the shutdown of logCh
    39  	doneCh chan struct{}
    40  
    41  	// droppedCount is the current count of messages
    42  	// that were dropped from the logCh buffer.
    43  	// only access under lock
    44  	droppedCount int
    45  	bufSize      int
    46  	// droppedDuration is the amount of time we should
    47  	// wait to check for dropped messages. Defaults
    48  	// to 3 seconds
    49  	droppedDuration time.Duration
    50  }
    51  
    52  // New creates a new Monitor. Start must be called in order to actually start
    53  // streaming logs
    54  func New(buf int, logger log.InterceptLogger, opts *log.LoggerOptions) Monitor {
    55  	return new(buf, logger, opts)
    56  }
    57  
    58  func new(buf int, logger log.InterceptLogger, opts *log.LoggerOptions) *monitor {
    59  	sw := &monitor{
    60  		logger:          logger,
    61  		logCh:           make(chan []byte, buf),
    62  		doneCh:          make(chan struct{}, 1),
    63  		bufSize:         buf,
    64  		droppedDuration: 3 * time.Second,
    65  	}
    66  
    67  	opts.Output = sw
    68  	sink := log.NewSinkAdapter(opts)
    69  	sw.sink = sink
    70  
    71  	return sw
    72  }
    73  
    74  // Stop deregisters the sink and stops the monitoring process
    75  func (d *monitor) Stop() {
    76  	d.logger.DeregisterSink(d.sink)
    77  	close(d.doneCh)
    78  }
    79  
    80  // Start registers a sink on the monitor's logger and starts sending
    81  // received log messages over the returned channel.
    82  func (d *monitor) Start() <-chan []byte {
    83  	// register our sink with the logger
    84  	d.logger.RegisterSink(d.sink)
    85  
    86  	streamCh := make(chan []byte, d.bufSize)
    87  
    88  	// run a go routine that listens for streamed
    89  	// log messages and sends them to streamCh
    90  	go func() {
    91  		defer close(streamCh)
    92  
    93  		for {
    94  			select {
    95  			case log := <-d.logCh:
    96  				select {
    97  				case <-d.doneCh:
    98  					return
    99  				case streamCh <- log:
   100  				}
   101  			case <-d.doneCh:
   102  				return
   103  			}
   104  		}
   105  	}()
   106  
   107  	// run a go routine that periodically checks for
   108  	// dropped messages and makes room on the logCh
   109  	// to add a dropped message count warning
   110  	go func() {
   111  		timer, stop := helper.NewSafeTimer(d.droppedDuration)
   112  		defer stop()
   113  
   114  		// loop and check for dropped messages
   115  		for {
   116  			timer.Reset(d.droppedDuration)
   117  
   118  			select {
   119  			case <-d.doneCh:
   120  				return
   121  			case <-timer.C:
   122  				d.Lock()
   123  
   124  				// Check if there have been any dropped messages.
   125  				if d.droppedCount > 0 {
   126  					dropped := fmt.Sprintf("[WARN] Monitor dropped %d logs during monitor request\n", d.droppedCount)
   127  					select {
   128  					case <-d.doneCh:
   129  						d.Unlock()
   130  						return
   131  					// Try sending dropped message count to logCh in case
   132  					// there is room in the buffer now.
   133  					case d.logCh <- []byte(dropped):
   134  					default:
   135  						// Drop a log message to make room for "Monitor dropped.." message
   136  						select {
   137  						case <-d.logCh:
   138  							d.droppedCount++
   139  							dropped = fmt.Sprintf("[WARN] Monitor dropped %d logs during monitor request\n", d.droppedCount)
   140  						default:
   141  						}
   142  						d.logCh <- []byte(dropped)
   143  					}
   144  					d.droppedCount = 0
   145  				}
   146  				// unlock after handling dropped message
   147  				d.Unlock()
   148  			}
   149  		}
   150  	}()
   151  
   152  	return streamCh
   153  }
   154  
   155  // Write attempts to send latest log to logCh
   156  // it drops the log if channel is unavailable to receive
   157  func (d *monitor) Write(p []byte) (n int, err error) {
   158  	d.Lock()
   159  	defer d.Unlock()
   160  
   161  	// ensure logCh is still open
   162  	select {
   163  	case <-d.doneCh:
   164  		return
   165  	default:
   166  	}
   167  
   168  	bytes := make([]byte, len(p))
   169  	copy(bytes, p)
   170  
   171  	select {
   172  	case d.logCh <- bytes:
   173  	default:
   174  		d.droppedCount++
   175  	}
   176  
   177  	return len(p), nil
   178  }