123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- // 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)
- }
- }
- }
|