Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add promlinter to lint metrics name #1265

Merged
merged 5 commits into from Mar 30, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
74 changes: 74 additions & 0 deletions pkg/golinters/promlinter.go
@@ -0,0 +1,74 @@
package golinters

import (
"fmt"
"go/ast"
"strings"
"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) {
files := make([]*ast.File, 0)

for _, f := range pass.Files {
if strings.HasSuffix(pass.Fset.Position(f.Pos()).Filename, "_test.go") {
ldez marked this conversation as resolved.
Show resolved Hide resolved
continue
}

files = append(files, f)
}
issues := promlinter.RunLint(pass.Fset, 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: "",
})
)