go.etcd.io/etcd@v3.3.27+incompatible/pkg/logutil/merge_logger.go (about) 1 // Copyright 2015 The etcd Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package logutil 16 17 import ( 18 "fmt" 19 "sync" 20 "time" 21 22 "github.com/coreos/pkg/capnslog" 23 ) 24 25 var ( 26 defaultMergePeriod = time.Second 27 defaultTimeOutputScale = 10 * time.Millisecond 28 29 outputInterval = time.Second 30 ) 31 32 // line represents a log line that can be printed out 33 // through capnslog.PackageLogger. 34 type line struct { 35 level capnslog.LogLevel 36 str string 37 } 38 39 func (l line) append(s string) line { 40 return line{ 41 level: l.level, 42 str: l.str + " " + s, 43 } 44 } 45 46 // status represents the merge status of a line. 47 type status struct { 48 period time.Duration 49 50 start time.Time // start time of latest merge period 51 count int // number of merged lines from starting 52 } 53 54 func (s *status) isInMergePeriod(now time.Time) bool { 55 return s.period == 0 || s.start.Add(s.period).After(now) 56 } 57 58 func (s *status) isEmpty() bool { return s.count == 0 } 59 60 func (s *status) summary(now time.Time) string { 61 ts := s.start.Round(defaultTimeOutputScale) 62 took := now.Round(defaultTimeOutputScale).Sub(ts) 63 return fmt.Sprintf("[merged %d repeated lines in %s]", s.count, took) 64 } 65 66 func (s *status) reset(now time.Time) { 67 s.start = now 68 s.count = 0 69 } 70 71 // MergeLogger supports merge logging, which merges repeated log lines 72 // and prints summary log lines instead. 73 // 74 // For merge logging, MergeLogger prints out the line when the line appears 75 // at the first time. MergeLogger holds the same log line printed within 76 // defaultMergePeriod, and prints out summary log line at the end of defaultMergePeriod. 77 // It stops merging when the line doesn't appear within the 78 // defaultMergePeriod. 79 type MergeLogger struct { 80 *capnslog.PackageLogger 81 82 mu sync.Mutex // protect statusm 83 statusm map[line]*status 84 } 85 86 func NewMergeLogger(logger *capnslog.PackageLogger) *MergeLogger { 87 l := &MergeLogger{ 88 PackageLogger: logger, 89 statusm: make(map[line]*status), 90 } 91 go l.outputLoop() 92 return l 93 } 94 95 func (l *MergeLogger) MergeInfo(entries ...interface{}) { 96 l.merge(line{ 97 level: capnslog.INFO, 98 str: fmt.Sprint(entries...), 99 }) 100 } 101 102 func (l *MergeLogger) MergeInfof(format string, args ...interface{}) { 103 l.merge(line{ 104 level: capnslog.INFO, 105 str: fmt.Sprintf(format, args...), 106 }) 107 } 108 109 func (l *MergeLogger) MergeNotice(entries ...interface{}) { 110 l.merge(line{ 111 level: capnslog.NOTICE, 112 str: fmt.Sprint(entries...), 113 }) 114 } 115 116 func (l *MergeLogger) MergeNoticef(format string, args ...interface{}) { 117 l.merge(line{ 118 level: capnslog.NOTICE, 119 str: fmt.Sprintf(format, args...), 120 }) 121 } 122 123 func (l *MergeLogger) MergeWarning(entries ...interface{}) { 124 l.merge(line{ 125 level: capnslog.WARNING, 126 str: fmt.Sprint(entries...), 127 }) 128 } 129 130 func (l *MergeLogger) MergeWarningf(format string, args ...interface{}) { 131 l.merge(line{ 132 level: capnslog.WARNING, 133 str: fmt.Sprintf(format, args...), 134 }) 135 } 136 137 func (l *MergeLogger) MergeError(entries ...interface{}) { 138 l.merge(line{ 139 level: capnslog.ERROR, 140 str: fmt.Sprint(entries...), 141 }) 142 } 143 144 func (l *MergeLogger) MergeErrorf(format string, args ...interface{}) { 145 l.merge(line{ 146 level: capnslog.ERROR, 147 str: fmt.Sprintf(format, args...), 148 }) 149 } 150 151 func (l *MergeLogger) merge(ln line) { 152 l.mu.Lock() 153 154 // increase count if the logger is merging the line 155 if status, ok := l.statusm[ln]; ok { 156 status.count++ 157 l.mu.Unlock() 158 return 159 } 160 161 // initialize status of the line 162 l.statusm[ln] = &status{ 163 period: defaultMergePeriod, 164 start: time.Now(), 165 } 166 // release the lock before IO operation 167 l.mu.Unlock() 168 // print out the line at its first time 169 l.PackageLogger.Logf(ln.level, ln.str) 170 } 171 172 func (l *MergeLogger) outputLoop() { 173 for now := range time.Tick(outputInterval) { 174 var outputs []line 175 176 l.mu.Lock() 177 for ln, status := range l.statusm { 178 if status.isInMergePeriod(now) { 179 continue 180 } 181 if status.isEmpty() { 182 delete(l.statusm, ln) 183 continue 184 } 185 outputs = append(outputs, ln.append(status.summary(now))) 186 status.reset(now) 187 } 188 l.mu.Unlock() 189 190 for _, o := range outputs { 191 l.PackageLogger.Logf(o.level, o.str) 192 } 193 } 194 }