forked from operator-framework/operator-controller
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsetuplognilerrorcheck.go
119 lines (102 loc) · 3.84 KB
/
setuplognilerrorcheck.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
package analyzers
import (
"bytes"
"go/ast"
"go/format"
"go/token"
"go/types"
"golang.org/x/tools/go/analysis"
)
var SetupLogErrorCheck = &analysis.Analyzer{
Name: "setuplogerrorcheck",
Doc: "Detects improper usage of logger.Error() calls, ensuring the first argument is a non-nil error.",
Run: runSetupLogErrorCheck,
}
func runSetupLogErrorCheck(pass *analysis.Pass) (interface{}, error) {
for _, f := range pass.Files {
ast.Inspect(f, func(n ast.Node) bool {
callExpr, ok := n.(*ast.CallExpr)
if !ok {
return true
}
// Ensure function being called is logger.Error
selectorExpr, ok := callExpr.Fun.(*ast.SelectorExpr)
if !ok || selectorExpr.Sel.Name != "Error" {
return true
}
// Ensure receiver (logger) is identified
ident, ok := selectorExpr.X.(*ast.Ident)
if !ok {
return true
}
// Verify if the receiver is logr.Logger
obj := pass.TypesInfo.ObjectOf(ident)
if obj == nil {
return true
}
named, ok := obj.Type().(*types.Named)
if !ok || named.Obj().Pkg() == nil || named.Obj().Pkg().Path() != "github.com/go-logr/logr" || named.Obj().Name() != "Logger" {
return true
}
if len(callExpr.Args) == 0 {
return true
}
// Get the actual source code line where the issue occurs
var srcBuffer bytes.Buffer
if err := format.Node(&srcBuffer, pass.Fset, callExpr); err != nil {
return true
}
sourceLine := srcBuffer.String()
// Check if the first argument of the error log is nil
firstArg, ok := callExpr.Args[0].(*ast.Ident)
if ok && firstArg.Name == "nil" {
suggestedError := "errors.New(\"kind error (i.e. configuration error)\")"
suggestedMessage := "\"error message describing the failed operation\""
if len(callExpr.Args) > 1 {
if msgArg, ok := callExpr.Args[1].(*ast.BasicLit); ok && msgArg.Kind == token.STRING {
suggestedMessage = msgArg.Value
}
}
pass.Reportf(callExpr.Pos(),
"Incorrect usage of 'logger.Error(nil, ...)'. The first argument must be a non-nil 'error'. "+
"Passing 'nil' results in silent failures, making debugging harder.\n\n"+
"\U0001F41B **What is wrong?**\n %s\n\n"+
"\U0001F4A1 **How to solve? Return the error, i.e.:**\n logger.Error(%s, %s, \"key\", value)\n\n",
sourceLine, suggestedError, suggestedMessage)
return true
}
// Ensure at least two arguments exist (error + message)
if len(callExpr.Args) < 2 {
pass.Reportf(callExpr.Pos(),
"Incorrect usage of 'logger.Error(error, ...)'. Expected at least an error and a message string.\n\n"+
"\U0001F41B **What is wrong?**\n %s\n\n"+
"\U0001F4A1 **How to solve?**\n Provide a message, e.g. logger.Error(err, \"descriptive message\")\n\n",
sourceLine)
return true
}
// Ensure key-value pairs (if any) are valid
if (len(callExpr.Args)-2)%2 != 0 {
pass.Reportf(callExpr.Pos(),
"Incorrect usage of 'logger.Error(error, \"msg\", ...)'. Key-value pairs must be provided after the message, but an odd number of arguments was found.\n\n"+
"\U0001F41B **What is wrong?**\n %s\n\n"+
"\U0001F4A1 **How to solve?**\n Ensure all key-value pairs are complete, e.g. logger.Error(err, \"msg\", \"key\", value, \"key2\", value2)\n\n",
sourceLine)
return true
}
for i := 2; i < len(callExpr.Args); i += 2 {
keyArg := callExpr.Args[i]
keyType := pass.TypesInfo.TypeOf(keyArg)
if keyType == nil || keyType.String() != "string" {
pass.Reportf(callExpr.Pos(),
"Incorrect usage of 'logger.Error(error, \"msg\", key, value)'. Keys in key-value pairs must be strings, but got: %s.\n\n"+
"\U0001F41B **What is wrong?**\n %s\n\n"+
"\U0001F4A1 **How to solve?**\n Ensure keys are strings, e.g. logger.Error(err, \"msg\", \"key\", value)\n\n",
keyType, sourceLine)
return true
}
}
return true
})
}
return nil, nil
}