From c97c816133b42257d0bcf1ee4bd178bb2a7a2b9e Mon Sep 17 00:00:00 2001 From: Taras Madan Date: Tue, 10 Sep 2024 12:16:33 +0200 Subject: vendor: update --- vendor/github.com/jjti/go-spancheck/Makefile | 12 +-- vendor/github.com/jjti/go-spancheck/README.md | 27 ++++++ vendor/github.com/jjti/go-spancheck/config.go | 100 +++++++++++++++++++++-- vendor/github.com/jjti/go-spancheck/go.work.sum | 1 + vendor/github.com/jjti/go-spancheck/spancheck.go | 85 ++++++++++++------- 5 files changed, 180 insertions(+), 45 deletions(-) (limited to 'vendor/github.com/jjti') diff --git a/vendor/github.com/jjti/go-spancheck/Makefile b/vendor/github.com/jjti/go-spancheck/Makefile index 39d80f7c6..8e9d07be3 100644 --- a/vendor/github.com/jjti/go-spancheck/Makefile +++ b/vendor/github.com/jjti/go-spancheck/Makefile @@ -14,12 +14,12 @@ test: testvendor # Follow https://github.com/golang/go/issues/37054 for more details. .PHONY: testvendor testvendor: - @rm -rf base/src - @cd testdata/base && go mod vendor - @cp -r testdata/base/vendor testdata/base/src - @cp -r testdata/base/vendor testdata/disableerrorchecks/src - @cp -r testdata/base/vendor testdata/enableall/src - @rm -rf testdata/base/vendor + rm -rf testdata/base/src + cd testdata/base && GOWORK=off go mod vendor + cp -r testdata/base/vendor testdata/base/src + cp -r testdata/base/vendor testdata/disableerrorchecks/src + cp -r testdata/base/vendor testdata/enableall/src + rm -rf testdata/base/vendor .PHONY: install install: diff --git a/vendor/github.com/jjti/go-spancheck/README.md b/vendor/github.com/jjti/go-spancheck/README.md index 953489d7a..393663ba7 100644 --- a/vendor/github.com/jjti/go-spancheck/README.md +++ b/vendor/github.com/jjti/go-spancheck/README.md @@ -63,6 +63,13 @@ linters-settings: # Default: [] ignore-check-signatures: - "telemetry.RecordError" + # A list of regexes for additional function signatures that create spans. This is useful if you have a utility + # method to create spans. Each entry should be of the form :, where `telemetry-type` + # can be `opentelemetry` or `opencensus`. + # https://github.com/jjti/go-spancheck#extra-start-span-signatures + # Default: [] + extra-start-span-signatures: + - "github.com/user/repo/telemetry/trace.Start:opentelemetry" ``` ### CLI @@ -82,6 +89,8 @@ $ spancheck -h Flags: -checks string comma-separated list of checks to enable (options: end, set-status, record-error) (default "end") + -extra-start-span-signatures string + comma-separated list of regex:telemetry-type for function signatures that indicate the start of a span -ignore-check-signatures string comma-separated list of regex for function signatures that disable checks on errors ``` @@ -123,6 +132,21 @@ The warnings are can be ignored by setting `-ignore-check-signatures` flag to `r spancheck -checks 'end,set-status,record-error' -ignore-check-signatures 'recordErr' ./... ``` +### Extra Start Span Signatures + +By default, Span creation will be tracked from calls to [(go.opentelemetry.io/otel/trace.Tracer).Start](https://github.com/open-telemetry/opentelemetry-go/blob/98b32a6c3a87fbee5d34c063b9096f416b250897/trace/trace.go#L523), [go.opencensus.io/trace.StartSpan](https://pkg.go.dev/go.opencensus.io/trace#StartSpan), or [go.opencensus.io/trace.StartSpanWithRemoteParent](https://github.com/census-instrumentation/opencensus-go/blob/v0.24.0/trace/trace_api.go#L66). + +You can use the `-extra-start-span-signatures` flag to list additional Span creation functions. For all such functions: + +1. their Spans will be linted (for all enable checks) +1. checks will be disabled (i.e. there is no linting of Spans within the creation functions) + +You must pass a comma-separated list of regex patterns and the telemetry library corresponding to the returned Span. Each entry should be of the form `:`, where `telemetry-type` can be `opentelemetry` or `opencensus`. For example, if you have created a function named `StartTrace` in a `telemetry` package, using the `go.opentelemetry.io/otel` library, you can include this function for analysis like so: + +```bash +spancheck -extra-start-span-signatures 'github.com/user/repo/telemetry/StartTrace:opentelemetry' ./... +``` + ## Problem Statement Tracing is a celebrated [[1](https://andydote.co.uk/2023/09/19/tracing-is-better/),[2](https://charity.wtf/2022/08/15/live-your-best-life-with-structured-events/)] and well marketed [[3](https://docs.datadoghq.com/tracing/),[4](https://www.honeycomb.io/distributed-tracing)] pillar of observability. But self-instrumented tracing requires a lot of easy-to-forget boilerplate: @@ -239,3 +263,6 @@ This linter is the product of liberal copying of: - [github.com/tomarrell/wrapcheck](https://github.com/tomarrell/wrapcheck) (error type checking and config) - [github.com/Antonboom/testifylint](https://github.com/Antonboom/testifylint) (README) - [github.com/ghostiam/protogetter](https://github.com/ghostiam/protogetter/blob/main/testdata/Makefile) (test setup) + +And the contributions of: +- [@trixnz](https://github.com/trixnz) who [added support for custom span start functions](https://github.com/jjti/go-spancheck/pull/16) diff --git a/vendor/github.com/jjti/go-spancheck/config.go b/vendor/github.com/jjti/go-spancheck/config.go index 4005f49e0..ed02a1ad9 100644 --- a/vendor/github.com/jjti/go-spancheck/config.go +++ b/vendor/github.com/jjti/go-spancheck/config.go @@ -22,6 +22,18 @@ const ( RecordErrorCheck ) +var ( + startSpanSignatureCols = 2 + defaultStartSpanSignatures = []string{ + // https://github.com/open-telemetry/opentelemetry-go/blob/98b32a6c3a87fbee5d34c063b9096f416b250897/trace/trace.go#L523 + `\(go.opentelemetry.io/otel/trace.Tracer\).Start:opentelemetry`, + // https://pkg.go.dev/go.opencensus.io/trace#StartSpan + `go.opencensus.io/trace.StartSpan:opencensus`, + // https://github.com/census-instrumentation/opencensus-go/blob/v0.24.0/trace/trace_api.go#L66 + `go.opencensus.io/trace.StartSpanWithRemoteParent:opencensus`, + } +) + func (c Check) String() string { switch c { case EndCheck: @@ -35,14 +47,17 @@ func (c Check) String() string { } } -var ( - // Checks is a list of all checks by name. - Checks = map[string]Check{ - EndCheck.String(): EndCheck, - SetStatusCheck.String(): SetStatusCheck, - RecordErrorCheck.String(): RecordErrorCheck, - } -) +// Checks is a list of all checks by name. +var Checks = map[string]Check{ + EndCheck.String(): EndCheck, + SetStatusCheck.String(): SetStatusCheck, + RecordErrorCheck.String(): RecordErrorCheck, +} + +type spanStartMatcher struct { + signature *regexp.Regexp + spanType spanType +} // Config is a configuration for the spancheck analyzer. type Config struct { @@ -55,6 +70,8 @@ type Config struct { // the IgnoreSetStatusCheckSignatures regex. IgnoreChecksSignaturesSlice []string + StartSpanMatchersSlice []string + endCheckEnabled bool setStatusEnabled bool recordErrorEnabled bool @@ -62,12 +79,16 @@ type Config struct { // ignoreChecksSignatures is a regex that, if matched, disables the // SetStatus and RecordError checks on error. ignoreChecksSignatures *regexp.Regexp + + startSpanMatchers []spanStartMatcher + startSpanMatchersCustomRegex *regexp.Regexp } // NewDefaultConfig returns a new Config with default values. func NewDefaultConfig() *Config { return &Config{ - EnabledChecks: []string{EndCheck.String()}, + EnabledChecks: []string{EndCheck.String()}, + StartSpanMatchersSlice: defaultStartSpanSignatures, } } @@ -83,6 +104,11 @@ func (c *Config) finalize() { // parseSignatures sets the Ignore*CheckSignatures regex from the string slices. func (c *Config) parseSignatures() { + c.parseIgnoreSignatures() + c.parseStartSpanSignatures() +} + +func (c *Config) parseIgnoreSignatures() { if c.ignoreChecksSignatures == nil && len(c.IgnoreChecksSignaturesSlice) > 0 { if len(c.IgnoreChecksSignaturesSlice) == 1 && c.IgnoreChecksSignaturesSlice[0] == "" { return @@ -92,6 +118,62 @@ func (c *Config) parseSignatures() { } } +func (c *Config) parseStartSpanSignatures() { + if c.startSpanMatchers != nil { + return + } + + customMatchers := []string{} + for i, sig := range c.StartSpanMatchersSlice { + parts := strings.Split(sig, ":") + + // Make sure we have both a signature and a telemetry type + if len(parts) != startSpanSignatureCols { + log.Default().Printf("[WARN] invalid start span signature \"%s\". expected regex:telemetry-type\n", sig) + + continue + } + + sig, sigType := parts[0], parts[1] + if len(sig) < 1 { + log.Default().Print("[WARN] invalid start span signature, empty pattern") + + continue + } + + spanType, ok := SpanTypes[sigType] + if !ok { + validSpanTypes := make([]string, 0, len(SpanTypes)) + for k := range SpanTypes { + validSpanTypes = append(validSpanTypes, k) + } + + log.Default(). + Printf("[WARN] invalid start span type \"%s\". expected one of %s\n", sigType, strings.Join(validSpanTypes, ", ")) + + continue + } + + regex, err := regexp.Compile(sig) + if err != nil { + log.Default().Printf("[WARN] failed to compile regex from signature %s: %v\n", sig, err) + + continue + } + + c.startSpanMatchers = append(c.startSpanMatchers, spanStartMatcher{ + signature: regex, + spanType: spanType, + }) + + if i >= len(defaultStartSpanSignatures) { + customMatchers = append(customMatchers, sig) + } + } + + c.startSpanMatchersCustomRegex = createRegex(customMatchers) +} + func parseChecks(checksSlice []string) []Check { if len(checksSlice) == 0 { return nil diff --git a/vendor/github.com/jjti/go-spancheck/go.work.sum b/vendor/github.com/jjti/go-spancheck/go.work.sum index f3cdef790..04eadf2c5 100644 --- a/vendor/github.com/jjti/go-spancheck/go.work.sum +++ b/vendor/github.com/jjti/go-spancheck/go.work.sum @@ -1,3 +1,4 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= diff --git a/vendor/github.com/jjti/go-spancheck/spancheck.go b/vendor/github.com/jjti/go-spancheck/spancheck.go index 6f069a033..8fc7945c6 100644 --- a/vendor/github.com/jjti/go-spancheck/spancheck.go +++ b/vendor/github.com/jjti/go-spancheck/spancheck.go @@ -23,11 +23,15 @@ const ( spanOpenCensus // from go.opencensus.io/trace ) -var ( - // this approach stolen from errcheck - // https://github.com/kisielk/errcheck/blob/7f94c385d0116ccc421fbb4709e4a484d98325ee/errcheck/errcheck.go#L22 - errorType = types.Universe.Lookup("error").Type().Underlying().(*types.Interface) -) +// SpanTypes is a list of all span types by name. +var SpanTypes = map[string]spanType{ + "opentelemetry": spanOpenTelemetry, + "opencensus": spanOpenCensus, +} + +// this approach stolen from errcheck +// https://github.com/kisielk/errcheck/blob/7f94c385d0116ccc421fbb4709e4a484d98325ee/errcheck/errcheck.go#L22 +var errorType = types.Universe.Lookup("error").Type().Underlying().(*types.Interface) // NewAnalyzerWithConfig returns a new analyzer configured with the Config passed in. // Its config can be set for testing. @@ -84,6 +88,12 @@ func runFunc(pass *analysis.Pass, node ast.Node, config *Config) { funcScope = pass.TypesInfo.Scopes[v.Type] case *ast.FuncDecl: funcScope = pass.TypesInfo.Scopes[v.Type] + fnSig := pass.TypesInfo.ObjectOf(v.Name).String() + + // Skip checking spans in this function if it's a custom starter/creator. + if config.startSpanMatchersCustomRegex != nil && config.startSpanMatchersCustomRegex.MatchString(fnSig) { + return + } } // Maps each span variable to its defining ValueSpec/AssignStmt. @@ -108,8 +118,12 @@ func runFunc(pass *analysis.Pass, node ast.Node, config *Config) { // ctx, span := otel.Tracer("app").Start(...) // ctx, span = otel.Tracer("app").Start(...) // var ctx, span = otel.Tracer("app").Start(...) - sType, sStart := isSpanStart(pass.TypesInfo, n) - if !sStart || !isCall(stack[len(stack)-2]) { + sType, isStart := isSpanStart(pass.TypesInfo, n, config.startSpanMatchers) + if !isStart { + return true + } + + if !isCall(stack[len(stack)-2]) { return true } @@ -169,7 +183,7 @@ func runFunc(pass *analysis.Pass, node ast.Node, config *Config) { for _, sv := range spanVars { if config.endCheckEnabled { // Check if there's no End to the span. - if ret := getMissingSpanCalls(pass, g, sv, "End", func(pass *analysis.Pass, ret *ast.ReturnStmt) *ast.ReturnStmt { return ret }, nil); ret != nil { + if ret := getMissingSpanCalls(pass, g, sv, "End", func(_ *analysis.Pass, ret *ast.ReturnStmt) *ast.ReturnStmt { return ret }, nil, config.startSpanMatchers); ret != nil { pass.ReportRangef(sv.stmt, "%s.End is not called on all paths, possible memory leak", sv.vr.Name()) pass.ReportRangef(ret, "return can be reached without calling %s.End", sv.vr.Name()) } @@ -177,7 +191,7 @@ func runFunc(pass *analysis.Pass, node ast.Node, config *Config) { if config.setStatusEnabled { // Check if there's no SetStatus to the span setting an error. - if ret := getMissingSpanCalls(pass, g, sv, "SetStatus", getErrorReturn, config.ignoreChecksSignatures); ret != nil { + if ret := getMissingSpanCalls(pass, g, sv, "SetStatus", getErrorReturn, config.ignoreChecksSignatures, config.startSpanMatchers); ret != nil { pass.ReportRangef(sv.stmt, "%s.SetStatus is not called on all paths", sv.vr.Name()) pass.ReportRangef(ret, "return can be reached without calling %s.SetStatus", sv.vr.Name()) } @@ -185,7 +199,7 @@ func runFunc(pass *analysis.Pass, node ast.Node, config *Config) { if config.recordErrorEnabled && sv.spanType == spanOpenTelemetry { // RecordError only exists in OpenTelemetry // Check if there's no RecordError to the span setting an error. - if ret := getMissingSpanCalls(pass, g, sv, "RecordError", getErrorReturn, config.ignoreChecksSignatures); ret != nil { + if ret := getMissingSpanCalls(pass, g, sv, "RecordError", getErrorReturn, config.ignoreChecksSignatures, config.startSpanMatchers); ret != nil { pass.ReportRangef(sv.stmt, "%s.RecordError is not called on all paths", sv.vr.Name()) pass.ReportRangef(ret, "return can be reached without calling %s.RecordError", sv.vr.Name()) } @@ -194,25 +208,22 @@ func runFunc(pass *analysis.Pass, node ast.Node, config *Config) { } // isSpanStart reports whether n is tracer.Start() -func isSpanStart(info *types.Info, n ast.Node) (spanType, bool) { +func isSpanStart(info *types.Info, n ast.Node, startSpanMatchers []spanStartMatcher) (spanType, bool) { sel, ok := n.(*ast.SelectorExpr) if !ok { return spanUnset, false } - switch sel.Sel.Name { - case "Start": // https://github.com/open-telemetry/opentelemetry-go/blob/98b32a6c3a87fbee5d34c063b9096f416b250897/trace/trace.go#L523 - obj, ok := info.Uses[sel.Sel] - return spanOpenTelemetry, ok && obj.Pkg().Path() == "go.opentelemetry.io/otel/trace" - case "StartSpan": // https://pkg.go.dev/go.opencensus.io/trace#StartSpan - obj, ok := info.Uses[sel.Sel] - return spanOpenCensus, ok && obj.Pkg().Path() == "go.opencensus.io/trace" - case "StartSpanWithRemoteParent": // https://github.com/census-instrumentation/opencensus-go/blob/v0.24.0/trace/trace_api.go#L66 - obj, ok := info.Uses[sel.Sel] - return spanOpenCensus, ok && obj.Pkg().Path() == "go.opencensus.io/trace" - default: - return spanUnset, false + fnSig := info.ObjectOf(sel.Sel).String() + + // Check if the function is a span start function + for _, matcher := range startSpanMatchers { + if matcher.signature.MatchString(fnSig) { + return matcher.spanType, true + } } + + return 0, false } func isCall(n ast.Node) bool { @@ -225,11 +236,16 @@ func getID(node ast.Node) *ast.Ident { case *ast.ValueSpec: if len(stmt.Names) > 1 { return stmt.Names[1] + } else if len(stmt.Names) == 1 { + return stmt.Names[0] } case *ast.AssignStmt: if len(stmt.Lhs) > 1 { id, _ := stmt.Lhs[1].(*ast.Ident) return id + } else if len(stmt.Lhs) == 1 { + id, _ := stmt.Lhs[0].(*ast.Ident) + return id } } return nil @@ -244,13 +260,14 @@ func getMissingSpanCalls( selName string, checkErr func(pass *analysis.Pass, ret *ast.ReturnStmt) *ast.ReturnStmt, ignoreCheckSig *regexp.Regexp, + spanStartMatchers []spanStartMatcher, ) *ast.ReturnStmt { // blockUses computes "uses" for each block, caching the result. memo := make(map[*cfg.Block]bool) blockUses := func(pass *analysis.Pass, b *cfg.Block) bool { res, ok := memo[b] if !ok { - res = usesCall(pass, b.Nodes, sv, selName, ignoreCheckSig, 0) + res = usesCall(pass, b.Nodes, sv, selName, ignoreCheckSig, spanStartMatchers, 0) memo[b] = res } return res @@ -272,7 +289,7 @@ outer: } // Is the call "used" in the remainder of its defining block? - if usesCall(pass, rest, sv, selName, ignoreCheckSig, 0) { + if usesCall(pass, rest, sv, selName, ignoreCheckSig, spanStartMatchers, 0) { return nil } @@ -314,7 +331,15 @@ outer: } // usesCall reports whether stmts contain a use of the selName call on variable v. -func usesCall(pass *analysis.Pass, stmts []ast.Node, sv spanVar, selName string, ignoreCheckSig *regexp.Regexp, depth int) bool { +func usesCall( + pass *analysis.Pass, + stmts []ast.Node, + sv spanVar, + selName string, + ignoreCheckSig *regexp.Regexp, + startSpanMatchers []spanStartMatcher, + depth int, +) bool { if depth > 1 { // for perf reasons, do not dive too deep thru func literals, just one level deep check. return false } @@ -329,7 +354,7 @@ func usesCall(pass *analysis.Pass, stmts []ast.Node, sv spanVar, selName string, cfgs := pass.ResultOf[ctrlflow.Analyzer].(*ctrlflow.CFGs) g := cfgs.FuncLit(n) if g != nil && len(g.Blocks) > 0 { - return usesCall(pass, g.Blocks[0].Nodes, sv, selName, ignoreCheckSig, depth+1) + return usesCall(pass, g.Blocks[0].Nodes, sv, selName, ignoreCheckSig, startSpanMatchers, depth+1) } return false @@ -352,8 +377,8 @@ func usesCall(pass *analysis.Pass, stmts []ast.Node, sv spanVar, selName string, stack = append(stack, n) // push // Check whether the span was assigned over top of its old value. - _, spanStart := isSpanStart(pass.TypesInfo, n) - if spanStart { + _, isStart := isSpanStart(pass.TypesInfo, n, startSpanMatchers) + if isStart { if id := getID(stack[len(stack)-3]); id != nil && id.Obj.Decl == sv.id.Obj.Decl { reAssigned = true return false @@ -364,7 +389,7 @@ func usesCall(pass *analysis.Pass, stmts []ast.Node, sv spanVar, selName string, // Selector (End, SetStatus, RecordError) hit. if n.Sel.Name == selName { id, ok := n.X.(*ast.Ident) - found = ok && id.Obj.Decl == sv.id.Obj.Decl + found = ok && id.Obj != nil && id.Obj.Decl == sv.id.Obj.Decl } // Check if an ignore signature matches. -- cgit mrf-deployment