diff --git a/.golangci.yml b/.golangci.yml index a6d4606..7f351ee 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,83 +1,415 @@ version: "2" -run: - concurrency: 4 - issues-exit-code: 1 - tests: true -output: - formats: - text: - path: stdout - print-linter-name: true - print-issued-lines: true + linters: - default: none enable: - - asciicheck - - bidichk - - bodyclose - - containedctx - - contextcheck - - decorder - - dogsled - - dupl - - errname - - gocognit - - goconst - - gocritic - - govet - - ineffassign - - makezero - - nilerr - - predeclared - - staticcheck - - unconvert - - unparam - - unused + - asasalint # checks for pass []any as any in variadic func(...any) + - asciicheck # checks that your code does not contain non-ASCII identifiers + - bidichk # checks for dangerous unicode character sequences + - bodyclose # checks whether HTTP response body is closed successfully + - canonicalheader # checks whether net/http.Header uses canonical header + - copyloopvar # detects places where loop variables are copied (Go 1.22+) + - cyclop # checks function and package cyclomatic complexity + # - depguard # checks if package imports are in a list of acceptable packages + - dupl # tool for code clone detection + - durationcheck # checks for two durations multiplied together + - errcheck # checking for unchecked errors, these unchecked errors can be critical bugs in some cases + - errname # checks that sentinel errors are prefixed with the Err and error types are suffixed with the Error + - errorlint # finds code that will cause problems with the error wrapping scheme introduced in Go 1.13 + - exhaustive # checks exhaustiveness of enum switch statements + - exptostd # detects functions from golang.org/x/exp/ that can be replaced by std functions + - fatcontext # detects nested contexts in loops +# - forbidigo # forbids identifiers + - funcorder # checks the order of functions, methods, and constructors + - funlen # tool for detection of long functions + - gocheckcompilerdirectives # validates go compiler directive comments (//go:) + # - gochecknoglobals # checks that no global variables exist + # - gochecknoinits # checks that no init functions are present in Go code + - gochecksumtype # checks exhaustiveness on Go "sum types" + - gocognit # computes and checks the cognitive complexity of functions + - goconst # finds repeated strings that could be replaced by a constant + - gocritic # provides diagnostics that check for bugs, performance and style issues + - gocyclo # computes and checks the cyclomatic complexity of functions + - godot # checks if comments end in a period + - gomoddirectives # manages the use of 'replace', 'retract', and 'excludes' directives in go.mod + - goprintffuncname # checks that printf-like functions are named with f at the end + - gosec # inspects source code for security problems + - govet # reports suspicious constructs, such as Printf calls whose arguments do not align with the format string + - iface # checks the incorrect use of interfaces, helping developers avoid interface pollution + - ineffassign # detects when assignments to existing variables are not used + - intrange # finds places where for loops could make use of an integer range + - loggercheck # checks key value pairs for common logger libraries (kitlog,klog,logr,zap) + - makezero # finds slice declarations with non-zero initial length + - mirror # reports wrong mirror patterns of bytes/strings usage + # - mnd # detects magic numbers +# - musttag # enforces field tags in (un)marshaled structs + - nakedret # finds naked returns in functions greater than a specified function length + - nestif # reports deeply nested if statements + - nilerr # finds the code that returns nil even if it checks that the error is not nil + - nilnesserr # reports that it checks for err != nil, but it returns a different nil value error (powered by nilness and nilerr) + - nilnil # checks that there is no simultaneous return of nil error and an invalid value + - noctx # finds sending http request without context.Context + - nolintlint # reports ill-formed or insufficient nolint directives + - nonamedreturns # reports all named returns + - nosprintfhostport # checks for misuse of Sprintf to construct a host with port in a URL + - perfsprint # checks that fmt.Sprintf can be replaced with a faster alternative + - predeclared # finds code that shadows one of Go's predeclared identifiers + - promlinter # checks Prometheus metrics naming via promlint + - protogetter # reports direct reads from proto message fields when getters should be used + - reassign # checks that package variables are not reassigned + - recvcheck # checks for receiver type consistency + # - revive # fast, configurable, extensible, flexible, and beautiful linter for Go, drop-in replacement of golint + - rowserrcheck # checks whether Err of rows is checked successfully + # - sloglint # ensure consistent code style when using log/slog + - spancheck # checks for mistakes with OpenTelemetry/Census spans + - sqlclosecheck # checks that sql.Rows and sql.Stmt are closed + - staticcheck # is a go vet on steroids, applying a ton of static analysis checks + - testableexamples # checks if examples are testable (have an expected output) + - testifylint # checks usage of github.com/stretchr/testify +# - testpackage # makes you use a separate _test package + - tparallel # detects inappropriate usage of t.Parallel() method in your Go test codes + - unconvert # removes unnecessary type conversions + - unparam # reports unused function parameters + - unused # checks for unused constants, variables, functions and types + - usestdlibvars # detects the possibility to use variables/constants from the Go standard library + - usetesting # reports uses of functions with replacement inside the testing package + # - wastedassign # finds wasted assignment statements + - whitespace # detects leading and trailing whitespace + + ## you may want to enable + - decorder # checks declaration order and count of types, constants, variables and functions + # - exhaustruct # [highly recommend to enable] checks if all structure fields are initialized + - ginkgolinter # [if you use ginkgo/gomega] enforces standards of using ginkgo and gomega + - godox # detects usage of FIXME, TODO and other keywords inside comments + - goheader # checks is file header matches to pattern + - inamedparam # [great idea, but too strict, need to ignore a lot of cases by default] reports interfaces with unnamed method parameters + - interfacebloat # checks the number of methods inside an interface + # - ireturn # accept interfaces, return concrete types + - prealloc # [premature optimization, but can be used in some cases] finds slice declarations that could potentially be preallocated + - tagalign # checks that struct tags are well aligned + # - varnamelen # [great idea, but too many false positives] checks that the length of a variable's name matches its scope + # - wrapcheck # checks that errors returned from external packages are wrapped + - zerologlint # detects the wrong usage of zerolog that a user forgets to dispatch zerolog.Event + + ## disabled + #- containedctx # detects struct contained context.Context field + #- contextcheck # [too many false positives] checks the function whether use a non-inherited context + #- dogsled # checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) + #- dupword # [useless without config] checks for duplicate words in the source code + #- err113 # [too strict] checks the errors handling expressions + #- errchkjson # [don't see profit + I'm against of omitting errors like in the first example https://github.com/breml/errchkjson] checks types passed to the json encoding functions. Reports unsupported types and optionally reports occasions, where the check for the returned error can be omitted + #- forcetypeassert # [replaced by errcheck] finds forced type assertions + #- gomodguard # [use more powerful depguard] allow and block lists linter for direct Go module dependencies + #- gosmopolitan # reports certain i18n/l10n anti-patterns in your Go codebase + #- grouper # analyzes expression groups + #- importas # enforces consistent import aliases + #- lll # [replaced by golines] reports long lines + #- maintidx # measures the maintainability index of each function + #- misspell # [useless] finds commonly misspelled English words in comments + #- nlreturn # [too strict and mostly code is not more readable] checks for a new line before return and branch statements to increase code clarity + #- paralleltest # [too many false positives] detects missing usage of t.Parallel() method in your Go test + #- tagliatelle # checks the struct tags + #- thelper # detects golang test helpers without t.Helper() call and checks the consistency of test helpers + #- wsl # [too strict and mostly code is not more readable] whitespace linter forces you to use empty lines settings: + cyclop: + # The maximal code complexity to report. + # Default: 10 + max-complexity: 30 + # The maximal average package complexity. + # If it's higher than 0.0 (float) the check is enabled. + # Default: 0.0 + package-average: 10.0 + + depguard: + # Rules to apply. + # + # Variables: + # - File Variables + # Use an exclamation mark `!` to negate a variable. + # Example: `!$test` matches any file that is not a go test file. + # + # `$all` - matches all go files + # `$test` - matches all go test files + # + # - Package Variables + # + # `$gostd` - matches all of go's standard library (Pulled from `GOROOT`) + # + # Default (applies if no custom rules are defined): Only allow $gostd in all files. + rules: + "deprecated": + # List of file globs that will match this list of settings to compare against. + # By default, if a path is relative, it is relative to the directory where the golangci-lint command is executed. + # The placeholder '${base-path}' is substituted with a path relative to the mode defined with `run.relative-path-mode`. + # The placeholder '${config-path}' is substituted with a path relative to the configuration file. + # Default: $all + files: + - "$all" + # List of packages that are not allowed. + # Entries can be a variable (starting with $), a string prefix, or an exact match (if ending with $). + # Default: [] + deny: + - pkg: github.com/golang/protobuf + desc: Use google.golang.org/protobuf instead, see https://developers.google.com/protocol-buffers/docs/reference/go/faq#modules + - pkg: github.com/satori/go.uuid + desc: Use github.com/google/uuid instead, satori's package is not maintained + - pkg: github.com/gofrs/uuid$ + desc: Use github.com/gofrs/uuid/v5 or later, it was not a go module before v5 + "non-test files": + files: + - "!$test" + deny: + - pkg: math/rand$ + desc: Use math/rand/v2 instead, see https://go.dev/blog/randv2 + "non-main files": + files: + - "!**/main.go" + deny: + - pkg: log$ + desc: Use log/slog instead, see https://go.dev/blog/slog + + errcheck: + # Report about not checking of errors in type assertions: `a := b.(MyStruct)`. + # Such cases aren't reported by default. + # Default: false + check-type-assertions: true + + exhaustive: + # Program elements to check for exhaustiveness. + # Default: [ switch ] + check: + - switch + - map + + exhaustruct: + # List of regular expressions to exclude struct packages and their names from checks. + # Regular expressions must match complete canonical struct package/name/structname. + # Default: [] + exclude: + # std libs + - ^net/http.Client$ + - ^net/http.Cookie$ + - ^net/http.Request$ + - ^net/http.Response$ + - ^net/http.Server$ + - ^net/http.Transport$ + - ^net/url.URL$ + - ^os/exec.Cmd$ + - ^reflect.StructField$ + # public libs + - ^github.com/Shopify/sarama.Config$ + - ^github.com/Shopify/sarama.ProducerMessage$ + - ^github.com/mitchellh/mapstructure.DecoderConfig$ + - ^github.com/prometheus/client_golang/.+Opts$ + - ^github.com/spf13/cobra.Command$ + - ^github.com/spf13/cobra.CompletionOptions$ + - ^github.com/stretchr/testify/mock.Mock$ + - ^github.com/testcontainers/testcontainers-go.+Request$ + - ^github.com/testcontainers/testcontainers-go.FromDockerfile$ + - ^golang.org/x/tools/go/analysis.Analyzer$ + - ^google.golang.org/protobuf/.+Options$ + - ^gopkg.in/yaml.v3.Node$ + + funcorder: + # Checks if the exported methods of a structure are placed before the non-exported ones. + # Default: true + struct-method: false + + funlen: + # Checks the number of lines in a function. + # If lower than 0, disable the check. + # Default: 60 + lines: 100 + # Checks the number of statements in a function. + # If lower than 0, disable the check. + # Default: 40 + statements: 50 + + gochecksumtype: + # Presence of `default` case in switch statements satisfies exhaustiveness, if all members are not listed. + # Default: true + default-signifies-exhaustive: false + gocognit: - min-complexity: 40 + # Minimal code complexity to report. + # Default: 30 (but we recommend 10-20) + min-complexity: 32 + gocritic: - enabled-checks: - - appendCombine - - boolExprSimplify - - emptyFallthrough - - emptyStringTest - - equalFold - - hexLiteral - - indexAlloc - - nilValReturn - - yodaStyleExpr - disabled-checks: - - ifElseChain + # Settings passed to gocritic. + # The settings key is the name of a supported gocritic checker. + # The list of supported checkers can be found at https://go-critic.com/overview. + settings: + captLocal: + # Whether to restrict checker to params only. + # Default: true + paramsOnly: false + underef: + # Whether to skip (*x).method() calls where x is a pointer receiver. + # Default: true + skipRecvDeref: false + + govet: + # Enable all analyzers. + # Default: false + enable-all: true + # Disable analyzers by name. + # Run `GL_DEBUG=govet golangci-lint run --enable=govet` to see default, all available analyzers, and enabled analyzers. + # Default: [] + disable: + - fieldalignment # too strict + # Settings per analyzer. + settings: + shadow: + # Whether to be strict about shadowing; can be noisy. + # Default: false + strict: true + + inamedparam: + # Skips check for interface methods with only a single parameter. + # Default: false + skip-single-param: true + + mnd: + # List of function patterns to exclude from analysis. + # Values always ignored: `time.Date`, + # `strconv.FormatInt`, `strconv.FormatUint`, `strconv.FormatFloat`, + # `strconv.ParseInt`, `strconv.ParseUint`, `strconv.ParseFloat`. + # Default: [] + ignored-functions: + - args.Error + - flag.Arg + - flag.Duration.* + - flag.Float.* + - flag.Int.* + - flag.Uint.* + - os.Chmod + - os.Mkdir.* + - os.OpenFile + - os.WriteFile + - prometheus.ExponentialBuckets.* + - prometheus.LinearBuckets + + nakedret: + # Make an issue if func has more lines of code than this setting, and it has naked returns. + # Default: 30 + max-func-lines: 0 + + nolintlint: + # Exclude following linters from requiring an explanation. + # Default: [] + allow-no-explanation: [ funlen, gocognit, golines, unparam ] + # Enable to require an explanation of nonzero length after each nolint directive. + # Default: false + require-explanation: true + # Enable to require nolint directives to mention the specific linter being suppressed. + # Default: false + require-specific: true + + perfsprint: + # Optimizes into strings concatenation. + # Default: true + strconcat: false + + reassign: + # Patterns for global variable names that are checked for reassignment. + # See https://github.com/curioswitch/go-reassign#usage + # Default: ["EOF", "Err.*"] + patterns: + - ".*" + + rowserrcheck: + # database/sql is always checked. + # Default: [] + packages: + - github.com/jmoiron/sqlx + + sloglint: + # Enforce not using global loggers. + # Values: + # - "": disabled + # - "all": report all global loggers + # - "default": report only the default slog logger + # https://github.com/go-simpler/sloglint?tab=readme-ov-file#no-global + # Default: "" + no-global: all + # Enforce using methods that accept a context. + # Values: + # - "": disabled + # - "all": report all contextless calls + # - "scope": report only if a context exists in the scope of the outermost function + # https://github.com/go-simpler/sloglint?tab=readme-ov-file#context-only + # Default: "" + context: scope + + staticcheck: + # SAxxxx checks in https://staticcheck.dev/docs/configuration/options/#checks + # Example (to disable some checks): [ "all", "-SA1000", "-SA1001"] + # Default: ["all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022"] + checks: + - all + # Incorrect or missing package comment. + # https://staticcheck.dev/docs/checks/#ST1000 + - -ST1000 + # Use consistent method receiver names. + # https://staticcheck.dev/docs/checks/#ST1016 + - -ST1016 + # Omit embedded fields from selector expression. + # https://staticcheck.dev/docs/checks/#QF1008 + - -QF1008 + + usetesting: + # Enable/disable `os.TempDir()` detections. + # Default: false + os-temp-dir: true + exclusions: - generated: lax + # Log a warning if an exclusion rule is unused. + # Default: false + warn-unused: false + # Predefined exclusion rules. + # Default: [] presets: - - comments - - common-false-positives - - legacy - std-error-handling - paths: - - .*\_gen\.go$ - - third_party$ - - builtin$ - - examples$ + - common-false-positives + # Excluding configuration per-path, per-linter, per-text and per-source. + rules: + - source: 'TODO' + linters: [ godot ] + - text: 'should have a package comment' + linters: [ revive ] + - text: 'exported \S+ \S+ should have comment( \(or a comment on this block\))? or be unexported' + linters: [ revive ] + - text: 'package comment should be of the form ".+"' + source: '// ?(nolint|TODO)' + linters: [ revive ] + - text: 'comment on exported \S+ \S+ should be of the form ".+"' + source: '// ?(nolint|TODO)' + linters: [ revive, staticcheck ] + - path: '_test\.go' + linters: + - bodyclose + - dupl + - errcheck + - funlen + - goconst + - gosec + - noctx + - wrapcheck + - path: 'examples/main\.go' + linters: + - gosec + formatters: enable: - - gofmt - - goimports + - gci settings: gci: sections: - standard + - localmodule - default no-inline-comments: true no-prefix-comments: true custom-order: true no-lex-order: true - exclusions: - generated: lax - paths: - - .*\_gen\.go$ - - third_party$ - - builtin$ - - examples$ diff --git a/Makefile b/Makefile index ccb9034..94e7434 100644 --- a/Makefile +++ b/Makefile @@ -12,8 +12,9 @@ fmt: @golangci-lint fmt lint: - @golangci-lint --version - @golangci-lint run -c .golangci.yml + @golangci-lint version + @golangci-lint config verify + @golangci-lint run run: @echo "Compiling" diff --git a/README.md b/README.md index 63f3945..f90e716 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ Run `curl -H 'Accept: application/json' http://localhost:2112/debug/cron` for js ## `WithMetrics` Middleware * `app_cron_evaluated_total` – total processed jobs by state. -* `app_cron_active_count` – active running jobs. +* `app_cron_active` – active running jobs. * `app_cron_evaluated_duration_seconds` – summary metric with durations. ## Example diff --git a/cron.go b/cron.go index 8bc9d21..4c9f0c6 100644 --- a/cron.go +++ b/cron.go @@ -167,7 +167,7 @@ func (cm *Manager) Run(ctx context.Context) error { // register main functions in cron library id, err := cm.cron.AddFunc(j.schedule.String(), func() { _ = cronFnCtx(ctx) }) if err != nil { - return fmt.Errorf("add cron=%v failed: %v", j.name, err) + return fmt.Errorf("add cron=%v failed: %w", j.name, err) } // set ID @@ -189,7 +189,7 @@ func (cm *Manager) Stop() context.Context { return cm.cron.Stop() } -// updateState set +// updateState set. func (cm *Manager) updateState(idx int, state cronState, err error) { cm.muState.Lock() defer cm.muState.Unlock() @@ -223,7 +223,7 @@ func (cm *Manager) updateID(idx int, id cron.EntryID, funcJob Func) { cm.jobs[idx].cronFn = funcJob } -// Use adds middleware for cron job +// Use adds middleware for cron job. func (cm *Manager) Use(m ...MiddlewareFunc) { cm.middleware = append(cm.middleware, m...) } diff --git a/cron_test.go b/cron_test.go index dd624ea..f4bf0f9 100644 --- a/cron_test.go +++ b/cron_test.go @@ -49,7 +49,7 @@ func TestManager_Validate(t *testing.T) { func TestManager_Run(t *testing.T) { Convey("Test validate function", t, func() { - ctx := context.Background() + ctx := t.Context() m := NewManager() m.Use( WithDevel(false), diff --git a/examples/main.go b/examples/main.go index beb401d..c17d9f9 100644 --- a/examples/main.go +++ b/examples/main.go @@ -10,8 +10,9 @@ import ( "os" "time" - "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/vmkteam/cron" + + "github.com/prometheus/client_golang/prometheus/promhttp" ) // newTask creates new random task with random sleep (0-70s) and with random error or panic. diff --git a/handler.go b/handler.go index 3264339..3d717b2 100644 --- a/handler.go +++ b/handler.go @@ -31,7 +31,7 @@ type State struct { type States []State -// LogValue реализует интерфейс slog.LogValuer +// LogValue implements slog.LogValuer. func (s States) LogValue() slog.Value { attrs := make([]slog.Attr, len(s)) for i, state := range s { @@ -90,7 +90,7 @@ func (cm *Manager) Handler(w http.ResponseWriter, r *http.Request) { startID := r.URL.Query().Get("start") if startID != "" { - go cm.ManualRun(context.WithoutCancel(r.Context()), startID) + go func() { _ = cm.ManualRun(context.WithoutCancel(r.Context()), startID) }() http.Redirect(w, r, r.URL.Path, http.StatusFound) return } @@ -152,7 +152,7 @@ func (printer) text(state []State, w io.Writer) { fmt.Fprintf(wr, tableRow("cron=%s%s", "%s", "%s", "%s"), st.Name, maintenance, st.Schedule, next, st.LastState) } - wr.Flush() + _ = wr.Flush() } // tableRow is a helper for tab separated strings. diff --git a/middleware.go b/middleware.go index dcc1afe..f018831 100644 --- a/middleware.go +++ b/middleware.go @@ -58,11 +58,12 @@ func WithSLog(lg Logger) MiddlewareFunc { err := next(ctx) d, name := time.Since(start), NameFromContext(ctx) - if errors.Is(err, ErrSkipped) { + switch { + case errors.Is(err, ErrSkipped): lg.Print(ctx, "cron job skipped", "job", name, "duration", d) - } else if err != nil { + case err != nil: lg.Error(ctx, "cron job failed", "job", name, "duration", d, "err", err) - } else { + default: lg.Print(ctx, "cron job finished", "job", name, "duration", d) } @@ -188,7 +189,6 @@ func WithMaintenance(p LogPrintf) MiddlewareFunc { pf("cron getting maintenance lock=%v", name) mutex.Lock() pf("cron got maintenance lock=%v", name) - } else { mutex.RLock() } @@ -217,7 +217,7 @@ func WithMetrics(app string) MiddlewareFunc { statActive := prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: "app", Subsystem: "cron", - Name: "active_count", + Name: "active", Help: "Track current status of cron.", }, []string{"app", "cron"})