github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/logger/logonce.go (about)

     1  // Copyright (c) 2015-2021 MinIO, Inc.
     2  //
     3  // This file is part of MinIO Object Storage stack
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package logger
    19  
    20  import (
    21  	"context"
    22  	"errors"
    23  	"sync"
    24  	"time"
    25  )
    26  
    27  // LogOnce provides the function type for logger.LogOnceIf() function
    28  type LogOnce func(ctx context.Context, err error, id string, errKind ...interface{})
    29  
    30  type onceErr struct {
    31  	Err   error
    32  	Count int
    33  }
    34  
    35  // Holds a map of recently logged errors.
    36  type logOnceType struct {
    37  	IDMap map[string]onceErr
    38  	sync.Mutex
    39  }
    40  
    41  func (l *logOnceType) logOnceConsoleIf(ctx context.Context, err error, id string, errKind ...interface{}) {
    42  	if err == nil {
    43  		return
    44  	}
    45  
    46  	nerr := unwrapErrs(err)
    47  	l.Lock()
    48  	shouldLog := true
    49  	prev, ok := l.IDMap[id]
    50  	if !ok {
    51  		l.IDMap[id] = onceErr{
    52  			Err:   nerr,
    53  			Count: 1,
    54  		}
    55  	} else if prev.Err.Error() == nerr.Error() {
    56  		// if errors are equal do not log.
    57  		prev.Count++
    58  		l.IDMap[id] = prev
    59  		shouldLog = false
    60  	}
    61  	l.Unlock()
    62  
    63  	if shouldLog {
    64  		consoleLogIf(ctx, err, errKind...)
    65  	}
    66  }
    67  
    68  const unwrapErrsDepth = 3
    69  
    70  // unwrapErrs upto the point where errors.Unwrap(err) returns nil
    71  func unwrapErrs(err error) (leafErr error) {
    72  	uerr := errors.Unwrap(err)
    73  	depth := 1
    74  	for uerr != nil {
    75  		// Save the current `uerr`
    76  		leafErr = uerr
    77  		// continue to look for leaf errors underneath
    78  		uerr = errors.Unwrap(leafErr)
    79  		depth++
    80  		if depth == unwrapErrsDepth {
    81  			// If we have reached enough depth we
    82  			// do not further recurse down, this
    83  			// is done to avoid any unnecessary
    84  			// latencies this might bring.
    85  			break
    86  		}
    87  	}
    88  	if uerr == nil {
    89  		leafErr = err
    90  	}
    91  	return leafErr
    92  }
    93  
    94  // One log message per error.
    95  func (l *logOnceType) logOnceIf(ctx context.Context, err error, id string, errKind ...interface{}) {
    96  	if err == nil {
    97  		return
    98  	}
    99  
   100  	nerr := unwrapErrs(err)
   101  	l.Lock()
   102  	shouldLog := true
   103  	prev, ok := l.IDMap[id]
   104  	if !ok {
   105  		l.IDMap[id] = onceErr{
   106  			Err:   nerr,
   107  			Count: 1,
   108  		}
   109  	} else if prev.Err.Error() == nerr.Error() {
   110  		// if errors are equal do not log.
   111  		prev.Count++
   112  		l.IDMap[id] = prev
   113  		shouldLog = false
   114  	}
   115  	l.Unlock()
   116  
   117  	if shouldLog {
   118  		logIf(ctx, err, errKind...)
   119  	}
   120  }
   121  
   122  // Cleanup the map every one hour so that the log message is printed again for the user to notice.
   123  func (l *logOnceType) cleanupRoutine() {
   124  	for {
   125  		time.Sleep(time.Hour)
   126  
   127  		l.Lock()
   128  		l.IDMap = make(map[string]onceErr)
   129  		l.Unlock()
   130  	}
   131  }
   132  
   133  // Returns logOnceType
   134  func newLogOnceType() *logOnceType {
   135  	l := &logOnceType{IDMap: make(map[string]onceErr)}
   136  	go l.cleanupRoutine()
   137  	return l
   138  }
   139  
   140  var logOnce = newLogOnceType()
   141  
   142  // LogOnceIf - Logs notification errors - once per error.
   143  // id is a unique identifier for related log messages, refer to cmd/notification.go
   144  // on how it is used.
   145  func LogOnceIf(ctx context.Context, err error, id string, errKind ...interface{}) {
   146  	if logIgnoreError(err) {
   147  		return
   148  	}
   149  	logOnce.logOnceIf(ctx, err, id, errKind...)
   150  }
   151  
   152  // LogOnceConsoleIf - similar to LogOnceIf but exclusively only logs to console target.
   153  func LogOnceConsoleIf(ctx context.Context, err error, id string, errKind ...interface{}) {
   154  	if logIgnoreError(err) {
   155  		return
   156  	}
   157  	logOnce.logOnceConsoleIf(ctx, err, id, errKind...)
   158  }