|
|
@@ -0,0 +1,192 @@
|
|
|
+// Copyright 2015 CoreOS, Inc.
|
|
|
+//
|
|
|
+// 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 includes utilities to faciliate logging.
|
|
|
+package logutil
|
|
|
+
|
|
|
+import (
|
|
|
+ "fmt"
|
|
|
+ "sync"
|
|
|
+ "time"
|
|
|
+
|
|
|
+ "github.com/coreos/etcd/Godeps/_workspace/src/github.com/coreos/pkg/capnslog"
|
|
|
+)
|
|
|
+
|
|
|
+var (
|
|
|
+ defaultMergePeriod = time.Second
|
|
|
+
|
|
|
+ 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 {
|
|
|
+ return fmt.Sprintf("[merged %d repeated lines in %s]", s.count, now.Sub(s.start))
|
|
|
+}
|
|
|
+
|
|
|
+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)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|