diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index c6c8c2bd..69b30417 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -14,82 +14,86 @@ // The optional 'workspaceFolder' property is the path VS Code should open by default when // connected. This is typically a file mount in .devcontainer/docker-compose.yml "workspaceFolder": "/app", - // All containers should stop if we close / reload the VSCode window. + // All containers should stop if we close / reload the VSCode window. "shutdownAction": "stopCompose", - // Set *default* container specific settings.json values on container create. - "settings": { - // https://github.com/golang/tools/blob/master/gopls/doc/vscode.md#vscode - "go.useLanguageServer": true, - "[go]": { - "editor.formatOnSave": true, - "editor.codeActionsOnSave": { - "source.organizeImports": true, + "customizations": { + "vscode": { + // Set *default* container specific settings.json values on container create. + "settings": { + // https://github.com/golang/tools/blob/master/gopls/doc/vscode.md#vscode + "go.useLanguageServer": true, + "[go]": { + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.organizeImports": true, + }, + // Optional: Disable snippets, as they conflict with completion ranking. + "editor.snippetSuggestions": "none", + }, + "[go.mod]": { + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.organizeImports": true, + }, + }, + "[sql]": { + "editor.formatOnSave": true + }, + // There are handly utility scripts within /scripts that we invoke via go run. + // These scripts (and its dependencies) should never be consumed by the actual server directly + // Thus they are flagged to require the "scripts" build tag. + // We only inform gopls and the vscode go compiler here, that it has to set this build tag if it sees such a file. + "go.buildTags": "scripts", + "gopls": { + // Add parameter placeholders when completing a function. + "usePlaceholders": true, + // If true, enable additional analyses with staticcheck. + // Warning: This will significantly increase memory usage. + // DISABLED, done via + "staticcheck": false, + }, + // https://code.visualstudio.com/docs/languages/go#_intellisense + "go.autocompleteUnimportedPackages": true, + // https://github.com/golangci/golangci-lint#editor-integration + "go.lintTool": "golangci-lint", + "go.lintFlags": [ + "--fast", + "--timeout", + "5m" + ], + // disable test caching, race and show coverage (in sync with makefile) + "go.testFlags": [ + "-cover", + "-race", + "-count=1", + "-v" + ], + "go.coverMode": "atomic", // atomic is required when utilizing -race + "go.delveConfig": { + "dlvLoadConfig": { + // increase max length of strings displayed in debugger + "maxStringLen": 2048, + }, + "apiVersion": 2, + }, + // ensure that the pgFormatter VSCode extension uses the pgFormatter that comes preinstalled in the Dockerfile + "pgFormatter.pgFormatterPath": "/usr/local/bin/pg_format" }, - // Optional: Disable snippets, as they conflict with completion ranking. - "editor.snippetSuggestions": "none", - }, - "[go.mod]": { - "editor.formatOnSave": true, - "editor.codeActionsOnSave": { - "source.organizeImports": true, - }, - }, - "[sql]": { - "editor.formatOnSave": true - }, - // There are handly utility scripts within /scripts that we invoke via go run. - // These scripts (and its dependencies) should never be consumed by the actual server directly - // Thus they are flagged to require the "scripts" build tag. - // We only inform gopls and the vscode go compiler here, that it has to set this build tag if it sees such a file. - "go.buildTags": "scripts", - "gopls": { - // Add parameter placeholders when completing a function. - "usePlaceholders": true, - // If true, enable additional analyses with staticcheck. - // Warning: This will significantly increase memory usage. - // DISABLED, done via - "staticcheck": false, - }, - // https://code.visualstudio.com/docs/languages/go#_intellisense - "go.autocompleteUnimportedPackages": true, - // https://github.com/golangci/golangci-lint#editor-integration - "go.lintTool": "golangci-lint", - "go.lintFlags": [ - "--fast", - "--timeout", - "5m" - ], - // disable test caching, race and show coverage (in sync with makefile) - "go.testFlags": [ - "-cover", - "-race", - "-count=1", - "-v" - ], - "go.coverMode": "atomic", // atomic is required when utilizing -race - "go.delveConfig": { - "dlvLoadConfig": { - // increase max length of strings displayed in debugger - "maxStringLen": 2048, - }, - "apiVersion": 2, - }, - // ensure that the pgFormatter VSCode extension uses the pgFormatter that comes preinstalled in the Dockerfile - "pgFormatter.pgFormatterPath": "/usr/local/bin/pg_format" + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + // required: + "golang.go", + "bradymholt.pgformatter", + // optional: + "42crunch.vscode-openapi", + "heaths.vscode-guid", + "bungcip.better-toml", + "eamodio.gitlens", + "casualjim.gotemplate", + "yzhang.markdown-all-in-one" + ] + } }, - // Add the IDs of extensions you want installed when the container is created. - "extensions": [ - // required: - "golang.go", - "bradymholt.pgformatter", - // optional: - "42crunch.vscode-openapi", - "heaths.vscode-guid", - "bungcip.better-toml", - "eamodio.gitlens", - "casualjim.gotemplate", - "yzhang.markdown-all-in-one" - ], // Uncomment the next line if you want start specific services in your Docker Compose config. // "runServices": [], // Uncomment the next line if you want to keep your containers running after VS Code shuts down. diff --git a/CHANGELOG.md b/CHANGELOG.md index 77797545..833fc49a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,18 @@ - Please follow the update process in *[I just want to update / upgrade my project!](https://github.com/allaboutapps/go-starter/wiki/FAQ#i-just-want-to-update--upgrade-my-project)*. ## Unreleased +- Switch [from Go 1.19.3 to Go 1.20.3](https://go.dev/doc/devel/release#go1.20) (requires `./docker-helper.sh --rebuild`). +- Add new log configuration: + - optional `output` param of `LoggerWithConfig` to redirect the log output + - optional caller info switched on with `SERVER_LOGGER_LOG_CALLER` +- Minor: rename unused function parameters to fix linter errors +- Minor: update devcontainer.json syntax to remove deprecation warning +- Minor: add `GetFieldsImplementing` to utils and use it to easier add new fixture fields. +- `go.mod` changes: + - Minor: [Bump github.com/golangci/golangci-lint from 1.50.1 to 1.52.2](https://github.com/golangci/golangci-lint/releases/tag/v1.52.2) + - Minor: [Bump golang.org/x/net from 0.2.0 to 0.7.0](https://cs.opensource.google/go/x/net) (Fixing CVE-2022-41723) + +## 2023-03-03 - Switch [from Go 1.17.9 to Go 1.19.3](https://go.dev/doc/devel/release#go1.19) (requires `./docker-helper.sh --rebuild`). - Major: Update base docker image from debian buster to bullseye - Minor: [Bump github.com/darold/pgFormatter from 5.2 to 5.3](https://github.com/darold/pgFormatter/releases/tag/v5.3) @@ -23,7 +35,6 @@ - Note that `/app/api/tmp`, `/app/tmp` and `/app/bin` are now baked by proper docker volumes when using our `docker-compose.yml`/`./docker-helper.sh --up`. You **cannot** remove these directories directly inside the container (but its contents) and you can also no longer see its files on your host machine directly! - Fix `make check-gen-dirs` false positives hidden files. - Allow to trace/benchmark `Makefile` targets execution by using a custom shell wrapper for make execution. See `SHELL` and `.SHELLFLAGS` within `Makefile` and the custom `rksh` script in the root working directory. Usage: `MAKE_TRACE_TIME=true make ` -- Minor: add `GetFieldsImplementing` to utils and use it to easier add new fixture fields. - `go.mod` changes: - Minor: [Bump github.com/BurntSushi/toml from 1.1.0 to 1.2.1](https://github.com/BurntSushi/toml/releases/tag/v1.2.1) - Minor: [Bump github.com/gabriel-vasile/mimetype from 1.4.0 to 1.4.1](https://github.com/gabriel-vasile/mimetype/releases/tag/v1.4.1) @@ -47,7 +58,6 @@ - Minor: [Bump golang.org/x/crypto from v0.0.0-20220411220226-7b82a4e95df4 to 0.3.0](https://cs.opensource.google/go/x/crypto) - Minor: [Bump golang.org/x/sys from v0.0.0-20220412211240-33da011f77ad to 0.2.0](https://cs.opensource.google/go/x/sys) - Minor: [Bump golang.org/x/text from 0.3.7 to 0.4.0](https://cs.opensource.google/go/x/text) (Fixing CVE-2022-32149) - - Minor: [Bump golang.org/x/net from 0.2.0 to 0.7.0](https://cs.opensource.google/go/x/net) (Fixing CVE-2022-41723) - Minor: [Bump google.golang.org/api from 0.74.0 to 0.103.0](https://github.com/googleapis/google-api-go-client/compare/v0.80.0...v0.103.0) ## 2022-09-13 diff --git a/Dockerfile b/Dockerfile index df415230..be57a287 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ # --- https://hub.docker.com/_/golang # --- https://github.com/microsoft/vscode-remote-try-go/blob/master/.devcontainer/Dockerfile ### ----------------------- -FROM golang:1.19.3-bullseye AS development +FROM golang:1.20.3-bullseye AS development # Avoid warnings by switching to noninteractive ENV DEBIAN_FRONTEND=noninteractive @@ -28,11 +28,11 @@ RUN apt-get update \ # Mandadory minimal linux packages # Installed at development stage and app stage # Do not forget to add mandadory linux packages to the final app Dockerfile stage below! - # + # # -- START MANDADORY -- ca-certificates \ # --- END MANDADORY --- - # + # # Development specific packages # Only installed at development stage and NOT available in the final Docker stage # based upon @@ -60,7 +60,7 @@ RUN apt-get update \ tmux \ rsync \ # --- END DEVELOPMENT --- - # + # && apt-get clean \ && rm -rf /var/lib/apt/lists/* @@ -84,7 +84,7 @@ RUN mkdir -p /tmp/pgFormatter \ && cd pgFormatter-5.3 \ && perl Makefile.PL \ && make && make install \ - && rm -rf /tmp/pgFormatter + && rm -rf /tmp/pgFormatter # go gotestsum: (this package should NOT be installed via go get) # https://github.com/gotestyourself/gotestsum/releases @@ -94,22 +94,22 @@ RUN mkdir -p /tmp/gotestsum \ && wget "https://github.com/gotestyourself/gotestsum/releases/download/v1.9.0/gotestsum_1.9.0_linux_${ARCH}.tar.gz" \ && tar xzf "gotestsum_1.9.0_linux_${ARCH}.tar.gz" \ && cp gotestsum /usr/local/bin/gotestsum \ - && rm -rf /tmp/gotestsum + && rm -rf /tmp/gotestsum # go linting: (this package should NOT be installed via go get) # https://github.com/golangci/golangci-lint#binary # https://github.com/golangci/golangci-lint/releases RUN curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh \ - | sh -s -- -b $(go env GOPATH)/bin v1.50.1 + | sh -s -- -b $(go env GOPATH)/bin v1.52.2 -# go swagger: (this package should NOT be installed via go get) +# go swagger: (this package should NOT be installed via go get) # https://github.com/go-swagger/go-swagger/releases RUN ARCH="$(arch | sed s/aarch64/arm64/ | sed s/x86_64/amd64/)" \ && curl -o /usr/local/bin/swagger -L'#' \ "https://github.com/go-swagger/go-swagger/releases/download/v0.29.0/swagger_linux_${ARCH}" \ && chmod +x /usr/local/bin/swagger -# lichen: go license util +# lichen: go license util # TODO: Install from static binary as soon as it becomes available. # https://github.com/uw-labs/lichen/tags RUN go install github.com/uw-labs/lichen@v0.1.7 @@ -136,7 +136,7 @@ RUN mkdir -p /tmp/yq \ && wget "https://github.com/mikefarah/yq/releases/download/v4.30.5/yq_linux_${ARCH}.tar.gz" \ && tar xzf "yq_linux_${ARCH}.tar.gz" \ && cp "yq_linux_${ARCH}" /usr/local/bin/yq \ - && rm -rf /tmp/yq + && rm -rf /tmp/yq # gsdev # The sole purpose of the "gsdev" cli util is to provide a handy short command for the following (all args are passed): @@ -145,7 +145,7 @@ RUN printf '#!/bin/bash\nset -Eeo pipefail\ncd /app && go run -tags scripts ./sc # linux permissions / vscode support: Add user to avoid linux file permission issues # Detail: Inside the container, any mounted files/folders will have the exact same permissions -# as outside the container - including the owner user ID (UID) and group ID (GID). +# as outside the container - including the owner user ID (UID) and group ID (GID). # Because of this, your container user will either need to have the same UID or be in a group with the same GID. # The actual name of the user / group does not matter. The first user on a machine typically gets a UID of 1000, # so most containers use this as the ID of the user to try to avoid this problem. @@ -170,11 +170,11 @@ RUN mkdir -p /home/$USERNAME/.vscode-server/extensions \ /home/$USERNAME/.vscode-server-insiders # linux permissions / vscode support: chown $GOPATH so $USERNAME can directly work with it -# Note that this should be the final step after installing all build deps +# Note that this should be the final step after installing all build deps RUN mkdir -p /$GOPATH/pkg && chown -R $USERNAME /$GOPATH # $GOBIN is where our own compiled binaries will live and other go.mod / VSCode binaries will be installed. -# It should always come AFTER our other $PATH segments and should be earliest targeted in stage "builder", +# It should always come AFTER our other $PATH segments and should be earliest targeted in stage "builder", # as /app/bin will the shadowed by a volume mount via docker-compose! # E.g. "which golangci-lint" should report "/go/bin" not "/app/bin" (where VSCode will place it). # https://github.com/go-modules-by-example/index/blob/master/010_tools/README.md#walk-through diff --git a/cmd/db_migrate.go b/cmd/db_migrate.go index 2f7066ed..1041dbbf 100644 --- a/cmd/db_migrate.go +++ b/cmd/db_migrate.go @@ -24,7 +24,7 @@ func init() { migrate.SetTable(config.DatabaseMigrationTable) } -func migrateCmdFunc(cmd *cobra.Command, args []string) { +func migrateCmdFunc(_ *cobra.Command, _ []string) { n, err := applyMigrations() if err != nil { fmt.Printf("Error while applying migrations: %v\n", err) diff --git a/cmd/db_seed.go b/cmd/db_seed.go index 73b891b9..e213e88c 100644 --- a/cmd/db_seed.go +++ b/cmd/db_seed.go @@ -25,7 +25,7 @@ func init() { dbCmd.AddCommand(seedCmd) } -func seedCmdFunc(cmd *cobra.Command, args []string) { +func seedCmdFunc(_ *cobra.Command, _ []string) { if err := applyFixtures(); err != nil { fmt.Printf("Error while seeding fixtures: %v", err) os.Exit(1) diff --git a/go.mod b/go.mod index b31b5546..c1cb7ace 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module allaboutapps.dev/aw/go-starter -go 1.19 +go 1.20 require ( github.com/BurntSushi/toml v1.2.1 diff --git a/internal/api/handlers/common/get_version.go b/internal/api/handlers/common/get_version.go index 9df50c02..9a9b31f7 100644 --- a/internal/api/handlers/common/get_version.go +++ b/internal/api/handlers/common/get_version.go @@ -13,7 +13,7 @@ func GetVersionRoute(s *api.Server) *echo.Route { } // Returns the version and build date baked into the binary. -func getVersionHandler(s *api.Server) echo.HandlerFunc { +func getVersionHandler(_ *api.Server) echo.HandlerFunc { return func(c echo.Context) error { return c.String(http.StatusOK, config.GetFormattedBuildArgs()) } diff --git a/internal/api/middleware/logger.go b/internal/api/middleware/logger.go index 6b5c5390..7fb095d9 100644 --- a/internal/api/middleware/logger.go +++ b/internal/api/middleware/logger.go @@ -43,7 +43,7 @@ type ResponseBodyLogSkipper func(req *http.Request, res *echo.Response) bool // DefaultResponseBodyLogSkipper returns false for all responses with Content-Type // application/json, preventing logging for all other types of payloads as those // might contain binary or URL-encoded data unfit for logging purposes. -func DefaultResponseBodyLogSkipper(req *http.Request, res *echo.Response) bool { +func DefaultResponseBodyLogSkipper(_ *http.Request, res *echo.Response) bool { contentType := res.Header().Get(echo.HeaderContentType) switch { case strings.HasPrefix(contentType, echo.MIMEApplicationJSON): @@ -127,6 +127,7 @@ type LoggerConfig struct { LogRequestBody bool LogRequestHeader bool LogRequestQuery bool + LogCaller bool RequestBodyLogSkipper RequestBodyLogSkipper RequestBodyLogReplacer BodyLogReplacer RequestHeaderLogReplacer HeaderLogReplacer @@ -138,11 +139,14 @@ type LoggerConfig struct { ResponseHeaderLogReplacer HeaderLogReplacer } +// Logger with default logger output and configuration func Logger() echo.MiddlewareFunc { - return LoggerWithConfig(DefaultLoggerConfig) + return LoggerWithConfig(DefaultLoggerConfig, nil) } -func LoggerWithConfig(config LoggerConfig) echo.MiddlewareFunc { +// LoggerWithConfig returns a new MiddlewareFunc which creates a logger with the desired configuration. +// If output is set to nil, the default output is used. If more output params are provided, the first is being used. +func LoggerWithConfig(config LoggerConfig, output ...io.Writer) echo.MiddlewareFunc { if config.Skipper == nil { config.Skipper = DefaultLoggerConfig.Skipper } @@ -195,6 +199,16 @@ func LoggerWithConfig(config LoggerConfig) echo.MiddlewareFunc { Str("url", req.URL.String()). Str("bytes_in", in), ).Logger() + + if len(output) > 0 { + l = l.Output(output[0]) + } + + if config.LogCaller { + // Caller uses https://pkg.go.dev/runtime#Caller underneath and might decrease the performance. + l = l.With().Caller().Logger() + } + le := l.WithLevel(config.Level) req = req.WithContext(l.WithContext(context.WithValue(req.Context(), util.CTXKeyRequestID, id))) diff --git a/internal/api/middleware/logger_test.go b/internal/api/middleware/logger_test.go new file mode 100644 index 00000000..b4a9fdc2 --- /dev/null +++ b/internal/api/middleware/logger_test.go @@ -0,0 +1,57 @@ +package middleware_test + +import ( + "net/http" + "net/http/httptest" + "testing" + + "allaboutapps.dev/aw/go-starter/internal/api/middleware" + "allaboutapps.dev/aw/go-starter/internal/util" + "github.com/labstack/echo/v4" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func logTestHandler(c echo.Context) error { + log := util.LogFromEchoContext(c) + log.Info().Msg("I'm here!") + return nil +} + +func TestLogWithCaller(t *testing.T) { + cfg := middleware.DefaultLoggerConfig + cfg.LogCaller = false + + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/", nil) + loggerMW := middleware.LoggerWithConfig(cfg, rec) + + e := echo.New() + c := e.NewContext(req, rec) + + middlewareChain := loggerMW(logTestHandler) + require.NoError(t, middlewareChain(c)) + + bufSize := 2000 + loggedData := make([]byte, bufSize) + n, err := rec.Body.Read(loggedData) + require.NoError(t, err) + assert.Less(t, n, bufSize) + // LogCaller was set to false + assert.NotContains(t, string(loggedData), "caller") + + // now log again with LogCaller set to true + cfg.LogCaller = true + loggerMW = middleware.LoggerWithConfig(cfg, rec) + + rec.Flush() + + middlewareChain = loggerMW(logTestHandler) + require.NoError(t, middlewareChain(c)) + + n, err = rec.Body.Read(loggedData) + require.NoError(t, err) + assert.Less(t, n, bufSize) + // logger_test.go:X should match the line where log.Info() is placed in the logTestHandler + assert.Contains(t, string(loggedData[:n]), `"caller":"/app/internal/api/middleware/logger_test.go:17"`) +} diff --git a/internal/api/router/router.go b/internal/api/router/router.go index 695fb612..3dfcf569 100644 --- a/internal/api/router/router.go +++ b/internal/api/router/router.go @@ -72,6 +72,7 @@ func Init(s *api.Server) { LogRequestQuery: s.Config.Logger.LogRequestQuery, LogResponseBody: s.Config.Logger.LogResponseBody, LogResponseHeader: s.Config.Logger.LogResponseHeader, + LogCaller: s.Config.Logger.LogCaller, RequestBodyLogSkipper: func(req *http.Request) bool { // We skip all body logging for auth endpoints as these might contain credentials if strings.HasPrefix(req.URL.Path, "/api/v1/auth") { diff --git a/internal/config/server_config.go b/internal/config/server_config.go index 4b43f12b..de966c2b 100644 --- a/internal/config/server_config.go +++ b/internal/config/server_config.go @@ -82,6 +82,7 @@ type LoggerServer struct { LogRequestQuery bool LogResponseBody bool LogResponseHeader bool + LogCaller bool PrettyPrintConsole bool } @@ -219,6 +220,7 @@ func DefaultServiceConfigFromEnv() Server { LogRequestQuery: util.GetEnvAsBool("SERVER_LOGGER_LOG_REQUEST_QUERY", false), LogResponseBody: util.GetEnvAsBool("SERVER_LOGGER_LOG_RESPONSE_BODY", false), LogResponseHeader: util.GetEnvAsBool("SERVER_LOGGER_LOG_RESPONSE_HEADER", false), + LogCaller: util.GetEnvAsBool("SERVER_LOGGER_LOG_CALLER", false), PrettyPrintConsole: util.GetEnvAsBool("SERVER_LOGGER_PRETTY_PRINT_CONSOLE", false), }, Push: PushService{ diff --git a/internal/test/test_database.go b/internal/test/test_database.go index 9366ac27..d9971bc2 100644 --- a/internal/test/test_database.go +++ b/internal/test/test_database.go @@ -344,11 +344,11 @@ func ApplyDump(ctx context.Context, t *testing.T, db *sql.DB, dumpFile string) e // we need to get the db name before being able to do anything. var targetDB string - if err := db.QueryRow("SELECT current_database();").Scan(&targetDB); err != nil { + if err := db.QueryRowContext(ctx, "SELECT current_database();").Scan(&targetDB); err != nil { return err } - cmd := exec.Command("bash", "-c", fmt.Sprintf("cat %q | psql %q", dumpFile, targetDB)) //nolint:gosec + cmd := exec.CommandContext(ctx, "bash", "-c", fmt.Sprintf("cat %q | psql %q", dumpFile, targetDB)) //nolint:gosec combinedOutput, err := cmd.CombinedOutput() if err != nil { diff --git a/internal/test/test_server_test.go b/internal/test/test_server_test.go index 8f12107b..280a1412 100644 --- a/internal/test/test_server_test.go +++ b/internal/test/test_server_test.go @@ -27,7 +27,7 @@ type TestResponsePayload struct { } // Validate validates this request payload -func (m *TestRequestPayload) Validate(formats strfmt.Registry) error { +func (m *TestRequestPayload) Validate(_ strfmt.Registry) error { return nil } @@ -50,7 +50,7 @@ func (m *TestRequestPayload) UnmarshalBinary(b []byte) error { } // Validate validates this response payload -func (m *TestResponsePayload) Validate(formats strfmt.Registry) error { +func (m *TestResponsePayload) Validate(_ strfmt.Registry) error { return nil }