Skip to content

Commit a9bf398

Browse files
authored
feat: add helper function for logging requeues (#137)
* feat: add helper function for logging requeues * add tests for LogRequeue function
1 parent 42c61eb commit a9bf398

File tree

3 files changed

+141
-4
lines changed

3 files changed

+141
-4
lines changed

pkg/logging/implementation.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,9 @@ func determineZapConfig(loggerConfig *Config) zap.Config {
132132
return zapConfig
133133
}
134134

135-
func levelToVerbosity(level LogLevel) int {
135+
// LevelToVerbosity converts the given LogLevel to a verbosity level understood by zapr.
136+
// Unknown LogLevels are silently treated as INFO.
137+
func LevelToVerbosity(level LogLevel) int {
136138
var res int
137139
switch level {
138140
case DEBUG:

pkg/logging/logger.go

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ import (
88
"context"
99
"fmt"
1010
"strings"
11+
"time"
1112

1213
"github.com/go-logr/logr"
14+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
1315
)
1416

1517
type Logger struct {
@@ -85,7 +87,7 @@ func ParseLogFormat(raw string) (LogFormat, error) {
8587
// Enabled tests whether logging at the provided level is enabled.
8688
// This deviates from the logr Enabled() function, which doesn't take an argument.
8789
func (l Logger) Enabled(lvl LogLevel) bool {
88-
return l.internal.GetSink() != nil && l.internal.GetSink().Enabled(levelToVerbosity(lvl))
90+
return l.internal.GetSink() != nil && l.internal.GetSink().Enabled(LevelToVerbosity(lvl))
8991
}
9092

9193
// Info logs a non-error message with the given key/value pairs as context.
@@ -95,7 +97,7 @@ func (l Logger) Enabled(lvl LogLevel) bool {
9597
// variable information. The key/value pairs should alternate string
9698
// keys and arbitrary values.
9799
func (l Logger) Info(msg string, keysAndValues ...interface{}) {
98-
l.internal.V(levelToVerbosity(INFO)).Info(msg, keysAndValues...)
100+
l.internal.V(LevelToVerbosity(INFO)).Info(msg, keysAndValues...)
99101
}
100102

101103
// Error logs an error, with the given message and key/value pairs as context.
@@ -213,7 +215,7 @@ func NewContextWithDiscard(ctx context.Context) context.Context {
213215

214216
// Debug logs a message at DEBUG level.
215217
func (l Logger) Debug(msg string, keysAndValues ...interface{}) {
216-
l.internal.V(levelToVerbosity(DEBUG)).Info(msg, keysAndValues...)
218+
l.internal.V(LevelToVerbosity(DEBUG)).Info(msg, keysAndValues...)
217219
}
218220

219221
// Log logs at the given log level. It can be used to log at dynamically determined levels.
@@ -252,3 +254,19 @@ func (l Logger) WithNameAndContext(ctx context.Context, name string) (Logger, co
252254
ctx = NewContext(ctx, log)
253255
return log, ctx
254256
}
257+
258+
// LogRequeue takes a reconcile.Result and prints a log message on the desired verbosity if the result indicates a requeue.
259+
// Logs at debug level, unless another verbosity is provided. Any but the first verbosity argument is ignored.
260+
// No message is logged if no requeue is specified in the reconcile.Result.
261+
// Returns the provided reconcile.Result unchanged.
262+
func (l Logger) LogRequeue(rr reconcile.Result, verbosity ...LogLevel) reconcile.Result {
263+
if rr.RequeueAfter > 0 {
264+
v := DEBUG
265+
if len(verbosity) > 0 {
266+
v = verbosity[0]
267+
}
268+
nextRequeueTime := time.Now().Add(rr.RequeueAfter)
269+
l.Log(v, "Requeuing object for reconciliation", "after", rr.RequeueAfter.String(), "at", nextRequeueTime.Format(time.RFC3339))
270+
}
271+
return rr
272+
}

pkg/logging/logging_suite_test.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,17 @@
55
package logging_test
66

77
import (
8+
"fmt"
89
"reflect"
910
"testing"
11+
"time"
1012

1113
"github.com/go-logr/logr"
1214
. "github.com/onsi/ginkgo/v2"
1315
. "github.com/onsi/gomega"
16+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
1417

18+
"github.com/openmcp-project/controller-utils/pkg/collections"
1519
"github.com/openmcp-project/controller-utils/pkg/logging"
1620
)
1721

@@ -43,4 +47,117 @@ var _ = Describe("Logging Framework Tests", func() {
4347
Expect(reflect.DeepEqual(log, compareToLogger)).To(BeTrue(), "calling log.WithValues should not modify the logger")
4448
})
4549

50+
Context("LogRequeue", func() {
51+
52+
var log logging.Logger
53+
var sink *TestLogSink
54+
55+
BeforeEach(func() {
56+
sink = NewTestLogSink(logging.DEBUG)
57+
log = logging.Wrap(logr.New(sink))
58+
})
59+
60+
It("should not log anything if RequeueAfter is 0", func() {
61+
log.LogRequeue(reconcile.Result{})
62+
Expect(sink.Messages.Size()).To(Equal(0))
63+
})
64+
65+
It("should log a message if RequeueAfter is set", func() {
66+
now := time.Now()
67+
requeueAfter := 42 * time.Second
68+
log.LogRequeue(reconcile.Result{RequeueAfter: requeueAfter})
69+
Expect(sink.Messages.Size()).To(Equal(1))
70+
msg := sink.Messages.Poll()
71+
Expect(msg.Verbosity).To(Equal(logging.LevelToVerbosity(logging.DEBUG)))
72+
Expect(msg.Message).To(Equal("Requeuing object for reconciliation"))
73+
Expect(msg.KeysAndVals).To(HaveKeyWithValue("after", requeueAfter.String()))
74+
Expect(msg.KeysAndVals).To(HaveKey("at"))
75+
at, err := time.Parse(time.RFC3339, msg.KeysAndVals["at"].(string))
76+
Expect(err).NotTo(HaveOccurred())
77+
Expect(at).To(BeTemporally("~", now.Add(requeueAfter), time.Second))
78+
})
79+
80+
It("should log at the provided verbosity level", func() {
81+
now := time.Now()
82+
requeueAfter := 42 * time.Second
83+
log.LogRequeue(reconcile.Result{RequeueAfter: requeueAfter}, logging.INFO)
84+
Expect(sink.Messages.Size()).To(Equal(1))
85+
msg := sink.Messages.Poll()
86+
Expect(msg.Verbosity).To(Equal(logging.LevelToVerbosity(logging.INFO)))
87+
Expect(msg.Message).To(Equal("Requeuing object for reconciliation"))
88+
Expect(msg.KeysAndVals).To(HaveKeyWithValue("after", requeueAfter.String()))
89+
Expect(msg.KeysAndVals).To(HaveKey("at"))
90+
at, err := time.Parse(time.RFC3339, msg.KeysAndVals["at"].(string))
91+
Expect(err).NotTo(HaveOccurred())
92+
Expect(at).To(BeTemporally("~", now.Add(requeueAfter), time.Second))
93+
})
94+
95+
})
96+
4697
})
98+
99+
func NewTestLogSink(level logging.LogLevel) *TestLogSink {
100+
return &TestLogSink{
101+
Messages: collections.NewLinkedList[*LogMessage](),
102+
enabledLevel: level,
103+
}
104+
}
105+
106+
type TestLogSink struct {
107+
Messages collections.Queue[*LogMessage]
108+
enabledLevel logging.LogLevel
109+
}
110+
111+
type LogMessage struct {
112+
Error error
113+
Verbosity int
114+
Message string
115+
KeysAndVals map[string]any
116+
}
117+
118+
// Enabled implements logr.LogSink.
119+
func (t *TestLogSink) Enabled(level int) bool {
120+
return t.enabledLevel >= logging.LogLevel(level)
121+
}
122+
123+
// Error implements logr.LogSink.
124+
func (t *TestLogSink) Error(err error, msg string, keysAndValues ...any) {
125+
t.log(int(logging.ERROR), err, msg, keysAndValues...)
126+
}
127+
128+
// Info implements logr.LogSink.
129+
func (t *TestLogSink) Info(level int, msg string, keysAndValues ...any) {
130+
t.log(level, nil, msg, keysAndValues...)
131+
}
132+
133+
func (t *TestLogSink) log(verbosity int, err error, msg string, keysAndValues ...any) {
134+
kv := make(map[string]any, (len(keysAndValues)+1)/2)
135+
for i := 0; i < len(keysAndValues)-1; i += 2 {
136+
k, ok := keysAndValues[i].(string)
137+
if !ok {
138+
k = fmt.Sprint(keysAndValues[i])
139+
}
140+
kv[k] = keysAndValues[i+1]
141+
}
142+
_ = t.Messages.Push(&LogMessage{
143+
Error: err,
144+
Verbosity: verbosity,
145+
Message: msg,
146+
KeysAndVals: kv,
147+
})
148+
}
149+
150+
// Init implements logr.LogSink.
151+
func (t *TestLogSink) Init(_ logr.RuntimeInfo) {}
152+
153+
// WithName implements logr.LogSink.
154+
func (t *TestLogSink) WithName(name string) logr.LogSink {
155+
panic("not implemented")
156+
}
157+
158+
// WithValues implements logr.LogSink.
159+
func (t *TestLogSink) WithValues(keysAndValues ...any) logr.LogSink {
160+
panic("not implemented")
161+
}
162+
163+
var _ logr.LogSink = &TestLogSink{}

0 commit comments

Comments
 (0)