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 }