Skip to content

Commit

Permalink
Add promlinter to lint metrics name (#1265)
Browse files Browse the repository at this point in the history
  • Loading branch information
yeya24 committed Mar 30, 2021
1 parent 5baff12 commit 6844f6a
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 1 deletion.
14 changes: 14 additions & 0 deletions .golangci.example.yml
Expand Up @@ -347,6 +347,20 @@ linters-settings:
simple: true
range-loops: true # Report preallocation suggestions on range loops, true by default
for-loops: false # Report preallocation suggestions on for loops, false by default
promlinter:
# Promlinter cannot infer all metrics name in static analysis.
# Enable strict mode will also include the errors caused by failing to parse the args.
strict: false
# Please refer to https://github.com/yeya24/promlinter#usage for detailed usage.
disabled-linters:
# - "Help"
# - "MetricUnits"
# - "Counter"
# - "HistogramSummaryReserved"
# - "MetricTypeInName"
# - "ReservedChars"
# - "CamelCase"
# - "lintUnitAbbreviations"
predeclared:
# comma-separated list of predeclared identifiers to not report on
ignore: ""
Expand Down
1 change: 1 addition & 0 deletions go.mod
Expand Up @@ -80,6 +80,7 @@ require (
github.com/ultraware/whitespace v0.0.4
github.com/uudashr/gocognit v1.0.1
github.com/valyala/quicktemplate v1.6.3
github.com/yeya24/promlinter v0.1.0
golang.org/x/tools v0.1.0
gopkg.in/yaml.v2 v2.4.0
honnef.co/go/tools v0.1.3
Expand Down
10 changes: 10 additions & 0 deletions go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions pkg/config/config.go
Expand Up @@ -277,6 +277,7 @@ type LintersSettings struct {
Cyclop Cyclop
ImportAs ImportAsSettings
GoModDirectives GoModDirectivesSettings
Promlinter PromlinterSettings

Custom map[string]CustomLinterSettings
}
Expand Down Expand Up @@ -457,6 +458,11 @@ type PredeclaredSettings struct {
Qualified bool `mapstructure:"q"`
}

type PromlinterSettings struct {
Strict bool `mapstructure:"strict"`
DisabledLinters []string `mapstructure:"disabled-linters"`
}

type Cyclop struct {
MaxComplexity int `mapstructure:"max-complexity"`
PackageAverage float64 `mapstructure:"package-average"`
Expand Down
63 changes: 63 additions & 0 deletions pkg/golinters/promlinter.go
@@ -0,0 +1,63 @@
package golinters

import (
"fmt"
"sync"

"github.com/yeya24/promlinter"
"golang.org/x/tools/go/analysis"

"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
"github.com/golangci/golangci-lint/pkg/lint/linter"
"github.com/golangci/golangci-lint/pkg/result"
)

func NewPromlinter() *goanalysis.Linter {
var mu sync.Mutex
var resIssues []goanalysis.Issue

const linterName = "promlinter"
analyzer := &analysis.Analyzer{
Name: linterName,
Doc: goanalysis.TheOnlyanalyzerDoc,
}
return goanalysis.NewLinter(
linterName,
"Check Prometheus metrics naming via promlint",
[]*analysis.Analyzer{analyzer},
nil,
).WithContextSetter(func(lintCtx *linter.Context) {
strict := lintCtx.Cfg.LintersSettings.Promlinter.Strict
disabledLinters := lintCtx.Cfg.LintersSettings.Promlinter.DisabledLinters

analyzer.Run = func(pass *analysis.Pass) (interface{}, error) {
issues := promlinter.RunLint(pass.Fset, pass.Files, promlinter.Setting{
Strict: strict,
DisabledLintFuncs: disabledLinters,
})

if len(issues) == 0 {
return nil, nil
}

res := make([]goanalysis.Issue, len(issues))
for k, i := range issues {
issue := result.Issue{
Pos: i.Pos,
Text: fmt.Sprintf("Metric: %s Error: %s", i.Metric, i.Text),
FromLinter: linterName,
}

res[k] = goanalysis.NewIssue(&issue, pass)
}

mu.Lock()
resIssues = append(resIssues, res...)
mu.Unlock()

return nil, nil
}
}).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue {
return resIssues
}).WithLoadMode(goanalysis.LoadModeSyntax)
}
5 changes: 4 additions & 1 deletion pkg/lint/lintersdb/manager.go
Expand Up @@ -479,7 +479,10 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
WithSince("v1.39.0").
WithPresets(linter.PresetStyle, linter.PresetModule).
WithURL("https://github.com/ldez/gomoddirectives"),

linter.NewConfig(golinters.NewPromlinter()).
WithSince("v1.40.0").
WithPresets(linter.PresetStyle).
WithURL("https://github.com/yeya24/promlinter"),
// nolintlint must be last because it looks at the results of all the previous linters for unused nolint directives
linter.NewConfig(golinters.NewNoLintLint()).
WithSince("v1.26.0").
Expand Down
34 changes: 34 additions & 0 deletions test/testdata/promlinter.go
@@ -0,0 +1,34 @@
//args: -Epromlinter
package testdata

import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)

var (
_ = promauto.NewCounterVec(
prometheus.CounterOpts{ // ERROR `Metric: test_metric_name Error: counter metrics should have "_total" suffix`
Name: "test_metric_name",
Help: "test help text",
}, []string{},
)

_ = promauto.NewCounterVec(
prometheus.CounterOpts{ // ERROR "Metric: test_metric_total Error: no help text"
Name: "test_metric_total",
}, []string{},
)

_ = promauto.NewCounterVec(
prometheus.CounterOpts{ // ERROR `Metric: metric_type_in_name_counter_total Error: metric name should not include type 'counter'`
Name: "metric_type_in_name_counter_total",
Help: "foo",
}, []string{},
)

_ = prometheus.NewHistogram(prometheus.HistogramOpts{ // ERROR `Metric: test_duration_milliseconds Error: use base unit "seconds" instead of "milliseconds"`
Name: "test_duration_milliseconds",
Help: "",
})
)

0 comments on commit 6844f6a

Please sign in to comment.