Skip to content

Commit

Permalink
add klog.v(0) and logger.v(0) check
Browse files Browse the repository at this point in the history
  • Loading branch information
yangjunmyfm192085 committed Aug 30, 2022
1 parent 601985d commit f7558df
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 0 deletions.
15 changes: 15 additions & 0 deletions logcheck/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,21 @@ func TestAnalyzer(t *testing.T) {
},
testPackage: "helpers",
},
{
name: "Do not allow Verbose Zero logs",
enabled: map[string]string{
"structured": "false",
},
testPackage: "doNotAllowVerboseZeroLogs",
},
{
name: "Allow Verbose Zero logs",
enabled: map[string]string{
"structured": "false",
"verbose-zero": "false",
},
testPackage: "allowVerboseZeroLogs",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
Expand Down
50 changes: 50 additions & 0 deletions logcheck/pkg/logcheck.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const (
parametersCheck = "parameters"
contextualCheck = "contextual"
withHelpersCheck = "with-helpers"
verboseZeroCheck = "verbose-zero"
)

type checks map[string]*bool
Expand All @@ -57,6 +58,7 @@ func Analyser() *analysis.Analyzer {
parametersCheck: new(bool),
contextualCheck: new(bool),
withHelpersCheck: new(bool),
verboseZeroCheck: new(bool),
},
}
c.fileOverrides.validChecks = map[string]bool{}
Expand All @@ -70,6 +72,7 @@ klog methods (Info, Infof, Error, Errorf, Warningf, etc).`)
logcheckFlags.BoolVar(c.enabled[parametersCheck], prefix+parametersCheck, true, `When true, logcheck will check parameters of structured logging calls.`)
logcheckFlags.BoolVar(c.enabled[contextualCheck], prefix+contextualCheck, false, `When true, logcheck will only allow log calls for contextual logging (retrieving a Logger from klog or the context and logging through that) and warn about all others.`)
logcheckFlags.BoolVar(c.enabled[withHelpersCheck], prefix+withHelpersCheck, false, `When true, logcheck will warn about direct calls to WithName, WithValues and NewContext.`)
logcheckFlags.BoolVar(c.enabled[verboseZeroCheck], prefix+verboseZeroCheck, true, `When true, logcheck will check whether the parameter for V() is 0.`)
logcheckFlags.Var(&c.fileOverrides, "config", `A file which overrides the global settings for checks on a per-file basis via regular expressions.`)

// Use env variables as defaults. This is necessary when used as plugin
Expand Down Expand Up @@ -172,6 +175,15 @@ func checkForFunctionExpr(fexpr *ast.CallExpr, pass *analysis.Pass, c *config) {
checkForFormatSpecifier(fexpr, pass)
}
}
// verbose Zero Check
if c.isEnabled(verboseZeroCheck, filename) && checkForVerboseZero(selExpr.X, pass) {
msg := fmt.Sprintf("Logging with V(0) is semantically equivalent to the same expression without it and just causes unnecessary overhead. It should get removed.")
pass.Report(analysis.Diagnostic{
Pos: fexpr.Fun.Pos(),
Message: msg,
})
}

} else if isGoLogger(selExpr.X, pass) {
if c.isEnabled(parametersCheck, filename) {
checkForFormatSpecifier(fexpr, pass)
Expand All @@ -193,6 +205,14 @@ func checkForFunctionExpr(fexpr *ast.CallExpr, pass *analysis.Pass, c *config) {
})
}
}
// verbose Zero Check
if c.isEnabled(verboseZeroCheck, filename) && checkForVerboseZero(selExpr.X, pass) {
msg := fmt.Sprintf("Logging with V(0) is semantically equivalent to the same expression without it and just causes unnecessary overhead. It should get removed.")
pass.Report(analysis.Diagnostic{
Pos: fexpr.Fun.Pos(),
Message: msg,
})
}
} else if fName == "NewContext" &&
isPackage(selExpr.X, "github.com/go-logr/logr", pass) &&
c.isEnabled(withHelpersCheck, filename) {
Expand Down Expand Up @@ -496,3 +516,33 @@ func checkForIfEnabled(i *ast.IfStmt, pass *analysis.Pass, c *config) {
funcCall, varName, funcCall, varName, varName),
})
}

func checkForVerboseZero(expr ast.Expr, pass *analysis.Pass) bool {
if !isKlogVerbose(expr, pass) && !isGoLogger(expr, pass) {
return false
}

subCallExpr, ok := expr.(*ast.CallExpr)
if !ok {
return false
}
subSelExpr, ok := subCallExpr.Fun.(*ast.SelectorExpr)
if !ok || subSelExpr.Sel.Name != "V" || len(subCallExpr.Args) != 1 {
return false
}

if lit, ok := subCallExpr.Args[0].(*ast.BasicLit); ok && lit.Value == "0" {
return true
}

if id, ok := subCallExpr.Args[0].(*ast.Ident); ok && id.Obj.Kind == 2 {
v, ok := id.Obj.Decl.(*ast.ValueSpec)
if !ok || len(v.Values) != 1 {
return false
}
if lit, ok := v.Values[0].(*ast.BasicLit); ok && lit.Value == "0" {
return true
}
}
return false
}
69 changes: 69 additions & 0 deletions logcheck/testdata/src/allowVerboseZeroLogs/allowVerboseZeroLogs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// This fake package is created as golang.org/x/tools/go/analysis/analysistest
// expects it to be here for loading. This package is used to test allow-unstructured
// flag which suppresses errors when unstructured logging is used.
// This is a test file for unstructured logging static check tool unit tests.

package verbose

import (
"github.com/go-logr/logr"
klog "k8s.io/klog/v2"
)

const (
zeroConst = 0
oneConst = 1
)

var (
zeroVar klog.Level = 0
oneVar klog.Level = 1
l, logger logr.Logger
)

func verboseLogging() {
klog.V(0).Info("test log")
klog.V(0).Infof("test log")
klog.V(0).Infoln("test log")
klog.V(0).InfoS("I'm logging at level 0.")
klog.V(zeroConst).InfoS("I'm logging at level 0.")
klog.V(zeroVar).InfoS("I'm logging at level 0.")
klog.V(1).Info("test log")
klog.V(1).Infof("test log")
klog.V(1).Infoln("test log")
klog.V(1).InfoS("I'm logging at level 1.")
klog.V(oneConst).InfoS("I'm logging at level 1.")
klog.V(oneVar).InfoS("I'm logging at level 1.")
klog.Info("test log")
klog.Infof("test log")
klog.Infoln("test log")
klog.InfoS("I'm logging at level 0.")

logger.Info("hello", "1", "2")
logger.Error(nil, "hello", "1", "2")
logger.WithValues("1", "2")

logger.V(0).Info("hello", "1", "2")
logger.V(0).Error(nil, "hello", "1", "2")
logger.V(0).WithValues("1", "2")

logger.V(1).Info("hello", "1", "2")
logger.V(1).Error(nil, "hello", "1", "2")
logger.V(1).WithValues("1", "2")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// This fake package is created as golang.org/x/tools/go/analysis/analysistest
// expects it to be here for loading. This package is used to test allow-unstructured
// flag which suppresses errors when unstructured logging is used.
// This is a test file for unstructured logging static check tool unit tests.

package verbose

import (
"github.com/go-logr/logr"
klog "k8s.io/klog/v2"
)

const (
zeroConst = 0
oneConst = 1
)

var (
zeroVar klog.Level = 0
oneVar klog.Level = 1
l, logger logr.Logger
)

func verboseLogging() {
klog.V(0).Info("test log") // want `Logging with V\(0\) is semantically equivalent to the same expression without it and just causes unnecessary overhead. It should get removed.`
klog.V(0).Infof("test log") // want `Logging with V\(0\) is semantically equivalent to the same expression without it and just causes unnecessary overhead. It should get removed.`
klog.V(0).Infoln("test log") // want `Logging with V\(0\) is semantically equivalent to the same expression without it and just causes unnecessary overhead. It should get removed.`
klog.V(0).InfoS("I'm logging at level 0.") // want `Logging with V\(0\) is semantically equivalent to the same expression without it and just causes unnecessary overhead. It should get removed.`
klog.V(zeroConst).InfoS("I'm logging at level 0.") // want `Logging with V\(0\) is semantically equivalent to the same expression without it and just causes unnecessary overhead. It should get removed.`
klog.V(zeroVar).InfoS("I'm logging at level 0.")
klog.V(1).Info("test log")
klog.V(1).Infof("test log")
klog.V(1).Infoln("test log")
klog.V(1).InfoS("I'm logging at level 1.")
klog.V(oneConst).InfoS("I'm logging at level 1.")
klog.V(oneVar).InfoS("I'm logging at level 1.")
klog.Info("test log")
klog.Infof("test log")
klog.Infoln("test log")
klog.InfoS("I'm logging at level 0.")

logger.Info("hello", "1", "2")
logger.Error(nil, "hello", "1", "2")
logger.WithValues("1", "2")

logger.V(0).Info("hello", "1", "2") // want `Logging with V\(0\) is semantically equivalent to the same expression without it and just causes unnecessary overhead. It should get removed.`
logger.V(0).Error(nil, "hello", "1", "2") // want `Logging with V\(0\) is semantically equivalent to the same expression without it and just causes unnecessary overhead. It should get removed.`
logger.V(0).WithValues("1", "2") // want `Logging with V\(0\) is semantically equivalent to the same expression without it and just causes unnecessary overhead. It should get removed.`

logger.V(1).Info("hello", "1", "2")
logger.V(1).Error(nil, "hello", "1", "2")
logger.V(1).WithValues("1", "2")
}

0 comments on commit f7558df

Please sign in to comment.