merge_logger.go 4.5 KB

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