vitess.io/vitess@v0.16.2/go/vt/logutil/throttled.go (about)

     1  /*
     2  Copyright 2019 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package logutil
    18  
    19  import (
    20  	"fmt"
    21  	"sync"
    22  	"time"
    23  
    24  	"vitess.io/vitess/go/vt/log"
    25  )
    26  
    27  // ThrottledLogger will allow logging of messages but won't spam the
    28  // logs.
    29  type ThrottledLogger struct {
    30  	// set at construction
    31  	name        string
    32  	maxInterval time.Duration
    33  
    34  	// mu protects the following members
    35  	mu           sync.Mutex
    36  	lastlogTime  time.Time
    37  	skippedCount int
    38  }
    39  
    40  // NewThrottledLogger will create a ThrottledLogger with the given
    41  // name and throttling interval.
    42  func NewThrottledLogger(name string, maxInterval time.Duration) *ThrottledLogger {
    43  	return &ThrottledLogger{
    44  		name:        name,
    45  		maxInterval: maxInterval,
    46  	}
    47  }
    48  
    49  type logFunc func(int, ...any)
    50  
    51  var (
    52  	infoDepth    = log.InfoDepth
    53  	warningDepth = log.WarningDepth
    54  	errorDepth   = log.ErrorDepth
    55  )
    56  
    57  func (tl *ThrottledLogger) log(logF logFunc, format string, v ...any) {
    58  	now := time.Now()
    59  
    60  	tl.mu.Lock()
    61  	defer tl.mu.Unlock()
    62  	logWaitTime := tl.maxInterval - (now.Sub(tl.lastlogTime))
    63  	if logWaitTime < 0 {
    64  		tl.lastlogTime = now
    65  		logF(2, fmt.Sprintf(tl.name+": "+format, v...))
    66  		return
    67  	}
    68  	// If this is the first message to be skipped, start a goroutine
    69  	// to log and reset skippedCount
    70  	if tl.skippedCount == 0 {
    71  		go func(d time.Duration) {
    72  			time.Sleep(d)
    73  			tl.mu.Lock()
    74  			defer tl.mu.Unlock()
    75  			// Because of the go func(), we lose the stack trace,
    76  			// so we just use the current line for this.
    77  			logF(0, fmt.Sprintf("%v: skipped %v log messages", tl.name, tl.skippedCount))
    78  			tl.skippedCount = 0
    79  		}(logWaitTime)
    80  	}
    81  	tl.skippedCount++
    82  }
    83  
    84  // Infof logs an info if not throttled.
    85  func (tl *ThrottledLogger) Infof(format string, v ...any) {
    86  	tl.log(infoDepth, format, v...)
    87  }
    88  
    89  // Warningf logs a warning if not throttled.
    90  func (tl *ThrottledLogger) Warningf(format string, v ...any) {
    91  	tl.log(warningDepth, format, v...)
    92  }
    93  
    94  // Errorf logs an error if not throttled.
    95  func (tl *ThrottledLogger) Errorf(format string, v ...any) {
    96  	tl.log(errorDepth, format, v...)
    97  }