From 99e6ad1096438f268af2ccb4a38de678bd10e35f Mon Sep 17 00:00:00 2001 From: Piotr Icikowski Date: Thu, 7 Apr 2022 23:59:08 +0200 Subject: [PATCH] Major code refactoring - Updated Go version to 1.18 - Renamed package `logging` to `logs` - Changed log level resolver - Changed log writer selection logic - Replaced general log instance fetcher with component-specific one - Changed log preparation logic across all packages - Changed environment variables getters to use generics - Changed `interface{}` notation wit `any` - Changed pointer-related tools to use generics - Changed `CurrentConfiguration` object creation logic - Removed unused constants - Rephrased some log messages - Updated project dependencies - Added missing EOL at the end of the `Dockerfile` - Updated `Makefile` --- application/Dockerfile | 2 +- application/Makefile | 2 +- application/common/constants.go | 21 ++-- application/common/env.go | 48 ++------- application/common/env_test.go | 138 ------------------------- application/common/utils.go | 21 ++++ application/common/utils_test.go | 144 +++++++++++++++++++++++++++ application/config/globals.go | 4 +- application/config/types.go | 10 +- application/config/types_test.go | 32 +++--- application/go.mod | 4 +- application/go.sum | 4 +- application/health/endpoints.go | 4 +- application/logger/logger.go | 39 -------- application/logs/log.go | 38 +++++++ application/main.go | 33 +++--- application/service/builder.go | 17 ++-- application/service/builder_test.go | 26 ++--- application/service/encoders.go | 4 +- application/service/encoders_test.go | 4 +- application/service/handlers.go | 2 +- application/utils/pointers.go | 15 +-- application/utils/pointers_test.go | 24 +++-- 23 files changed, 309 insertions(+), 327 deletions(-) delete mode 100644 application/common/env_test.go create mode 100644 application/common/utils.go create mode 100644 application/common/utils_test.go delete mode 100644 application/logger/logger.go create mode 100644 application/logs/log.go diff --git a/application/Dockerfile b/application/Dockerfile index ea3a9ad..ee53088 100644 --- a/application/Dockerfile +++ b/application/Dockerfile @@ -21,4 +21,4 @@ LABEL org.opencontainers.image.licenses "GPL-3.0-or-later" WORKDIR / COPY --from=builder /app/gpts . USER 65532:65532 -ENTRYPOINT ["/gpts"] \ No newline at end of file +ENTRYPOINT ["/gpts"] diff --git a/application/Makefile b/application/Makefile index a8b865d..d69b9bf 100644 --- a/application/Makefile +++ b/application/Makefile @@ -1,4 +1,4 @@ -.PHONY: clean build test coverage image +.PHONY: clean build build-static test coverage image .SILENT: ${.PHONY} GO_TEST := go test ./... -race -p 1 diff --git a/application/common/constants.go b/application/common/constants.go index a9cf35a..0ea5d28 100644 --- a/application/common/constants.go +++ b/application/common/constants.go @@ -3,44 +3,41 @@ package common // MIME types const ( // ContentTypeJSON represents MIME media type for JSON - ContentTypeJSON = "application/json" + ContentTypeJSON string = "application/json" // ContentTypeYAML represents MIME media type for YAML - ContentTypeYAML = "text/yaml" + ContentTypeYAML string = "text/yaml" ) // Headers const ( // HeaderContentType represents "Content-Type" header key - HeaderContentType = "Content-Type" + HeaderContentType string = "Content-Type" ) // Messages const ( // MsgContentTypeNotAllowed represent a human-readable message for wrong media type error - MsgContentTypeNotAllowed = "wrong media type (accepting application/json or text/yaml)" + MsgContentTypeNotAllowed string = "wrong media type (accepting application/json or text/yaml)" ) // Build variables default value const ( // BuildValueUnknown represents an "unknown" word - BuildValueUnknown = "unknown" + BuildValueUnknown string = "unknown" ) // Component names const ( - // ComponentField represents a name of component field - ComponentField = "component" - // ComponentConfig represents a name of configuration manager component - ComponentConfig = "configuration" + ComponentConfig string = "configuration" // ComponentHealth represents a name of health component - ComponentHealth = "health" + ComponentHealth string = "health" // ComponentService represents a name of service component - ComponentService = "service" + ComponentService string = "service" // ComponentCLI represents a name of CLI component - ComponentCLI = "cli" + ComponentCLI string = "cli" ) diff --git a/application/common/env.go b/application/common/env.go index 573d594..bc40492 100644 --- a/application/common/env.go +++ b/application/common/env.go @@ -1,57 +1,23 @@ package common -import ( - "os" - "strconv" - "strings" -) - -func getStringFromEnvironment(variableName string, fallback string) string { - value, exists := os.LookupEnv(variableName) - if !exists { - return fallback - } - return value -} - -func getBooleanFromEnvironment(variableName string, fallback bool) bool { - value, exists := os.LookupEnv(variableName) - if !exists { - return fallback - } - value = strings.ToLower(value) - return (value == "true" || value == "1" || value == "yes") -} - -func getIntegerFromEnvironment(variableName string, fallback int) int { - value, exists := os.LookupEnv(variableName) - if !exists { - return fallback - } - - if parsedValue, err := strconv.Atoi(value); err != nil { - return fallback - } else { - return parsedValue - } -} +import "strconv" var ( // ServicePort determines the port number on which service will be running (defaults to 80) - ServicePort = getIntegerFromEnvironment("GPTS_SERVICE_PORT", 80) + ServicePort int = getFromEnvironment("GPTS_SERVICE_PORT", 80, strconv.Atoi) // HealthchecksPort determines the port number on which liveness & readiness endpoints will be running (defaults to 8081) - HealthchecksPort = getIntegerFromEnvironment("GPTS_HEALTHCHECKS_PORT", 8081) + HealthchecksPort int = getFromEnvironment("GPTS_HEALTHCHECKS_PORT", 8081, strconv.Atoi) // ConfigurationEndpoint determines the path of the configuration endpoint (defaults to /config) - ConfigurationEndpoint = getStringFromEnvironment("GPTS_CONFIG_ENDPOINT", "/config") + ConfigurationEndpoint string = getFromEnvironment("GPTS_CONFIG_ENDPOINT", "/config", stringPassthrough) // DefaultConfigOnStartup determines if default config should be loaded when application starts (defaults to false) - DefaultConfigOnStartup = getBooleanFromEnvironment("GPTS_DEFAULT_CONFIG_ON_STARTUP", false) + DefaultConfigOnStartup bool = getFromEnvironment("GPTS_DEFAULT_CONFIG_ON_STARTUP", false, strconv.ParseBool) // PrettyLog determines if pretty logging should be enabled (defaults to false) - PrettyLog = getBooleanFromEnvironment("GPTS_PRETTY_LOG", false) + PrettyLog bool = getFromEnvironment("GPTS_PRETTY_LOG", false, strconv.ParseBool) // LogLevel determines the level of application log (defaults to "info") - LogLevel = getStringFromEnvironment("GPTS_LOG_LEVEL", "info") + LogLevel string = getFromEnvironment("GPTS_LOG_LEVEL", "info", stringPassthrough) ) diff --git a/application/common/env_test.go b/application/common/env_test.go deleted file mode 100644 index f600d5d..0000000 --- a/application/common/env_test.go +++ /dev/null @@ -1,138 +0,0 @@ -package common - -import ( - "os" - "testing" - - "github.com/stretchr/testify/require" - "icikowski.pl/gpts/utils" -) - -func TestGetStringFromEnvironment(t *testing.T) { - tests := map[string]struct { - expectedVariableName string - valueToSet *string - expectedVariableDefaultValue string - expectedVariableValue string - }{ - "variable not set": { - valueToSet: nil, - expectedVariableDefaultValue: "ABC", - expectedVariableValue: "ABC", - }, - "variable set with value same as default": { - valueToSet: utils.StringToPointer("ABC"), - expectedVariableDefaultValue: "ABC", - expectedVariableValue: "ABC", - }, - "variable set with value different than default": { - valueToSet: utils.StringToPointer("XYZ"), - expectedVariableDefaultValue: "ABC", - expectedVariableValue: "XYZ", - }, - } - - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - _ = os.Unsetenv("TEST") - if tc.valueToSet != nil { - os.Setenv("TEST", string(*tc.valueToSet)) - } - - actualVariable := getStringFromEnvironment("TEST", tc.expectedVariableDefaultValue) - - require.Equal(t, tc.expectedVariableValue, actualVariable, "got value different than expected") - }) - } -} - -func TestGetBooleanFromEnvironment(t *testing.T) { - tests := map[string]struct { - expectedVariableName string - valueToSet *string - expectedVariableDefaultValue bool - expectedVariableValue bool - }{ - "variable not set": { - valueToSet: nil, - expectedVariableDefaultValue: true, - expectedVariableValue: true, - }, - "variable set with value same as default": { - valueToSet: utils.StringToPointer("true"), - expectedVariableDefaultValue: true, - expectedVariableValue: true, - }, - "variable set with value different than default (case 1)": { - valueToSet: utils.StringToPointer("true"), - expectedVariableDefaultValue: false, - expectedVariableValue: true, - }, - "variable set with value different than default (case 2)": { - valueToSet: utils.StringToPointer("yes"), - expectedVariableDefaultValue: false, - expectedVariableValue: true, - }, - "variable set with value different than default (case 3)": { - valueToSet: utils.StringToPointer("1"), - expectedVariableDefaultValue: false, - expectedVariableValue: true, - }, - } - - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - _ = os.Unsetenv("TEST") - if tc.valueToSet != nil { - os.Setenv("TEST", string(*tc.valueToSet)) - } - - actualVariable := getBooleanFromEnvironment("TEST", tc.expectedVariableDefaultValue) - - require.Equal(t, tc.expectedVariableValue, actualVariable, "got value different than expected") - }) - } -} - -func TestGetIntegerFromEnvironment(t *testing.T) { - tests := map[string]struct { - expectedVariableName string - valueToSet *string - expectedVariableDefaultValue int - expectedVariableValue int - }{ - "variable not set": { - valueToSet: nil, - expectedVariableDefaultValue: 80, - expectedVariableValue: 80, - }, - "variable set with value same as default": { - valueToSet: utils.StringToPointer("80"), - expectedVariableDefaultValue: 80, - expectedVariableValue: 80, - }, - "variable set with value different than default": { - valueToSet: utils.StringToPointer("8181"), - expectedVariableDefaultValue: 80, - expectedVariableValue: 8181, - }, - "variable set with inconvertible value different than default": { - valueToSet: utils.StringToPointer("NotANumber"), - expectedVariableDefaultValue: 80, - expectedVariableValue: 80, - }, - } - - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - _ = os.Unsetenv("TEST") - if tc.valueToSet != nil { - os.Setenv("TEST", string(*tc.valueToSet)) - } - - actualVariable := getIntegerFromEnvironment("TEST", tc.expectedVariableDefaultValue) - - require.Equal(t, tc.expectedVariableValue, actualVariable, "got value different than expected") - }) - } -} diff --git a/application/common/utils.go b/application/common/utils.go new file mode 100644 index 0000000..4346375 --- /dev/null +++ b/application/common/utils.go @@ -0,0 +1,21 @@ +package common + +import ( + "os" +) + +func getFromEnvironment[T any](key string, fallback T, parser func(string) (T, error)) T { + value, ok := os.LookupEnv(key) + if !ok { + return fallback + } + + if parsedValue, err := parser(value); err == nil { + return parsedValue + } + return fallback +} + +func stringPassthrough(value string) (string, error) { + return value, nil +} diff --git a/application/common/utils_test.go b/application/common/utils_test.go new file mode 100644 index 0000000..b37c6ac --- /dev/null +++ b/application/common/utils_test.go @@ -0,0 +1,144 @@ +package common + +import ( + "os" + "strconv" + "testing" + + "github.com/stretchr/testify/require" + "icikowski.pl/gpts/utils" +) + +func TestStringPassthrough(t *testing.T) { + expected := "Lorem ipsum" + actual, err := stringPassthrough(expected) + require.NoError(t, err) + require.Equal(t, expected, actual) +} + +func TestGetFromEnvironment(t *testing.T) { + testsForStrings := map[string]struct { + expectedVariableName string + valueToSet *string + expectedVariableDefaultValue string + expectedVariableValue string + }{ + "variable not set": { + valueToSet: nil, + expectedVariableDefaultValue: "ABC", + expectedVariableValue: "ABC", + }, + "variable set with value same as default": { + valueToSet: utils.PointerTo("ABC"), + expectedVariableDefaultValue: "ABC", + expectedVariableValue: "ABC", + }, + "variable set with value different than default": { + valueToSet: utils.PointerTo("XYZ"), + expectedVariableDefaultValue: "ABC", + expectedVariableValue: "XYZ", + }, + } + + testsForBooleans := map[string]struct { + expectedVariableName string + valueToSet *string + expectedVariableDefaultValue bool + expectedVariableValue bool + }{ + "variable not set": { + valueToSet: nil, + expectedVariableDefaultValue: true, + expectedVariableValue: true, + }, + "variable set with value same as default": { + valueToSet: utils.PointerTo("true"), + expectedVariableDefaultValue: true, + expectedVariableValue: true, + }, + "variable set with value different than default (case 1)": { + valueToSet: utils.PointerTo("true"), + expectedVariableDefaultValue: false, + expectedVariableValue: true, + }, + "variable set with value different than default (case 2)": { + valueToSet: utils.PointerTo("1"), + expectedVariableDefaultValue: false, + expectedVariableValue: true, + }, + "variable set with malformed value different than default": { + valueToSet: utils.PointerTo("yes"), + expectedVariableDefaultValue: false, + expectedVariableValue: false, + }, + } + + testsForIntegers := map[string]struct { + expectedVariableName string + valueToSet *string + expectedVariableDefaultValue int + expectedVariableValue int + }{ + "variable not set": { + valueToSet: nil, + expectedVariableDefaultValue: 80, + expectedVariableValue: 80, + }, + "variable set with value same as default": { + valueToSet: utils.PointerTo("80"), + expectedVariableDefaultValue: 80, + expectedVariableValue: 80, + }, + "variable set with value different than default": { + valueToSet: utils.PointerTo("8181"), + expectedVariableDefaultValue: 80, + expectedVariableValue: 8181, + }, + "variable set with inconvertible value different than default": { + valueToSet: utils.PointerTo("NotANumber"), + expectedVariableDefaultValue: 80, + expectedVariableValue: 80, + }, + } + + t.Run("strings", func(t *testing.T) { + for name, tc := range testsForStrings { + t.Run(name, func(t *testing.T) { + _ = os.Unsetenv("TEST") + if tc.valueToSet != nil { + os.Setenv("TEST", string(*tc.valueToSet)) + } + + actualVariable := getFromEnvironment("TEST", tc.expectedVariableDefaultValue, stringPassthrough) + require.Equal(t, tc.expectedVariableValue, actualVariable, "got value different than expected") + }) + } + }) + t.Run("booleans", func(t *testing.T) { + for name, tc := range testsForBooleans { + t.Run(name, func(t *testing.T) { + _ = os.Unsetenv("TEST") + if tc.valueToSet != nil { + os.Setenv("TEST", string(*tc.valueToSet)) + } + + actualVariable := getFromEnvironment("TEST", tc.expectedVariableDefaultValue, strconv.ParseBool) + require.Equal(t, tc.expectedVariableValue, actualVariable, "got value different than expected") + }) + } + }) + t.Run("integers", func(t *testing.T) { + for name, tc := range testsForIntegers { + t.Run(name, func(t *testing.T) { + _ = os.Unsetenv("TEST") + if tc.valueToSet != nil { + os.Setenv("TEST", string(*tc.valueToSet)) + } + + actualVariable := getFromEnvironment("TEST", tc.expectedVariableDefaultValue, strconv.Atoi) + + require.Equal(t, tc.expectedVariableValue, actualVariable, "got value different than expected") + }) + } + }) +} diff --git a/application/config/globals.go b/application/config/globals.go index 1b6e7f2..2ffdee0 100644 --- a/application/config/globals.go +++ b/application/config/globals.go @@ -1,4 +1,6 @@ package config // CurrentConfiguration represents currently running configuration -var CurrentConfiguration = configuration{} +var CurrentConfiguration = configuration{ + routes: make(map[string]Route, 0), +} diff --git a/application/config/types.go b/application/config/types.go index 46ceef8..e3449ac 100644 --- a/application/config/types.go +++ b/application/config/types.go @@ -60,16 +60,14 @@ func (c *configuration) SetConfiguration(routes map[string]Route) { // SetDefaultConfiguration sets up startup configuration with one prepared route func (c *configuration) SetDefaultConfiguration(log zerolog.Logger) { - l := log.With().Str(common.ComponentField, common.ComponentConfig).Logger() - c.mutex.Lock() c.routes = map[string]Route{ "/hello": { AllowSubpaths: true, Default: &Response{ - Status: utils.IntToPointer(http.StatusOK), - ContentType: utils.StringToPointer(common.ContentTypeJSON), - Content: utils.StringToPointer(`{"message":"Hello World!"}`), + Status: utils.PointerTo(http.StatusOK), + ContentType: utils.PointerTo(common.ContentTypeJSON), + Content: utils.PointerTo(`{"message":"Hello World!"}`), Headers: &map[string]string{ "X-SentBy": "GPTS - General Purpose Test Service", }, @@ -77,7 +75,7 @@ func (c *configuration) SetDefaultConfiguration(log zerolog.Logger) { }, } c.mutex.Unlock() - l.Info().Msg("loaded default config as current") + log.Info().Msg("loaded default config as current") } var _ zerolog.LogObjectMarshaler = &Response{} diff --git a/application/config/types_test.go b/application/config/types_test.go index 39a8f41..1937580 100644 --- a/application/config/types_test.go +++ b/application/config/types_test.go @@ -45,24 +45,24 @@ func TestResponseMarshalZerologObject(t *testing.T) { testLog := zerolog.New(buffer) response := Response{ - Status: utils.IntToPointer(http.StatusCreated), - ContentType: utils.StringToPointer(common.ContentTypeJSON), + Status: utils.PointerTo(http.StatusCreated), + ContentType: utils.PointerTo(common.ContentTypeJSON), Headers: &map[string]string{"a": "b"}, } testLog.Info().Object("response", &response).Send() - var contents map[string]interface{} + var contents map[string]any err := json.Unmarshal(buffer.Bytes(), &contents) require.NoError(t, err, "no error expected during reading logged Response") - require.EqualValues(t, map[string]interface{}{ + require.EqualValues(t, map[string]any{ "level": "info", - "response": map[string]interface{}{ + "response": map[string]any{ "configured": true, "status": float64(http.StatusCreated), "contentType": common.ContentTypeJSON, - "headers": map[string]interface{}{ + "headers": map[string]any{ "a": "b", }, }, @@ -82,21 +82,21 @@ func TestRouteMarshalZerologObject(t *testing.T) { testLog.Info().Object("route", route).Send() - var contents map[string]interface{} + var contents map[string]any err := json.Unmarshal(buffer.Bytes(), &contents) require.NoError(t, err, "no error expected during reading logged Route") - require.EqualValues(t, map[string]interface{}{ + require.EqualValues(t, map[string]any{ "level": "info", - "route": map[string]interface{}{ + "route": map[string]any{ "allowSubpaths": true, - "methods": map[string]interface{}{ - "default": map[string]interface{}{"configured": true}, - "get": map[string]interface{}{"configured": true}, - "post": map[string]interface{}{"configured": true}, - "put": map[string]interface{}{"configured": false}, - "patch": map[string]interface{}{"configured": false}, - "delete": map[string]interface{}{"configured": false}, + "methods": map[string]any{ + "default": map[string]any{"configured": true}, + "get": map[string]any{"configured": true}, + "post": map[string]any{"configured": true}, + "put": map[string]any{"configured": false}, + "patch": map[string]any{"configured": false}, + "delete": map[string]any{"configured": false}, }, }, }, contents, "log contains unexpected Route properties") diff --git a/application/go.mod b/application/go.mod index eb42f24..3c5b4c3 100644 --- a/application/go.mod +++ b/application/go.mod @@ -1,12 +1,12 @@ module icikowski.pl/gpts -go 1.17 +go 1.18 require ( github.com/Icikowski/kubeprobes v1.1.0 github.com/gorilla/mux v1.8.0 github.com/rs/zerolog v1.26.1 - github.com/stretchr/testify v1.7.0 + github.com/stretchr/testify v1.7.1 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/application/go.sum b/application/go.sum index 7a026fb..3c88878 100644 --- a/application/go.sum +++ b/application/go.sum @@ -25,8 +25,8 @@ github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc= github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= diff --git a/application/health/endpoints.go b/application/health/endpoints.go index cc0f023..95c6cc5 100644 --- a/application/health/endpoints.go +++ b/application/health/endpoints.go @@ -6,13 +6,11 @@ import ( "github.com/Icikowski/kubeprobes" "github.com/rs/zerolog" - "icikowski.pl/gpts/common" ) // PrepareHealthEndpoints prepares and configures health endpoints func PrepareHealthEndpoints(log zerolog.Logger, port int) *http.Server { - l := log.With().Str(common.ComponentField, common.ComponentHealth).Logger() - l.Debug(). + log.Debug(). Int("port", port). Msg("preparing readiness & liveness endpoints") diff --git a/application/logger/logger.go b/application/logger/logger.go deleted file mode 100644 index 1c89ad6..0000000 --- a/application/logger/logger.go +++ /dev/null @@ -1,39 +0,0 @@ -package logger - -import ( - "os" - "strings" - - "github.com/rs/zerolog" -) - -var mainLog zerolog.Logger - -var logLevels = map[string]zerolog.Level{ - "debug": zerolog.DebugLevel, - "info": zerolog.InfoLevel, - "warn": zerolog.WarnLevel, - "error": zerolog.ErrorLevel, - "fatal": zerolog.FatalLevel, - "panic": zerolog.PanicLevel, - "trace": zerolog.TraceLevel, -} - -// InitializeLog prepares log component for first use -func InitializeLog(pretty bool, level string) { - zerolog.SetGlobalLevel(logLevels[strings.ToLower(level)]) - if pretty { - mainLog = zerolog.New(zerolog.ConsoleWriter{ - Out: os.Stdout, - NoColor: false, - TimeFormat: "2006-01-02 15:04:05", - }).With().Timestamp().Logger() - } else { - mainLog = zerolog.New(os.Stdout).With().Timestamp().Logger() - } -} - -// GetLogger returns a logger instance -func GetLogger() zerolog.Logger { - return mainLog -} diff --git a/application/logs/log.go b/application/logs/log.go new file mode 100644 index 0000000..b3a9288 --- /dev/null +++ b/application/logs/log.go @@ -0,0 +1,38 @@ +package logs + +import ( + "io" + "os" + + "github.com/rs/zerolog" +) + +var logger zerolog.Logger + +// Initialize prepares logger for first use +func Initialize(pretty bool, level string) { + desiredLevel, err := zerolog.ParseLevel(level) + if err != nil { + desiredLevel = zerolog.InfoLevel + defer func() { + logger.Warn().Str("levelName", level).Msg("unknown log level selected, falling back to INFO") + }() + } + zerolog.SetGlobalLevel(desiredLevel) + + var writer io.Writer = os.Stdout + if pretty { + writer = zerolog.ConsoleWriter{ + Out: os.Stdout, + NoColor: false, + TimeFormat: "2006-01-02 15:04:05", + } + } + + logger = zerolog.New(writer).With().Timestamp().Logger() +} + +// For returns a logger instance for given component +func For(component string) zerolog.Logger { + return logger.With().Str("component", component).Logger() +} diff --git a/application/main.go b/application/main.go index 2f2f431..47fadeb 100644 --- a/application/main.go +++ b/application/main.go @@ -8,7 +8,7 @@ import ( "icikowski.pl/gpts/common" "icikowski.pl/gpts/config" "icikowski.pl/gpts/health" - "icikowski.pl/gpts/logger" + "icikowski.pl/gpts/logs" "icikowski.pl/gpts/service" ) @@ -21,7 +21,7 @@ func init() { flag.StringVar(&common.LogLevel, "log-level", common.LogLevel, "Global log level; one of [debug, info, warn, error, fatal, panic, trace]") flag.Parse() - logger.InitializeLog(common.PrettyLog, common.LogLevel) + logs.Initialize(common.PrettyLog, common.LogLevel) } var version = common.BuildValueUnknown @@ -29,46 +29,49 @@ var gitCommit = common.BuildValueUnknown var binaryType = common.BuildValueUnknown func main() { - log := logger.GetLogger() - l := log.With().Str(common.ComponentField, common.ComponentCLI).Logger() + log := logs.For(common.ComponentCLI) - l.Info(). + log.Info(). Str("version", version). Str("gitCommit", gitCommit). Str("binaryType", binaryType). Str("goVersion", runtime.Version()). Msg("version information") - l.Info(). + log.Info(). Int("servicePort", common.ServicePort). Int("healthchecksPort", common.HealthchecksPort). Str("configurationEndpoint", common.ConfigurationEndpoint). - Msg("starting application with provided configuration") + Msg("configuration applied") - healthServer := health.PrepareHealthEndpoints(log, common.HealthchecksPort) + healthServer := health.PrepareHealthEndpoints( + logs.For(common.ComponentHealth), + common.HealthchecksPort, + ) go func() { - l.Debug().Msg("health endpoints starting") + log.Debug().Msg("health endpoints starting") if err := healthServer.ListenAndServe(); err != nil { - l.Fatal().Err(err).Msg("health endpoints have been shut down unexpectedly") + log.Fatal().Err(err).Msg("health endpoints have been shut down unexpectedly") } }() if common.DefaultConfigOnStartup { - config.CurrentConfiguration.SetDefaultConfiguration(log) + log.Info().Msg("loading default configuration") + config.CurrentConfiguration.SetDefaultConfiguration(logs.For(common.ComponentConfig)) } - l.Debug().Msg("marking application liveness as UP") + log.Debug().Msg("marking application liveness as UP") health.ApplicationStatus.MarkAsUp() for { service.ExpectingShutdown = false - server := service.PrepareServer(log, common.ServicePort) + server := service.PrepareServer(logs.For(common.ComponentService), common.ServicePort) health.ServiceStatus.MarkAsUp() if err := server.ListenAndServe(); err != nil { if service.ExpectingShutdown && err == http.ErrServerClosed { - l.Info().Msg("service has been shut down for configuration change") + log.Info().Msg("service has been shut down for configuration change") } else { - l.Fatal().Err(err).Msg("service has been shut down unexpectedly") + log.Fatal().Err(err).Msg("service has been shut down unexpectedly") } } } diff --git a/application/service/builder.go b/application/service/builder.go index 1763c0a..00bec0e 100644 --- a/application/service/builder.go +++ b/application/service/builder.go @@ -15,8 +15,7 @@ import ( // PrepareServer prepares, configures and runs test service server func PrepareServer(log zerolog.Logger, port int) *http.Server { - l := log.With().Str(common.ComponentField, common.ComponentService).Logger() - l.Info().Msg("preparing test service's router & server") + log.Info().Msg("preparing test service's router & server") r := mux.NewRouter().StrictSlash(true) server := &http.Server{ @@ -24,22 +23,22 @@ func PrepareServer(log zerolog.Logger, port int) *http.Server { Addr: fmt.Sprintf(":%d", port), } - r.HandleFunc(common.ConfigurationEndpoint, getConfigHandlerFunction(l, server)) + r.HandleFunc(common.ConfigurationEndpoint, getConfigHandlerFunction(log, server)) entries := config.CurrentConfiguration.GetConfiguration() sortedRoutes := getSortedRoutes(entries) - l.Debug().Msg("paths registration order determined") + log.Debug().Msg("paths registration order determined") for _, path := range sortedRoutes { path := path route := entries[path] - l.Info(). + log.Info(). Str("path", path). Object("route", route). Msg("preparing handler") var handler = func(w http.ResponseWriter, r *http.Request) { - innerLog := l.With(). + innerLog := log.With(). Dict( "request", zerolog.Dict(). @@ -140,13 +139,13 @@ func PrepareServer(log zerolog.Logger, port int) *http.Server { } } - r.NotFoundHandler = getDefaultHandler(l) + r.NotFoundHandler = getDefaultHandler(log) - l.Debug().Msg("registering shutdown hooks") + log.Debug().Msg("registering shutdown hooks") server.RegisterOnShutdown(func() { health.ServiceStatus.MarkAsDown() }) - l.Info().Msg("server prepared") + log.Info().Msg("server prepared") return server } diff --git a/application/service/builder_test.go b/application/service/builder_test.go index a27f7ed..e456a39 100644 --- a/application/service/builder_test.go +++ b/application/service/builder_test.go @@ -118,32 +118,32 @@ func TestPrepareServer(t *testing.T) { extendedConfig["/multiple-methods"] = config.Route{ GET: &config.Response{ - Content: utils.StringToPointer(`{"get": true}`), - ContentType: utils.StringToPointer("application/json"), + Content: utils.PointerTo(`{"get": true}`), + ContentType: utils.PointerTo("application/json"), }, POST: &config.Response{ - Content: utils.StringToPointer(`{"post": true}`), - ContentType: utils.StringToPointer("application/json"), + Content: utils.PointerTo(`{"post": true}`), + ContentType: utils.PointerTo("application/json"), }, PUT: &config.Response{ - Content: utils.StringToPointer(`{"put": true}`), - ContentType: utils.StringToPointer("application/json"), + Content: utils.PointerTo(`{"put": true}`), + ContentType: utils.PointerTo("application/json"), }, PATCH: &config.Response{ - Content: utils.StringToPointer(`{"patch": true}`), - ContentType: utils.StringToPointer("application/json"), + Content: utils.PointerTo(`{"patch": true}`), + ContentType: utils.PointerTo("application/json"), }, DELETE: &config.Response{ - Content: utils.StringToPointer(`{"delete": true}`), - ContentType: utils.StringToPointer("application/json"), + Content: utils.PointerTo(`{"delete": true}`), + ContentType: utils.PointerTo("application/json"), }, } extendedConfig["/sub"] = config.Route{ AllowSubpaths: true, Default: &config.Response{ - Status: utils.IntToPointer(http.StatusTeapot), - Content: utils.StringToPointer("I'm a teapot and I'm everywhere"), + Status: utils.PointerTo(http.StatusTeapot), + Content: utils.PointerTo("I'm a teapot and I'm everywhere"), }, } @@ -164,7 +164,7 @@ func TestPrepareServer(t *testing.T) { require.Equal(t, tc.expectedStatus, response.StatusCode, "status code is different than expected") if tc.expectedKeys != nil { - var output map[string]interface{} + var output map[string]any err = json.NewDecoder(response.Body).Decode(&output) require.NoError(t, err, "response should be decoded properly") diff --git a/application/service/encoders.go b/application/service/encoders.go index 44f86a1..d9f9f0d 100644 --- a/application/service/encoders.go +++ b/application/service/encoders.go @@ -8,7 +8,7 @@ import ( "icikowski.pl/gpts/common" ) -func getEncoder(contentType string, writer io.Writer) func(v interface{}) error { +func getEncoder(contentType string, writer io.Writer) func(v any) error { switch contentType { case common.ContentTypeJSON: return json.NewEncoder(writer).Encode @@ -18,7 +18,7 @@ func getEncoder(contentType string, writer io.Writer) func(v interface{}) error return nil } -func getDecoder(contentType string, reader io.Reader) func(v interface{}) error { +func getDecoder(contentType string, reader io.Reader) func(v any) error { switch contentType { case common.ContentTypeJSON: return json.NewDecoder(reader).Decode diff --git a/application/service/encoders_test.go b/application/service/encoders_test.go index 1a9ca1d..500c7da 100644 --- a/application/service/encoders_test.go +++ b/application/service/encoders_test.go @@ -9,7 +9,7 @@ import ( "icikowski.pl/gpts/common" ) -var testSubject = map[string]interface{}{ +var testSubject = map[string]any{ "hello": "world", "test": true, } @@ -85,7 +85,7 @@ func TestGetDecoder(t *testing.T) { return } - var actualOutput map[string]interface{} + var actualOutput map[string]any function(&actualOutput) require.Equal(t, testSubject, actualOutput, "output different than expected") diff --git a/application/service/handlers.go b/application/service/handlers.go index bd37703..835e070 100644 --- a/application/service/handlers.go +++ b/application/service/handlers.go @@ -31,7 +31,7 @@ func getConfigHandlerFunction(log zerolog.Logger, server *http.Server) func(w ht ). Logger() - resolveContent := func(target interface{}) error { + resolveContent := func(target any) error { var resolverError error switch r.Header.Get(common.HeaderContentType) { case common.ContentTypeJSON: diff --git a/application/utils/pointers.go b/application/utils/pointers.go index 7b88628..97d3a77 100644 --- a/application/utils/pointers.go +++ b/application/utils/pointers.go @@ -1,15 +1,6 @@ package utils -// StringToPointer takes string literal and converts it to a pointer -func StringToPointer(in string) *string { - out := new(string) - *out = in - return out -} - -// IntToPointer takes int literal and converts it to a pointer -func IntToPointer(in int) *int { - out := new(int) - *out = in - return out +// PointerTo takes some literal and returns a pointer +func PointerTo[T any](in T) *T { + return &in } diff --git a/application/utils/pointers_test.go b/application/utils/pointers_test.go index 6171f34..ea7b29d 100644 --- a/application/utils/pointers_test.go +++ b/application/utils/pointers_test.go @@ -6,16 +6,18 @@ import ( "github.com/stretchr/testify/require" ) -func TestStringToPointer(t *testing.T) { - original := "The quick brown fox jumped over the lazy dog" - pointer := StringToPointer(original) +func TestPointerTo(t *testing.T) { + tests := map[string]any{ + "pointer to integer": 50, + "pointer to string": "Lorem ipsum", + "pointer to bool": true, + } - require.EqualValues(t, original, *pointer, "values of input string and produced pointer should be equal") -} - -func TestIntToPointer(t *testing.T) { - original := int(12345) - pointer := IntToPointer(original) - - require.EqualValues(t, original, *pointer, "values of input integer and produced pointer should be equal") + for name, tc := range tests { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + actual := PointerTo(tc) + require.Equal(t, tc, *actual, "got value different than expected") + }) + } }