forked from prysmaticlabs/prysm
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Static Analyzer: require log.WithError instead of log.Errorf("bad: %v…
…", err) (prysmaticlabs#11143) * Add analysis template with failing test * Works for the most common use case * Progress on tool and more test cases * Improvements * handle nil * works for the most part * Fix some TODOs in the tool * Fix some TODOs in the tool
- Loading branch information
1 parent
699bfdf
commit d17826f
Showing
4 changed files
with
225 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
load("@prysm//tools/go:def.bzl", "go_library", "go_test") | ||
|
||
go_library( | ||
name = "go_default_library", | ||
srcs = ["analyzer.go"], | ||
importpath = "github.com/prysmaticlabs/prysm/tools/analyzers/logruswitherror", | ||
visibility = ["//visibility:public"], | ||
deps = [ | ||
"@org_golang_x_tools//go/analysis:go_default_library", | ||
"@org_golang_x_tools//go/analysis/passes/inspect:go_default_library", | ||
"@org_golang_x_tools//go/ast/inspector:go_default_library", | ||
], | ||
) | ||
|
||
go_test( | ||
name = "go_default_test", | ||
srcs = ["analyzer_test.go"], | ||
data = glob(["testdata/**"]) + [ | ||
"@go_sdk//:files", | ||
], | ||
embed = [":go_default_library"], | ||
deps = [ | ||
"//build/bazel:go_default_library", | ||
"@org_golang_x_tools//go/analysis/analysistest:go_default_library", | ||
], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
// Package logruswitherror implements a static analyzer to ensure that log statements do not use | ||
// errors in templated log statements. Authors should use logrus.WithError(). | ||
package logruswitherror | ||
|
||
import ( | ||
"errors" | ||
"go/ast" | ||
"go/types" | ||
|
||
"golang.org/x/tools/go/analysis" | ||
"golang.org/x/tools/go/analysis/passes/inspect" | ||
"golang.org/x/tools/go/ast/inspector" | ||
) | ||
|
||
// Doc explaining the tool. | ||
const Doc = "This analyzer requires that log statements do not use errors in templated log statements." | ||
|
||
const errImproperUsage = "use log.WithError rather than templated log statements with errors" | ||
|
||
// Map of logrus templated log functions. | ||
var logFns = map[string]interface{}{ | ||
"Debugf": nil, | ||
"Infof": nil, | ||
"Printf": nil, | ||
"Warnf": nil, | ||
"Warningf": nil, | ||
"Errorf": nil, | ||
"Fatalf": nil, | ||
"Panicf": nil, | ||
} | ||
|
||
// Analyzer runs static analysis. | ||
var Analyzer = &analysis.Analyzer{ | ||
Name: "logruswitherror", | ||
Doc: Doc, | ||
Requires: []*analysis.Analyzer{inspect.Analyzer}, | ||
Run: run, | ||
} | ||
|
||
func run(pass *analysis.Pass) (interface{}, error) { | ||
inspect, ok := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) | ||
if !ok { | ||
return nil, errors.New("analyzer is not type *inspector.Inspector") | ||
} | ||
|
||
nodeFilter := []ast.Node{ | ||
(*ast.CallExpr)(nil), | ||
} | ||
|
||
inspect.Preorder(nodeFilter, func(node ast.Node) { | ||
switch stmt := node.(type) { | ||
case *ast.CallExpr: | ||
fse, ok := stmt.Fun.(*ast.SelectorExpr) | ||
if !ok { | ||
return | ||
} | ||
|
||
// Only complain on log functions. | ||
if x, ok := fse.X.(*ast.Ident); !ok || x.Name != "log" { | ||
return | ||
} | ||
|
||
// Lookup function name | ||
fnName := fse.Sel.Name | ||
|
||
// If function matches any of the logrus functions, check if it uses errors. | ||
if _, ok := logFns[fnName]; !ok { | ||
return | ||
} | ||
|
||
for _, arg := range stmt.Args { | ||
switch a := arg.(type) { | ||
case *ast.Ident: | ||
// Check if the error is a variable. | ||
if a.Obj == nil { | ||
return | ||
} | ||
|
||
var typ types.Type | ||
|
||
switch f := a.Obj.Decl.(type) { | ||
case *ast.AssignStmt: | ||
name := a.Name | ||
for _, lhs := range f.Lhs { | ||
if l, ok := lhs.(*ast.Ident); ok && l.Name == name { | ||
typ = pass.TypesInfo.TypeOf(l) | ||
break | ||
} | ||
} | ||
case *ast.Field: | ||
typ = pass.TypesInfo.TypeOf(f.Type) | ||
} | ||
|
||
if typ != nil && typ.String() == "error" { | ||
pass.Reportf(a.Pos(), errImproperUsage) | ||
} | ||
} | ||
} | ||
} | ||
}) | ||
|
||
return nil, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package logruswitherror | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/prysmaticlabs/prysm/build/bazel" | ||
"golang.org/x/tools/go/analysis/analysistest" | ||
) | ||
|
||
func init() { | ||
if bazel.BuiltWithBazel() { | ||
bazel.SetGoEnv() | ||
} | ||
} | ||
|
||
func TestAnalyzer(t *testing.T) { | ||
testdata := bazel.TestDataPath(t) | ||
analysistest.TestData = func() string { return testdata } | ||
analysistest.Run(t, testdata, Analyzer, "a") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
package testdata | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
) | ||
|
||
var log = &logger{} | ||
|
||
func LogThis(err error) { | ||
// Most common use cases with all types of formatted log methods. | ||
log.Debugf("Something really bad happened: %v", err) // want "use log.WithError rather than templated log statements with errors" | ||
log.Infof("Something really bad happened: %v", err) // want "use log.WithError rather than templated log statements with errors" | ||
log.Printf("Something really bad happened: %v", err) // want "use log.WithError rather than templated log statements with errors" | ||
log.Warnf("Something really bad happened: %v", err) // want "use log.WithError rather than templated log statements with errors" | ||
log.Warningf("Something really bad happened: %v", err) // want "use log.WithError rather than templated log statements with errors" | ||
log.Errorf("Something really bad happened: %v", err) // want "use log.WithError rather than templated log statements with errors" | ||
log.Fatalf("Something really bad happened: %v", err) // want "use log.WithError rather than templated log statements with errors" | ||
log.Panicf("Something really bad happened: %v", err) // want "use log.WithError rather than templated log statements with errors" | ||
|
||
// Other common use cases. | ||
log.Panicf("Something really bad happened %d times: %v", 12, err) // want "use log.WithError rather than templated log statements with errors" | ||
|
||
if _, err := do(); err != nil { | ||
log.Errorf("Something really bad happened: %v", err) // want "use log.WithError rather than templated log statements with errors" | ||
} | ||
|
||
if ok, err := false, do2(); !ok || err != nil { | ||
log.Errorf("Something really bad happened: %v", err) // want "use log.WithError rather than templated log statements with errors" | ||
} | ||
|
||
log.WithError(err).Error("Something bad happened, but this log statement is OK :)") | ||
|
||
_ = fmt.Errorf("this is ok: %v", err) | ||
} | ||
|
||
func do() (bool, error) { | ||
return false, errors.New("bad") | ||
} | ||
|
||
func do2() error { | ||
return errors.New("bad2") | ||
} | ||
|
||
type logger struct{} | ||
|
||
func (*logger) Debugf(format string, args ...interface{}) { | ||
} | ||
|
||
func (*logger) Infof(format string, args ...interface{}) { | ||
} | ||
|
||
func (*logger) Printf(format string, args ...interface{}) { | ||
} | ||
|
||
func (*logger) Warnf(format string, args ...interface{}) { | ||
} | ||
|
||
func (*logger) Warningf(format string, args ...interface{}) { | ||
} | ||
|
||
func (*logger) Errorf(format string, args ...interface{}) { | ||
} | ||
|
||
func (*logger) Error(msg string) { | ||
} | ||
|
||
func (*logger) Fatalf(format string, args ...interface{}) { | ||
} | ||
|
||
func (*logger) Panicf(format string, args ...interface{}) { | ||
} | ||
|
||
func (l *logger) WithError(err error) *logger { | ||
return l | ||
} |