blob: 866b6f7a8946350d2b5989a41a3f9c1891f8b01a [file] [log] [blame]
// 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)
}
}
}