github.com/hernad/nomad@v1.6.112/command/agent/monitor/monitor.go (about)

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