Skip to content

Commit e3be328

Browse files
authored
✨ Log catalogd feature gate states (#1930)
* Log catalogd feature gate states * Unify feature-gate logs in shared util Signed-off-by: Brett Tofel <[email protected]> --------- Signed-off-by: Brett Tofel <[email protected]>
1 parent e529653 commit e3be328

File tree

5 files changed

+121
-16
lines changed

5 files changed

+121
-16
lines changed

cmd/catalogd/main.go

+3
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,9 @@ func validateConfig(cfg *config) error {
188188
}
189189

190190
func run(ctx context.Context) error {
191+
// log startup message and feature gate status
192+
setupLog.Info("starting up catalogd", "version info", version.String())
193+
features.LogFeatureGateStates(setupLog, features.CatalogdFeatureGate)
191194
authFilePath := filepath.Join(os.TempDir(), fmt.Sprintf("%s-%s.json", authFilePrefix, apimachineryrand.String(8)))
192195

193196
protocol := "http://"

internal/catalogd/features/features.go

+8
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package features
22

33
import (
4+
"github.com/go-logr/logr"
45
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
56
"k8s.io/component-base/featuregate"
7+
8+
fgutil "github.com/operator-framework/operator-controller/internal/shared/util/featuregates"
69
)
710

811
const (
@@ -18,3 +21,8 @@ var CatalogdFeatureGate featuregate.MutableFeatureGate = featuregate.NewFeatureG
1821
func init() {
1922
utilruntime.Must(CatalogdFeatureGate.Add(catalogdFeatureGates))
2023
}
24+
25+
// LogFeatureGateStates logs the state of all known feature gates for catalogd
26+
func LogFeatureGateStates(log logr.Logger, fg featuregate.FeatureGate) {
27+
fgutil.LogFeatureGateStates(log, "catalogd feature gate status", fg, catalogdFeatureGates)
28+
}

internal/operator-controller/features/features.go

+3-16
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package features
22

33
import (
4-
"sort"
5-
64
"github.com/go-logr/logr"
75
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
86
"k8s.io/component-base/featuregate"
7+
8+
fgutil "github.com/operator-framework/operator-controller/internal/shared/util/featuregates"
99
)
1010

1111
const (
@@ -42,18 +42,5 @@ func init() {
4242

4343
// LogFeatureGateStates logs the state of all known feature gates.
4444
func LogFeatureGateStates(log logr.Logger, fg featuregate.FeatureGate) {
45-
// Sort the keys for consistent logging order
46-
featureKeys := make([]featuregate.Feature, 0, len(operatorControllerFeatureGates))
47-
for k := range operatorControllerFeatureGates {
48-
featureKeys = append(featureKeys, k)
49-
}
50-
sort.Slice(featureKeys, func(i, j int) bool {
51-
return string(featureKeys[i]) < string(featureKeys[j]) // Sort by string representation
52-
})
53-
54-
featurePairs := make([]interface{}, 0, len(featureKeys))
55-
for _, feature := range featureKeys {
56-
featurePairs = append(featurePairs, feature, fg.Enabled(feature))
57-
}
58-
log.Info("feature gate status", featurePairs...)
45+
fgutil.LogFeatureGateStates(log, "feature gate status", fg, operatorControllerFeatureGates)
5946
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package featuregates
2+
3+
import (
4+
"sort"
5+
6+
"github.com/go-logr/logr"
7+
"k8s.io/component-base/featuregate"
8+
)
9+
10+
// LogFeatureGateStates logs a sorted list of features and their enabled state
11+
// message is the log message under which to record the feature states
12+
// fg is the feature gate instance, and featureDefs is the map of feature specs
13+
func LogFeatureGateStates(log logr.Logger, message string, fg featuregate.FeatureGate, featureDefs map[featuregate.Feature]featuregate.FeatureSpec) {
14+
// Collect and sort feature keys for deterministic ordering
15+
keys := make([]featuregate.Feature, 0, len(featureDefs))
16+
for f := range featureDefs {
17+
keys = append(keys, f)
18+
}
19+
sort.Slice(keys, func(i, j int) bool {
20+
return string(keys[i]) < string(keys[j])
21+
})
22+
23+
// Build key/value pairs for logging
24+
pairs := make([]interface{}, 0, len(keys)*2)
25+
for _, f := range keys {
26+
pairs = append(pairs, f, fg.Enabled(f))
27+
}
28+
log.Info(message, pairs...)
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package featuregates_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/go-logr/logr"
7+
"github.com/stretchr/testify/require"
8+
"k8s.io/component-base/featuregate"
9+
10+
"github.com/operator-framework/operator-controller/internal/shared/util/featuregates"
11+
)
12+
13+
// fakeSink implements logr.LogSink, capturing Info calls for testing
14+
type fakeSink struct {
15+
level int
16+
msg string
17+
keysAndValues []interface{}
18+
}
19+
20+
// Init is part of logr.LogSink
21+
func (f *fakeSink) Init(info logr.RuntimeInfo) {}
22+
23+
// Enabled is part of logr.LogSink
24+
func (f *fakeSink) Enabled(level int) bool { return true }
25+
26+
// Info captures the log level, message, and key/value pairs
27+
func (f *fakeSink) Info(level int, msg string, keysAndValues ...interface{}) {
28+
f.level = level
29+
f.msg = msg
30+
f.keysAndValues = append([]interface{}{}, keysAndValues...)
31+
}
32+
33+
// Error is part of logr.LogSink; not used in this test
34+
func (f *fakeSink) Error(err error, msg string, keysAndValues ...interface{}) {}
35+
36+
// WithValues returns a sink with additional values; for testing, return self
37+
func (f *fakeSink) WithValues(keysAndValues ...interface{}) logr.LogSink { return f }
38+
39+
// WithName returns a sink with a new name; for testing, return self
40+
func (f *fakeSink) WithName(name string) logr.LogSink { return f }
41+
42+
// TestLogFeatureGateStates verifies that LogFeatureGateStates logs features
43+
// sorted alphabetically with their enabled state
44+
func TestLogFeatureGateStates(t *testing.T) {
45+
// Define a set of feature specs with default states
46+
defs := map[featuregate.Feature]featuregate.FeatureSpec{
47+
"AFeature": {Default: false},
48+
"BFeature": {Default: true},
49+
"CFeature": {Default: false},
50+
}
51+
52+
// create a mutable gate and register our definitions
53+
gate := featuregate.NewFeatureGate()
54+
require.NoError(t, gate.Add(defs))
55+
56+
// override CFeature to true.
57+
require.NoError(t, gate.SetFromMap(map[string]bool{
58+
"CFeature": true,
59+
}))
60+
61+
// prepare a fake sink and logger
62+
sink := &fakeSink{}
63+
logger := logr.New(sink)
64+
65+
// log the feature states
66+
featuregates.LogFeatureGateStates(logger, "feature states", gate, defs)
67+
68+
// verify the message
69+
require.Equal(t, "feature states", sink.msg)
70+
71+
// Expect keys sorted: AFeature, BFeature, CFeature
72+
want := []interface{}{
73+
featuregate.Feature("AFeature"), false,
74+
featuregate.Feature("BFeature"), true,
75+
featuregate.Feature("CFeature"), true,
76+
}
77+
require.Equal(t, want, sink.keysAndValues)
78+
}

0 commit comments

Comments
 (0)