2021-04-18 15:41:13 -04:00
|
|
|
// Copyright (c) 2015-2021 MinIO, Inc.
|
|
|
|
//
|
|
|
|
// This file is part of MinIO Object Storage stack
|
|
|
|
//
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU Affero General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
2018-05-09 18:59:45 -04:00
|
|
|
|
|
|
|
package logger
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2022-07-27 12:44:59 -04:00
|
|
|
"errors"
|
2018-05-09 18:59:45 -04:00
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
2022-07-27 12:44:59 -04:00
|
|
|
// LogOnce provides the function type for logger.LogOnceIf() function
|
|
|
|
type LogOnce func(ctx context.Context, err error, id string, errKind ...interface{})
|
|
|
|
|
2023-03-16 14:59:42 -04:00
|
|
|
type onceErr struct {
|
|
|
|
Err error
|
|
|
|
Count int
|
|
|
|
}
|
|
|
|
|
2018-05-09 18:59:45 -04:00
|
|
|
// Holds a map of recently logged errors.
|
|
|
|
type logOnceType struct {
|
2023-03-16 14:59:42 -04:00
|
|
|
IDMap map[string]onceErr
|
2018-05-09 18:59:45 -04:00
|
|
|
sync.Mutex
|
|
|
|
}
|
|
|
|
|
2024-04-04 08:04:40 -04:00
|
|
|
func (l *logOnceType) logOnceConsoleIf(ctx context.Context, subsystem string, err error, id string, errKind ...interface{}) {
|
2022-05-12 10:20:58 -04:00
|
|
|
if err == nil {
|
|
|
|
return
|
|
|
|
}
|
2022-07-27 12:44:59 -04:00
|
|
|
|
|
|
|
nerr := unwrapErrs(err)
|
2022-05-12 10:20:58 -04:00
|
|
|
l.Lock()
|
2022-07-27 12:44:59 -04:00
|
|
|
shouldLog := true
|
2023-03-16 14:59:42 -04:00
|
|
|
prev, ok := l.IDMap[id]
|
2022-07-27 12:44:59 -04:00
|
|
|
if !ok {
|
2023-03-16 14:59:42 -04:00
|
|
|
l.IDMap[id] = onceErr{
|
|
|
|
Err: nerr,
|
|
|
|
Count: 1,
|
|
|
|
}
|
|
|
|
} else if prev.Err.Error() == nerr.Error() {
|
2022-07-27 12:44:59 -04:00
|
|
|
// if errors are equal do not log.
|
2023-03-16 14:59:42 -04:00
|
|
|
prev.Count++
|
|
|
|
l.IDMap[id] = prev
|
|
|
|
shouldLog = false
|
2022-05-12 10:20:58 -04:00
|
|
|
}
|
|
|
|
l.Unlock()
|
|
|
|
|
|
|
|
if shouldLog {
|
2024-04-04 08:04:40 -04:00
|
|
|
consoleLogIf(ctx, subsystem, err, errKind...)
|
2022-05-12 10:20:58 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-27 12:44:59 -04:00
|
|
|
const unwrapErrsDepth = 3
|
|
|
|
|
|
|
|
// unwrapErrs upto the point where errors.Unwrap(err) returns nil
|
|
|
|
func unwrapErrs(err error) (leafErr error) {
|
|
|
|
uerr := errors.Unwrap(err)
|
|
|
|
depth := 1
|
|
|
|
for uerr != nil {
|
|
|
|
// Save the current `uerr`
|
|
|
|
leafErr = uerr
|
|
|
|
// continue to look for leaf errors underneath
|
|
|
|
uerr = errors.Unwrap(leafErr)
|
|
|
|
depth++
|
|
|
|
if depth == unwrapErrsDepth {
|
|
|
|
// If we have reached enough depth we
|
|
|
|
// do not further recurse down, this
|
|
|
|
// is done to avoid any unnecessary
|
|
|
|
// latencies this might bring.
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2022-08-02 18:10:11 -04:00
|
|
|
if uerr == nil {
|
|
|
|
leafErr = err
|
|
|
|
}
|
2022-07-27 12:44:59 -04:00
|
|
|
return leafErr
|
|
|
|
}
|
|
|
|
|
2018-05-09 18:59:45 -04:00
|
|
|
// One log message per error.
|
2024-04-04 08:04:40 -04:00
|
|
|
func (l *logOnceType) logOnceIf(ctx context.Context, subsystem string, err error, id string, errKind ...interface{}) {
|
2018-05-09 18:59:45 -04:00
|
|
|
if err == nil {
|
|
|
|
return
|
|
|
|
}
|
2022-07-27 12:44:59 -04:00
|
|
|
|
|
|
|
nerr := unwrapErrs(err)
|
2018-05-09 18:59:45 -04:00
|
|
|
l.Lock()
|
2022-07-27 12:44:59 -04:00
|
|
|
shouldLog := true
|
2023-03-16 14:59:42 -04:00
|
|
|
prev, ok := l.IDMap[id]
|
2022-07-27 12:44:59 -04:00
|
|
|
if !ok {
|
2023-03-16 14:59:42 -04:00
|
|
|
l.IDMap[id] = onceErr{
|
|
|
|
Err: nerr,
|
|
|
|
Count: 1,
|
|
|
|
}
|
|
|
|
} else if prev.Err.Error() == nerr.Error() {
|
2022-07-27 12:44:59 -04:00
|
|
|
// if errors are equal do not log.
|
2023-03-16 14:59:42 -04:00
|
|
|
prev.Count++
|
|
|
|
l.IDMap[id] = prev
|
|
|
|
shouldLog = false
|
2018-05-09 18:59:45 -04:00
|
|
|
}
|
|
|
|
l.Unlock()
|
|
|
|
|
|
|
|
if shouldLog {
|
2024-04-04 08:04:40 -04:00
|
|
|
logIf(ctx, subsystem, err, errKind...)
|
2018-05-09 18:59:45 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-28 04:11:51 -04:00
|
|
|
// Cleanup the map every one hour so that the log message is printed again for the user to notice.
|
2018-05-09 18:59:45 -04:00
|
|
|
func (l *logOnceType) cleanupRoutine() {
|
|
|
|
for {
|
2023-03-16 14:59:42 -04:00
|
|
|
time.Sleep(time.Hour)
|
|
|
|
|
2019-02-13 07:59:36 -05:00
|
|
|
l.Lock()
|
2023-03-16 14:59:42 -04:00
|
|
|
l.IDMap = make(map[string]onceErr)
|
2019-02-13 07:59:36 -05:00
|
|
|
l.Unlock()
|
2018-05-09 18:59:45 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns logOnceType
|
|
|
|
func newLogOnceType() *logOnceType {
|
2023-03-16 14:59:42 -04:00
|
|
|
l := &logOnceType{IDMap: make(map[string]onceErr)}
|
2018-05-09 18:59:45 -04:00
|
|
|
go l.cleanupRoutine()
|
|
|
|
return l
|
|
|
|
}
|
|
|
|
|
|
|
|
var logOnce = newLogOnceType()
|
|
|
|
|
|
|
|
// LogOnceIf - Logs notification errors - once per error.
|
|
|
|
// id is a unique identifier for related log messages, refer to cmd/notification.go
|
|
|
|
// on how it is used.
|
2024-04-04 08:04:40 -04:00
|
|
|
func LogOnceIf(ctx context.Context, subsystem string, err error, id string, errKind ...interface{}) {
|
2022-07-25 20:53:03 -04:00
|
|
|
if logIgnoreError(err) {
|
2020-10-30 17:55:50 -04:00
|
|
|
return
|
|
|
|
}
|
2024-04-04 08:04:40 -04:00
|
|
|
logOnce.logOnceIf(ctx, subsystem, err, id, errKind...)
|
2018-05-09 18:59:45 -04:00
|
|
|
}
|
2022-05-12 10:20:58 -04:00
|
|
|
|
|
|
|
// LogOnceConsoleIf - similar to LogOnceIf but exclusively only logs to console target.
|
2024-04-04 08:04:40 -04:00
|
|
|
func LogOnceConsoleIf(ctx context.Context, subsystem string, err error, id string, errKind ...interface{}) {
|
2022-07-25 20:53:03 -04:00
|
|
|
if logIgnoreError(err) {
|
2022-05-12 10:20:58 -04:00
|
|
|
return
|
|
|
|
}
|
2024-04-04 08:04:40 -04:00
|
|
|
logOnce.logOnceConsoleIf(ctx, subsystem, err, id, errKind...)
|
2022-05-12 10:20:58 -04:00
|
|
|
}
|