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 }