merge_logger.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. // Copyright 2015 CoreOS, Inc.
  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. // Package logutil includes utilities to faciliate logging.
  15. package logutil
  16. import (
  17. "fmt"
  18. "sync"
  19. "time"
  20. "github.com/coreos/etcd/Godeps/_workspace/src/github.com/coreos/pkg/capnslog"
  21. )
  22. var (
  23. defaultMergePeriod = time.Second
  24. outputInterval = time.Second
  25. )
  26. // line represents a log line that can be printed out
  27. // through capnslog.PackageLogger.
  28. type line struct {
  29. level capnslog.LogLevel
  30. str string
  31. }
  32. func (l line) append(s string) line {
  33. return line{
  34. level: l.level,
  35. str: l.str + " " + s,
  36. }
  37. }
  38. // status represents the merge status of a line.
  39. type status struct {
  40. period time.Duration
  41. start time.Time // start time of latest merge period
  42. count int // number of merged lines from starting
  43. }
  44. func (s *status) isInMergePeriod(now time.Time) bool {
  45. return s.period == 0 || s.start.Add(s.period).After(now)
  46. }
  47. func (s *status) isEmpty() bool { return s.count == 0 }
  48. func (s *status) summary(now time.Time) string {
  49. return fmt.Sprintf("[merged %d repeated lines in %s]", s.count, now.Sub(s.start))
  50. }
  51. func (s *status) reset(now time.Time) {
  52. s.start = now
  53. s.count = 0
  54. }
  55. // MergeLogger supports merge logging, which merges repeated log lines
  56. // and prints summary log lines instead.
  57. //
  58. // For merge logging, MergeLogger prints out the line when the line appears
  59. // at the first time. MergeLogger holds the same log line printed within
  60. // defaultMergePeriod, and prints out summary log line at the end of defaultMergePeriod.
  61. // It stops merging when the line doesn't appear within the
  62. // defaultMergePeriod.
  63. type MergeLogger struct {
  64. *capnslog.PackageLogger
  65. mu sync.Mutex // protect statusm
  66. statusm map[line]*status
  67. }
  68. func NewMergeLogger(logger *capnslog.PackageLogger) *MergeLogger {
  69. l := &MergeLogger{
  70. PackageLogger: logger,
  71. statusm: make(map[line]*status),
  72. }
  73. go l.outputLoop()
  74. return l
  75. }
  76. func (l *MergeLogger) MergeInfo(entries ...interface{}) {
  77. l.merge(line{
  78. level: capnslog.INFO,
  79. str: fmt.Sprint(entries...),
  80. })
  81. }
  82. func (l *MergeLogger) MergeInfof(format string, args ...interface{}) {
  83. l.merge(line{
  84. level: capnslog.INFO,
  85. str: fmt.Sprintf(format, args...),
  86. })
  87. }
  88. func (l *MergeLogger) MergeNotice(entries ...interface{}) {
  89. l.merge(line{
  90. level: capnslog.NOTICE,
  91. str: fmt.Sprint(entries...),
  92. })
  93. }
  94. func (l *MergeLogger) MergeNoticef(format string, args ...interface{}) {
  95. l.merge(line{
  96. level: capnslog.NOTICE,
  97. str: fmt.Sprintf(format, args...),
  98. })
  99. }
  100. func (l *MergeLogger) MergeWarning(entries ...interface{}) {
  101. l.merge(line{
  102. level: capnslog.WARNING,
  103. str: fmt.Sprint(entries...),
  104. })
  105. }
  106. func (l *MergeLogger) MergeWarningf(format string, args ...interface{}) {
  107. l.merge(line{
  108. level: capnslog.WARNING,
  109. str: fmt.Sprintf(format, args...),
  110. })
  111. }
  112. func (l *MergeLogger) MergeError(entries ...interface{}) {
  113. l.merge(line{
  114. level: capnslog.ERROR,
  115. str: fmt.Sprint(entries...),
  116. })
  117. }
  118. func (l *MergeLogger) MergeErrorf(format string, args ...interface{}) {
  119. l.merge(line{
  120. level: capnslog.ERROR,
  121. str: fmt.Sprintf(format, args...),
  122. })
  123. }
  124. func (l *MergeLogger) merge(ln line) {
  125. l.mu.Lock()
  126. // increase count if the logger is merging the line
  127. if status, ok := l.statusm[ln]; ok {
  128. status.count++
  129. l.mu.Unlock()
  130. return
  131. }
  132. // initialize status of the line
  133. l.statusm[ln] = &status{
  134. period: defaultMergePeriod,
  135. start: time.Now(),
  136. }
  137. // release the lock before IO operation
  138. l.mu.Unlock()
  139. // print out the line at its first time
  140. l.PackageLogger.Logf(ln.level, ln.str)
  141. }
  142. func (l *MergeLogger) outputLoop() {
  143. for now := range time.Tick(outputInterval) {
  144. var outputs []line
  145. l.mu.Lock()
  146. for ln, status := range l.statusm {
  147. if status.isInMergePeriod(now) {
  148. continue
  149. }
  150. if status.isEmpty() {
  151. delete(l.statusm, ln)
  152. continue
  153. }
  154. outputs = append(outputs, ln.append(status.summary(now)))
  155. status.reset(now)
  156. }
  157. l.mu.Unlock()
  158. for _, o := range outputs {
  159. l.PackageLogger.Logf(o.level, o.str)
  160. }
  161. }
  162. }