@@ -407,6 +407,7 @@ func init() {
407407 flag .Var (& logging .stderrThreshold , "stderrthreshold" , "logs at or above this threshold go to stderr" )
408408 flag .Var (& logging .vmodule , "vmodule" , "comma-separated list of pattern=N settings for file-filtered logging" )
409409 flag .Var (& logging .traceLocation , "log_backtrace_at" , "when logging hits line file:N, emit a stack trace" )
410+ flag .UintVar (& logging .dedupeAfter , "logdedupeafter" , 0 , "Will de-duplicate the same log statement after N consecutive repititions" )
410411
411412 // Default stderrThreshold is ERROR.
412413 logging .stderrThreshold = errorLog
@@ -435,6 +436,10 @@ type loggingT struct {
435436 // Level flag. Handled atomically.
436437 stderrThreshold severity // The -stderrthreshold flag.
437438
439+ // Determines when the log deduplication code will kick in. Disabled
440+ // when value is 0.
441+ dedupeAfter uint
442+
438443 // Used to signal the flushDaemon to flush a particular log
439444 flushCh [numSeverity ]chan struct {}
440445
@@ -477,6 +482,13 @@ type loggingT struct {
477482 currStdErrTmpBuffer * bytes.Buffer
478483 // stdErrWriterCh used to notify outputStdErrLoop which buffer to write to stderr
479484 stdErrWriterCh chan * bytes.Buffer
485+
486+ // lastStatement* tracks who our last log message came from and how many we
487+ // have since consecutively received from this caller.
488+ lastStatementMu sync.Mutex
489+ lastStatementCaller caller
490+ lastStatementSeverity severity
491+ lastStatementCount uint
480492}
481493
482494// buffer holds a byte Buffer for reuse. The zero value is ready for use.
@@ -537,6 +549,87 @@ func (l *loggingT) putBuffer(b *buffer) {
537549 l .freeListMu .Unlock ()
538550}
539551
552+ // caller is a simple structure that tracks the output of runtime.Caller. It is
553+ // done this way to make function calls easier.
554+ type caller struct {
555+ pc uintptr
556+ file string
557+ line int
558+ }
559+
560+ // getCaller returns runtime information about the caller of this function. This
561+ // data is used while printing log lines along with de-duplicating noisy log
562+ // statements. It is the same logic as before, just liberated into its own
563+ // function with the caller struct used to track the info.
564+ func getCaller (depth int ) caller {
565+ pc , file , line , ok := runtime .Caller (3 + depth )
566+
567+ if ! ok {
568+ pc = 0
569+ file = "???"
570+ line = 0
571+ } else {
572+ slash := strings .LastIndex (file , "/" )
573+ if slash >= 0 {
574+ file = file [slash + 1 :]
575+ }
576+ // Unsure if needed, but since this was in code previously I am carrying it
577+ // forward here to avoid any surprise regressions.
578+ if line < 0 {
579+ line = 0
580+ }
581+ }
582+
583+ return caller {
584+ pc : pc ,
585+ file : file ,
586+ line : line ,
587+ }
588+ }
589+
590+ // dedupe determines whether or not we need to de-duplicate a log
591+ // message. Method will return true if we do, false otherwise. Method is also
592+ // responsible for noticing a different logging statement is being called and
593+ // adding any relevant de-duplication log information about our previously
594+ // de-dupled log statements. As such IT MUST BE CALLED AS PART OF EVERY LOG
595+ // STATEMENT in order to be effective.
596+ func (l * loggingT ) dedupe (s severity , c caller ) bool {
597+ // Deduplication of logs is disabled if 0
598+ dedupeAfter := l .dedupeAfter
599+ if dedupeAfter == 0 {
600+ return false
601+ }
602+
603+ // Ensure lock is not held during any potential logging events to keep
604+ // function performant. Thus we must not use a defer unlock here.
605+ l .lastStatementMu .Lock ()
606+
607+ if l .lastStatementCaller .pc == c .pc {
608+ l .lastStatementCount ++
609+ skip := l .lastStatementCount > dedupeAfter
610+ l .lastStatementMu .Unlock ()
611+ return skip
612+ }
613+
614+ prevCount := l .lastStatementCount
615+ prevCaller := l .lastStatementCaller
616+ prevSeverity := l .lastStatementSeverity
617+ l .lastStatementCaller = c
618+ l .lastStatementSeverity = s
619+ l .lastStatementCount = 1
620+ l .lastStatementMu .Unlock ()
621+
622+ // Check if we deduplicated the previous message, and include de-dupe info
623+ // before allowing the next log message to proceed.
624+ if prevCount > dedupeAfter {
625+ buf := l .header (prevSeverity , prevCaller )
626+ fmt .Fprintf (buf , "...statement repeated %d time(s)...\n " , prevCount - dedupeAfter )
627+ l .output (prevSeverity , buf , prevCaller , false )
628+ }
629+
630+ return false
631+ }
632+
540633var timeNow = time .Now // Stubbed out for testing.
541634
542635/*
@@ -556,26 +649,8 @@ where the fields are defined as follows:
556649 line The line number
557650 msg The user-supplied message
558651*/
559- func (l * loggingT ) header (s severity , depth int ) (* buffer , string , int ) {
560- _ , file , line , ok := runtime .Caller (3 + depth )
561- if ! ok {
562- file = "???"
563- line = 1
564- } else {
565- slash := strings .LastIndex (file , "/" )
566- if slash >= 0 {
567- file = file [slash + 1 :]
568- }
569- }
570- return l .formatHeader (s , file , line ), file , line
571- }
572-
573- // formatHeader formats a log header using the provided file name and line number.
574- func (l * loggingT ) formatHeader (s severity , file string , line int ) * buffer {
652+ func (l * loggingT ) header (s severity , c caller ) * buffer {
575653 now := timeNow ()
576- if line < 0 {
577- line = 0 // not a real line number, but acceptable to someDigits
578- }
579654 if s > fatalLog {
580655 s = infoLog // for safety.
581656 }
@@ -601,9 +676,9 @@ func (l *loggingT) formatHeader(s severity, file string, line int) *buffer {
601676 buf .nDigits (7 , 22 , pid , ' ' ) // TODO: should be TID
602677 buf .tmp [29 ] = ' '
603678 buf .Write (buf .tmp [:30 ])
604- buf .WriteString (file )
679+ buf .WriteString (c . file )
605680 buf .tmp [0 ] = ':'
606- n := buf .someDigits (1 , line )
681+ n := buf .someDigits (1 , c . line )
607682 buf .tmp [n + 1 ] = ']'
608683 buf .tmp [n + 2 ] = ' '
609684 buf .Write (buf .tmp [:n + 3 ])
@@ -652,43 +727,62 @@ func (buf *buffer) someDigits(i, d int) int {
652727}
653728
654729func (l * loggingT ) println (s severity , args ... interface {}) {
655- buf , file , line := l .header (s , 0 )
730+ c := getCaller (0 )
731+ skip := l .dedupe (s , c )
732+ if skip {
733+ return
734+ }
735+ buf := l .header (s , c )
656736 fmt .Fprintln (buf , args ... )
657- l .output (s , buf , file , line , false )
737+ l .output (s , buf , c , false )
658738}
659739
660740func (l * loggingT ) print (s severity , args ... interface {}) {
661741 l .printDepth (s , 1 , args ... )
662742}
663743
664744func (l * loggingT ) printDepth (s severity , depth int , args ... interface {}) {
665- buf , file , line := l .header (s , depth )
745+ c := getCaller (depth )
746+ skip := l .dedupe (s , c )
747+ if skip {
748+ return
749+ }
750+ buf := l .header (s , c )
666751 fmt .Fprint (buf , args ... )
667752 if buf .Bytes ()[buf .Len ()- 1 ] != '\n' {
668753 buf .WriteByte ('\n' )
669754 }
670- l .output (s , buf , file , line , false )
755+ l .output (s , buf , c , false )
671756}
672757
673758func (l * loggingT ) printf (s severity , format string , args ... interface {}) {
674- buf , file , line := l .header (s , 0 )
759+ c := getCaller (0 )
760+ skip := l .dedupe (s , c )
761+ if skip {
762+ return
763+ }
764+ buf := l .header (s , c )
675765 fmt .Fprintf (buf , format , args ... )
676766 if buf .Bytes ()[buf .Len ()- 1 ] != '\n' {
677767 buf .WriteByte ('\n' )
678768 }
679- l .output (s , buf , file , line , false )
769+ l .output (s , buf , c , false )
680770}
681771
682772// printWithFileLine behaves like print but uses the provided file and line number. If
683773// alsoLogToStderr is true, the log message always appears on standard error; it
684774// will also appear in the log file unless --logtostderr is set.
685775func (l * loggingT ) printWithFileLine (s severity , file string , line int , alsoToStderr bool , args ... interface {}) {
686- buf := l .formatHeader (s , file , line )
776+ c := caller {
777+ file : file ,
778+ line : line ,
779+ }
780+ buf := l .header (s , c )
687781 fmt .Fprint (buf , args ... )
688782 if buf .Bytes ()[buf .Len ()- 1 ] != '\n' {
689783 buf .WriteByte ('\n' )
690784 }
691- l .output (s , buf , file , line , alsoToStderr )
785+ l .output (s , buf , c , alsoToStderr )
692786}
693787
694788// initializeStdErrFixtures sets up the stdErrWriterCh and currStdErrTmpBuffer
@@ -724,10 +818,10 @@ func (l *loggingT) outputStdErrLoop() {
724818}
725819
726820// output writes the data to the log files and releases the buffer.
727- func (l * loggingT ) output (s severity , buf * buffer , file string , line int , alsoToStderr bool ) {
821+ func (l * loggingT ) output (s severity , buf * buffer , c caller , alsoToStderr bool ) {
728822 l .mu .Lock ()
729823 if l .traceLocation .isSet () {
730- if l .traceLocation .match (file , line ) {
824+ if l .traceLocation .match (c . file , c . line ) {
731825 buf .Write (stacks (false ))
732826 }
733827 }
0 commit comments