Skip to content

Commit f076494

Browse files
committed
feat: add verbose logging option to controller-gen
1 parent 1ad88b0 commit f076494

File tree

4 files changed

+95
-1
lines changed

4 files changed

+95
-1
lines changed

pkg/crd/gen.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"fmt"
2121
"go/ast"
2222
"go/types"
23+
"log/slog"
2324
"sort"
2425
"strings"
2526

@@ -124,6 +125,12 @@ func transformPreserveUnknownFields(value bool) func(map[string]interface{}) err
124125
}
125126

126127
func (g Generator) Generate(ctx *genall.GenerationContext) error {
128+
// Extract logger and use a discard logger if nil to avoid repeated nil checks
129+
logger := ctx.Logger
130+
if logger == nil {
131+
logger = slog.New(slog.DiscardHandler)
132+
}
133+
127134
parser := &Parser{
128135
Collector: ctx.Collector,
129136
Checker: ctx.Checker,
@@ -134,30 +141,44 @@ func (g Generator) Generate(ctx *genall.GenerationContext) error {
134141
GenerateEmbeddedObjectMeta: g.GenerateEmbeddedObjectMeta != nil && *g.GenerateEmbeddedObjectMeta,
135142
}
136143

144+
logger.Debug("starting CRD generation", "ignoreUnexported", parser.IgnoreUnexportedFields, "allowDangerous", parser.AllowDangerousTypes)
145+
137146
AddKnownTypes(parser)
138147
for _, root := range ctx.Roots {
139148
parser.NeedPackage(root)
149+
logger.Debug("processing package", "package", root.PkgPath)
140150
}
141151

142152
metav1Pkg := FindMetav1(ctx.Roots)
143153
if metav1Pkg == nil {
144154
// no objects in the roots, since nothing imported metav1
155+
logger.Debug("no metav1 package found in roots, no CRDs to generate")
145156
return nil
146157
}
147158

159+
logger.Debug("found metav1 package", "package", metav1Pkg.PkgPath)
160+
148161
// TODO: allow selecting a specific object
149162
kubeKinds := FindKubeKinds(parser, metav1Pkg)
150163
if len(kubeKinds) == 0 {
151164
// no objects in the roots
165+
logger.Debug("no Kubernetes kinds found in packages")
152166
return nil
153167
}
154168

169+
logger.Info("found Kubernetes kinds for CRD generation", "count", len(kubeKinds))
170+
for _, kind := range kubeKinds {
171+
logger.Debug("processing Kubernetes kind", "group", kind.Group, "kind", kind.Kind)
172+
}
173+
155174
crdVersions := g.CRDVersions
156175

157176
if len(crdVersions) == 0 {
158177
crdVersions = []string{defaultVersion}
159178
}
160179

180+
logger.Debug("using CRD versions", "versions", crdVersions)
181+
161182
var headerText string
162183

163184
if g.HeaderFile != "" {
@@ -166,6 +187,7 @@ func (g Generator) Generate(ctx *genall.GenerationContext) error {
166187
return err
167188
}
168189
headerText = string(headerBytes)
190+
logger.Debug("loaded header file", "file", g.HeaderFile, "size", len(headerBytes))
169191
}
170192
headerText = strings.ReplaceAll(headerText, " YEAR", " "+g.Year)
171193

@@ -178,6 +200,8 @@ func (g Generator) Generate(ctx *genall.GenerationContext) error {
178200
}
179201

180202
for _, groupKind := range kubeKinds {
203+
logger.Debug("generating CRD", "group", groupKind.Group, "kind", groupKind.Kind)
204+
181205
parser.NeedCRDFor(groupKind, g.MaxDescLen)
182206
crdRaw := parser.CustomResourceDefinitions[groupKind]
183207
addAttribution(&crdRaw)
@@ -202,9 +226,14 @@ func (g Generator) Generate(ctx *genall.GenerationContext) error {
202226
} else {
203227
fileName = fmt.Sprintf("%s_%s.%s.yaml", crdRaw.Spec.Group, crdRaw.Spec.Names.Plural, crdVersions[i])
204228
}
229+
230+
logger.Debug("writing CRD file", "filename", fileName, "group", groupKind.Group, "kind", groupKind.Kind)
231+
205232
if err := ctx.WriteYAML(fileName, headerText, []interface{}{crd}, yamlOpts...); err != nil {
206233
return err
207234
}
235+
236+
logger.Info("generated CRD", "filename", fileName, "group", groupKind.Group, "kind", groupKind.Kind)
208237
}
209238
}
210239

pkg/genall/genall.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"encoding/json"
2121
"fmt"
2222
"io"
23+
"log/slog"
2324
"os"
2425

2526
"golang.org/x/tools/go/packages"
@@ -101,6 +102,9 @@ type Runtime struct {
101102
OutputRules OutputRules
102103
// ErrorWriter defines where to write error messages.
103104
ErrorWriter io.Writer
105+
// LogLevel sets the logging level for generator operations.
106+
// Defaults to slog.LevelInfo if not specified.
107+
LogLevel slog.Level
104108
}
105109

106110
// GenerationContext defines the common information needed for each Generator
@@ -117,6 +121,8 @@ type GenerationContext struct {
117121
// InputRule describes how to load associated boilerplate artifacts.
118122
// It should *not* be used to load source files.
119123
InputRule
124+
// Logger is the logger for verbose output. If nil, logging is disabled.
125+
Logger *slog.Logger
120126
}
121127

122128
// WriteYAMLOptions implements the Options Pattern for WriteYAML.
@@ -261,17 +267,37 @@ func (r *Runtime) Run() bool {
261267
return true
262268
}
263269

270+
// Set up logging based on log level setting
271+
var logger *slog.Logger
272+
273+
// Use the specified log level, defaulting to Info if not set
274+
logLevel := r.LogLevel
275+
if logLevel == 0 {
276+
logLevel = slog.LevelInfo
277+
}
278+
279+
logger = slog.New(slog.NewTextHandler(r.ErrorWriter, &slog.HandlerOptions{
280+
Level: logLevel,
281+
}))
282+
283+
if logLevel <= slog.LevelDebug {
284+
logger.Info("debug logging enabled")
285+
}
286+
264287
hadErrs := false
265288
for _, gen := range r.Generators {
266289
ctx := r.GenerationContext // make a shallow copy
267290
ctx.OutputRule = r.OutputRules.ForGenerator(gen)
291+
ctx.Logger = logger
268292

269293
// don't pass a typechecker to generators that don't provide a filter
270294
// to avoid accidents
271295
if _, needsChecking := (*gen).(NeedsTypeChecking); !needsChecking {
272296
ctx.Checker = nil
273297
}
274298

299+
logger.Debug("running generator", "generator", fmt.Sprintf("%T", *gen))
300+
275301
if err := (*gen).Generate(&ctx); err != nil {
276302
fmt.Fprintln(r.ErrorWriter, err)
277303
hadErrs = true

pkg/genall/options.go

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package genall
1818

1919
import (
2020
"fmt"
21+
"log/slog"
2122
"strings"
2223

2324
"golang.org/x/tools/go/packages"
@@ -26,6 +27,7 @@ import (
2627

2728
var (
2829
InputPathsMarker = markers.Must(markers.MakeDefinition("paths", markers.DescribesPackage, InputPaths(nil)))
30+
VerboseMarker = markers.Must(markers.MakeDefinition("verbose", markers.DescribesPackage, Verbose(false)))
2931
)
3032

3133
// +controllertools:marker:generateHelp:category=""
@@ -35,16 +37,30 @@ var (
3537
// Multiple paths can be specified using "{path1, path2, path3}".
3638
type InputPaths []string
3739

40+
// +controllertools:marker:generateHelp:category=""
41+
42+
// Verbose enables verbose/debug logging for generator operations.
43+
// When enabled, generators will output additional debug information
44+
// about their processing, which can help troubleshoot issues when
45+
// generators don't produce expected output.
46+
type Verbose bool
47+
3848
// RegisterOptionsMarkers registers "mandatory" options markers for FromOptions into the given registry.
39-
// At this point, that's just InputPaths.
49+
// At this point, that's just InputPaths and Verbose.
4050
func RegisterOptionsMarkers(into *markers.Registry) error {
4151
if err := into.Register(InputPathsMarker); err != nil {
4252
return err
4353
}
54+
if err := into.Register(VerboseMarker); err != nil {
55+
return err
56+
}
4457
// NB(directxman12): we make this optional so we don't have a bootstrap problem with helpgen
4558
if helpGiver, hasHelp := ((interface{})(InputPaths(nil))).(HasHelp); hasHelp {
4659
into.AddHelp(InputPathsMarker, helpGiver.Help())
4760
}
61+
if helpGiver, hasHelp := ((interface{})(Verbose(false))).(HasHelp); hasHelp {
62+
into.AddHelp(VerboseMarker, helpGiver.Help())
63+
}
4864
return nil
4965
}
5066

@@ -90,6 +106,13 @@ func FromOptionsWithConfig(cfg *packages.Config, optionsRegistry *markers.Regist
90106
return nil, err
91107
}
92108

109+
// Set log level based on verbose flag
110+
if protoRt.Verbose {
111+
genRuntime.LogLevel = slog.LevelDebug
112+
} else {
113+
genRuntime.LogLevel = slog.LevelInfo
114+
}
115+
93116
// attempt to figure out what the user wants without a lot of verbose specificity:
94117
// if the user specifies a default rule, assume that they probably want to fall back
95118
// to that. Otherwise, assume that they just wanted to customize one option from the
@@ -117,6 +140,7 @@ func protoFromOptions(optionsRegistry *markers.Registry, options []string) (prot
117140
ByGenerator: make(map[*Generator]OutputRule),
118141
}
119142
var paths []string
143+
var verbose bool
120144

121145
// collect the generators first, so that we can key the output on the actual
122146
// generator, which matters if there's settings in the gen object and it's not a pointer.
@@ -156,6 +180,8 @@ func protoFromOptions(optionsRegistry *markers.Registry, options []string) (prot
156180
continue
157181
case InputPaths:
158182
paths = append(paths, val...)
183+
case Verbose:
184+
verbose = bool(val)
159185
default:
160186
return protoRuntime{}, fmt.Errorf("unknown option marker %q", defn.Name)
161187
}
@@ -176,6 +202,7 @@ func protoFromOptions(optionsRegistry *markers.Registry, options []string) (prot
176202
Generators: gens,
177203
OutputRules: rules,
178204
GeneratorsByName: gensByName,
205+
Verbose: verbose,
179206
}, nil
180207
}
181208

@@ -186,6 +213,7 @@ type protoRuntime struct {
186213
Generators Generators
187214
OutputRules OutputRules
188215
GeneratorsByName map[string]*Generator
216+
Verbose bool
189217
}
190218

191219
// splitOutputRuleOption splits a marker name of "output:rule:gen" or "output:rule"

pkg/genall/zz_generated.markerhelp.go

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)