diff --git a/.golangci.yml b/.golangci.yml index 4040c12..0e47575 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -26,32 +26,139 @@ linters-settings: settings: hugeParam: sizeThreshold: 512 + # https://golangci-lint.run/usage/linters/#gosec + gosec: + config: + G306: "0700" + excludes: + - G101 # Potential hardcoded credentials + - G102 # Bind to all interfaces + - G112 # Potential slowloris attack + - G401 # Detect the usage of DES, RC4, MD5 or SHA1 + - G402 # Look for bad TLS connection settings + - G404 # Insecure random number source (rand) + - G501 # Import blocklist: crypto/md5 + - G505 # Import blocklist: crypto/sha1 linters: enable: - - bodyclose - - dogsled - - exportloopref - # - gci TODO re-enable - - goconst - - gocritic - # - gocyclo - - gofmt - - goprintffuncname - #- gosec - - ifshort - - misspell - - nakedret - - noctx - - nolintlint - - prealloc - - revive - - promlinter - - rowserrcheck - - sqlclosecheck - - stylecheck - - thelper - - tparallel - - unconvert - - unparam - - whitespace +# - bodyclose +# - dogsled +# - exportloopref +# # - gci TODO re-enable +# - goconst +# - gocritic +# # - gocyclo +# - gofmt +# - goprintffuncname +# #- gosec +# - ifshort +# - misspell +# - nakedret +# - noctx +# - nolintlint +# - prealloc +# - revive +# - promlinter +# - rowserrcheck +# - sqlclosecheck +# - stylecheck +# - thelper +# - tparallel +# - unconvert +# - unparam +# - whitespace + + # Enabled by default linters: + - deadcode # Finds unused code [fast: false, auto-fix: false] + - errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases [fast: false, auto-fix: false] + - gosimple # (megacheck): Linter for Go source code that specializes in simplifying code [fast: false, auto-fix: false] + - govet # (vet, vetshadow): Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string [fast: false, auto-fix: false] + - ineffassign # Detects when assignments to existing variables are not used [fast: true, auto-fix: false] + - staticcheck # (megacheck): It's a set of rules from staticcheck. It's not the same thing as the staticcheck binary. The author of staticcheck doesn't support or approve the use of staticcheck as a library inside golangci-lint. [fast: false, auto-fix: false] + - typecheck # Like the front-end of a Go compiler, parses and type-checks Go code [fast: false, auto-fix: false] + - unused # (megacheck): Checks Go code for unused constants, variables, functions and types [fast: false, auto-fix: false] + - varcheck # Finds unused global variables and constants [fast: false, auto-fix: false] + + # Disabled by default linters: + - asasalint # check for pass []any as any in variadic func(...any) [fast: false, auto-fix: false] + - asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers [fast: true, auto-fix: false] + - bidichk # Checks for dangerous unicode character sequences [fast: true, auto-fix: false] + - bodyclose # checks whether HTTP response body is closed successfully [fast: false, auto-fix: false] + #- containedctx # containedctx is a linter that detects struct contained context.Context field [fast: true, auto-fix: false] + #- contextcheck # check the function whether to use a non-inherited context [fast: false, auto-fix: false] + #- cyclop # checks function and package cyclomatic complexity [fast: false, auto-fix: false] + - decorder # check declaration order and count of types, constants, variables and functions [fast: true, auto-fix: false] + - depguard # Go linter that checks if package imports are in a list of acceptable packages [fast: true, auto-fix: false] + - dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) [fast: true, auto-fix: false] + #- dupl # Tool for code clone detection [fast: true, auto-fix: false] + - durationcheck # check for two durations multiplied together [fast: false, auto-fix: false] + - 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. [fast: false, auto-fix: false] + - errname # Checks that sentinel errors are prefixed with the `Err` and error types are suffixed with the `Error`. [fast: false, auto-fix: false] + - errorlint # errorlint is a linter for that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13. [fast: false, auto-fix: false] + - execinquery # execinquery is a linter about query string checker in Query function which reads your Go src files and warning it finds [fast: false, auto-fix: false] + - exhaustive # check exhaustiveness of enum switch statements [fast: false, auto-fix: false] + #- exhaustruct # Checks if all structure fields are initialized [fast: false, auto-fix: false] + - exportloopref # checks for pointers to enclosing loop variables [fast: false, auto-fix: false] + - forbidigo # Forbids identifiers [fast: true, auto-fix: false] + - forcetypeassert # finds forced type assertions [fast: true, auto-fix: false] + #- funlen # Tool for detection of long functions [fast: true, auto-fix: false] + #- gci # Gci controls golang package import order and makes it always deterministic. [fast: true, auto-fix: false] + #- gochecknoglobals # check that no global variables exist [fast: true, auto-fix: false] + #- gochecknoinits # Checks that no init functions are present in Go code [fast: true, auto-fix: false] + #- gocognit # Computes and checks the cognitive complexity of functions [fast: true, auto-fix: false] + - goconst # Finds repeated strings that could be replaced by a constant [fast: true, auto-fix: false] + - gocritic # Provides diagnostics that check for bugs, performance and style issues. [fast: false, auto-fix: false] + #- gocyclo # Computes and checks the cyclomatic complexity of functions [fast: true, auto-fix: false] + #- godot # Check if comments end in a period [fast: true, auto-fix: true] + #- godox # Tool for detection of FIXME, TODO and other comment keywords [fast: true, auto-fix: false] + - goerr113 # Golang linter to check the errors handling expressions [fast: false, auto-fix: false] + - gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification [fast: true, auto-fix: true] + #- gofumpt # Gofumpt checks whether code was gofumpt-ed. [fast: true, auto-fix: true] + - goheader # Checks is file header matches to pattern [fast: true, auto-fix: false] + - goimports # In addition to fixing imports, goimports also formats your code in the same style as gofmt. [fast: true, auto-fix: true] + #- gomnd # An analyzer to detect magic numbers. [fast: true, auto-fix: false] + #- gomoddirectives # Manage the use of 'replace', 'retract', and 'excludes' directives in go.mod. [fast: true, auto-fix: false] + - gomodguard # Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations. [fast: true, auto-fix: false] + - goprintffuncname # Checks that printf-like functions are named with `f` at the end [fast: true, auto-fix: false] + - gosec # (gas): Inspects source code for security problems [fast: false, auto-fix: false] + - grouper # An analyzer to analyze expression groups. [fast: true, auto-fix: false] + - importas # Enforces consistent import aliases [fast: false, auto-fix: false] + #- ireturn # Accept Interfaces, Return Concrete Types [fast: false, auto-fix: false] + #- lll # Reports long lines [fast: true, auto-fix: false] + - maintidx # maintidx measures the maintainability index of each function. [fast: true, auto-fix: false] + - makezero # Finds slice declarations with non-zero initial length [fast: false, auto-fix: false] + - misspell # Finds commonly misspelled English words in comments [fast: true, auto-fix: true] + - nakedret # Finds naked returns in functions greater than a specified function length [fast: true, auto-fix: false] + #- nestif # Reports deeply nested if statements [fast: true, auto-fix: false] + - nilerr # Finds the code that returns nil even if it checks that the error is not nil. [fast: false, auto-fix: false] + - nilnil # Checks that there is no simultaneous return of `nil` error and an invalid value. [fast: false, auto-fix: false] + #- nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity [fast: true, auto-fix: false] + - noctx # noctx finds sending http request without context.Context [fast: false, auto-fix: false] + - nolintlint # Reports ill-formed or insufficient nolint directives [fast: true, auto-fix: false] + - nonamedreturns # Reports all named returns [fast: false, auto-fix: false] + - nosnakecase # nosnakecase is a linter that detects snake case of variable naming and function name. [fast: true, auto-fix: false] + - nosprintfhostport # Checks for misuse of Sprintf to construct a host with port in a URL. [fast: true, auto-fix: false] + #- paralleltest # paralleltest detects missing usage of t.Parallel() method in your Go test [fast: false, auto-fix: false] + - prealloc # Finds slice declarations that could potentially be pre-allocated [fast: true, auto-fix: false] + - predeclared # find code that shadows one of Go's predeclared identifiers [fast: true, auto-fix: false] + - promlinter # Check Prometheus metrics naming via promlint [fast: true, auto-fix: false] + - revive # Fast, configurable, extensible, flexible, and beautiful linter for Go. Drop-in replacement of golint. [fast: false, auto-fix: false] + - rowserrcheck # checks whether Err of rows is checked successfully [fast: false, auto-fix: false] + - sqlclosecheck # Checks that sql.Rows and sql.Stmt are closed. [fast: false, auto-fix: false] + - structcheck # Finds unused struct fields [fast: false, auto-fix: false] + - stylecheck # Stylecheck is a replacement for golint [fast: false, auto-fix: false] + - tagliatelle # Checks the struct tags. [fast: true, auto-fix: false] + - tenv # tenv is analyzer that detects using os.Setenv instead of t.Setenv since Go1.17 [fast: false, auto-fix: false] + - testpackage # linter that makes you use a separate _test package [fast: true, auto-fix: false] + - thelper # thelper detects golang test helpers without t.Helper() call and checks the consistency of test helpers [fast: false, auto-fix: false] + - tparallel # tparallel detects inappropriate usage of t.Parallel() method in your Go test codes [fast: false, auto-fix: false] + - unconvert # Remove unnecessary type conversions [fast: false, auto-fix: false] + - unparam # Reports unused function parameters [fast: false, auto-fix: false] + - usestdlibvars # A linter that detect the possibility to use variables/constants from the Go standard library. [fast: true, auto-fix: false] + #- varnamelen # checks that the length of a variable's name matches its scope [fast: false, auto-fix: false] + - wastedassign # wastedassign finds wasted assignment statements. [fast: false, auto-fix: false] + - whitespace # Tool for detection of leading and trailing whitespace [fast: true, auto-fix: true] + #- wrapcheck # Checks that errors returned from external packages are wrapped [fast: false, auto-fix: false] + #- wsl # Whitespace Linter - Forces you to use empty lines! [fast: true, auto-fix: false] + diff --git a/example/config/main.go b/example/config/main.go index 6a6fe94..bf89a65 100644 --- a/example/config/main.go +++ b/example/config/main.go @@ -19,7 +19,7 @@ type ( Bool bool `yaml:"bool"` String string `yaml:"string"` CamelCaseString string `yaml:"camelCaseString"` - SnakeCaseString string `yaml:"snake_case_string"` + SnakeCaseString string `yaml:"snake_case_string"` //nolint:tagliatelle } Config struct { Int int `yaml:"int"` diff --git a/example/errors/main.go b/example/errors/main.go index cf0ccc7..7412360 100644 --- a/example/errors/main.go +++ b/example/errors/main.go @@ -18,12 +18,12 @@ func main() { err2 := keelerrors.NewWrappedError(err1, ErrTwo) if errors.Is(err1, ErrOne) { - fmt.Println("err1 = ErrOne") + fmt.Println("err1 = ErrOne") //nolint:forbidigo } if errors.Is(err2, ErrTwo) { - fmt.Println("err2 = ErrTwo") + fmt.Println("err2 = ErrTwo") //nolint:forbidigo } if errors.Is(err2, ErrOne) { - fmt.Println("err2 = ErrOne") + fmt.Println("err2 = ErrOne") //nolint:forbidigo } } diff --git a/example/middlewares/jwtfromcookie/main.go b/example/middlewares/jwtfromcookie/main.go index 6ce819d..d4b6051 100644 --- a/example/middlewares/jwtfromcookie/main.go +++ b/example/middlewares/jwtfromcookie/main.go @@ -50,9 +50,10 @@ func main() { svs := http.NewServeMux() svs.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { // retrieve from context - claims := r.Context().Value(contextKey).(*CustomClaims) - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(claims.Name)) + if claims, ok := r.Context().Value(contextKey).(*CustomClaims); ok { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(claims.Name)) + } }) svr.AddService( diff --git a/example/middlewares/jwtfromtoken/main.go b/example/middlewares/jwtfromtoken/main.go index 7e12ba6..0876339 100644 --- a/example/middlewares/jwtfromtoken/main.go +++ b/example/middlewares/jwtfromtoken/main.go @@ -48,9 +48,10 @@ func main() { svs := http.NewServeMux() svs.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { // retrieve from context - claims := r.Context().Value(contextKey).(*CustomClaims) - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(claims.Name)) + if claims, ok := r.Context().Value(contextKey).(*CustomClaims); ok { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(claims.Name)) + } }) svs.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) { if token, err := jwtInst.GetSignedToken(CustomClaims{ diff --git a/example/persistence/mongo/repository/dummy.go b/example/persistence/mongo/repository/dummy.go index 98e22f2..f49a7b9 100644 --- a/example/persistence/mongo/repository/dummy.go +++ b/example/persistence/mongo/repository/dummy.go @@ -30,7 +30,7 @@ func (r *DummyRepository) Get(ctx context.Context, id string, opts ...*options.F } // Upsert entity -func (r *DummyRepository) Upsert(ctx context.Context, entity *store.Dummy) (err error) { +func (r *DummyRepository) Upsert(ctx context.Context, entity *store.Dummy) error { if err := r.collection.Upsert(ctx, entity.GetID(), entity); err != nil { return err } diff --git a/example/persistence/mongo/store/codec.go b/example/persistence/mongo/store/codec.go index ebdf0df..7ca2a19 100644 --- a/example/persistence/mongo/store/codec.go +++ b/example/persistence/mongo/store/codec.go @@ -5,6 +5,7 @@ import ( "reflect" "time" + "github.com/pkg/errors" "go.mongodb.org/mongo-driver/bson/bsoncodec" "go.mongodb.org/mongo-driver/bson/bsonrw" "go.mongodb.org/mongo-driver/bson/bsontype" @@ -20,7 +21,10 @@ func (d *DateTimeCodec) EncodeValue(_ bsoncodec.EncodeContext, vw bsonrw.ValueWr if !val.IsValid() || val.Type() != TDateTime { return bsoncodec.ValueEncoderError{Name: "DateTimeEncodeValue", Types: []reflect.Type{TDateTime}, Received: val} } - td := val.Interface().(DateTime) + td, ok := val.Interface().(DateTime) + if !ok { + return errors.New("failed to encode date time") + } tt, err := td.Time() if err != nil { return bsoncodec.ValueEncoderError{Name: "DateTimeEncodeValue", Types: []reflect.Type{TDateTime}, Received: val} @@ -34,7 +38,7 @@ func (d *DateTimeCodec) DecodeValue(_ bsoncodec.DecodeContext, vr bsonrw.ValueRe } var dateTimeVal DateTime - switch t := vr.Type(); t { + switch t := vr.Type(); t { //nolint:exhaustive case bsontype.DateTime: dt, err := vr.ReadDateTime() if err != nil { @@ -48,7 +52,7 @@ func (d *DateTimeCodec) DecodeValue(_ bsoncodec.DecodeContext, vr bsonrw.ValueRe } dateTimeVal = DateTime(decimalStr) default: - return fmt.Errorf("cannot decode %v into a DateTime", t) + return fmt.Errorf("cannot decode %v into a DateTime", t) //nolint:goerr113 } val.Set(reflect.ValueOf(dateTimeVal)) diff --git a/example/persistence/mongo/store/entity.go b/example/persistence/mongo/store/entity.go index eac6b40..dbb0de6 100644 --- a/example/persistence/mongo/store/entity.go +++ b/example/persistence/mongo/store/entity.go @@ -19,7 +19,7 @@ var ( // Entity type type Entity struct { ID string `json:"id" bson:"id" yaml:"id"` - BsonID primitive.ObjectID `json:"_id,omitempty" bson:"_id,omitempty" yaml:"_id,omitempty"` + BsonID primitive.ObjectID `json:"_id,omitempty" bson:"_id,omitempty" yaml:"_id,omitempty"` //nolint:tagliatelle } func NewEntity(id string) Entity { diff --git a/example/remoteconfig/main.go b/example/remoteconfig/main.go index 97aab7d..6cdddc9 100644 --- a/example/remoteconfig/main.go +++ b/example/remoteconfig/main.go @@ -23,11 +23,11 @@ func main() { // create config reader fooFn := config.GetString(c, "foo", "default_foo") - fmt.Println("initial foo:", fooFn()) + fmt.Println("initial foo:", fooFn()) //nolint:forbidigo // watch changes config.WatchString(svr.CancelContext(), fooFn, func(s string) { - fmt.Println("change foo:", fooFn()) + fmt.Println("change foo:", fooFn()) //nolint:forbidigo }) ch := make(chan string) @@ -36,7 +36,7 @@ func main() { go func(ch chan string) { for { value := <-ch - fmt.Println("channel foo:", value) + fmt.Println("channel foo:", value) //nolint:forbidigo } }(ch) @@ -44,7 +44,7 @@ func main() { svr.AddService( keel.NewServiceHTTP(l, "demo", "localhost:8080", http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { - fmt.Println("current foo:", fooFn()) + fmt.Println("current foo:", fooFn()) //nolint:forbidigo }), ), ) diff --git a/example/roundtripwares/logger/client.go b/example/roundtripwares/logger/client.go index 86dce04..fc448d4 100644 --- a/example/roundtripwares/logger/client.go +++ b/example/roundtripwares/logger/client.go @@ -17,9 +17,9 @@ func client() { var err error - _, err = client.Get("http://localhost:8080") // nolint + _, err = client.Get("http://localhost:8080") //nolint:all log.Must(l, err, "failed to retrieve response") - _, err = client.Get("http://localhost:8080/404") // nolint + _, err = client.Get("http://localhost:8080/404") //nolint:all log.Must(l, err, "failed to retrieve response") } diff --git a/example/roundtripwares/requestid/main.go b/example/roundtripwares/requestid/main.go index 9788c4e..13e315c 100644 --- a/example/roundtripwares/requestid/main.go +++ b/example/roundtripwares/requestid/main.go @@ -20,14 +20,17 @@ func main() { httpClient := keelhttp.NewHTTPClient( keelhttp.HTTPClientWithRoundTripware(l, roundtripware.RequestID(), + roundtripware.SessionID(), + roundtripware.TrackingID(), ), ) // create demo service svs := http.NewServeMux() + + // send internal http request svs.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - l := log.WithHTTPRequest(l, r) - // send internal http request + // send request if req, err := http.NewRequestWithContext(r.Context(), http.MethodGet, "http://localhost:8080/internal", nil); err != nil { httputils.InternalServerError(l, w, r, err) return @@ -37,19 +40,22 @@ func main() { } else { defer resp.Body.Close() w.WriteHeader(http.StatusOK) + log.WithHTTPRequest(l, r).Info("handled request") _, _ = w.Write([]byte(r.Header.Get(keelhttp.HeaderXRequestID) + " - " + resp.Header.Get(keelhttp.HeaderXRequestID))) log.WithHTTPRequestOut(l, req).Info("sent internal request") } }) + // handle internal http request svs.HandleFunc("/internal", func(w http.ResponseWriter, r *http.Request) { - l := log.WithHTTPRequest(l, r) - l.Info("handled internal request") + log.WithHTTPRequest(l, r).Info("handled internal request") w.WriteHeader(http.StatusOK) }) svr.AddService( keel.NewServiceHTTP(l, "demo", "localhost:8080", svs, middleware.RequestID(), + middleware.SessionID(), + middleware.TrackingID(), ), ) diff --git a/example/telemetry/main.go b/example/telemetry/main.go index 96b5d3b..fa4f37d 100644 --- a/example/telemetry/main.go +++ b/example/telemetry/main.go @@ -85,7 +85,7 @@ func main() { log.Must(l, err, "failed to create up down meter") svs.HandleFunc("/histogram", func(w http.ResponseWriter, r *http.Request) { - histogram.Record(r.Context(), int64(rand.Int()), attribute.String("key", "value")) // nolint:gosec + histogram.Record(r.Context(), int64(rand.Int()), attribute.String("key", "value")) w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte("OK!")) }) diff --git a/integration/temporal/client.go b/integration/temporal/client.go index d1ee963..1972580 100644 --- a/integration/temporal/client.go +++ b/integration/temporal/client.go @@ -84,7 +84,7 @@ func NewClient(ctx context.Context, endpoint string, opts ...ClientOption) (clie } } else if err != nil { return nil, errors.Wrap(err, "failed to retrieve temporal namespace info") - } else if nsInfo := ns.GetNamespaceInfo(); nsInfo.State != enums.NAMESPACE_STATE_REGISTERED { + } else if nsInfo := ns.GetNamespaceInfo(); nsInfo.State != enums.NAMESPACE_STATE_REGISTERED { //nolint:nosnakecase return nil, errors.New("Could not register namespace due to existing state: " + nsInfo.State.String()) } else if err := nsc.Update(ctx, &workflowservice.UpdateNamespaceRequest{ Namespace: o.RegisterNamespace.Namespace, diff --git a/integration/temporal/metrics.go b/integration/temporal/metrics.go index 2a1302b..cf28f4e 100644 --- a/integration/temporal/metrics.go +++ b/integration/temporal/metrics.go @@ -17,7 +17,6 @@ type metricsHandler struct { attr []attribute.KeyValue } -// scope, _ := tally.NewRootScope(opts, time.Second) func NewMetricsHandler(meter metric.Meter) client.MetricsHandler { return metricsHandler{meter: meter} } diff --git a/jwt/jwtkey.go b/jwt/jwtkey.go index 3f71f92..b55d872 100644 --- a/jwt/jwtkey.go +++ b/jwt/jwtkey.go @@ -5,7 +5,7 @@ import ( "crypto/rsa" "crypto/sha256" "encoding/hex" - "io/ioutil" + "os" "strings" "github.com/golang-jwt/jwt" @@ -38,7 +38,7 @@ func NewKeyFromFilenames(publicKeyPemFilename, privateKeyPemFilename string) (Ke // load private key if privateKeyPemFilename != "" { - if bytes, err := ioutil.ReadFile(privateKeyPemFilename); err != nil { + if bytes, err := os.ReadFile(privateKeyPemFilename); err != nil { return Key{}, errors.Wrap(err, "failed to read private key: "+privateKeyPemFilename) } else if key, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(strings.ReplaceAll(string(bytes), `\n`, "\n"))); err != nil { return Key{}, errors.Wrap(err, "failed to parse private key: "+privateKeyPemFilename) @@ -48,7 +48,7 @@ func NewKeyFromFilenames(publicKeyPemFilename, privateKeyPemFilename string) (Ke } // load public key - if v, err := ioutil.ReadFile(publicKeyPemFilename); err != nil { + if v, err := os.ReadFile(publicKeyPemFilename); err != nil { return Key{}, errors.Wrap(err, "failed to read public key: "+publicKeyPemFilename) } else if key, err := jwt.ParseRSAPublicKeyFromPEM([]byte(strings.ReplaceAll(string(v), `\n`, "\n"))); err != nil { return Key{}, errors.Wrap(err, "failed to parse public key: "+publicKeyPemFilename) diff --git a/log/fields_http.go b/log/fields_http.go index e939731..04e7923 100644 --- a/log/fields_http.go +++ b/log/fields_http.go @@ -40,11 +40,14 @@ const ( // HTTPFlavorKey represents the Kind of HTTP protocol used. HTTPFlavorKey = "http_flavor" - // HTTPRequestIDKey represents the HTTP request id if known (e.g from X-Request-ID). + // HTTPRequestIDKey represents the HTTP request id if known (e.g. from X-Request-ID). HTTPRequestIDKey = "http_request_id" - // HTTPSessionIDKey represents the HTTP session id if known (e.g from X-Session-ID). + // HTTPSessionIDKey represents the HTTP session id if known (e.g. from X-Session-ID). HTTPSessionIDKey = "http_session_id" + + // HTTPTrackingIDKey represents the HTTP tracking id if known (e.g. from X-Tracking-ID). + HTTPTrackingIDKey = "http_tracking_id" ) func FHTTPServerName(id string) zap.Field { @@ -59,6 +62,10 @@ func FHTTPSessionID(id string) zap.Field { return zap.String(HTTPSessionIDKey, id) } +func FHTTPTrackingID(id string) zap.Field { + return zap.String(HTTPTrackingIDKey, id) +} + func FHTTPRequestContentLength(bytes int64) zap.Field { return zap.Int64(HTTPRequestContentLengthKey, bytes) } diff --git a/log/with.go b/log/with.go index f3a6a9e..554726b 100644 --- a/log/with.go +++ b/log/with.go @@ -46,7 +46,7 @@ func WithHTTPRequest(l *zap.Logger, r *http.Request) *zap.Logger { FHTTPUserAgent(r.UserAgent()), FHTTPTarget(r.RequestURI), } - + // host if value := r.Header.Get("X-Forwarded-Host"); value != "" { fields = append(fields, FHTTPHost(value)) } else if !r.URL.IsAbs() { @@ -54,21 +54,31 @@ func WithHTTPRequest(l *zap.Logger, r *http.Request) *zap.Logger { } else { fields = append(fields, FHTTPHost(r.URL.Host)) } + // request id if id := r.Header.Get("X-Request-ID"); id != "" { fields = append(fields, FHTTPRequestID(id)) } else if id, ok := keelhttpcontext.GetRequestID(r.Context()); ok && id != "" { fields = append(fields, FHTTPRequestID(id)) } + // session id if id := r.Header.Get("X-Session-ID"); id != "" { fields = append(fields, FHTTPSessionID(id)) } else if id, ok := keelhttpcontext.GetSessionID(r.Context()); ok && id != "" { fields = append(fields, FHTTPSessionID(id)) } + // tracking id + if id := r.Header.Get("X-Tracking-ID"); id != "" { + fields = append(fields, FHTTPTrackingID(id)) + } else if id, ok := keelhttpcontext.GetTrackingID(r.Context()); ok && id != "" { + fields = append(fields, FHTTPTrackingID(id)) + } + // schema if r.TLS != nil { fields = append(fields, FHTTPScheme("https")) } else { fields = append(fields, FHTTPScheme("http")) } + // flavor if r.ProtoMajor == 1 { fields = append(fields, FHTTPFlavor(fmt.Sprintf("1.%d", r.ProtoMinor))) } else if r.ProtoMajor == 2 { @@ -106,25 +116,35 @@ func WithHTTPRequestOut(l *zap.Logger, r *http.Request) *zap.Logger { FHTTPMethod(r.Method), FHTTPTarget(r.URL.Path), } - + // host if r.URL.Host != "" { fields = append(fields, FHTTPHost(r.URL.Host)) } + // request id if id := r.Header.Get("X-Request-ID"); id != "" { fields = append(fields, FHTTPRequestID(id)) } else if id, ok := keelhttpcontext.GetRequestID(r.Context()); ok && id != "" { fields = append(fields, FHTTPRequestID(id)) } + // session id if id := r.Header.Get("X-Session-ID"); id != "" { fields = append(fields, FHTTPSessionID(id)) } else if id, ok := keelhttpcontext.GetSessionID(r.Context()); ok && id != "" { fields = append(fields, FHTTPSessionID(id)) } + // tracking id + if id := r.Header.Get("X-Tracking-ID"); id != "" { + fields = append(fields, FHTTPTrackingID(id)) + } else if id, ok := keelhttpcontext.GetTrackingID(r.Context()); ok && id != "" { + fields = append(fields, FHTTPTrackingID(id)) + } + // schema if r.TLS != nil { fields = append(fields, FHTTPScheme("https")) } else { fields = append(fields, FHTTPScheme("http")) } + // flavor if r.ProtoMajor == 1 { fields = append(fields, FHTTPFlavor(fmt.Sprintf("1.%d", r.ProtoMinor))) } else if r.ProtoMajor == 2 { diff --git a/net/gotsrpc/errors.go b/net/gotsrpc/errors.go index 882f97d..6105d64 100644 --- a/net/gotsrpc/errors.go +++ b/net/gotsrpc/errors.go @@ -5,9 +5,9 @@ type Error string // Common errors const ( - ErrorNotFound Error = "notFound" - ErrorForbidden Error = "forbidden" - ErrorPermissionDenied Error = "permissionDenied" + ErrorNotFound Error = "notFound" //nolint:errname + ErrorForbidden Error = "forbidden" //nolint:errname + ErrorPermissionDenied Error = "permissionDenied" //nolint:errname ) // NewError returns a pointer error @@ -19,7 +19,7 @@ func NewError(e Error) *Error { func (e *Error) Is(err error) bool { if e == nil || err == nil { return false - } else if v, ok := err.(*Error); ok && v != nil { + } else if v, ok := err.(*Error); ok && v != nil { //nolint:errorlint return e.Error() == v.Error() } return false diff --git a/net/http/context/sessionid.go b/net/http/context/sessionid.go index 40601b4..f713c63 100644 --- a/net/http/context/sessionid.go +++ b/net/http/context/sessionid.go @@ -14,6 +14,6 @@ func GetSessionID(ctx context.Context) (string, bool) { } } -func SetSessionID(ctx context.Context, requestID string) context.Context { - return context.WithValue(ctx, ContextKeySessionID, requestID) +func SetSessionID(ctx context.Context, sessionID string) context.Context { + return context.WithValue(ctx, ContextKeySessionID, sessionID) } diff --git a/net/http/header.go b/net/http/header.go index 6b6d769..1524c12 100644 --- a/net/http/header.go +++ b/net/http/header.go @@ -61,7 +61,7 @@ const ( HeaderXFrameOptions = "X-Frame-Options" HeaderContentSecurityPolicy = "Content-Security-Policy" HeaderContentSecurityPolicyReportOnly = "Content-Security-Policy-Report-Only" - HeaderXCSRFToken = "X-CSRF-Token" //nolint:gosec + HeaderXCSRFToken = "X-CSRF-Token" HeaderReferrerPolicy = "Referrer-Policy" // Telementry diff --git a/net/http/middleware/recover.go b/net/http/middleware/recover.go index 815f0c7..ae79b22 100644 --- a/net/http/middleware/recover.go +++ b/net/http/middleware/recover.go @@ -51,7 +51,7 @@ func RecoverWithOptions(opts RecoverOptions) Middleware { if e := recover(); e != nil { err, ok := e.(error) if !ok { - err = fmt.Errorf("%v", e) + err = fmt.Errorf("%v", e) //nolint:goerr113 } ll := log.WithError(l, err) if !opts.DisablePrintStack { diff --git a/net/http/roundtripware/dump.go b/net/http/roundtripware/dump.go index 7d7e010..8a11a56 100644 --- a/net/http/roundtripware/dump.go +++ b/net/http/roundtripware/dump.go @@ -44,20 +44,20 @@ func dumpRequest(req *http.Request) { if req.Header != nil && req.Header.Get("Content-Type") != "" { var body string if req.Body, body = readBodyPretty(req.Header.Get("Content-Type"), req.Body); body != "" { - fmt.Printf("Request %s:\n%s\n", req.URL, body) + fmt.Printf("Request %s:\n%s\n", req.URL, body) //nolint:forbidigo } } } func dumpResponse(req *http.Request, resp *http.Response) { if resp == nil { - fmt.Println("Response is nil") + fmt.Println("Response is nil") //nolint:forbidigo return } if resp.Header != nil && resp.Header.Get("Content-Type") != "" { var body string if resp.Body, body = readBodyPretty(resp.Header.Get("Content-Type"), resp.Body); body != "" { - fmt.Printf("Response %s:\n%s\n", req.URL, body) + fmt.Printf("Response %s:\n%s\n", req.URL, body) //nolint:forbidigo } } } diff --git a/net/http/roundtripware/helper.go b/net/http/roundtripware/helper.go index b1dbdec..99ff5f1 100644 --- a/net/http/roundtripware/helper.go +++ b/net/http/roundtripware/helper.go @@ -4,7 +4,6 @@ import ( "bytes" "encoding/json" "io" - "io/ioutil" "strings" "github.com/tinylib/msgp/msgp" @@ -41,5 +40,5 @@ func readBodyPretty(contentType string, original io.ReadCloser) (io.ReadCloser, } // return copy of the original - return ioutil.NopCloser(strings.NewReader(bs.String())), body + return io.NopCloser(strings.NewReader(bs.String())), body } diff --git a/net/http/roundtripware/metric.go b/net/http/roundtripware/metric.go index fb8948d..d446432 100644 --- a/net/http/roundtripware/metric.go +++ b/net/http/roundtripware/metric.go @@ -21,17 +21,17 @@ func Metric(meter metric.Meter, name, description string) RoundTripware { } return func(l *zap.Logger, next Handler) Handler { - return func(req *http.Request) (*http.Response, error) { - ctx, labeler := LabelerFromContext(req.Context()) + return func(r *http.Request) (*http.Response, error) { + ctx, labeler := LabelerFromContext(r.Context()) start := time.Now() - resp, err := next(req.WithContext(ctx)) + resp, err := next(r.WithContext(ctx)) duration := time.Since(start) if err != nil { return resp, err } - attributes := append(labeler.Get(), attribute.String("method", req.Method)) + attributes := append(labeler.Get(), attribute.String("method", r.Method)) if resp != nil { attributes = append(labeler.Get(), attribute.Int("status_code", resp.StatusCode)) diff --git a/net/http/roundtripware/recover.go b/net/http/roundtripware/recover.go index 766e3c4..fd697f0 100644 --- a/net/http/roundtripware/recover.go +++ b/net/http/roundtripware/recover.go @@ -44,12 +44,12 @@ func Recover(opts ...RecoverOption) RoundTripware { // RecoverWithOptions returns a RoundTripper which catches any panics func RecoverWithOptions(opts RecoverOptions) RoundTripware { return func(l *zap.Logger, next Handler) Handler { - return func(req *http.Request) (*http.Response, error) { + return func(r *http.Request) (*http.Response, error) { defer func() { if e := recover(); e != nil { err, ok := e.(error) if !ok { - err = fmt.Errorf("%v", e) + err = fmt.Errorf("%v", e) //nolint:goerr113 } ll := log.WithError(l, err) if !opts.DisablePrintStack { @@ -58,7 +58,7 @@ func RecoverWithOptions(opts RecoverOptions) RoundTripware { ll.Error("recovering from panic") } }() - return next(req) + return next(r) } } } diff --git a/net/http/roundtripware/roundtripware.go b/net/http/roundtripware/roundtripware.go index fbba955..56aa344 100644 --- a/net/http/roundtripware/roundtripware.go +++ b/net/http/roundtripware/roundtripware.go @@ -28,6 +28,6 @@ func NewRoundTripper(l *zap.Logger, parent http.RoundTripper, roundTripwares ... } } -func (rt *RoundTripper) RoundTrip(req *http.Request) (resp *http.Response, err error) { +func (rt *RoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { return rt.handler(req) } diff --git a/net/http/roundtripware/sessionid.go b/net/http/roundtripware/sessionid.go new file mode 100644 index 0000000..ae6abcd --- /dev/null +++ b/net/http/roundtripware/sessionid.go @@ -0,0 +1,51 @@ +package roundtripware + +import ( + "net/http" + + "go.uber.org/zap" + + keelhttpcontext "github.com/foomo/keel/net/http/context" +) + +type ( + SessionIDOptions struct { + Header string + } + SessionIDOption func(*SessionIDOptions) + SessionIDGenerator func() string +) + +// GetDefaultSessionIDOptions returns the default options +func GetDefaultSessionIDOptions() SessionIDOptions { + return SessionIDOptions{ + Header: "X-Session-ID", + } +} + +// SessionIDWithHeader middleware option +func SessionIDWithHeader(v string) SessionIDOption { + return func(o *SessionIDOptions) { + o.Header = v + } +} + +// SessionID returns a RoundTripper which prints out the request & response object +func SessionID(opts ...SessionIDOption) RoundTripware { + o := GetDefaultSessionIDOptions() + for _, opt := range opts { + if opt != nil { + opt(&o) + } + } + return func(l *zap.Logger, next Handler) Handler { + return func(r *http.Request) (*http.Response, error) { + if value := r.Header.Get(o.Header); value == "" { + if value, ok := keelhttpcontext.GetSessionID(r.Context()); ok && value != "" { + r.Header.Set(o.Header, value) + } + } + return next(r) + } + } +} diff --git a/net/http/roundtripware/trackingid.go b/net/http/roundtripware/trackingid.go new file mode 100644 index 0000000..48c1593 --- /dev/null +++ b/net/http/roundtripware/trackingid.go @@ -0,0 +1,51 @@ +package roundtripware + +import ( + "net/http" + + "go.uber.org/zap" + + keelhttpcontext "github.com/foomo/keel/net/http/context" +) + +type ( + TrackingIDOptions struct { + Header string + } + TrackingIDOption func(*TrackingIDOptions) + TrackingIDGenerator func() string +) + +// GetDefaultTrackingIDOptions returns the default options +func GetDefaultTrackingIDOptions() TrackingIDOptions { + return TrackingIDOptions{ + Header: "X-Tracking-ID", + } +} + +// TrackingIDWithHeader middleware option +func TrackingIDWithHeader(v string) TrackingIDOption { + return func(o *TrackingIDOptions) { + o.Header = v + } +} + +// TrackingID returns a RoundTripper which prints out the request & response object +func TrackingID(opts ...TrackingIDOption) RoundTripware { + o := GetDefaultTrackingIDOptions() + for _, opt := range opts { + if opt != nil { + opt(&o) + } + } + return func(l *zap.Logger, next Handler) Handler { + return func(r *http.Request) (*http.Response, error) { + if value := r.Header.Get(o.Header); value == "" { + if value, ok := keelhttpcontext.GetTrackingID(r.Context()); ok && value != "" { + r.Header.Set(o.Header, value) + } + } + return next(r) + } + } +} diff --git a/server_test.go b/server_test.go index f3d0c47..c998dbe 100644 --- a/server_test.go +++ b/server_test.go @@ -3,7 +3,7 @@ package keel_test import ( "bytes" "context" - "io/ioutil" + "io" "net/http" "strings" "syscall" @@ -209,7 +209,7 @@ func (s *KeelTestSuite) httpGet(url string) (int, string, error) { return 0, "", err } else if resp, err := http.DefaultClient.Do(req); err != nil { return 0, "", err - } else if body, err := ioutil.ReadAll(resp.Body); err != nil { + } else if body, err := io.ReadAll(resp.Body); err != nil { return 0, "", err } else if err := resp.Body.Close(); err != nil { return 0, "", err @@ -224,7 +224,7 @@ func (s *KeelTestSuite) httpPut(url, data string) (int, string, error) { return 0, "", err } else if resp, err := http.DefaultClient.Do(req); err != nil { return 0, "", err - } else if body, err := ioutil.ReadAll(resp.Body); err != nil { + } else if body, err := io.ReadAll(resp.Body); err != nil { return 0, "", err } else if err := resp.Body.Close(); err != nil { return 0, "", err diff --git a/servicehttp.go b/servicehttp.go index 0f2428b..e798576 100644 --- a/servicehttp.go +++ b/servicehttp.go @@ -6,6 +6,7 @@ import ( "net/http" "strings" + "github.com/pkg/errors" "go.uber.org/zap" "github.com/foomo/keel/log" @@ -64,7 +65,7 @@ func (s *ServiceHTTP) Start(ctx context.Context) error { s.running = false }) s.running = true - if err := s.server.ListenAndServe(); err != http.ErrServerClosed { + if err := s.server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) { log.WithError(s.l, err).Error("service error") return err } diff --git a/servicehttphealthz.go b/servicehttphealthz.go index 6d28722..1bdfe96 100644 --- a/servicehttphealthz.go +++ b/servicehttphealthz.go @@ -16,6 +16,14 @@ const ( DefaultServiceHTTPHealthzPath = "/healthz" ) +var ( + ErrUnhandledHealthzProbe = errors.New("unhandled healthz probe") + ErrProbeFailed = errors.New("probe failed") + ErrLivenessProbeFailed = errors.New("liveness probe failed") + ErrReadinessProbeFailed = errors.New("readiness probe failed") + ErrStartupProbeFailed = errors.New("startup probe failed") +) + func NewServiceHTTPHealthz(l *zap.Logger, name, addr, path string, probes map[HealthzType][]interface{}) *ServiceHTTP { handler := http.NewServeMux() @@ -41,7 +49,7 @@ func NewServiceHTTPHealthz(l *zap.Logger, name, addr, path string, probes map[He case ErrorPingerWithContext: return true, h.Ping(ctx) default: - return false, errors.New("unhandled healthz probe") + return false, ErrUnhandledHealthzProbe } } @@ -55,7 +63,7 @@ func NewServiceHTTPHealthz(l *zap.Logger, name, addr, path string, probes map[He unavailable(l, w, r, err) return } else if !ok { - unavailable(l, w, r, errors.New("probe failed")) + unavailable(l, w, r, ErrProbeFailed) return } } @@ -77,7 +85,7 @@ func NewServiceHTTPHealthz(l *zap.Logger, name, addr, path string, probes map[He unavailable(l, w, r, err) return } else if !ok { - unavailable(l, w, r, errors.New("liveness probe failed")) + unavailable(l, w, r, ErrLivenessProbeFailed) return } } @@ -98,7 +106,7 @@ func NewServiceHTTPHealthz(l *zap.Logger, name, addr, path string, probes map[He unavailable(l, w, r, err) return } else if !ok { - unavailable(l, w, r, errors.New("readiness probe failed")) + unavailable(l, w, r, ErrReadinessProbeFailed) return } } @@ -119,7 +127,7 @@ func NewServiceHTTPHealthz(l *zap.Logger, name, addr, path string, probes map[He unavailable(l, w, r, err) return } else if !ok { - unavailable(l, w, r, errors.New("startup probe failed")) + unavailable(l, w, r, ErrStartupProbeFailed) return } } diff --git a/test/client.go b/test/client.go index 4c109d3..a3b66a1 100644 --- a/test/client.go +++ b/test/client.go @@ -4,7 +4,7 @@ import ( "bytes" "context" "encoding/json" - "io/ioutil" + "io" "net/http" "net/http/cookiejar" ) @@ -75,7 +75,7 @@ func (c *HTTPClient) Post(ctx context.Context, path string, data interface{}) ([ func (c *HTTPClient) readBody(resp *http.Response) ([]byte, error) { defer resp.Body.Close() - if body, err := ioutil.ReadAll(resp.Body); err != nil { + if body, err := io.ReadAll(resp.Body); err != nil { return nil, err } else { return body, nil diff --git a/test/log.go b/test/log.go index 928a942..82d2b5f 100644 --- a/test/log.go +++ b/test/log.go @@ -89,9 +89,9 @@ func newTestingWriter(t zaptest.TestingT) *testingWriter { return &testingWriter{t: t} } -func (w *testingWriter) Write(p []byte) (n int, err error) { +func (w *testingWriter) Write(p []byte) (int, error) { if w.t == nil { - return fmt.Printf("%s", p) + return fmt.Printf("%s", p) //nolint:forbidigo } else { // Note: t.Log is safe for concurrent use. // Strip trailing newline because t.Log always adds one.