195 lines
4.5 KiB
Go
195 lines
4.5 KiB
Go
|
// Copyright 2015 The etcd Authors
|
||
|
//
|
||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
// you may not use this file except in compliance with the License.
|
||
|
// You may obtain a copy of the License at
|
||
|
//
|
||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||
|
//
|
||
|
// Unless required by applicable law or agreed to in writing, software
|
||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
// See the License for the specific language governing permissions and
|
||
|
// limitations under the License.
|
||
|
|
||
|
package logutil
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"sync"
|
||
|
"time"
|
||
|
|
||
|
"github.com/coreos/pkg/capnslog"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
defaultMergePeriod = time.Second
|
||
|
defaultTimeOutputScale = 10 * time.Millisecond
|
||
|
|
||
|
outputInterval = time.Second
|
||
|
)
|
||
|
|
||
|
// line represents a log line that can be printed out
|
||
|
// through capnslog.PackageLogger.
|
||
|
type line struct {
|
||
|
level capnslog.LogLevel
|
||
|
str string
|
||
|
}
|
||
|
|
||
|
func (l line) append(s string) line {
|
||
|
return line{
|
||
|
level: l.level,
|
||
|
str: l.str + " " + s,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// status represents the merge status of a line.
|
||
|
type status struct {
|
||
|
period time.Duration
|
||
|
|
||
|
start time.Time // start time of latest merge period
|
||
|
count int // number of merged lines from starting
|
||
|
}
|
||
|
|
||
|
func (s *status) isInMergePeriod(now time.Time) bool {
|
||
|
return s.period == 0 || s.start.Add(s.period).After(now)
|
||
|
}
|
||
|
|
||
|
func (s *status) isEmpty() bool { return s.count == 0 }
|
||
|
|
||
|
func (s *status) summary(now time.Time) string {
|
||
|
ts := s.start.Round(defaultTimeOutputScale)
|
||
|
took := now.Round(defaultTimeOutputScale).Sub(ts)
|
||
|
return fmt.Sprintf("[merged %d repeated lines in %s]", s.count, took)
|
||
|
}
|
||
|
|
||
|
func (s *status) reset(now time.Time) {
|
||
|
s.start = now
|
||
|
s.count = 0
|
||
|
}
|
||
|
|
||
|
// MergeLogger supports merge logging, which merges repeated log lines
|
||
|
// and prints summary log lines instead.
|
||
|
//
|
||
|
// For merge logging, MergeLogger prints out the line when the line appears
|
||
|
// at the first time. MergeLogger holds the same log line printed within
|
||
|
// defaultMergePeriod, and prints out summary log line at the end of defaultMergePeriod.
|
||
|
// It stops merging when the line doesn't appear within the
|
||
|
// defaultMergePeriod.
|
||
|
type MergeLogger struct {
|
||
|
*capnslog.PackageLogger
|
||
|
|
||
|
mu sync.Mutex // protect statusm
|
||
|
statusm map[line]*status
|
||
|
}
|
||
|
|
||
|
func NewMergeLogger(logger *capnslog.PackageLogger) *MergeLogger {
|
||
|
l := &MergeLogger{
|
||
|
PackageLogger: logger,
|
||
|
statusm: make(map[line]*status),
|
||
|
}
|
||
|
go l.outputLoop()
|
||
|
return l
|
||
|
}
|
||
|
|
||
|
func (l *MergeLogger) MergeInfo(entries ...interface{}) {
|
||
|
l.merge(line{
|
||
|
level: capnslog.INFO,
|
||
|
str: fmt.Sprint(entries...),
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func (l *MergeLogger) MergeInfof(format string, args ...interface{}) {
|
||
|
l.merge(line{
|
||
|
level: capnslog.INFO,
|
||
|
str: fmt.Sprintf(format, args...),
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func (l *MergeLogger) MergeNotice(entries ...interface{}) {
|
||
|
l.merge(line{
|
||
|
level: capnslog.NOTICE,
|
||
|
str: fmt.Sprint(entries...),
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func (l *MergeLogger) MergeNoticef(format string, args ...interface{}) {
|
||
|
l.merge(line{
|
||
|
level: capnslog.NOTICE,
|
||
|
str: fmt.Sprintf(format, args...),
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func (l *MergeLogger) MergeWarning(entries ...interface{}) {
|
||
|
l.merge(line{
|
||
|
level: capnslog.WARNING,
|
||
|
str: fmt.Sprint(entries...),
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func (l *MergeLogger) MergeWarningf(format string, args ...interface{}) {
|
||
|
l.merge(line{
|
||
|
level: capnslog.WARNING,
|
||
|
str: fmt.Sprintf(format, args...),
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func (l *MergeLogger) MergeError(entries ...interface{}) {
|
||
|
l.merge(line{
|
||
|
level: capnslog.ERROR,
|
||
|
str: fmt.Sprint(entries...),
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func (l *MergeLogger) MergeErrorf(format string, args ...interface{}) {
|
||
|
l.merge(line{
|
||
|
level: capnslog.ERROR,
|
||
|
str: fmt.Sprintf(format, args...),
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func (l *MergeLogger) merge(ln line) {
|
||
|
l.mu.Lock()
|
||
|
|
||
|
// increase count if the logger is merging the line
|
||
|
if status, ok := l.statusm[ln]; ok {
|
||
|
status.count++
|
||
|
l.mu.Unlock()
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// initialize status of the line
|
||
|
l.statusm[ln] = &status{
|
||
|
period: defaultMergePeriod,
|
||
|
start: time.Now(),
|
||
|
}
|
||
|
// release the lock before IO operation
|
||
|
l.mu.Unlock()
|
||
|
// print out the line at its first time
|
||
|
l.PackageLogger.Logf(ln.level, ln.str)
|
||
|
}
|
||
|
|
||
|
func (l *MergeLogger) outputLoop() {
|
||
|
for now := range time.Tick(outputInterval) {
|
||
|
var outputs []line
|
||
|
|
||
|
l.mu.Lock()
|
||
|
for ln, status := range l.statusm {
|
||
|
if status.isInMergePeriod(now) {
|
||
|
continue
|
||
|
}
|
||
|
if status.isEmpty() {
|
||
|
delete(l.statusm, ln)
|
||
|
continue
|
||
|
}
|
||
|
outputs = append(outputs, ln.append(status.summary(now)))
|
||
|
status.reset(now)
|
||
|
}
|
||
|
l.mu.Unlock()
|
||
|
|
||
|
for _, o := range outputs {
|
||
|
l.PackageLogger.Logf(o.level, o.str)
|
||
|
}
|
||
|
}
|
||
|
}
|