action: include TS in LogBuffer
This provides more context to individual log entries (and the duration between individual log lines) while e.g. printing them in an event. Signed-off-by: Hidde Beydals <hello@hidde.co>
This commit is contained in:
		
							parent
							
								
									b975b3f999
								
							
						
					
					
						commit
						ea81c8e099
					
				| 
						 | 
				
			
			@ -21,6 +21,7 @@ import (
 | 
			
		|||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/go-logr/logr"
 | 
			
		||||
	helmaction "helm.sh/helm/v3/pkg/action"
 | 
			
		||||
| 
						 | 
				
			
			@ -29,6 +30,9 @@ import (
 | 
			
		|||
// DefaultLogBufferSize is the default size of the LogBuffer.
 | 
			
		||||
const DefaultLogBufferSize = 5
 | 
			
		||||
 | 
			
		||||
// nowTS can be used to stub out time.Now() in tests.
 | 
			
		||||
var nowTS = time.Now
 | 
			
		||||
 | 
			
		||||
// NewDebugLog returns an action.DebugLog that logs to the given logr.Logger.
 | 
			
		||||
func NewDebugLog(log logr.Logger) helmaction.DebugLog {
 | 
			
		||||
	return func(format string, v ...interface{}) {
 | 
			
		||||
| 
						 | 
				
			
			@ -43,6 +47,35 @@ type LogBuffer struct {
 | 
			
		|||
	buffer *ring.Ring
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// logLine is a log message with a timestamp.
 | 
			
		||||
type logLine struct {
 | 
			
		||||
	ts     time.Time
 | 
			
		||||
	lastTS time.Time
 | 
			
		||||
	msg    string
 | 
			
		||||
	count  int64
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// String returns the log line as a string, in the format of:
 | 
			
		||||
// '<RFC3339 nano timestamp>: <message>'. But only if the message is not empty.
 | 
			
		||||
func (l *logLine) String() string {
 | 
			
		||||
	if l == nil || l.msg == "" {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	msg := fmt.Sprintf("%s %s", l.ts.Format(time.RFC3339Nano), l.msg)
 | 
			
		||||
	if c := l.count; c > 0 {
 | 
			
		||||
		msg += fmt.Sprintf("\n%s %s", l.lastTS.Format(time.RFC3339Nano), l.msg)
 | 
			
		||||
	}
 | 
			
		||||
	if c := l.count - 1; c > 0 {
 | 
			
		||||
		var dup = "line"
 | 
			
		||||
		if c > 1 {
 | 
			
		||||
			dup += "s"
 | 
			
		||||
		}
 | 
			
		||||
		msg += fmt.Sprintf(" (%d duplicate %s omitted)", c, dup)
 | 
			
		||||
	}
 | 
			
		||||
	return msg
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewLogBuffer creates a new LogBuffer with the given log function
 | 
			
		||||
// and a buffer of the given size. If size <= 0, it defaults to
 | 
			
		||||
// DefaultLogBufferSize.
 | 
			
		||||
| 
						 | 
				
			
			@ -64,8 +97,17 @@ func (l *LogBuffer) Log(format string, v ...interface{}) {
 | 
			
		|||
	// Filter out duplicate log lines, this happens for example when
 | 
			
		||||
	// Helm is waiting on workloads to become ready.
 | 
			
		||||
	msg := fmt.Sprintf(format, v...)
 | 
			
		||||
	if prev := l.buffer.Prev(); prev.Value != msg {
 | 
			
		||||
		l.buffer.Value = msg
 | 
			
		||||
	prev, ok := l.buffer.Prev().Value.(*logLine)
 | 
			
		||||
	if ok && prev.msg == msg {
 | 
			
		||||
		prev.count++
 | 
			
		||||
		prev.lastTS = nowTS().UTC()
 | 
			
		||||
		l.buffer.Prev().Value = prev
 | 
			
		||||
	}
 | 
			
		||||
	if !ok || prev.msg != msg {
 | 
			
		||||
		l.buffer.Value = &logLine{
 | 
			
		||||
			ts:  nowTS().UTC(),
 | 
			
		||||
			msg: msg,
 | 
			
		||||
		}
 | 
			
		||||
		l.buffer = l.buffer.Next()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -74,19 +116,20 @@ func (l *LogBuffer) Log(format string, v ...interface{}) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
// Len returns the count of non-empty values in the buffer.
 | 
			
		||||
func (l *LogBuffer) Len() int {
 | 
			
		||||
	var count int
 | 
			
		||||
func (l *LogBuffer) Len() (count int) {
 | 
			
		||||
	l.mu.RLock()
 | 
			
		||||
	l.buffer.Do(func(s interface{}) {
 | 
			
		||||
		if s == nil {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		if s.(string) != "" {
 | 
			
		||||
			count++
 | 
			
		||||
		ll, ok := s.(*logLine)
 | 
			
		||||
		if !ok || ll.String() == "" {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		count++
 | 
			
		||||
	})
 | 
			
		||||
	l.mu.RUnlock()
 | 
			
		||||
	return count
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Reset clears the buffer.
 | 
			
		||||
| 
						 | 
				
			
			@ -104,7 +147,13 @@ func (l *LogBuffer) String() string {
 | 
			
		|||
		if s == nil {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		str += s.(string) + "\n"
 | 
			
		||||
		ll, ok := s.(*logLine)
 | 
			
		||||
		if !ok {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		if msg := ll.String(); msg != "" {
 | 
			
		||||
			str += msg + "\n"
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
	l.mu.RUnlock()
 | 
			
		||||
	return strings.TrimSpace(str)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,12 +17,16 @@ limitations under the License.
 | 
			
		|||
package action
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/go-logr/logr"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestLogBuffer_Log(t *testing.T) {
 | 
			
		||||
	nowTS = stubNowTS
 | 
			
		||||
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name      string
 | 
			
		||||
		size      int
 | 
			
		||||
| 
						 | 
				
			
			@ -30,7 +34,7 @@ func TestLogBuffer_Log(t *testing.T) {
 | 
			
		|||
		wantCount int
 | 
			
		||||
		want      string
 | 
			
		||||
	}{
 | 
			
		||||
		{name: "log", size: 2, fill: []string{"a", "b", "c"}, wantCount: 3, want: "b\nc"},
 | 
			
		||||
		{name: "log", size: 2, fill: []string{"a", "b", "c"}, wantCount: 3, want: fmt.Sprintf("%[1]s b\n%[1]s c", stubNowTS().Format(time.RFC3339Nano))},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
| 
						 | 
				
			
			@ -102,6 +106,8 @@ func TestLogBuffer_Reset(t *testing.T) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
func TestLogBuffer_String(t *testing.T) {
 | 
			
		||||
	nowTS = stubNowTS
 | 
			
		||||
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name string
 | 
			
		||||
		size int
 | 
			
		||||
| 
						 | 
				
			
			@ -109,8 +115,11 @@ func TestLogBuffer_String(t *testing.T) {
 | 
			
		|||
		want string
 | 
			
		||||
	}{
 | 
			
		||||
		{name: "empty buffer", fill: []string{}, want: ""},
 | 
			
		||||
		{name: "filled buffer", size: 2, fill: []string{"a", "b", "c"}, want: "b\nc"},
 | 
			
		||||
		{name: "duplicate buffer items", fill: []string{"b", "b", "b"}, want: "b"},
 | 
			
		||||
		{name: "filled buffer", size: 2, fill: []string{"a", "b", "c"}, want: fmt.Sprintf("%[1]s b\n%[1]s c", stubNowTS().Format(time.RFC3339Nano))},
 | 
			
		||||
		{name: "duplicate buffer items", fill: []string{"b", "b"}, want: fmt.Sprintf("%[1]s b\n%[1]s b", stubNowTS().Format(time.RFC3339Nano))},
 | 
			
		||||
		{name: "duplicate buffer items", fill: []string{"b", "b", "b"}, want: fmt.Sprintf("%[1]s b\n%[1]s b (1 duplicate line omitted)", stubNowTS().Format(time.RFC3339Nano))},
 | 
			
		||||
		{name: "duplicate buffer items", fill: []string{"b", "b", "b", "b"}, want: fmt.Sprintf("%[1]s b\n%[1]s b (2 duplicate lines omitted)", stubNowTS().Format(time.RFC3339Nano))},
 | 
			
		||||
		{name: "duplicate buffer items", fill: []string{"a", "b", "b", "b", "c", "c"}, want: fmt.Sprintf("%[1]s a\n%[1]s b\n%[1]s b (1 duplicate line omitted)\n%[1]s c\n%[1]s c", stubNowTS().Format(time.RFC3339Nano))},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
| 
						 | 
				
			
			@ -124,3 +133,8 @@ func TestLogBuffer_String(t *testing.T) {
 | 
			
		|||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// stubNowTS returns a fixed time for testing purposes.
 | 
			
		||||
func stubNowTS() time.Time {
 | 
			
		||||
	return time.Date(2016, 2, 18, 12, 24, 5, 12345600, time.UTC)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue