Skip to content

Commit 2bfd746

Browse files
committed
Fix #78: log all warnings
1. Log all warnings, don't hide none of them 2. Write fatal messages (stop analysis) with error log level 3. Remove ugly timestamp counter from logrus output 4. Print nested module prefix in log 5. Make logger abstraction: no global logging anymore 6. Refactor config reading to config.FileReader struct to avoid passing logger into every function 7. Replace exit codes hardcoding with constants in exitcodes package 8. Fail test if any warning was logged 9. Fix calculation of relative path if we analyze parent dir ../ 10. Move Runner initialization from Executor to NewRunner func 11. Log every AST parsing error 12. Properly print used config file path in verbose mode 13. Print package files if only 1 package is analyzedin verbose mode, print not compiling packages in verbose mode 14. Forbid usage of github.com/sirupsen/logrus by DepGuard linter 15. Add default ignore pattern to folint: "comment on exported const"
1 parent 219a547 commit 2bfd746

40 files changed

+694
-432
lines changed

Diff for: .gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
/*.pprof
33
/dist/
44
/.idea/
5+
/test/path

Diff for: .golangci.yml

+6
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ linters-settings:
1212
goconst:
1313
min-len: 2
1414
min-occurrences: 2
15+
depguard:
16+
list-type: blacklist
17+
packages:
18+
# logging is allowed only by logutils.Log, logrus
19+
# is allowed to use only in logutils package
20+
- github.com/sirupsen/logrus
1521

1622
linters:
1723
enable-all: true

Diff for: Makefile

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
test:
2-
go install ./cmd/... # needed for govet and golint
3-
golangci-lint run -v
4-
golangci-lint run --fast --no-config -v
5-
golangci-lint run --no-config -v
6-
go test -v ./...
2+
go install ./cmd/... # needed for govet and golint if go < 1.10
3+
GL_TEST_RUN=1 golangci-lint run -v
4+
GL_TEST_RUN=1 golangci-lint run --fast --no-config -v
5+
GL_TEST_RUN=1 golangci-lint run --no-config -v
6+
GL_TEST_RUN=1 go test -v ./...
77

88
assets:
99
svg-term --cast=183662 --out docs/demo.svg --window --width 110 --height 30 --from 2000 --to 20000 --profile Dracula --term iterm2

Diff for: README.md

+7-1
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ Flags:
255255
- Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv). is not checked
256256
257257
# golint: Annoying issue about not having a comment. The rare codebase has such comments
258-
- (comment on exported (method|function|type)|should have( a package)? comment|comment should be of the form)
258+
- (comment on exported (method|function|type|const)|should have( a package)? comment|comment should be of the form)
259259
260260
# golint: False positive when tests are defined in package 'test'
261261
- func name will be used as test\.Test.* by other packages, and that stutters; consider calling this
@@ -329,6 +329,12 @@ linters-settings:
329329
goconst:
330330
min-len: 2
331331
min-occurrences: 2
332+
depguard:
333+
list-type: blacklist
334+
packages:
335+
# logging is allowed only by logutils.Log, logrus
336+
# is allowed to use only in logutils package
337+
- github.com/sirupsen/logrus
332338

333339
linters:
334340
enable-all: true

Diff for: pkg/commands/executor.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package commands
22

33
import (
44
"github.com/golangci/golangci-lint/pkg/config"
5-
"github.com/sirupsen/logrus"
5+
"github.com/golangci/golangci-lint/pkg/logutils"
66
"github.com/spf13/cobra"
77
)
88

@@ -14,6 +14,8 @@ type Executor struct {
1414
exitCode int
1515

1616
version, commit, date string
17+
18+
log logutils.Log
1719
}
1820

1921
func NewExecutor(version, commit, date string) *Executor {
@@ -22,10 +24,9 @@ func NewExecutor(version, commit, date string) *Executor {
2224
version: version,
2325
commit: commit,
2426
date: date,
27+
log: logutils.NewStderrLog(""),
2528
}
2629

27-
logrus.SetLevel(logrus.WarnLevel)
28-
2930
e.initRoot()
3031
e.initRun()
3132
e.initLinters()

Diff for: pkg/commands/root.go

+10-17
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,17 @@ package commands
22

33
import (
44
"fmt"
5-
"log"
65
"os"
76
"runtime"
87
"runtime/pprof"
98

109
"github.com/golangci/golangci-lint/pkg/config"
1110
"github.com/golangci/golangci-lint/pkg/logutils"
1211
"github.com/golangci/golangci-lint/pkg/printers"
13-
"github.com/sirupsen/logrus"
1412
"github.com/spf13/cobra"
1513
"github.com/spf13/pflag"
1614
)
1715

18-
func setupLog(isVerbose bool) {
19-
log.SetFlags(0) // don't print time
20-
if logutils.IsDebugEnabled() {
21-
logrus.SetLevel(logrus.DebugLevel)
22-
} else if isVerbose {
23-
logrus.SetLevel(logrus.InfoLevel)
24-
}
25-
}
26-
2716
func (e *Executor) persistentPreRun(cmd *cobra.Command, args []string) {
2817
if e.cfg.Run.PrintVersion {
2918
fmt.Fprintf(printers.StdOut, "golangci-lint has version %s built from %s on %s\n", e.version, e.commit, e.date)
@@ -32,15 +21,15 @@ func (e *Executor) persistentPreRun(cmd *cobra.Command, args []string) {
3221

3322
runtime.GOMAXPROCS(e.cfg.Run.Concurrency)
3423

35-
setupLog(e.cfg.Run.IsVerbose)
24+
logutils.SetupVerboseLog(e.log, e.cfg.Run.IsVerbose)
3625

3726
if e.cfg.Run.CPUProfilePath != "" {
3827
f, err := os.Create(e.cfg.Run.CPUProfilePath)
3928
if err != nil {
40-
logrus.Fatal(err)
29+
e.log.Fatalf("Can't create file %s: %s", e.cfg.Run.CPUProfilePath, err)
4130
}
4231
if err := pprof.StartCPUProfile(f); err != nil {
43-
logrus.Fatal(err)
32+
e.log.Fatalf("Can't start CPU profiling: %s", err)
4433
}
4534
}
4635
}
@@ -52,11 +41,11 @@ func (e *Executor) persistentPostRun(cmd *cobra.Command, args []string) {
5241
if e.cfg.Run.MemProfilePath != "" {
5342
f, err := os.Create(e.cfg.Run.MemProfilePath)
5443
if err != nil {
55-
logrus.Fatal(err)
44+
e.log.Fatalf("Can't create file %s: %s", e.cfg.Run.MemProfilePath, err)
5645
}
5746
runtime.GC() // get up-to-date statistics
5847
if err := pprof.WriteHeapProfile(f); err != nil {
59-
logrus.Fatal("could not write memory profile: ", err)
48+
e.log.Fatalf("Can't write heap profile: %s", err)
6049
}
6150
}
6251

@@ -78,7 +67,7 @@ func (e *Executor) initRoot() {
7867
Long: `Smart, fast linters runner. Run it in cloud for every GitHub pull request on https://golangci.com`,
7968
Run: func(cmd *cobra.Command, args []string) {
8069
if err := cmd.Help(); err != nil {
81-
logrus.Fatal(err)
70+
e.log.Fatalf("Can't run help: %s", err)
8271
}
8372
},
8473
PersistentPreRun: e.persistentPreRun,
@@ -89,6 +78,10 @@ func (e *Executor) initRoot() {
8978
e.rootCmd = rootCmd
9079
}
9180

81+
func (e *Executor) needVersionOption() bool {
82+
return e.date != ""
83+
}
84+
9285
func initRootFlagSet(fs *pflag.FlagSet, cfg *config.Config, needVersionOption bool) {
9386
fs.BoolVarP(&cfg.Run.IsVerbose, "verbose", "v", false, wh("verbose output"))
9487
fs.StringVar(&cfg.Run.CPUProfilePath, "cpu-profile-path", "", wh("Path to CPU profile output file"))

Diff for: pkg/commands/run.go

+61-54
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,16 @@ import (
1212

1313
"github.com/fatih/color"
1414
"github.com/golangci/golangci-lint/pkg/config"
15+
"github.com/golangci/golangci-lint/pkg/exitcodes"
1516
"github.com/golangci/golangci-lint/pkg/lint"
1617
"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
1718
"github.com/golangci/golangci-lint/pkg/logutils"
1819
"github.com/golangci/golangci-lint/pkg/printers"
1920
"github.com/golangci/golangci-lint/pkg/result"
20-
"github.com/golangci/golangci-lint/pkg/result/processors"
21-
"github.com/sirupsen/logrus"
2221
"github.com/spf13/cobra"
2322
"github.com/spf13/pflag"
2423
)
2524

26-
const (
27-
exitCodeIfFailure = 3
28-
exitCodeIfTimeout = 4
29-
)
30-
3125
func getDefaultExcludeHelp() string {
3226
parts := []string{"Use or not use default excludes:"}
3327
for _, ep := range config.DefaultExcludePatterns {
@@ -64,7 +58,7 @@ func initFlagSet(fs *pflag.FlagSet, cfg *config.Config) {
6458
// Run config
6559
rc := &cfg.Run
6660
fs.IntVar(&rc.ExitCodeIfIssuesFound, "issues-exit-code",
67-
1, wh("Exit code when issues were found"))
61+
exitcodes.IssuesFound, wh("Exit code when issues were found"))
6862
fs.StringSliceVar(&rc.BuildTags, "build-tags", nil, wh("Build tags"))
6963
fs.DurationVar(&rc.Deadline, "deadline", time.Minute, wh("Deadline for total work"))
7064
fs.BoolVar(&rc.AnalyzeTests, "tests", true, wh("Analyze tests (*_test.go)"))
@@ -165,65 +159,76 @@ func (e *Executor) initRun() {
165159
// init e.cfg by values from config: flags parse will see these values
166160
// like the default ones. It will overwrite them only if the same option
167161
// is found in command-line: it's ok, command-line has higher priority.
168-
e.parseConfig()
169162

170-
// Slice options must be explicitly set for properly merging.
163+
r := config.NewFileReader(e.cfg, e.log.Child("config_reader"), func(fs *pflag.FlagSet, cfg *config.Config) {
164+
// Don't do `fs.AddFlagSet(cmd.Flags())` because it shares flags representations:
165+
// `changed` variable inside string slice vars will be shared.
166+
// Use another config variable here, not e.cfg, to not
167+
// affect main parsing by this parsing of only config option.
168+
initFlagSet(fs, cfg)
169+
170+
// Parse max options, even force version option: don't want
171+
// to get access to Executor here: it's error-prone to use
172+
// cfg vs e.cfg.
173+
initRootFlagSet(fs, cfg, true)
174+
})
175+
if err := r.Read(); err != nil {
176+
e.log.Fatalf("Can't read config: %s", err)
177+
}
178+
179+
// Slice options must be explicitly set for proper merging of config and command-line options.
171180
fixSlicesFlags(fs)
172181
}
173182

183+
func fixSlicesFlags(fs *pflag.FlagSet) {
184+
// It's a dirty hack to set flag.Changed to true for every string slice flag.
185+
// It's necessary to merge config and command-line slices: otherwise command-line
186+
// flags will always overwrite ones from the config.
187+
fs.VisitAll(func(f *pflag.Flag) {
188+
if f.Value.Type() != "stringSlice" {
189+
return
190+
}
191+
192+
s, err := fs.GetStringSlice(f.Name)
193+
if err != nil {
194+
return
195+
}
196+
197+
if s == nil { // assume that every string slice flag has nil as the default
198+
return
199+
}
200+
201+
// calling Set sets Changed to true: next Set calls will append, not overwrite
202+
_ = f.Value.Set(strings.Join(s, ","))
203+
})
204+
}
205+
174206
func (e *Executor) runAnalysis(ctx context.Context, args []string) (<-chan result.Issue, error) {
175207
e.cfg.Run.Args = args
176208

177-
linters, err := lintersdb.GetEnabledLinters(e.cfg)
209+
linters, err := lintersdb.GetEnabledLinters(e.cfg, e.log.Child("lintersdb"))
178210
if err != nil {
179211
return nil, err
180212
}
181213

182-
lintCtx, err := lint.LoadContext(ctx, linters, e.cfg)
214+
lintCtx, err := lint.LoadContext(ctx, linters, e.cfg, e.log.Child("load"))
183215
if err != nil {
184216
return nil, err
185217
}
186218

187-
excludePatterns := e.cfg.Issues.ExcludePatterns
188-
if e.cfg.Issues.UseDefaultExcludes {
189-
excludePatterns = append(excludePatterns, config.GetDefaultExcludePatternsStrings()...)
190-
}
191-
var excludeTotalPattern string
192-
if len(excludePatterns) != 0 {
193-
excludeTotalPattern = fmt.Sprintf("(%s)", strings.Join(excludePatterns, "|"))
194-
}
195-
196-
skipFilesProcessor, err := processors.NewSkipFiles(e.cfg.Run.SkipFiles)
219+
runner, err := lint.NewRunner(lintCtx.ASTCache, e.cfg, e.log.Child("runner"))
197220
if err != nil {
198221
return nil, err
199222
}
200223

201-
runner := lint.SimpleRunner{
202-
Processors: []processors.Processor{
203-
processors.NewPathPrettifier(), // must be before diff, nolint and exclude autogenerated processor at least
204-
processors.NewCgo(),
205-
skipFilesProcessor,
206-
207-
processors.NewAutogeneratedExclude(lintCtx.ASTCache),
208-
processors.NewExclude(excludeTotalPattern),
209-
processors.NewNolint(lintCtx.ASTCache),
210-
211-
processors.NewUniqByLine(),
212-
processors.NewDiff(e.cfg.Issues.Diff, e.cfg.Issues.DiffFromRevision, e.cfg.Issues.DiffPatchFilePath),
213-
processors.NewMaxPerFileFromLinter(),
214-
processors.NewMaxSameIssues(e.cfg.Issues.MaxSameIssues),
215-
processors.NewMaxFromLinter(e.cfg.Issues.MaxIssuesPerLinter),
216-
},
217-
}
218-
219224
return runner.Run(ctx, linters, lintCtx), nil
220225
}
221226

222-
func setOutputToDevNull() (savedStdout, savedStderr *os.File) {
227+
func (e *Executor) setOutputToDevNull() (savedStdout, savedStderr *os.File) {
223228
savedStdout, savedStderr = os.Stdout, os.Stderr
224229
devNull, err := os.Open(os.DevNull)
225230
if err != nil {
226-
logrus.Warnf("can't open null device %q: %s", os.DevNull, err)
231+
e.log.Warnf("Can't open null device %q: %s", os.DevNull, err)
227232
return
228233
}
229234

@@ -235,7 +240,7 @@ func (e *Executor) runAndPrint(ctx context.Context, args []string) error {
235240
if !logutils.HaveDebugTag("linters_output") {
236241
// Don't allow linters and loader to print anything
237242
log.SetOutput(ioutil.Discard)
238-
savedStdout, savedStderr := setOutputToDevNull()
243+
savedStdout, savedStderr := e.setOutputToDevNull()
239244
defer func() {
240245
os.Stdout, os.Stderr = savedStdout, savedStderr
241246
}()
@@ -253,9 +258,11 @@ func (e *Executor) runAndPrint(ctx context.Context, args []string) error {
253258
p = printers.NewJSON()
254259
case config.OutFormatColoredLineNumber, config.OutFormatLineNumber:
255260
p = printers.NewText(e.cfg.Output.PrintIssuedLine,
256-
format == config.OutFormatColoredLineNumber, e.cfg.Output.PrintLinterName)
261+
format == config.OutFormatColoredLineNumber, e.cfg.Output.PrintLinterName,
262+
e.log.Child("text_printer"))
257263
case config.OutFormatTab:
258-
p = printers.NewTab(e.cfg.Output.PrintLinterName)
264+
p = printers.NewTab(e.cfg.Output.PrintLinterName,
265+
e.log.Child("tab_printer"))
259266
case config.OutFormatCheckstyle:
260267
p = printers.NewCheckstyle()
261268
default:
@@ -288,22 +295,22 @@ func (e *Executor) executeRun(cmd *cobra.Command, args []string) {
288295
defer cancel()
289296

290297
if needTrackResources {
291-
go watchResources(ctx, trackResourcesEndCh)
298+
go watchResources(ctx, trackResourcesEndCh, e.log)
292299
}
293300

294301
if err := e.runAndPrint(ctx, args); err != nil {
295-
logrus.Warnf("running error: %s", err)
296-
if e.exitCode == 0 {
297-
e.exitCode = exitCodeIfFailure
302+
e.log.Errorf("Running error: %s", err)
303+
if e.exitCode == exitcodes.Success {
304+
e.exitCode = exitcodes.Failure
298305
}
299306
}
300307

301-
if e.exitCode == 0 && ctx.Err() != nil {
302-
e.exitCode = exitCodeIfTimeout
308+
if e.exitCode == exitcodes.Success && ctx.Err() != nil {
309+
e.exitCode = exitcodes.Timeout
303310
}
304311
}
305312

306-
func watchResources(ctx context.Context, done chan struct{}) {
313+
func watchResources(ctx context.Context, done chan struct{}, log logutils.Log) {
307314
startedAt := time.Now()
308315

309316
rssValues := []uint64{}
@@ -339,8 +346,8 @@ func watchResources(ctx context.Context, done chan struct{}) {
339346

340347
const MB = 1024 * 1024
341348
maxMB := float64(max) / MB
342-
logrus.Infof("Memory: %d samples, avg is %.1fMB, max is %.1fMB",
349+
log.Infof("Memory: %d samples, avg is %.1fMB, max is %.1fMB",
343350
len(rssValues), float64(avg)/MB, maxMB)
344-
logrus.Infof("Execution took %s", time.Since(startedAt))
351+
log.Infof("Execution took %s", time.Since(startedAt))
345352
close(done)
346353
}

0 commit comments

Comments
 (0)