diff --git a/cmd/gen-docs/main.go b/cmd/gen-docs/main.go index afe1a86a8..b09e0b248 100644 --- a/cmd/gen-docs/main.go +++ b/cmd/gen-docs/main.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "log/slog" "os" "strings" @@ -12,11 +13,9 @@ import ( "github.com/reubenmiller/go-c8y-cli/v2/pkg/config" "github.com/reubenmiller/go-c8y-cli/v2/pkg/console" "github.com/reubenmiller/go-c8y-cli/v2/pkg/dataview" - "github.com/reubenmiller/go-c8y-cli/v2/pkg/logger" "github.com/reubenmiller/go-c8y/pkg/c8y" "github.com/spf13/pflag" "github.com/spf13/viper" - "go.uber.org/zap/zapcore" ) func main() { @@ -102,18 +101,11 @@ func createCmdRoot() *root.CmdRoot { var client *c8y.Client var dataView *dataview.DataView var consoleHandler *console.Console - var logHandler *logger.Logger var activityLoggerHandler *activitylogger.ActivityLogger var configHandler = config.NewConfig(viper.GetViper()) - // init logger - logHandler = logger.NewLogger("", logger.Options{ - Level: zapcore.WarnLevel, - Debug: false, - }) - if _, err := configHandler.ReadConfigFiles(nil); err != nil { - logHandler.Infof("Failed to read configuration. Trying to proceed anyway. %s", err) + slog.Info("Failed to read configuration. Trying to proceed anyway", "err", err) } // cmd factory @@ -129,12 +121,6 @@ func createCmdRoot() *root.CmdRoot { } return client, nil } - loggerFunc := func() (*logger.Logger, error) { - if logHandler == nil { - return nil, fmt.Errorf("logger is missing") - } - return logHandler, nil - } activityLoggerFunc := func() (*activitylogger.ActivityLogger, error) { if activityLoggerHandler == nil { return nil, fmt.Errorf("activityLogger is missing") @@ -153,7 +139,7 @@ func createCmdRoot() *root.CmdRoot { } return consoleHandler, nil } - cmdFactory := factory.New("", "", configFunc, clientFunc, loggerFunc, activityLoggerFunc, dataViewFunc, consoleFunc) + cmdFactory := factory.New("", "", configFunc, clientFunc, activityLoggerFunc, dataViewFunc, consoleFunc) return root.NewCmdRoot(cmdFactory, "", "") } diff --git a/cmd/gen-tests/main.go b/cmd/gen-tests/main.go index 0b1d1f407..3c49e862f 100644 --- a/cmd/gen-tests/main.go +++ b/cmd/gen-tests/main.go @@ -4,6 +4,8 @@ import ( "encoding/json" "fmt" "io" + "log" + "log/slog" "os" "path" "path/filepath" @@ -11,56 +13,23 @@ import ( "strings" "github.com/spf13/cobra" - "go.uber.org/zap" - "go.uber.org/zap/zapcore" "github.com/google/shlex" "github.com/reubenmiller/go-c8y-cli/v2/internal/integration/models" "github.com/reubenmiller/go-c8y-cli/v2/pkg/flatten" "github.com/reubenmiller/go-c8y-cli/v2/pkg/jsonUtilities" + "github.com/reubenmiller/go-c8y-cli/v2/pkg/logger" "gopkg.in/yaml.v3" ) -var logger *zap.Logger -var loggerS *zap.SugaredLogger - func init() { createLogger() } func createLogger() { - consoleEncCfg := zapcore.EncoderConfig{ - // Keys can be anything except the empty string. - TimeKey: "T", - LevelKey: "L", - NameKey: "N", - CallerKey: "C", - FunctionKey: zapcore.OmitKey, - MessageKey: "M", - StacktraceKey: "S", - LineEnding: zapcore.DefaultLineEnding, - EncodeLevel: zapcore.CapitalColorLevelEncoder, - EncodeTime: zapcore.ISO8601TimeEncoder, - EncodeDuration: zapcore.StringDurationEncoder, - EncodeCaller: zapcore.ShortCallerEncoder, - } - - consoleLevel := zapcore.InfoLevel - consoleLevelEnabler := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { - return lvl >= consoleLevel + logger.NewLogger("gen-test", logger.Options{ + Level: slog.LevelInfo, }) - var cores []zapcore.Core - cores = append(cores, zapcore.NewCore( - zapcore.NewConsoleEncoder(consoleEncCfg), - zapcore.Lock(zapcore.AddSync(os.Stderr)), - consoleLevelEnabler, - )) - core := zapcore.NewTee(cores...) - logger = zap.New(core) - defer func() { - _ = logger.Sync() - }() - loggerS = logger.Sugar() } type Generator struct { @@ -75,13 +44,13 @@ func NewGenerator(name string, mockConfig *models.MockConfiguration) (gen *Gener } contents, err := io.ReadAll(f) if err != nil { - loggerS.Fatalf("Failed to read spec file. file=%s err=%s", name, err) + log.Fatalf("Failed to read spec file. file=%s err=%s", name, err) return } spec := &models.Specification{} err = yaml.Unmarshal(contents, spec) if err != nil { - loggerS.Fatalf("Failed to marshal spec file. file=%s err=%s", name, err) + log.Fatalf("Failed to marshal spec file. file=%s err=%s", name, err) return } @@ -120,7 +89,7 @@ func (g *Generator) CreateTests(outDir string) error { testcase.Command = fmt.Sprintf("$TEST_SHELL -c '%s'", command) } - loggerS.Debugf("Processing endpoint: %s", testcase.Command) + slog.Debug("Processing endpoint", "command", testcase.Command) testcase.StdOut = buildAssertions(g.Spec.Group.Name, &endpoint, i) testsuite.Tests[key] = *testcase @@ -129,7 +98,7 @@ func (g *Generator) CreateTests(outDir string) error { suitekey := CreateSuiteKey(g.Spec, &endpoint) if err := WriteTestSuite(testsuite, suitekey, outDir); err != nil { - loggerS.Fatalf("Failed to write test suite to file. %s", err) + log.Fatalf("Failed to write test suite to file. %s", err) } } @@ -164,7 +133,7 @@ func CreateFakeCommand(parentCmd string, endpoint *models.Command) *cobra.Comman cmd.PersistentFlags().String("output", "o", "Output format i.e. table, json, csv, csvheader") for _, parameter := range endpoint.GetAllParameters() { - loggerS.Debugf("Adding parameter. name=%s", parameter.Name) + slog.Debug("Adding parameter", "name", parameter.Name) if strings.Contains(parameter.Type, "[]") { cmd.Flags().StringSlice(parameter.Name, nil, "") } else if parameter.Type == "boolean" || parameter.Type == "optional_fragment" || parameter.Type == "booleanDefault" { @@ -199,7 +168,7 @@ func CreateFakeCommand(parentCmd string, endpoint *models.Command) *cobra.Comman func parseFakeCommand(parentCmd string, command string, endpoint *models.Command) *cobra.Command { cmd := CreateFakeCommand(parentCmd, endpoint) if err := cmd.ParseFlags(parseCommand(parentCmd, command, endpoint)); err != nil { - loggerS.Fatalf("Failed to parse command. command=%s, err=%s", command, err) + log.Fatalf("Failed to parse command. command=%s, err=%s", command, err) } return cmd @@ -272,7 +241,7 @@ func buildAssertions(parentCmd string, endpoint *models.Command, exampleIdx int) } else { for _, parameter := range endpoint.Body { value := getParameterValue(cmd, ¶meter) - loggerS.Debugf("Adding body property. name=%s, value=%s", parameter.Name, value) + slog.Debug("Adding body property", "name", parameter.Name, "value", value) if value != "" { switch parameter.Type { case "attachment", "file", "fileContents": @@ -301,7 +270,7 @@ func formatJsonAssertion(jsonAssertion map[string]string, propType string, prop if strings.HasSuffix(prop, ".data") || strings.EqualFold(propType, "json_custom") { data := make(map[string]interface{}) if err := jsonUtilities.ParseJSON(values[0], data); err != nil { - loggerS.Warnf("Could not parse shorthand json. error=%s, data=%s", err, values[0]) + slog.Warn("Could not parse shorthand json", "err", err, "data", values[0]) return } @@ -311,7 +280,7 @@ func formatJsonAssertion(jsonAssertion map[string]string, propType string, prop } flatData, err := flatten.Flatten(data, prefix, flatten.DotStyle) if err != nil { - loggerS.Fatalf("Could not flatten map. %s", err) + log.Fatalf("Could not flatten map. %s", err) } for k, v := range flatData { switch tv := v.(type) { @@ -416,7 +385,7 @@ func substituteVariables(cmd *cobra.Command, endpoint *models.Command) (out stri } } - loggerS.Debugf("Detected variables: %v", variableNames) + slog.Debug("Detected variables", "variables", variableNames) // replace variable values from fake command for _, parameter := range endpoint.PathParameters { @@ -446,7 +415,7 @@ func WriteTestSuite(t *models.TestSuite, id string, outDir string) (err error) { } if err := os.MkdirAll(filepath.Dir(outFile), 0755); err != nil { - loggerS.Fatal(err) + log.Fatal(err) } f, err := os.OpenFile(outFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) @@ -504,7 +473,7 @@ func main() { // Ignore skipped specs if gen.Spec.Group.Skip { - loggerS.Warnf("Specification is marked as skipped. Ignoring. file=%s", os.Args[2]) + slog.Warn("Specification is marked as skipped. Ignoring file", "file", os.Args[2]) os.Exit(0) } diff --git a/go.mod b/go.mod index 7aa657a5f..354fe5b04 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0 github.com/pkg/errors v0.9.1 github.com/pquerna/otp v1.5.0 - github.com/reubenmiller/go-c8y v0.37.6 + github.com/reubenmiller/go-c8y v0.37.7-0.20250823091328-e9f46442afa8 github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 github.com/spf13/cobra v1.9.1 github.com/spf13/pflag v1.0.6 @@ -33,9 +33,8 @@ require ( github.com/tidwall/pretty v1.2.1 github.com/tidwall/sjson v1.2.5 github.com/vbauerster/mpb/v6 v6.0.4 - go.uber.org/zap v1.27.0 - golang.org/x/crypto v0.39.0 - golang.org/x/term v0.32.0 + golang.org/x/crypto v0.41.0 + golang.org/x/term v0.34.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -44,9 +43,10 @@ require github.com/hashicorp/go-version v1.7.0 require ( github.com/cli/browser v1.3.0 github.com/dustin/go-humanize v1.0.1 - github.com/golang-jwt/jwt/v5 v5.2.2 + github.com/golang-jwt/jwt/v5 v5.3.0 github.com/hashicorp/go-retryablehttp v0.7.7 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 + github.com/lmittmann/tint v1.1.2 github.com/reubenmiller/gojsonq/v2 v2.0.0-20221119213524-0fd921ac20a3 ) @@ -79,7 +79,6 @@ require ( github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/microcosm-cc/bluemonday v1.0.27 // indirect github.com/muesli/reflow v0.3.0 // indirect - github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rivo/uniseg v0.4.7 // indirect @@ -96,14 +95,14 @@ require ( go.mozilla.org/pkcs7 v0.9.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/exp v0.0.0-20231226003508-02704c960a9b // indirect - golang.org/x/net v0.41.0 // indirect - golang.org/x/sys v0.33.0 // indirect - golang.org/x/text v0.26.0 // indirect + golang.org/x/net v0.43.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/text v0.28.0 // indirect gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 // indirect rsc.io/qr v0.2.0 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) -go 1.23.7 +go 1.24 -toolchain go1.24.2 +toolchain go1.24.5 diff --git a/go.sum b/go.sum index c7bc4e00f..07a5cc5e6 100644 --- a/go.sum +++ b/go.sum @@ -78,8 +78,8 @@ github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/ github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= -github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -116,6 +116,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w= +github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= @@ -150,8 +152,6 @@ github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0 h1:LiZB1h0GIcudcDci2bxbqI6DXV8bF8POAnArqvRrIyw= github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0/go.mod h1:F/7q8/HZz+TXjlsoZQQKVYvXTZaFH4QRa3y+j1p7MS0= -github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= -github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -161,8 +161,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs= github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= -github.com/reubenmiller/go-c8y v0.37.6 h1:4OLwu9PHLw9sPNMfggQtGuzVh1+gk6U/ZCrIooFUr7g= -github.com/reubenmiller/go-c8y v0.37.6/go.mod h1:63lPvPX6B8xf5GT9PvRrXgM0u5uU5tgZiQRAIfYQDQY= +github.com/reubenmiller/go-c8y v0.37.7-0.20250823091328-e9f46442afa8 h1:NA5pJOaCxdwcM/19JxERbU4Uo8qmVVhBpHiwXwteSD0= +github.com/reubenmiller/go-c8y v0.37.7-0.20250823091328-e9f46442afa8/go.mod h1:u2pwiVXmZr7X687S8h8gUocpFx7jbhm2Oppmn96xB80= github.com/reubenmiller/gojsonq/v2 v2.0.0-20221119213524-0fd921ac20a3 h1:v8Q77ObTxkm0Wj9iAjcc0VMLxqEzKIdAnaTNPzSiw8Q= github.com/reubenmiller/gojsonq/v2 v2.0.0-20221119213524-0fd921ac20a3/go.mod h1:QidmUT4ebNVwyjKXAQgx9VFHxpOxBKWs32EEXaXnEfE= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -222,24 +222,20 @@ github.com/yuin/goldmark-emoji v1.0.5 h1:EMVWyCGPlXJfUXBXpuMu+ii3TIaxbVBnEX9uaDC github.com/yuin/goldmark-emoji v1.0.5/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U= go.mozilla.org/pkcs7 v0.9.0 h1:yM4/HS9dYv7ri2biPtxt8ikvB37a980dg69/pKmS+eI= go.mozilla.org/pkcs7 v0.9.0/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= -golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/exp v0.0.0-20231226003508-02704c960a9b h1:kLiC65FbiHWFAOu+lxwNPujcsl8VYyTYYEZnsOO1WK4= golang.org/x/exp v0.0.0-20231226003508-02704c960a9b/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= -golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -252,18 +248,18 @@ golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= -golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= +golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= +golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= -golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= diff --git a/pkg/c8yextensions/plugins/plugins.go b/pkg/c8yextensions/plugins/plugins.go index fe2a5d9f3..173a6b494 100644 --- a/pkg/c8yextensions/plugins/plugins.go +++ b/pkg/c8yextensions/plugins/plugins.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "io" + "log/slog" "net/http" "regexp" "strings" @@ -231,11 +232,6 @@ func buildBody(f *cmdutil.Factory, applicationID string, managerOptions *PluginC return nil, err } - log, err := f.Logger() - if err != nil { - return nil, err - } - // Lookup application where the extension will be applied to // TODO: Check if the user is trying to update an extension that is owned by another tenant refs, err := c8yfetcher.FindHostedApplications(f, []string{applicationID}, true, "", true) @@ -311,7 +307,7 @@ func buildBody(f *cmdutil.Factory, applicationID string, managerOptions *PluginC if err := buildUIRemotes(remotes, ext, versionOrTag); err != nil { if managerOptions.RemoveInvalid { // ignore error - log.Warnf("Removing reference to revoked plugin version: name=%s, version=%s, remote=%s", ext.Get("name").String(), versionOrTag, nameVersion) + slog.Warn("Removing reference to revoked plugin version", "name", ext.Get("name").String(), "version", versionOrTag, "remote", nameVersion) continue } return nil, err @@ -320,7 +316,7 @@ func buildBody(f *cmdutil.Factory, applicationID string, managerOptions *PluginC } } else if managerOptions.RemoveInvalid { // ignore error - log.Warnf("Removing reference to orphaned plugin: remote=%s", nameVersion) + slog.Warn("Removing reference to orphaned plugin", "remote", nameVersion) continue } else { bodyErrors = append(bodyErrors, fmt.Errorf("plugin does not exist. name=%s, tag/version=%s, remote=%s", name, versionOrTag, nameVersion)) diff --git a/pkg/c8ylogin/c8ylogin.go b/pkg/c8ylogin/c8ylogin.go index b4f06dc40..526820291 100644 --- a/pkg/c8ylogin/c8ylogin.go +++ b/pkg/c8ylogin/c8ylogin.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "io" + "log/slog" "net/http" "os" "sort" @@ -19,7 +20,6 @@ import ( "github.com/reubenmiller/go-c8y-cli/v2/pkg/c8ysession" "github.com/reubenmiller/go-c8y-cli/v2/pkg/config" "github.com/reubenmiller/go-c8y-cli/v2/pkg/iostreams" - "github.com/reubenmiller/go-c8y-cli/v2/pkg/logger" "github.com/reubenmiller/go-c8y-cli/v2/pkg/prompt" "github.com/reubenmiller/go-c8y/pkg/c8y" "github.com/reubenmiller/go-c8y/pkg/oauth/api" @@ -101,7 +101,6 @@ type LoginHandler struct { state chan LoginState Attempts int Writer io.Writer - Logger *logger.Logger LoginType string LoginAttempted bool @@ -118,28 +117,22 @@ func NewLoginHandler(IO *iostreams.IOStreams, c *c8y.Client, w io.Writer, onSave C8Yclient: c, Writer: w, onSave: onSave, - Logger: logger.NewDummyLogger("c8ylogin"), SSO: config.SSOSettings{}, } h.state = make(chan LoginState, 1) return h } -// SetLogger sets the logger to use to -func (lh *LoginHandler) SetLogger(l *logger.Logger) { - lh.Logger = l -} - func (lh *LoginHandler) do(op func() error) { lh.Err = op() } // Clear clears any existing authorization state stored for the current client func (lh *LoginHandler) Clear() { - lh.Logger.Info("Clearing authentication") + slog.Info("Clearing authentication") if lh.Attempts > 1 && lh.TFACodeRequired { - lh.Logger.Infof("Clearing TFA code. code=%s", lh.TFACode) + slog.Info("Clearing TFA code", "code", lh.TFACode) lh.TFACode = "" } lh.Authorized = false @@ -180,7 +173,7 @@ func (lh *LoginHandler) Run() error { for { c := <-lh.state - lh.Logger.Infof("Current State: %s", c.String()) + slog.Info(fmt.Sprintf("Current State: %s", c.String())) if c == LoginStateUnknown { lh.Clear() @@ -248,10 +241,10 @@ func (lh *LoginHandler) sortLoginOptions() { if lh.LoginType != "" { if _, ok := optionOrder[lh.LoginType]; ok { - lh.Logger.Infof("Setting preferred login method. %s", lh.LoginType) + slog.Info("Setting preferred login method", "value", lh.LoginType) optionOrder[lh.LoginType] = -999 // Try preferred method first } else { - lh.Logger.Infof("Unsupported login method. The given option will be ignored. %s", lh.LoginType) + slog.Info("Unsupported login method. The given option will be ignored", "value", lh.LoginType) } } @@ -281,14 +274,14 @@ func (lh *LoginHandler) init() { if err != nil { if strings.Contains(err.Error(), "401") { - lh.Logger.Infof("Login options returned 401. This may happen when your c8y hostname is not setup correctly") + slog.Info("Login options returned 401. This may happen when your c8y hostname is not setup correctly") return nil } - lh.Logger.Warnf("Failed to get login options. %s", err) + slog.Warn("Failed to get login options", "err", err) return err } if loginOptions == nil { - lh.Logger.Warnf("Login options are empty. %s", err) + slog.Warn("Login options are empty", "err", err) return err } @@ -301,7 +294,7 @@ func (lh *LoginHandler) init() { tenantSelfURL = loginOptions.Self } if strings.HasPrefix(tenantSelfURL, "http") && !strings.Contains(tenantSelfURL, lh.C8Yclient.TenantName+".") { - lh.Logger.Warningf("Detected invalid tenant name. expected %s to include %s. Tenant name will be ignored", lh.C8Yclient.TenantName, tenantSelfURL) + slog.Warn(fmt.Sprintf("Detected invalid tenant name. expected %s to include %s. Tenant name will be ignored", lh.C8Yclient.TenantName, tenantSelfURL)) lh.C8Yclient.TenantName = "" } } @@ -313,7 +306,7 @@ func (lh *LoginHandler) init() { lh.LoginType = lh.LoginOptions.LoginOptions[0].Type // Setting preferred login method - lh.Logger.Debugf("Preferred login method. type=%s", lh.LoginType) + slog.Debug("Preferred login method", "type", lh.LoginType) } return err }) @@ -332,7 +325,7 @@ func (lh *LoginHandler) promptForPassword() error { } if lh.C8Yclient.Username == "" { - prompter := prompt.NewPrompt(lh.Logger) + prompter := prompt.NewPrompt() username, err := prompter.Username("Enter username", " ") if err != nil { lh.state <- LoginStateAbort @@ -382,7 +375,7 @@ func (lh *LoginHandler) login() { lh.do(func() error { if lh.LoginOptions == nil || len(lh.LoginOptions.LoginOptions) == 0 { // Don't fail on no login options. Default to using BASIC auth - lh.Logger.Infof("No login options available. Using BASIC auth") + slog.Info("No login options available. Using BASIC auth") if lh.Authorized { lh.state <- LoginStateAuth } else { @@ -403,10 +396,10 @@ func (lh *LoginHandler) login() { for _, option := range lh.LoginOptions.LoginOptions { order = append(order, option.Type) } - lh.Logger.Infof("Login type order: %v", order) + slog.Info("Login type order", "value", order) for _, option := range lh.LoginOptions.LoginOptions { - lh.Logger.Infof("Trying login option. %s", option.Type) + slog.Info("Trying login option", "type", option.Type) switch option.Type { case c8y.LoginTypeOAuth2: // Device Authorization Flow (for external providers) @@ -449,7 +442,7 @@ func (lh *LoginHandler) login() { if loginErr != nil { // Ignore SSO if invalid configuration is found if errors.Is(loginErr, c8y.ErrSSOInvalidConfiguration) { - lh.Logger.Warnf("Skipping login type (%s) as SSO configuration is invalid. err=%s", option.Type, loginErr) + slog.Warn(fmt.Sprintf("Skipping login type (%s) as SSO configuration is invalid. err=%s", option.Type, loginErr)) continue } @@ -461,7 +454,7 @@ func (lh *LoginHandler) login() { return lh.Err } - lh.Logger.Infof("Received access token via device flow. type=%s, scope=%s, tokenPresent=%v, refreshTokenPresent=%v", accessToken.Type, accessToken.Scope, accessToken.Token != "", accessToken.RefreshToken != "") + slog.Info("Received access token via device flow", "type", accessToken.Type, "scope", accessToken.Scope, "tokenPresent", accessToken.Token != "", "refreshTokenPresent", accessToken.RefreshToken != "") lh.onSave() lh.state <- LoginStateVerify return nil @@ -510,23 +503,23 @@ func (lh *LoginHandler) login() { ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Timeout)*time.Millisecond) defer cancel() - lh.Logger.Debugf("Logging in using %s", c8y.LoginTypeOAuth2Internal) + slog.Debug("Logging in", "method", c8y.LoginTypeOAuth2Internal) // Check if username/password are provided if lh.C8Yclient.Username == "" { - lh.Logger.Warnf("Skipping login type (%s) as a username is empty", option.Type) + slog.Warn("Skipping login type as a username is empty", "loginType", option.Type) continue } if lh.C8Yclient.Password == "" { - lh.Logger.Warnf("Skipping login type (%s) as password is empty", option.Type) + slog.Warn("Skipping login type as password is empty", "loginType", option.Type) continue } if err := lh.C8Yclient.LoginUsingOAuth2(ctx, option.InitRequest); err != nil { if v, ok := err.(*c8y.ErrorResponse); ok { - lh.Logger.Errorf("OAuth2 failed. %s", v.Message) + slog.Error("OAuth2 failed", "err", v.Message) } else { - lh.Logger.Errorf("OAuth2 failed. %s", err) + slog.Error("OAuth2 failed", "err", err) } if strings.Contains(err.Error(), "There was a change in authentication strategy for your tenant or user account") { @@ -589,17 +582,17 @@ func (lh *LoginHandler) verify() { if resp != nil && resp.StatusCode() == http.StatusUnauthorized { if v, ok := err.(*c8y.ErrorResponse); ok { - lh.Logger.Infof("error message from server. %s", v.Message) + slog.Info("error message from server", "msg", v.Message) if lh.errorContains(v.Message, "TFA TOTP setup required") { lh.TFACodeRequired = true lh.state <- LoginStateTFASetup } else if lh.errorContains(v.Message, "TFA TOTP code required") { - lh.Logger.Debugf("TFA code is required. server response: %s", v.Message) + slog.Debug("TFA code is required", "serverResponse", v.Message) lh.TFACodeRequired = true lh.state <- LoginStateNoAuth } else if lh.errorContains(v.Message, "User has been logged out") { - lh.Logger.Warning("User had been logged out. Clearing token and trying again") + slog.Warn("User had been logged out. Clearing token and trying again") lh.state <- LoginStateNoAuth lh.C8Yclient.ClearToken() lh.onSave() @@ -608,7 +601,7 @@ func (lh *LoginHandler) verify() { lh.LoginType = c8y.LoginTypeBasic lh.state <- LoginStateVerify } else if lh.errorContains(v.Message, "Bad credentials") || lh.errorContains(v.Message, "Invalid credentials") { - lh.Logger.Infof("Bad credentials, using auth method: %s", lh.C8Yclient.AuthorizationType) + slog.Info(fmt.Sprintf("Bad credentials, using auth method: %s", lh.C8Yclient.AuthorizationType)) // try resetting the tenant (in case if it is incorrect) lh.C8Yclient.TenantName = "" @@ -631,7 +624,7 @@ func (lh *LoginHandler) verify() { lh.state <- LoginStateNoAuth } else { lh.state <- LoginStateAuth - lh.Logger.Infof("Detected tenant: %s", tenant.Name) + slog.Info(fmt.Sprintf("Detected tenant: %s", tenant.Name)) lh.C8Yclient.TenantName = tenant.Name // Get Cumulocity system version @@ -639,18 +632,18 @@ func (lh *LoginHandler) verify() { if version, err := lh.C8Yclient.TenantOptions.GetVersion(context.Background()); err == nil { lh.C8Yclient.Version = version } else { - lh.Logger.Warnf("Could not get Cumulocity System version. %s", err) + slog.Warn("Could not get Cumulocity System version", "err", err) } } // Get user information if not set (e.g. external SSO tokens don't generally include the username in a known field) if lh.C8Yclient.Username == "" { - lh.Logger.Infof("Getting the current user's information as the username is not set") + slog.Info("Getting the current user's information as the username is not set") currentUser, _, err := lh.C8Yclient.User.GetCurrentUser(context.Background()) if err != nil { - lh.Logger.Warnf("Could not get username. %s", err) + slog.Warn("Could not get username", "err", err) } else { - lh.Logger.Infof("Found current username. %s", currentUser.Username) + slog.Info("Found current username", "value", currentUser.Username) lh.C8Yclient.Username = currentUser.Username } } @@ -661,13 +654,13 @@ func (lh *LoginHandler) verify() { func (lh LoginHandler) writeMessage(m string) { if _, err := io.WriteString(lh.Writer, m); err != nil { - lh.Logger.Warnf("Failed to write message. %s", err) + slog.Warn("Failed to write message", "err", err) } } func (lh LoginHandler) writeMessageF(format string, a interface{}) { if _, err := io.WriteString(lh.Writer, fmt.Sprintf(format, a)); err != nil { - lh.Logger.Warnf("Failed to write message. %s", err) + slog.Warn("Failed to write message", "err", err) } } @@ -688,11 +681,11 @@ func (lh *LoginHandler) setupTFA() error { }) if err != nil { - lh.Logger.Infof("Could not get tot") + slog.Info("Could not get totp") return err } - lh.Logger.Infof("secret-raw: %s", resp.String()) + slog.Info(fmt.Sprintf("secret-raw: %s", resp.String())) secretQRURL := "" if v := resp.JSON("secretQrUrl"); v.Exists() { diff --git a/pkg/c8ysession/c8ysession.go b/pkg/c8ysession/c8ysession.go index 1f62ce712..d84228c29 100644 --- a/pkg/c8ysession/c8ysession.go +++ b/pkg/c8ysession/c8ysession.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "io" + "log/slog" "net/url" "os" "strings" @@ -12,7 +13,6 @@ import ( "github.com/fatih/color" "github.com/golang-jwt/jwt/v5" "github.com/reubenmiller/go-c8y-cli/v2/pkg/config" - "github.com/reubenmiller/go-c8y-cli/v2/pkg/logger" "github.com/reubenmiller/go-c8y-cli/v2/pkg/utilities" "github.com/reubenmiller/go-c8y/pkg/c8y" ) @@ -53,7 +53,6 @@ type CumulocitySession struct { // How to identify the session SessionUri string `json:"sessionUri,omitempty"` - Logger *logger.Logger `json:"-"` Config *config.Config `json:"-"` } @@ -96,9 +95,7 @@ func (s CumulocitySession) GetPassword() string { pass, err := s.Config.SecureData.TryDecryptString(s.Password, s.GetSessionPassphrase()) if err != nil { - if s.Logger != nil { - s.Logger.Errorf("Could not decrypt password. %s", err) - } + slog.Error("Could not decrypt password", "err", err) return "" } @@ -263,7 +260,7 @@ func GetVariablesFromSession(session *CumulocitySession, cfg *config.Config, cli } cache := cfg.CachePassphraseVariables() - cfg.Logger.Debugf("Cache passphrase: %v", cache) + slog.Debug("Cache passphrase", "value", cache) if cache { if cfg.Passphrase != "" { output[config.EnvPassphrase] = cfg.Passphrase @@ -338,7 +335,7 @@ func IsSessionFilePath(path string) bool { return !strings.Contains(path, "://") } -func shouldRenewToken(log *logger.Logger, t string, validFor time.Duration) (bool, *time.Time) { +func shouldRenewToken(t string, validFor time.Duration) (bool, *time.Time) { claims := jwt.RegisteredClaims{} parser := jwt.NewParser() _, _, err := parser.ParseUnverified(t, &claims) @@ -353,7 +350,7 @@ func shouldRenewToken(log *logger.Logger, t string, validFor time.Duration) (boo if claims.ExpiresAt != nil && claims.IssuedAt != nil { tokenValidityPeriod := claims.ExpiresAt.Sub(claims.IssuedAt.Time) if tokenValidityPeriod < validFor { - log.Warnf("SSO token validity period is less than the given token validFor, so the token will be used regardless. minimumValidFor=%v, tokenValidity=%v", validFor, tokenValidityPeriod) + slog.Warn("SSO token validity period is less than the given token validFor, so the token will be used regardless", "minimumValidFor", validFor, "tokenValidity", tokenValidityPeriod) return false, nil } } @@ -367,7 +364,7 @@ func shouldRenewToken(log *logger.Logger, t string, validFor time.Duration) (boo } // ShouldReuseToken checks if the token should be reused or not -func ShouldReuseToken(cfg *config.Config, log *logger.Logger, token string, loginType string) bool { +func ShouldReuseToken(cfg *config.Config, token string, loginType string) bool { if loginType != "" && !LoginTypeRequiresToken(loginType) { return false } @@ -378,20 +375,20 @@ func ShouldReuseToken(cfg *config.Config, log *logger.Logger, token string, logi // Check if token is valid for the minimum period shouldBeValidFor := cfg.TokenValidFor() - expiresSoon, expiresAt := shouldRenewToken(log, token, shouldBeValidFor) + expiresSoon, expiresAt := shouldRenewToken(token, shouldBeValidFor) if expiresAt != nil { if time.Now().After(*expiresAt) { - log.Infof("Token has expired. tokenExpiresAt=%s", expiresAt.Format(time.RFC3339)) + slog.Info("Token has expired", "tokenExpiresAt", expiresAt.Format(time.RFC3339)) reuse = false } else if expiresSoon { - log.Warnf("Ignoring existing token as it will expire soon. minimumValidFor=%s, tokenExpiresAt=%s", shouldBeValidFor, expiresAt.Format(time.RFC3339)) + slog.Warn("Ignoring existing token as it will expire soon", "minimumValidFor", shouldBeValidFor, "tokenExpiresAt", expiresAt.Format(time.RFC3339)) reuse = false } else { - log.Infof("Token expiresAt: %s", expiresAt.Format(time.RFC3339)) + slog.Info(fmt.Sprintf("Token expiresAt: %s", expiresAt.Format(time.RFC3339))) } } else { - log.Infof("Ignoring invalid token") + slog.Info("Ignoring invalid token") reuse = false } return reuse diff --git a/pkg/c8ysubscribe/c8ysubscribe.go b/pkg/c8ysubscribe/c8ysubscribe.go index ccf671ddc..c94f5cd25 100644 --- a/pkg/c8ysubscribe/c8ysubscribe.go +++ b/pkg/c8ysubscribe/c8ysubscribe.go @@ -2,6 +2,7 @@ package c8ysubscribe import ( "fmt" + "log/slog" "os" "os/signal" "strings" @@ -9,7 +10,6 @@ import ( "time" "github.com/reubenmiller/go-c8y-cli/v2/pkg/jsonUtilities" - "github.com/reubenmiller/go-c8y-cli/v2/pkg/logger" "github.com/reubenmiller/go-c8y/pkg/c8y" ) @@ -29,7 +29,7 @@ type Options struct { } // Subscribe subscribe to a single channel -func Subscribe(client *c8y.Client, log *logger.Logger, channelPattern string, opts Options) (err error) { +func Subscribe(client *c8y.Client, channelPattern string, opts Options) (err error) { defer func() { if r := recover(); r != nil { err = fmt.Errorf("could not create realtime client. %s", r) @@ -37,7 +37,7 @@ func Subscribe(client *c8y.Client, log *logger.Logger, channelPattern string, op }() if err := client.Realtime.Connect(); err != nil { - log.Errorf("Could not connect to /cep/realtime. %s", err) + slog.Error("Could not connect to /cep/realtime", "err", err) return err } @@ -47,7 +47,7 @@ func Subscribe(client *c8y.Client, log *logger.Logger, channelPattern string, op signalCh := make(chan os.Signal, 1) signal.Notify(signalCh, os.Interrupt) - log.Infof("Listening to subscriptions: %s", channelPattern) + slog.Info("Listening to subscriptions", "value", channelPattern) client.Realtime.Subscribe(channelPattern, msgCh) @@ -65,7 +65,7 @@ func Subscribe(client *c8y.Client, log *logger.Logger, channelPattern string, op for { select { case <-timeoutCh: - log.Info("Duration has expired. Stopping realtime client") + slog.Info("Duration has expired. Stopping realtime client") return nil case msg := <-msgCh: if actionTypes == "" || strings.Contains(actionTypes, strings.ToLower(msg.Payload.RealtimeAction)) { @@ -82,14 +82,14 @@ func Subscribe(client *c8y.Client, log *logger.Logger, channelPattern string, op case <-signalCh: // Enable ctrl-c to stop - log.Info("Stopping realtime client") + slog.Info("Stopping realtime client") return nil } } } // SubscribeMultiple subscribe to multiple channels -func SubscribeMultiple(client *c8y.Client, log *logger.Logger, channelPatterns []string, opts Options) (err error) { +func SubscribeMultiple(client *c8y.Client, channelPatterns []string, opts Options) (err error) { defer func() { if r := recover(); r != nil { err = fmt.Errorf("could not create realtime client. %s", r) @@ -97,7 +97,7 @@ func SubscribeMultiple(client *c8y.Client, log *logger.Logger, channelPatterns [ }() if err := client.Realtime.Connect(); err != nil { - log.Errorf("Could not connect to /cep/realtime. %s", err) + slog.Error("Could not connect to /cep/realtime", "err", err) return nil } @@ -108,7 +108,7 @@ func SubscribeMultiple(client *c8y.Client, log *logger.Logger, channelPatterns [ signal.Notify(signalCh, os.Interrupt) for _, pattern := range channelPatterns { - log.Infof("Listening to subscriptions: %s", pattern) + slog.Info("Listening to subscriptions", "value", pattern) client.Realtime.Subscribe(pattern, msgCh) } @@ -125,7 +125,7 @@ func SubscribeMultiple(client *c8y.Client, log *logger.Logger, channelPatterns [ for { select { case <-timeoutCh: - log.Info("Duration has expired. Stopping realtime client") + slog.Info("Duration has expired. Stopping realtime client") return nil case msg := <-msgCh: @@ -144,7 +144,7 @@ func SubscribeMultiple(client *c8y.Client, log *logger.Logger, channelPatterns [ case <-signalCh: // Enable ctrl-c to stop - log.Info("Stopping realtime client") + slog.Info("Stopping realtime client") return nil } } diff --git a/pkg/cmd/activitylog/list/list.manual.go b/pkg/cmd/activitylog/list/list.manual.go index 53b115153..e073961fe 100644 --- a/pkg/cmd/activitylog/list/list.manual.go +++ b/pkg/cmd/activitylog/list/list.manual.go @@ -1,6 +1,7 @@ package list import ( + "log/slog" "net/url" "strings" @@ -105,7 +106,7 @@ func (n *CmdList) RunE(cmd *cobra.Command, args []string) error { } else { filter.Host = u.Host } - cfg.Logger.Debugf("activity log filter: path=%s, host=%s, datefrom=%s, dateto=%s", activitylog.GetPath(), filter.Host, filter.DateFrom, filter.DateTo) + slog.Debug("activity log filter", "path", activitylog.GetPath(), "host", filter.Host, "datefrom", filter.DateFrom, "dateto", filter.DateTo) err = activitylog.GetLogEntries(filter, func(line []byte) error { return n.factory.WriteOutputWithoutPropertyGuess(line, cmdutil.OutputContext{}) diff --git a/pkg/cmd/alarms/subscribe/subscribe.manual.go b/pkg/cmd/alarms/subscribe/subscribe.manual.go index bb5e0413d..cb7db6de3 100644 --- a/pkg/cmd/alarms/subscribe/subscribe.manual.go +++ b/pkg/cmd/alarms/subscribe/subscribe.manual.go @@ -74,10 +74,6 @@ func (n *CmdSubscribe) RunE(cmd *cobra.Command, args []string) error { if err != nil { return err } - log, err := n.factory.Logger() - if err != nil { - return err - } inputIterators, err := cmdutil.NewRequestInputIterators(cmd, cfg) if err != nil { return err @@ -114,5 +110,5 @@ func (n *CmdSubscribe) RunE(cmd *cobra.Command, args []string) error { return n.factory.WriteOutputWithoutPropertyGuess([]byte(msg), cmdutil.OutputContext{}) }, } - return c8ysubscribe.Subscribe(client, log, c8y.RealtimeAlarms(device), opts) + return c8ysubscribe.Subscribe(client, c8y.RealtimeAlarms(device), opts) } diff --git a/pkg/cmd/api/api.manual.go b/pkg/cmd/api/api.manual.go index 75bf5c64e..5bef69893 100644 --- a/pkg/cmd/api/api.manual.go +++ b/pkg/cmd/api/api.manual.go @@ -3,6 +3,7 @@ package api import ( "fmt" "io" + "log/slog" "net/http" "net/url" "strings" @@ -214,7 +215,7 @@ func (n *CmdAPI) RunE(cmd *cobra.Command, args []string) error { // path. if err != nil { - cfg.Logger.Warn("something is not being detected") + slog.Warn("something is not being detected") return err } diff --git a/pkg/cmd/applications/createhostedapplication/createHostedApplication.manual.go b/pkg/cmd/applications/createhostedapplication/createHostedApplication.manual.go index 4939c2f10..ec5ac72ac 100644 --- a/pkg/cmd/applications/createhostedapplication/createHostedApplication.manual.go +++ b/pkg/cmd/applications/createhostedapplication/createHostedApplication.manual.go @@ -7,6 +7,7 @@ import ( "encoding/json" "fmt" "io" + "log/slog" "os" "path/filepath" "strings" @@ -19,7 +20,6 @@ import ( "github.com/reubenmiller/go-c8y-cli/v2/pkg/cmdutil" "github.com/reubenmiller/go-c8y-cli/v2/pkg/completion" "github.com/reubenmiller/go-c8y-cli/v2/pkg/flags" - "github.com/reubenmiller/go-c8y-cli/v2/pkg/logger" "github.com/reubenmiller/go-c8y-cli/v2/pkg/zipUtilities" "github.com/reubenmiller/go-c8y/pkg/c8y" "github.com/spf13/cobra" @@ -114,7 +114,7 @@ func NewCmdCreateHostedApplication(f *cmdutil.Factory) *CmdCreateHostedApplicati return ccmd } -func (n *CmdCreateHostedApplication) getApplicationDetails(log *logger.Logger) (*Application, error) { +func (n *CmdCreateHostedApplication) getApplicationDetails() (*Application, error) { app := Application{} @@ -125,14 +125,14 @@ func (n *CmdCreateHostedApplication) getApplicationDetails(log *logger.Logger) ( if strings.EqualFold(filepath.Ext(n.file), ".zip") { // Try loading manifest file directly from the zip (without unzipping it) - log.Infof("Trying to detect manifest from a zip file. path=%s", n.file) + slog.Info("Trying to detect manifest from a zip file", "path", n.file) if err := GetManifestContents(n.file, &app.Manifest); err != nil { - log.Infof("Could not find manifest file. Expected %s to contain %s. %s", n.file, CumulocityManifestFile, err) + slog.Info(fmt.Sprintf("Could not find manifest file. Expected %s to contain %s. %s", n.file, CumulocityManifestFile, err)) } } else if n.file != "" { // Assume json (regardless of file type) manifestPath := filepath.Join(n.file, CumulocityManifestFile) - log.Infof("Assuming file is json (regardless of file extension). path=%s", manifestPath) + slog.Info("Assuming file is json (regardless of file extension)", "path", manifestPath) if _, err := os.Stat(manifestPath); err == nil { jsonFile, err := os.Open(manifestPath) @@ -142,7 +142,7 @@ func (n *CmdCreateHostedApplication) getApplicationDetails(log *logger.Logger) ( byteValue, _ := io.ReadAll(jsonFile) if err := json.Unmarshal(byteValue, &app.Manifest); err != nil { - log.Warnf("invalid manifest file. Only json or zip files are accepted. %s", strings.TrimSpace(err.Error())) + slog.Warn("invalid manifest file. Only json or zip files are accepted", "err", strings.TrimSpace(err.Error())) } } } @@ -219,9 +219,7 @@ func (n *CmdCreateHostedApplication) packageAppIfRequired(src string) (zipFile s return } - if log, err := n.factory.Logger(); log != nil && err == nil { - log.Infof("zipping folder %s", src) - } + slog.Info("zipping folder", "path", src) zipFile, err = n.packageWebApplication(src) if err != nil { @@ -239,24 +237,20 @@ func (n *CmdCreateHostedApplication) RunE(cmd *cobra.Command, args []string) err if err != nil { return err } - log, err := n.factory.Logger() - if err != nil { - return err - } var application *c8y.Application var response *c8y.Response var applicationID string // note: use POST when checking if it should use try run or not, even though it could actually be PUT as well dryRun := cfg.ShouldUseDryRun(cmd.CommandPath()) - appDetails, err := n.getApplicationDetails(log) + appDetails, err := n.getApplicationDetails() if err != nil { return err } // TODO: Use the default name value from n.Name rather then reading it from the args again. - log.Infof("application name: %s", appDetails.Name) + slog.Info(fmt.Sprintf("application name: %s", appDetails.Name)) if appDetails.Name != "" { refs, err := c8yfetcher.FindHostedApplications(n.factory, []string{appDetails.Name}, true, "", true) @@ -271,7 +265,7 @@ func (n *CmdCreateHostedApplication) RunE(cmd *cobra.Command, args []string) err if applicationID == "" { // Create the application - log.Info("Creating new application") + slog.Info("Creating new application") application, response, err = client.Application.Create(context.Background(), &appDetails.Application) if err != nil { @@ -280,7 +274,7 @@ func (n *CmdCreateHostedApplication) RunE(cmd *cobra.Command, args []string) err applicationID = application.ID } else { // Get existing application - log.Infof("Getting existing application. id=%s", applicationID) + slog.Info("Getting existing application", "id", applicationID) application, response, err = client.Application.GetApplication( c8y.WithDisabledDryRunContext(context.Background()), applicationID, @@ -299,11 +293,11 @@ func (n *CmdCreateHostedApplication) RunE(cmd *cobra.Command, args []string) err if !dryRun { zipfile, err := n.packageAppIfRequired(n.file) if err != nil { - log.Errorf("Failed to package file. %s", err) + slog.Error("Failed to package file", "err", err) return fmt.Errorf("failed to package app. %s", err) } - log.Infof("uploading binary [app=%s]", application.ID) + slog.Info("uploading binary", "appId", application.ID) progress := n.factory.IOStreams.ProgressIndicator() resp, err := c8ybinary.CreateBinaryWithProgress( context.Background(), @@ -326,7 +320,7 @@ func (n *CmdCreateHostedApplication) RunE(cmd *cobra.Command, args []string) err // App activation (only if a new version was uploaded) if !skipUpload && !n.skipActivation { if !dryRun { - log.Infof("Activating application") + slog.Info("Activating application") if applicationBinaryID == "" { return fmt.Errorf("failed to activate new application version because binary id is empty") @@ -342,7 +336,7 @@ func (n *CmdCreateHostedApplication) RunE(cmd *cobra.Command, args []string) err if err != nil { if resp != nil && resp.StatusCode() == 409 { - log.Infof("application is already enabled") + slog.Info("application is already enabled") } else { return fmt.Errorf("failed to activate application. %s", err) } diff --git a/pkg/cmd/applications/open/open.manual.go b/pkg/cmd/applications/open/open.manual.go index f337989d4..5e258cf97 100644 --- a/pkg/cmd/applications/open/open.manual.go +++ b/pkg/cmd/applications/open/open.manual.go @@ -3,6 +3,7 @@ package open import ( "fmt" "io" + "log/slog" "net/url" "strings" @@ -186,7 +187,7 @@ func (n *OpenCmd) RunE(cmd *cobra.Command, args []string) error { } else if cfg.DryRun() { fmt.Fprintf(n.factory.IOStreams.Out, "WHATIF: open %s in default browser\n", currentURL.String()) } else { - cfg.Logger.Infof("Opening web app: %s", currentURL.String()) + slog.Info("Opening web app", "name", currentURL.String()) if err := n.factory.Browser.Browse(currentURL.String()); err != nil { return err } diff --git a/pkg/cmd/cli/install/install.manual.go b/pkg/cmd/cli/install/install.manual.go index 8ff112250..77f1e44e3 100644 --- a/pkg/cmd/cli/install/install.manual.go +++ b/pkg/cmd/cli/install/install.manual.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "io/fs" + "log/slog" "os" "path/filepath" "strings" @@ -76,11 +77,6 @@ type Shell struct { } func (n *CmdInstall) RunE(cmd *cobra.Command, args []string) error { - cfg, err := n.factory.Config() - if err != nil { - return err - } - shells := map[string]Shell{ shell.ShellBash: {Name: "bash", Binary: "bash"}, shell.ShellZsh: {Name: "zsh", Binary: "zsh"}, @@ -98,15 +94,15 @@ func (n *CmdInstall) RunE(cmd *cobra.Command, args []string) error { for _, name := range n.shell { shell, ok := shells[name] if !ok { - cfg.Logger.Warningf("Skipping invalid shell type. name=%s", name) + slog.Warn("Skipping invalid shell type", "name", name) continue } - cfg.Logger.Debugf("Checking shell. name=%s, shell=%s", shell.Name, shell.Binary) + slog.Debug("Checking shell", "name", shell.Name, "shell", shell.Binary) if _, err := safeexec.LookPath(shell.Binary); err == nil { detectedShells = append(detectedShells, shell) } else { - cfg.Logger.Debugf("Shell was not found. name=%s, shell=%s", shell.Name, shell.Binary) + slog.Debug("Shell was not found", "name", shell.Name, "shell", shell.Binary) } } @@ -165,10 +161,6 @@ func addExecutableBinaryToPowerShellPath(existingSnippet string) (string, error) func (n *CmdInstall) InstallProfile(shellType string) (bool, error) { changed := false - cfg, err := n.factory.Config() - if err != nil { - return changed, err - } profilePath := "" profileSnippet := "" @@ -204,14 +196,14 @@ func (n *CmdInstall) InstallProfile(shellType string) (bool, error) { if snippet, err := addExecutableBinaryToPowerShellPath("c8y cli profile --shell powershell | Out-String | Invoke-Expression"); err == nil { profileSnippet = snippet } else { - cfg.Logger.Warnf("Failed to detect binary path. %s", err) + slog.Warn("Failed to detect binary path", "err", err) } case shell.ShellPwsh: profilePath = "~/.config/powershell/Microsoft.PowerShell_profile.ps1" if snippet, err := addExecutableBinaryToPowerShellPath("c8y cli profile --shell powershell | Out-String | Invoke-Expression"); err == nil { profileSnippet = snippet } else { - cfg.Logger.Warnf("Failed to detect binary path. %s", err) + slog.Warn("Failed to detect binary path", "err", err) } } @@ -247,7 +239,7 @@ func (n *CmdInstall) InstallProfile(shellType string) (bool, error) { } } } else { - cfg.Logger.Debugf("Profile file does not exist. path=%s", expandedV) + slog.Debug("Profile file does not exist", "path", expandedV) } cs := n.factory.IOStreams.ColorScheme() @@ -255,14 +247,14 @@ func (n *CmdInstall) InstallProfile(shellType string) (bool, error) { if appendToProfile { message = fmt.Sprintf("Added snippet to %s profile. path: %s", shellType, expandedV) - cfg.Logger.Debugf("Adding snippet to %s", expandedV) + slog.Debug("Adding snippet to profile", "path", expandedV) err = AppendToFile(expandedV, profileSnippet) if err != nil { return changed, err } changed = true } else { - cfg.Logger.Debugf("Snippet already found in profile. path=%s", expandedV) + slog.Debug("Snippet already found in profile", "path", expandedV) message = fmt.Sprintf("Already added snippet to %s profile. path: %s", shellType, expandedV) } diff --git a/pkg/cmd/cmd.go b/pkg/cmd/cmd.go index e7a029e40..dca8380c2 100644 --- a/pkg/cmd/cmd.go +++ b/pkg/cmd/cmd.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "log" + "log/slog" "os" "os/exec" "strings" @@ -23,12 +24,8 @@ import ( "github.com/reubenmiller/go-c8y-cli/v2/pkg/logger" "github.com/spf13/cobra" "github.com/spf13/viper" - "go.uber.org/zap/zapcore" ) -// Logger is used to record the log messages which should be visible to the user when using the verbose flag -var Logger *logger.Logger - // Build data // These variables should be set using the -ldflags "-X github.com/reubenmiller/go-c8y-cli/v2/pkg/cmd.version=1.0.0" when running go build var buildVersion string @@ -39,8 +36,6 @@ const ( ) func init() { - Logger = logger.NewLogger(module, logger.Options{}) - // Enable case insensitive matches cobra.EnableCaseInsensitive = true } @@ -55,7 +50,7 @@ func MainRun() { // Expand any aliases expandedArgs, err := setArgs(rootCmd.Command, rootCmd.Factory) if err != nil { - Logger.Errorf("Could not expand aliases. %s", err) + slog.Error("Could not expand aliases", "err", err) os.Exit(int(cmderrors.ExitInvalidAlias)) } @@ -114,7 +109,7 @@ func MainRun() { return results, cobra.ShellCompDirectiveNoFileComp } - Logger.Debugf("Expanded args: %v", expandedArgs) + slog.Debug("Expanded args", "value", expandedArgs) rootCmd.SetArgs(expandedArgs) if err := rootCmd.Execute(); err != nil { @@ -141,10 +136,6 @@ func CheckCommandError(cmd *cobra.Command, f *cmdutil.Factory, err error) error if configErr != nil { log.Fatalf("Could not load configuration. %s", configErr) } - logg, logErr := f.Logger() - if logErr != nil { - log.Fatalf("Could not configure logger. %s", logErr) - } w := io.Discard if errors.Is(err, cmderrors.ErrHelp) { @@ -153,7 +144,7 @@ func CheckCommandError(cmd *cobra.Command, f *cmdutil.Factory, err error) error if iterator.IsEmptyPipeInputError(err) && !cfg.AllowEmptyPipe() { // Ignore empty pipe errors - logg.Debug("detected empty piped data") + slog.Debug("detected empty piped data") return cmderrors.NewUserErrorWithExitCode(cmderrors.ExitOK) } @@ -179,7 +170,7 @@ func CheckCommandError(cmd *cobra.Command, f *cmdutil.Factory, err error) error if cErr, ok := err.(cmderrors.CommandError); ok { if cErr.StatusCode == 403 || cErr.StatusCode == 401 { - logg.Error(fmt.Sprintf("Authentication failed (statusCode=%d). Try to run set-session again, or check the password", cErr.StatusCode)) + slog.Error(fmt.Sprintf("Authentication failed (statusCode=%d). Try to run set-session again, or check the password", cErr.StatusCode)) } // format errors as json messages @@ -191,16 +182,16 @@ func CheckCommandError(cmd *cobra.Command, f *cmdutil.Factory, err error) error if !cErr.IsSilent() && !strings.Contains(silentStatusCodes, fmt.Sprintf("%d", cErr.StatusCode)) { if !cErr.Processed { - logg.Errorf("%s", cErr) + slog.Error(cErr.Error()) fmt.Fprintf(w, "%s\n", cErr.JSONString()) } else { - logg.Debugf("Error has already been logged. %s", cErr) + slog.Debug("Error has already been logged", "err", cErr) } } } else { // unexpected error cErr := cmderrors.NewSystemErrorF("%s", err) - logg.Errorf("%s", cErr) + slog.Error(cErr.Error()) fmt.Fprintf(w, "%s\n", cErr.JSONString()) } return err @@ -241,7 +232,7 @@ func setArgs(cmd *cobra.Command, cmdFactory *cmdutil.Factory) ([]string, error) return nil, err } - Logger.Debugf("%v -> %v", originalArgs, expandedArgs) + slog.Debug("Command arguments", "before", originalArgs, "after", expandedArgs) if isShell { exe, err := safeexec.LookPath(expandedArgs[0]) @@ -284,7 +275,7 @@ func setArgs(cmd *cobra.Command, cmdFactory *cmdutil.Factory) ([]string, error) func getOutputHeaders(c *console.Console, cfg *config.Config, input []string) (headers []byte) { if !c.IsCSV() || !c.WithCSVHeader() || len(input) == 0 { - Logger.Debugf("Ignoring csv headers: isCSV=%v, WithHeader=%v", c.IsCSV(), c.WithCSVHeader()) + slog.Debug("Ignoring csv headers", "isCSV", c.IsCSV(), "withHeader", c.WithCSVHeader()) return } if len(input) > 0 { @@ -312,16 +303,16 @@ func getOutputHeaders(c *console.Console, cfg *config.Config, input []string) (h // the configuration and extensions etc. func GetInitLoggerOptions(args []string) logger.Options { color := true - level := zapcore.WarnLevel + level := slog.LevelWarn debug := false for _, item := range args { switch item { case "--debug", "--debug=true": - level = zapcore.DebugLevel + level = slog.LevelDebug debug = true case "--verbose", "-v", "--verbose=true": - level = zapcore.InfoLevel + level = slog.LevelInfo case "--noColor", "--noColor=true", "-M", "-M=true": color = false } diff --git a/pkg/cmd/deviceregistration/registerBulk/register-basic.manual.go b/pkg/cmd/deviceregistration/registerBulk/register-basic.manual.go index dbf946a47..85caacb71 100644 --- a/pkg/cmd/deviceregistration/registerBulk/register-basic.manual.go +++ b/pkg/cmd/deviceregistration/registerBulk/register-basic.manual.go @@ -86,11 +86,6 @@ func (n *RegisterBasicCmd) RunE(cmd *cobra.Command, args []string) error { return err } - llog, err := n.factory.Logger() - if err != nil { - return err - } - c8yclient, err := n.factory.Client() if err != nil { return err @@ -155,7 +150,6 @@ func (n *RegisterBasicCmd) RunE(cmd *cobra.Command, args []string) error { return n.factory.RunWithGenericWorkers(cmd, inputIterators, iter, RunBulkRegistrationJob(cmd, &RegistrationOptions{ Config: cfg, - Log: llog, Client: c8yclient, Factory: n.factory, CommonOptions: commonOptions, diff --git a/pkg/cmd/deviceregistration/registerBulk/register-ca.manual.go b/pkg/cmd/deviceregistration/registerBulk/register-ca.manual.go index 6884889e3..ea29d4fc8 100644 --- a/pkg/cmd/deviceregistration/registerBulk/register-ca.manual.go +++ b/pkg/cmd/deviceregistration/registerBulk/register-ca.manual.go @@ -91,11 +91,6 @@ func (n *RegisterCumulocityCACmd) RunE(cmd *cobra.Command, args []string) error return err } - llog, err := n.factory.Logger() - if err != nil { - return err - } - c8yclient, err := n.factory.Client() if err != nil { return err @@ -160,7 +155,6 @@ func (n *RegisterCumulocityCACmd) RunE(cmd *cobra.Command, args []string) error return n.factory.RunWithGenericWorkers(cmd, inputIterators, iter, RunBulkRegistrationJob(cmd, &RegistrationOptions{ Config: cfg, - Log: llog, Client: c8yclient, Factory: n.factory, CommonOptions: commonOptions, diff --git a/pkg/cmd/deviceregistration/registerBulk/register-external-ca.go b/pkg/cmd/deviceregistration/registerBulk/register-external-ca.go index 02d0e8235..2a1924fb8 100644 --- a/pkg/cmd/deviceregistration/registerBulk/register-external-ca.go +++ b/pkg/cmd/deviceregistration/registerBulk/register-external-ca.go @@ -88,11 +88,6 @@ func (n *RegisterExternalCACmd) RunE(cmd *cobra.Command, args []string) error { return err } - llog, err := n.factory.Logger() - if err != nil { - return err - } - c8yclient, err := n.factory.Client() if err != nil { return err @@ -155,7 +150,6 @@ func (n *RegisterExternalCACmd) RunE(cmd *cobra.Command, args []string) error { return n.factory.RunWithGenericWorkers(cmd, inputIterators, iter, RunBulkRegistrationJob(cmd, &RegistrationOptions{ Config: cfg, - Log: llog, Client: c8yclient, Factory: n.factory, CommonOptions: commonOptions, diff --git a/pkg/cmd/deviceregistration/registerBulk/utils.go b/pkg/cmd/deviceregistration/registerBulk/utils.go index 15d606ae7..99f0568c7 100644 --- a/pkg/cmd/deviceregistration/registerBulk/utils.go +++ b/pkg/cmd/deviceregistration/registerBulk/utils.go @@ -7,13 +7,13 @@ import ( "encoding/json" "fmt" "io" + "log/slog" "net/http" "strings" "github.com/reubenmiller/go-c8y-cli/v2/pkg/cmderrors" "github.com/reubenmiller/go-c8y-cli/v2/pkg/cmdutil" "github.com/reubenmiller/go-c8y-cli/v2/pkg/config" - "github.com/reubenmiller/go-c8y-cli/v2/pkg/logger" "github.com/reubenmiller/go-c8y-cli/v2/pkg/randdata" "github.com/reubenmiller/go-c8y-cli/v2/pkg/worker" "github.com/reubenmiller/go-c8y/pkg/c8y" @@ -110,7 +110,6 @@ func CreateCSVPayload(b io.ReadWriter, input gjson.Result, mappings []PayloadMap type RegistrationOptions struct { Config *config.Config - Log *logger.Logger Client *c8y.Client Factory *cmdutil.Factory CommonOptions config.CommonCommandOptions @@ -149,7 +148,7 @@ func RunBulkRegistrationJob(cmd *cobra.Command, opts *RegistrationOptions, mappi body := response.Body() totalFailed := gjson.GetBytes(body, "numberOfFailed").Int() if totalFailed != 0 { - opts.Log.Infof("Response: %v", response) + slog.Info(fmt.Sprintf("Response: %v", response)) failuresReasons := make([]string, 0) response.JSON("failedCreationList").ForEach(func(key, value gjson.Result) bool { if v := value.Get("failureReason"); v.Exists() { @@ -161,7 +160,7 @@ func RunBulkRegistrationJob(cmd *cobra.Command, opts *RegistrationOptions, mappi }) return response, cmderrors.NewUserError(fmt.Sprintf("bulk registration has some failures. failed=%d, reasons=%v", totalFailed, failuresReasons)) } - opts.Log.Infof("Bulk registration was successful. %v", response) + slog.Info(fmt.Sprintf("Bulk registration was successful. %v", response)) // Lookup device id so that the command can be piped to downstream items identity, _, identityErr := opts.Client.Identity.GetExternalID(context.Background(), options.Get("external-type").String(), options.Get("id").String()) @@ -184,7 +183,7 @@ func RunBulkRegistrationJob(cmd *cobra.Command, opts *RegistrationOptions, mappi } contentType := response.Response.Header.Get("Content-Type") - opts.Log.Infof("API Content-Type: %s", contentType) + slog.Info(fmt.Sprintf("API Content-Type: %s", contentType)) err := opts.Factory.WriteOutput(outB, cmdutil.OutputContext{ Input: j.Input, diff --git a/pkg/cmd/events/subscribe/subscribe.manual.go b/pkg/cmd/events/subscribe/subscribe.manual.go index 623eaa681..d6fe95eb6 100644 --- a/pkg/cmd/events/subscribe/subscribe.manual.go +++ b/pkg/cmd/events/subscribe/subscribe.manual.go @@ -74,10 +74,6 @@ func (n *CmdSubscribe) RunE(cmd *cobra.Command, args []string) error { if err != nil { return err } - log, err := n.factory.Logger() - if err != nil { - return err - } inputIterators, err := cmdutil.NewRequestInputIterators(cmd, cfg) if err != nil { return err @@ -114,5 +110,5 @@ func (n *CmdSubscribe) RunE(cmd *cobra.Command, args []string) error { return n.factory.WriteOutputWithoutPropertyGuess([]byte(msg), cmdutil.OutputContext{}) }, } - return c8ysubscribe.Subscribe(client, log, c8y.RealtimeEvents(device), opts) + return c8ysubscribe.Subscribe(client, c8y.RealtimeEvents(device), opts) } diff --git a/pkg/cmd/extension/command.go b/pkg/cmd/extension/command.go index 3e665484e..a336601fb 100644 --- a/pkg/cmd/extension/command.go +++ b/pkg/cmd/extension/command.go @@ -27,7 +27,7 @@ var ExtPrefix = "c8y-" func NewCmdExtension(f *cmdutil.Factory) *cobra.Command { m := f.ExtensionManager io := f.IOStreams - prompter := prompt.NewPrompt(nil) + prompter := prompt.NewPrompt() extCmd := cobra.Command{ Use: "extensions", diff --git a/pkg/cmd/factory/c8yclient.go b/pkg/cmd/factory/c8yclient.go index b3797de7f..1f4e11054 100644 --- a/pkg/cmd/factory/c8yclient.go +++ b/pkg/cmd/factory/c8yclient.go @@ -4,6 +4,7 @@ import ( "context" "crypto/tls" "fmt" + "log/slog" "net/http" "os" "strings" @@ -14,7 +15,6 @@ import ( "github.com/reubenmiller/go-c8y-cli/v2/pkg/c8ysession" "github.com/reubenmiller/go-c8y-cli/v2/pkg/cmdutil" "github.com/reubenmiller/go-c8y-cli/v2/pkg/config" - "github.com/reubenmiller/go-c8y-cli/v2/pkg/logger" "github.com/reubenmiller/go-c8y-cli/v2/pkg/request" "github.com/reubenmiller/go-c8y/pkg/c8y" "github.com/spf13/viper" @@ -48,13 +48,11 @@ func WithCompression(enable bool) c8y.ClientOption { } // RetryLogger to customize the log messages produced by the http retry client -type RetryLogger struct { - l *logger.Logger -} +type RetryLogger struct{} -func (l RetryLogger) Printf(format string, args ...interface{}) { - format = strings.TrimPrefix(format, "[DEBUG] ") - l.l.Infof(format, args...) +func (l RetryLogger) Printf(msg string, args ...interface{}) { + msg = strings.TrimPrefix(msg, "[DEBUG] ") + slog.Info(msg, args...) } func CreateCumulocityClient(f *cmdutil.Factory, sessionFile, username, password string, disableEncryptionCheck bool) func() (*c8y.Client, error) { @@ -63,10 +61,6 @@ func CreateCumulocityClient(f *cmdutil.Factory, sessionFile, username, password if err != nil { return nil, err } - log, err := f.Logger() - if err != nil { - return nil, err - } consol, err := f.Console() if err != nil { return nil, err @@ -76,8 +70,8 @@ func CreateCumulocityClient(f *cmdutil.Factory, sessionFile, username, password os.Setenv(c8y.EnvVarLoggerHideSensitive, "true") } - log.Debug("Creating c8y client") - configureProxySettings(cfg, log) + slog.Debug("Creating c8y client") + configureProxySettings(cfg) internalHttpClient := c8y.NewHTTPClient( WithProxyDisabled(cfg.IgnoreProxy()), @@ -94,10 +88,10 @@ func CreateCumulocityClient(f *cmdutil.Factory, sessionFile, username, password retryClient.RetryMax = cfg.HTTPRetryMax() retryClient.RetryWaitMin = cfg.HTTPRetryWaitMin() retryClient.RetryWaitMax = cfg.HTTPRetryWaitMax() - retryClient.Logger = RetryLogger{l: log} + retryClient.Logger = RetryLogger{} retryClient.ErrorHandler = func(resp *http.Response, err error, numTries int) (*http.Response, error) { // Pass error back so that the activity log is processed - log.Warnf("Giving up after %d attempt/s. err=%s", numTries, err) + slog.Warn(fmt.Sprintf("Giving up after %d attempt/s", numTries), "err", err) if resp != nil && resp.Body != nil { defer resp.Body.Close() } @@ -111,7 +105,7 @@ func CreateCumulocityClient(f *cmdutil.Factory, sessionFile, username, password cacheBodyPaths := cfg.CacheBodyKeys() if len(cacheBodyPaths) > 0 { - log.Infof("Caching of body only includes paths: %s", strings.Join(cacheBodyPaths, ", ")) + slog.Info("Caching of body only includes paths", "paths", strings.Join(cacheBodyPaths, ", ")) } if cfg.CacheEnabled() && cfg.CacheTTL() > 0 { @@ -135,9 +129,9 @@ func CreateCumulocityClient(f *cmdutil.Factory, sessionFile, username, password if sessionFile != "" { // Do nothing } else { - log.Info("Binding authorization environment variables") + slog.Info("Binding authorization environment variables") if err := cfg.BindAuthorization(); err != nil { - log.Warnf("Failed to bind to authorization variables. %s", err) + slog.Warn("Failed to bind to authorization variables", "err", err) } } @@ -170,7 +164,7 @@ func CreateCumulocityClient(f *cmdutil.Factory, sessionFile, username, password c8yURLFromEnv := GetHostFromEnvironment() if c8yURL == "" && c8yURLFromEnv != "" { // Get url from env variable if it is empty - log.Debugf("Using URL from env variable. %s", c8yURLFromEnv) + slog.Debug("Using URL from env variable", "value", c8yURLFromEnv) c8yURL = c8yURLFromEnv } @@ -203,7 +197,6 @@ func CreateCumulocityClient(f *cmdutil.Factory, sessionFile, username, password IO: f.IOStreams, Client: client, Config: cfg, - Logger: log, Console: consol, HideSensitive: cfg.HideSensitiveInformationIfActive, } @@ -220,14 +213,14 @@ func CreateCumulocityClient(f *cmdutil.Factory, sessionFile, username, password authErrors = nil } - log.Infof("Using client auth type: %s", client.AuthorizationType.String()) + slog.Info("Using client auth type", "value", client.AuthorizationType.String()) if !disableEncryptionCheck && len(authErrors) > 0 { - log.Warnf("Could not load authentication. error=%v", authErrors[0]) + slog.Warn("Could not load authentication", "err", authErrors[0]) } timeout := cfg.RequestTimeout() - log.Debugf("timeout: %v", timeout) + slog.Debug("Request timeout setting", "value", timeout) // Should we use the tenant in the name or not if viper.IsSet("useTenantPrefix") { @@ -254,11 +247,11 @@ func CreateCumulocityClient(f *cmdutil.Factory, sessionFile, username, password if client.TenantName == "" { // Set the tenant either from token, or by looking it up as the tenant is required for a lot of API calls - log.Debug("Looking up tenant name as it is not set (it is required by some API)") + slog.Debug("Looking up tenant name as it is not set (it is required by some API)") client.TenantName = client.GetTenantName(c8y.WithDisabledDryRunContext(context.Background())) if client.TenantName == "" { - log.Info("Failed to lookup tenant name. API calls which require the tenant name will not work!") + slog.Info("Failed to lookup tenant name. API calls which require the tenant name will not work!") } } @@ -346,7 +339,7 @@ func WithProxyDisabled(disable bool) c8y.ClientOption { } } -func configureProxySettings(cfg *config.Config, log *logger.Logger) { +func configureProxySettings(cfg *config.Config) { // Proxy settings // Either use explicit proxy, ignore proxy, or use existing env variables @@ -357,14 +350,14 @@ func configureProxySettings(cfg *config.Config, log *logger.Logger) { proxy := cfg.Proxy() noProxy := cfg.IgnoreProxy() if noProxy { - log.Debug("using explicit noProxy setting") + slog.Debug("using explicit noProxy setting") os.Setenv("HTTP_PROXY", "") os.Setenv("HTTPS_PROXY", "") os.Setenv("http_proxy", "") os.Setenv("https_proxy", "") } else { if proxy != "" { - log.Debugf("using explicit proxy [%s]", proxy) + slog.Debug("using explicit proxy", "value", proxy) os.Setenv("HTTP_PROXY", proxy) os.Setenv("HTTPS_PROXY", proxy) @@ -382,7 +375,7 @@ func configureProxySettings(cfg *config.Config, log *logger.Logger) { } } if proxySettings.Len() > 0 { - log.Debugf("Using existing env variables.%s", proxySettings.String()) + slog.Debug("Using existing env variables", "value", proxySettings.String()) } } } diff --git a/pkg/cmd/factory/factory.go b/pkg/cmd/factory/factory.go index 3263258fd..d84fa6c79 100644 --- a/pkg/cmd/factory/factory.go +++ b/pkg/cmd/factory/factory.go @@ -14,11 +14,10 @@ import ( "github.com/reubenmiller/go-c8y-cli/v2/pkg/dataview" "github.com/reubenmiller/go-c8y-cli/v2/pkg/extensions" "github.com/reubenmiller/go-c8y-cli/v2/pkg/iostreams" - "github.com/reubenmiller/go-c8y-cli/v2/pkg/logger" "github.com/reubenmiller/go-c8y/pkg/c8y" ) -func New(appVersion string, buildBranch string, configFunc func() (*config.Config, error), clientFunc func() (*c8y.Client, error), loggerFunc func() (*logger.Logger, error), activityLoggerFunc func() (*activitylogger.ActivityLogger, error), dataViewFunc func() (*dataview.DataView, error), consoleFunc func() (*console.Console, error)) *cmdutil.Factory { +func New(appVersion string, buildBranch string, configFunc func() (*config.Config, error), clientFunc func() (*c8y.Client, error), activityLoggerFunc func() (*activitylogger.ActivityLogger, error), dataViewFunc func() (*dataview.DataView, error), consoleFunc func() (*console.Console, error)) *cmdutil.Factory { io := iostreams.System(false, true) c8yExecutable := "c8y" @@ -31,7 +30,6 @@ func New(appVersion string, buildBranch string, configFunc func() (*config.Confi Config: configFunc, Client: clientFunc, Executable: c8yExecutable, - Logger: loggerFunc, ActivityLogger: activityLoggerFunc, DataView: dataViewFunc, Console: consoleFunc, diff --git a/pkg/cmd/inventory/subscribe/subscribe.manual.go b/pkg/cmd/inventory/subscribe/subscribe.manual.go index 33265c8e4..737cdd44a 100644 --- a/pkg/cmd/inventory/subscribe/subscribe.manual.go +++ b/pkg/cmd/inventory/subscribe/subscribe.manual.go @@ -72,10 +72,6 @@ func (n *CmdSubscribe) RunE(cmd *cobra.Command, args []string) error { if err != nil { return err } - log, err := n.factory.Logger() - if err != nil { - return err - } inputIterators, err := cmdutil.NewRequestInputIterators(cmd, cfg) if err != nil { return err @@ -112,5 +108,5 @@ func (n *CmdSubscribe) RunE(cmd *cobra.Command, args []string) error { return n.factory.WriteOutputWithoutPropertyGuess([]byte(msg), cmdutil.OutputContext{}) }, } - return c8ysubscribe.Subscribe(client, log, c8y.RealtimeManagedObjects(device), opts) + return c8ysubscribe.Subscribe(client, c8y.RealtimeManagedObjects(device), opts) } diff --git a/pkg/cmd/inventory/wait/wait.manual.go b/pkg/cmd/inventory/wait/wait.manual.go index a3a09a77e..6809d0b1f 100644 --- a/pkg/cmd/inventory/wait/wait.manual.go +++ b/pkg/cmd/inventory/wait/wait.manual.go @@ -3,6 +3,7 @@ package wait import ( "fmt" "io" + "log/slog" "time" "github.com/MakeNowJust/heredoc/v2" @@ -136,7 +137,7 @@ func (n *CmdWait) RunE(cmd *cobra.Command, args []string) error { if err != nil { totalErrors++ - cfg.Logger.Infof("%s", err) + slog.Info(err.Error()) lastErr = err } } diff --git a/pkg/cmd/measurements/subscribe/subscribe.manual.go b/pkg/cmd/measurements/subscribe/subscribe.manual.go index 8bfe95763..0d2fb440b 100644 --- a/pkg/cmd/measurements/subscribe/subscribe.manual.go +++ b/pkg/cmd/measurements/subscribe/subscribe.manual.go @@ -74,10 +74,6 @@ func (n *CmdSubscribe) RunE(cmd *cobra.Command, args []string) error { if err != nil { return err } - log, err := n.factory.Logger() - if err != nil { - return err - } inputIterators, err := cmdutil.NewRequestInputIterators(cmd, cfg) if err != nil { return err @@ -114,5 +110,5 @@ func (n *CmdSubscribe) RunE(cmd *cobra.Command, args []string) error { return n.factory.WriteOutputWithoutPropertyGuess([]byte(msg), cmdutil.OutputContext{}) }, } - return c8ysubscribe.Subscribe(client, log, c8y.RealtimeMeasurements(device), opts) + return c8ysubscribe.Subscribe(client, c8y.RealtimeMeasurements(device), opts) } diff --git a/pkg/cmd/microservices/create/create.manual.go b/pkg/cmd/microservices/create/create.manual.go index 2a228ebc0..54f0881a2 100644 --- a/pkg/cmd/microservices/create/create.manual.go +++ b/pkg/cmd/microservices/create/create.manual.go @@ -7,6 +7,7 @@ import ( "encoding/json" "fmt" "io" + "log/slog" "os" "path/filepath" "strings" @@ -19,7 +20,6 @@ import ( "github.com/reubenmiller/go-c8y-cli/v2/pkg/cmderrors" "github.com/reubenmiller/go-c8y-cli/v2/pkg/cmdutil" "github.com/reubenmiller/go-c8y-cli/v2/pkg/flags" - "github.com/reubenmiller/go-c8y-cli/v2/pkg/logger" "github.com/reubenmiller/go-c8y/pkg/c8y" "github.com/spf13/cobra" ) @@ -105,7 +105,7 @@ Create or update an existing microservice using it's manifest file. This will se return ccmd } -func (n *CmdCreate) getApplicationDetails(log *logger.Logger) (*Application, error) { +func (n *CmdCreate) getApplicationDetails() (*Application, error) { app := Application{} @@ -117,13 +117,13 @@ func (n *CmdCreate) getApplicationDetails(log *logger.Logger) (*Application, err if strings.HasSuffix(n.file, ".zip") { // Try loading manifest file directly from the zip (without unzipping it) - log.Infof("Trying to detect manifest from a zip file. path=%s", n.file) + slog.Info("Trying to detect manifest from a zip file", "path", n.file) if err := GetManifestContents(n.file, &app.Manifest); err != nil { - log.Infof("Could not find manifest file. Expected %s to contain %s. %s", n.file, CumulocityManifestFile, err) + slog.Info(fmt.Sprintf("Could not find manifest file. Expected %s to contain %s. %s", n.file, CumulocityManifestFile, err)) } } else if n.file != "" { // Assume json (regardless of file type) - log.Infof("Assuming file is json (regardless of file extension). path=%s", n.file) + slog.Info("Assuming file is json (regardless of file extension)", "path", n.file) jsonFile, err := os.Open(n.file) if err != nil { return nil, err @@ -131,7 +131,7 @@ func (n *CmdCreate) getApplicationDetails(log *logger.Logger) (*Application, err byteValue, _ := io.ReadAll(jsonFile) if err := json.Unmarshal(byteValue, &app.Manifest); err != nil { - log.Warnf("invalid manifest file. Only json or zip files are accepted. %s", strings.TrimSpace(err.Error())) + slog.Warn("invalid manifest file. Only json or zip files are accepted", "err", strings.TrimSpace(err.Error())) } } @@ -184,17 +184,14 @@ func (n *CmdCreate) RunE(cmd *cobra.Command, args []string) error { if err != nil { return err } - log, err := n.factory.Logger() - if err != nil { - return err - } + var application *c8y.Application var response *c8y.Response var applicationID string var applicationName string dryRun := cfg.ShouldUseDryRun(cmd.CommandPath()) - applicationDetails, err := n.getApplicationDetails(log) + applicationDetails, err := n.getApplicationDetails() if err != nil { return err @@ -236,7 +233,7 @@ func (n *CmdCreate) RunE(cmd *cobra.Command, args []string) error { if applicationID == "" { // Create the application - log.Info("Creating new application") + slog.Info("Creating new application") application, response, err = client.Application.Create(context.Background(), &applicationDetails.Application) if err != nil { @@ -244,7 +241,7 @@ func (n *CmdCreate) RunE(cmd *cobra.Command, args []string) error { } } else { // Get existing application - log.Infof("Getting existing application. id=%s", applicationID) + slog.Info("Getting existing application", "id", applicationID) application, response, err = client.Application.GetApplication(context.Background(), applicationID) if err != nil { @@ -260,13 +257,13 @@ func (n *CmdCreate) RunE(cmd *cobra.Command, args []string) error { // Only upload zip files if !strings.HasSuffix(n.file, ".zip") { - log.Info("Skipping microservice binary upload") + slog.Info("Skipping microservice binary upload") skipUpload = true } // Upload binary if !skipUpload { - log.Infof("uploading binary [id=%s]", application.ID) + slog.Info("uploading binary", "id", application.ID) if !dryRun { progress := n.factory.IOStreams.ProgressIndicator() @@ -291,10 +288,13 @@ func (n *CmdCreate) RunE(cmd *cobra.Command, args []string) error { // will be hosted outside of the platform // // Read the Cumulocity.json file, and upload - log.Infof( - "updating application details [id=%s], requiredRoles=%s, roles=%s", + slog.Info( + "updating application details", + "id", application.ID, + "requiredRoles", strings.Join(applicationDetails.Manifest.RequiredRoles, ","), + "roles", strings.Join(applicationDetails.Manifest.Roles, ","), ) if !dryRun { @@ -310,15 +310,15 @@ func (n *CmdCreate) RunE(cmd *cobra.Command, args []string) error { // App subscription if !n.skipSubscription { - log.Infof("Subscribing to application") + slog.Info("Subscribing to application") if !dryRun { _, resp, err := client.Tenant.AddApplicationReference(context.Background(), client.TenantName, application.Self) if err != nil { if resp != nil && resp.StatusCode() == 409 { - log.Infof("microservice is already enabled") + slog.Info("microservice is already enabled") } else { - return fmt.Errorf("Failed to subscribe to application. %s", err) + return fmt.Errorf("failed to subscribe to application. %s", err) } } } diff --git a/pkg/cmd/microservices/serviceuser/create/create.manual.go b/pkg/cmd/microservices/serviceuser/create/create.manual.go index 9e3dfa32c..3e20359d8 100644 --- a/pkg/cmd/microservices/serviceuser/create/create.manual.go +++ b/pkg/cmd/microservices/serviceuser/create/create.manual.go @@ -3,6 +3,7 @@ package create import ( "context" "fmt" + "log/slog" "github.com/MakeNowJust/heredoc/v2" "github.com/reubenmiller/go-c8y-cli/v2/pkg/cmd/subcommand" @@ -84,10 +85,6 @@ func (n *CmdCreate) RunE(cmd *cobra.Command, args []string) error { if err != nil { return err } - log, err := n.factory.Logger() - if err != nil { - return err - } var application *c8y.Application var response *c8y.Response @@ -97,7 +94,7 @@ func (n *CmdCreate) RunE(cmd *cobra.Command, args []string) error { return cmderrors.NewUserError("Could not detect application name for the given input") } - log.Info("Creating new application") + slog.Info("Creating new application") application, response, err = client.Application.Create(context.Background(), applicationDetails) if err != nil { @@ -107,14 +104,14 @@ func (n *CmdCreate) RunE(cmd *cobra.Command, args []string) error { // App subscription if len(n.tenants) > 0 { for _, tenant := range n.tenants { - log.Infof("Subscribing to application in tenant %s", tenant) + slog.Info("Subscribing to application in tenant", "tenant", tenant) _, resp, err := client.Tenant.AddApplicationReference(context.Background(), tenant, application.Self) if err != nil { if resp != nil && resp.StatusCode() == 409 { - log.Infof("microservice is already enabled") + slog.Info("microservice is already enabled") } else { - return fmt.Errorf("Failed to subscribe to application. %s", err) + return fmt.Errorf("failed to subscribe to application. %s", err) } } } diff --git a/pkg/cmd/notification2/subscriptions/subscribe/subscribe.manual.go b/pkg/cmd/notification2/subscriptions/subscribe/subscribe.manual.go index 37a85103b..e6c110998 100644 --- a/pkg/cmd/notification2/subscriptions/subscribe/subscribe.manual.go +++ b/pkg/cmd/notification2/subscriptions/subscribe/subscribe.manual.go @@ -2,6 +2,7 @@ package subscribe import ( "context" + "log/slog" "os" "os/signal" "time" @@ -106,7 +107,6 @@ func (n *SubscribeCmd) RunE(cmd *cobra.Command, args []string) error { return err } - notification2.SetLogger(cfg.Logger) realtime, err := client.Notification2.CreateClient(context.Background(), c8y.Notification2ClientOptions{ Consumer: n.Consumer, Token: n.Token, @@ -152,7 +152,7 @@ func (n *SubscribeCmd) RunE(cmd *cobra.Command, args []string) error { for { select { case msg := <-messagesCh: - cfg.Logger.Infof("Received message: (id=%s, action=%s, description=%s) %s", msg.Identifier, msg.Action, msg.Description, msg.Payload) + slog.Info("Received message", "id", msg.Identifier, "action", msg.Action, "description", msg.Description, "payload", msg.Payload) if len(n.ActionTypes) == 0 { isMatch = true @@ -169,12 +169,12 @@ func (n *SubscribeCmd) RunE(cmd *cobra.Command, args []string) error { if isMatch { if err := n.factory.WriteOutputWithoutPropertyGuess(msg.Payload, cmdutil.OutputContext{}); err != nil { - cfg.Logger.Warnf("Could not process line. only json lines are accepted. %s", err) + slog.Warn("Could not process line. only json lines are accepted", "err", err) } } if err := realtime.SendMessageAck(msg.Identifier); err != nil { - cfg.Logger.Warnf("Failed to send ack. %s", err) + slog.Warn("Failed to send ack", "err", err) } case <-signalCh: realtime.Close() diff --git a/pkg/cmd/operations/subscribe/subscribe.manual.go b/pkg/cmd/operations/subscribe/subscribe.manual.go index 7cec28d9e..e09073943 100644 --- a/pkg/cmd/operations/subscribe/subscribe.manual.go +++ b/pkg/cmd/operations/subscribe/subscribe.manual.go @@ -72,10 +72,6 @@ func (n *CmdSubscribe) RunE(cmd *cobra.Command, args []string) error { if err != nil { return err } - log, err := n.factory.Logger() - if err != nil { - return err - } inputIterators, err := cmdutil.NewRequestInputIterators(cmd, cfg) if err != nil { return err @@ -112,5 +108,5 @@ func (n *CmdSubscribe) RunE(cmd *cobra.Command, args []string) error { return n.factory.WriteOutputWithoutPropertyGuess([]byte(msg), cmdutil.OutputContext{}) }, } - return c8ysubscribe.Subscribe(client, log, c8y.RealtimeOperations(device), opts) + return c8ysubscribe.Subscribe(client, c8y.RealtimeOperations(device), opts) } diff --git a/pkg/cmd/operations/wait/wait.manual.go b/pkg/cmd/operations/wait/wait.manual.go index 388d5c3fd..b51135a32 100644 --- a/pkg/cmd/operations/wait/wait.manual.go +++ b/pkg/cmd/operations/wait/wait.manual.go @@ -3,6 +3,7 @@ package wait import ( "fmt" "io" + "log/slog" "time" "github.com/MakeNowJust/heredoc/v2" @@ -124,7 +125,7 @@ func (n *CmdWait) RunE(cmd *cobra.Command, args []string) error { if v, ok := result.(*c8y.Operation); ok { if v.FailureReason != "" { - cfg.Logger.Warnf("Failure reason: %s", v.FailureReason) + slog.Warn(fmt.Sprintf("Failure reason: %s", v.FailureReason)) } _ = n.factory.WriteOutputWithoutPropertyGuess([]byte(v.Item.Raw), cmdutil.OutputContext{}) @@ -132,7 +133,7 @@ func (n *CmdWait) RunE(cmd *cobra.Command, args []string) error { if err != nil { totalErrors++ - cfg.Logger.Infof("%s", err) + slog.Info(err.Error()) lastErr = err } } diff --git a/pkg/cmd/realtime/subscribe/subscribe.manual.go b/pkg/cmd/realtime/subscribe/subscribe.manual.go index d31c284aa..222d04a6f 100644 --- a/pkg/cmd/realtime/subscribe/subscribe.manual.go +++ b/pkg/cmd/realtime/subscribe/subscribe.manual.go @@ -70,10 +70,6 @@ func (n *CmdSubscribe) RunE(cmd *cobra.Command, args []string) error { if err != nil { return err } - log, err := n.factory.Logger() - if err != nil { - return err - } duration, err := flags.GetDurationFlag(cmd, "duration", true, time.Second) if err != nil { return err @@ -86,5 +82,5 @@ func (n *CmdSubscribe) RunE(cmd *cobra.Command, args []string) error { return n.factory.WriteOutputWithoutPropertyGuess([]byte(msg), cmdutil.OutputContext{}) }, } - return c8ysubscribe.Subscribe(client, log, n.flagChannel, opts) + return c8ysubscribe.Subscribe(client, n.flagChannel, opts) } diff --git a/pkg/cmd/realtime/subscribeall/subscribeall.manual.go b/pkg/cmd/realtime/subscribeall/subscribeall.manual.go index 2f15fe542..f98668156 100644 --- a/pkg/cmd/realtime/subscribeall/subscribeall.manual.go +++ b/pkg/cmd/realtime/subscribeall/subscribeall.manual.go @@ -66,10 +66,6 @@ func (n *CmdSubscribeAll) RunE(cmd *cobra.Command, args []string) error { if err != nil { return err } - log, err := n.factory.Logger() - if err != nil { - return err - } inputIterators, err := cmdutil.NewRequestInputIterators(cmd, cfg) if err != nil { return err @@ -113,5 +109,5 @@ func (n *CmdSubscribeAll) RunE(cmd *cobra.Command, args []string) error { return n.factory.WriteOutputWithoutPropertyGuess([]byte(msg), cmdutil.OutputContext{}) }, } - return c8ysubscribe.SubscribeMultiple(client, log, patterns, opts) + return c8ysubscribe.SubscribeMultiple(client, patterns, opts) } diff --git a/pkg/cmd/remoteaccess/connect/run/run.manual.go b/pkg/cmd/remoteaccess/connect/run/run.manual.go index 56e6eee44..0d3295758 100644 --- a/pkg/cmd/remoteaccess/connect/run/run.manual.go +++ b/pkg/cmd/remoteaccess/connect/run/run.manual.go @@ -3,6 +3,7 @@ package run import ( "context" "fmt" + "log/slog" "os" "os/exec" "strings" @@ -104,10 +105,6 @@ func (n *CmdRun) RunE(cmd *cobra.Command, args []string) error { if err != nil { return err } - log, err := n.factory.Logger() - if err != nil { - return err - } inputIterators, err := cmdutil.NewRequestInputIterators(cmd, cfg) if err != nil { @@ -148,7 +145,7 @@ func (n *CmdRun) RunE(cmd *cobra.Command, args []string) error { return nil, err } - log.Debugf("Using remote access configuration: id=%s, name=%s", craConfig.ID, craConfig.Name) + slog.Debug("Using remote access configuration", "id", craConfig.ID, "name", craConfig.Name) // Lookup configuration craClient := remoteaccess.NewRemoteAccessClient(client, remoteaccess.RemoteAccessOptions{ @@ -208,7 +205,7 @@ func (n *CmdRun) RunE(cmd *cobra.Command, args []string) error { runCmd.Stdin = n.factory.IOStreams.In runCmd.Stderr = n.factory.IOStreams.ErrOut - log.Infof("Executing command: %s %s\n", run, strings.Join(runArgs, " ")) + slog.Info("Executing command", "command", fmt.Sprintf("%s %s", run, strings.Join(runArgs, " "))) cs := n.factory.IOStreams.ColorScheme() fmt.Fprintln(n.factory.IOStreams.ErrOut, cs.Green(fmt.Sprintf("Starting external command on %s (%s)\n", device, strings.TrimRight(client.BaseURL.String(), "/")))) diff --git a/pkg/cmd/remoteaccess/connect/ssh/ssh.manual.go b/pkg/cmd/remoteaccess/connect/ssh/ssh.manual.go index ebf0ddf82..04da90434 100644 --- a/pkg/cmd/remoteaccess/connect/ssh/ssh.manual.go +++ b/pkg/cmd/remoteaccess/connect/ssh/ssh.manual.go @@ -3,6 +3,7 @@ package ssh import ( "context" "fmt" + "log/slog" "os/exec" "strings" "time" @@ -138,10 +139,6 @@ func (n *CmdSSH) RunE(cmd *cobra.Command, args []string) error { if err != nil { return err } - log, err := n.factory.Logger() - if err != nil { - return err - } inputIterators, err := cmdutil.NewRequestInputIterators(cmd, cfg) if err != nil { @@ -182,7 +179,7 @@ func (n *CmdSSH) RunE(cmd *cobra.Command, args []string) error { return nil, err } - log.Debugf("Using remote access configuration: id=%s, name=%s", craConfig.ID, craConfig.Name) + slog.Debug("Using remote access configuration", "id", craConfig.ID, "name", craConfig.Name) // Lookup configuration craClient := remoteaccess.NewRemoteAccessClient(client, remoteaccess.RemoteAccessOptions{ @@ -240,7 +237,7 @@ func (n *CmdSSH) RunE(cmd *cobra.Command, args []string) error { sshCmd.Stdout = n.factory.IOStreams.Out sshCmd.Stdin = n.factory.IOStreams.In sshCmd.Stderr = n.factory.IOStreams.ErrOut - log.Infof("Executing command: ssh %s\n", strings.Join(sshArgs, " ")) + slog.Info("Executing command", "command", fmt.Sprintf("ssh %s", strings.Join(sshArgs, " "))) cs := n.factory.IOStreams.ColorScheme() if portForwarding != "" { diff --git a/pkg/cmd/remoteaccess/server/server.manual.go b/pkg/cmd/remoteaccess/server/server.manual.go index 42203baa0..b4f50e289 100644 --- a/pkg/cmd/remoteaccess/server/server.manual.go +++ b/pkg/cmd/remoteaccess/server/server.manual.go @@ -2,6 +2,7 @@ package server import ( "fmt" + "log/slog" "strings" "text/template" @@ -121,14 +122,9 @@ func (n *CmdServer) RunE(cmd *cobra.Command, args []string) error { return err } - log, err := n.factory.Logger() - if err != nil { - return err - } - // Disable if stdio mode is being used if n.listen == "-" { - log.Debug("Disabling pipeline stdin parsing when in stdio mode") + slog.Debug("Disabling pipeline stdin parsing when in stdio mode") cfg.SetDisableStdin(true) } @@ -171,7 +167,7 @@ func (n *CmdServer) RunE(cmd *cobra.Command, args []string) error { return nil, err } - log.Debugf("Using remote access configuration: id=%s, name=%s", craConfig.ID, craConfig.Name) + slog.Debug("Using remote access configuration", "id", craConfig.ID, "name", craConfig.Name) // Lookup configuration craClient := remoteaccess.NewRemoteAccessClient(client, remoteaccess.RemoteAccessOptions{ @@ -180,7 +176,7 @@ func (n *CmdServer) RunE(cmd *cobra.Command, args []string) error { }) if n.listen == "-" { - log.Debugf("Listening to request from stdin") + slog.Debug("Listening to request from stdin") serverErr := craClient.ListenServe(n.factory.IOStreams.In, n.factory.IOStreams.Out) return nil, serverErr } @@ -248,7 +244,7 @@ func (n *CmdServer) RunE(cmd *cobra.Command, args []string) error { go func() { targetURL := fmt.Sprintf("%s://%s:%s", n.browserScheme, host, port) if err := n.factory.Browser.Browse(targetURL); err != nil { - cfg.Logger.Warnf("%s", err) + slog.Warn("Could not open browser", "err", err) } }() } diff --git a/pkg/cmd/root/command.go b/pkg/cmd/root/command.go index c233729ce..25cfd5c9d 100644 --- a/pkg/cmd/root/command.go +++ b/pkg/cmd/root/command.go @@ -3,6 +3,7 @@ package root import ( "bytes" "fmt" + "log/slog" "os" "strings" @@ -16,19 +17,13 @@ import ( "github.com/reubenmiller/go-c8y/pkg/c8y" "github.com/spf13/cobra" "github.com/spf13/viper" - "go.uber.org/zap/zapcore" ) -// Logger is used to record the log messages which should be visible to the user when using the verbose flag -var Logger *logger.Logger - const ( module = "c8yapi" ) func init() { - Logger = logger.NewLogger(module, logger.Options{}) - // Enable case insensitive matches cobra.EnableCaseInsensitive = true } @@ -39,16 +34,16 @@ func init() { // the configuration and extensions etc. func GetInitLoggerOptions(args []string) logger.Options { color := true - level := zapcore.WarnLevel + level := slog.LevelWarn debug := false for _, item := range args { switch item { case "--debug", "--debug=true": - level = zapcore.DebugLevel + level = slog.LevelDebug debug = true case "--verbose", "-v", "--verbose=true": - level = zapcore.InfoLevel + level = slog.LevelInfo case "--noColor", "--noColor=true", "-M", "-M=true": color = false } @@ -63,7 +58,7 @@ func GetInitLoggerOptions(args []string) logger.Options { func getOutputHeaders(c *console.Console, cfg *config.Config, input []string) (headers []byte) { if !c.IsCSV() || !c.WithCSVHeader() || len(input) == 0 { - Logger.Debugf("Ignoring csv headers: isCSV=%v, WithHeader=%v", c.IsCSV(), c.WithCSVHeader()) + slog.Debug("Ignoring csv headers", "isCSV", c.IsCSV(), "withHeader", c.WithCSVHeader()) return } if len(input) > 0 { @@ -102,16 +97,12 @@ func NewCommand(buildVersion, buildBranch string) (*CmdRoot, error) { var client *c8y.Client var dataView *dataview.DataView var consoleHandler *console.Console - var logHandler *logger.Logger var activityLoggerHandler *activitylogger.ActivityLogger var configHandler = config.NewConfig(viper.GetViper()) - // init logger - logHandler = logger.NewLogger(module, GetInitLoggerOptions(os.Args)) - // Note: Loading of the session should be deferred until the commands are loaded if _, err := configHandler.ReadConfigFiles(nil, ShouldIgnoreSessionFile(os.Args)); err != nil { - logHandler.Infof("Failed to read configuration. Trying to proceed anyway. %s", err) + slog.Info("Failed to read configuration. Trying to proceed anyway", "err", err) } // cmd factory @@ -127,12 +118,6 @@ func NewCommand(buildVersion, buildBranch string) (*CmdRoot, error) { } return client, nil } - loggerFunc := func() (*logger.Logger, error) { - if logHandler == nil { - return nil, fmt.Errorf("logger is missing") - } - return logHandler, nil - } activityLoggerFunc := func() (*activitylogger.ActivityLogger, error) { if activityLoggerHandler == nil { return nil, fmt.Errorf("activityLogger is missing") @@ -152,7 +137,7 @@ func NewCommand(buildVersion, buildBranch string) (*CmdRoot, error) { } return consoleHandler, nil } - cmdFactory := factory.New(buildVersion, buildBranch, configFunc, clientFunc, loggerFunc, activityLoggerFunc, dataViewFunc, consoleFunc) + cmdFactory := factory.New(buildVersion, buildBranch, configFunc, clientFunc, activityLoggerFunc, dataViewFunc, consoleFunc) // Register the template resolver so the configuration can lookup values as needed configHandler.RegisterTemplateResolver(cmdutil.NewTemplateResolver(cmdFactory)) diff --git a/pkg/cmd/root/root.go b/pkg/cmd/root/root.go index 126d3415a..80f2332ff 100644 --- a/pkg/cmd/root/root.go +++ b/pkg/cmd/root/root.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "io/fs" + "log/slog" "os" "path/filepath" "strings" @@ -128,7 +129,6 @@ import ( "github.com/reubenmiller/go-c8y-cli/v2/pkg/utilities" "github.com/reubenmiller/go-c8y/pkg/c8y" "github.com/spf13/cobra" - "go.uber.org/zap/zapcore" ) type CmdRoot struct { @@ -148,7 +148,6 @@ type CmdRoot struct { Factory *cmdutil.Factory client *c8y.Client - log *logger.Logger activitylog *activitylogger.ActivityLogger dataview *dataview.DataView mu sync.RWMutex @@ -200,11 +199,8 @@ func NewCmdRoot(f *cmdutil.Factory, version, buildDate string) *CmdRoot { cmdErr := ccmd.checkSessionExists(cmd, args) if cmdErr != nil { - logg, logErr := f.Logger() - if logg != nil && logErr == nil { - if !errors.Is(cmderrors.ErrHelp, cmdErr) { - logg.Warnf("Check existing session failed. %s", cmdErr) - } + if !errors.Is(cmderrors.ErrHelp, cmdErr) { + slog.Warn("Check existing session failed", "err", cmdErr) } } return cmdErr @@ -516,9 +512,7 @@ func NewCmdRoot(f *cmdutil.Factory, version, buildDate string) *CmdRoot { // Add sub commands for the extensions extensions := f.ExtensionManager().List() if err := ConvertToCobraCommands(f, cmd, extensions); err != nil { - if log, logErr := f.Logger(); logErr == nil { - log.Warnf("Errors while loading some extensions. Functionality may be reduced. %s", err) - } + slog.Warn("Errors while loading some extensions. Functionality may be reduced", "err", err) } // Handle errors (not in cobra library) @@ -538,11 +532,6 @@ func ConvertToCobraCommands(f *cmdutil.Factory, cmd *cobra.Command, extensions [ // as it affects passing the arguments to the extension binary disableFlagParsing := !isTabCompletionCommand() - log, err := f.Logger() - if err != nil { - return err - } - var extError error for _, ext := range extensions { commands, err := ext.Commands() @@ -577,7 +566,7 @@ func ConvertToCobraCommands(f *cmdutil.Factory, cmd *cobra.Command, extensions [ return nil } - log.Debugf("Reading extension file: %s", path) + slog.Debug("Reading extension file", "path", path) spec, err := os.Open(path) if err != nil { return err @@ -586,7 +575,7 @@ func ConvertToCobraCommands(f *cmdutil.Factory, cmd *cobra.Command, extensions [ extCommand, err := cmdparser.ParseCommand(spec, f, cmd.Root()) if err != nil { // Only log a warning for the user, don't prevent the whole cli from working - log.Warnf("Invalid extension file. reason=%s. file=%s", err, path) + slog.Warn("Invalid extension file", "err", err, "file", path) // return fmt.Errorf("%w. file=%s", err, path) } else { if extCommand != nil { @@ -639,11 +628,7 @@ func ConvertToCobraCommands(f *cmdutil.Factory, cmd *cobra.Command, extensions [ DisableFlagParsing: disableFlagParsing, RunE: func(name, exe string) func(*cobra.Command, []string) error { return func(cmd *cobra.Command, args []string) error { - log, err := f.Logger() - if err != nil { - return err - } - log.Infof("Executing extension. name: %s, command: %s, args: %v", name, exe, args) + slog.Info("Executing extension. name", "name", name, "command", exe, "args", args) _, err = f.ExtensionManager().Execute(exe, args, false, f.IOStreams.In, f.IOStreams.Out, f.IOStreams.ErrOut) return err } @@ -661,11 +646,7 @@ func ConvertToCobraCommands(f *cmdutil.Factory, cmd *cobra.Command, extensions [ DisableFlagParsing: disableFlagParsing, RunE: func(name, exe string) func(*cobra.Command, []string) error { return func(cmd *cobra.Command, args []string) error { - log, err := f.Logger() - if err != nil { - return err - } - log.Infof("Executing extension. name: %s, command: %s, args: %v", name, exe, args) + slog.Info("Executing extension", "name", name, "command", exe, "args", args) _, err = f.ExtensionManager().Execute(exe, args, false, f.IOStreams.In, f.IOStreams.Out, f.IOStreams.ErrOut) return err } @@ -688,11 +669,7 @@ func (c *CmdRoot) Configure(disableEncryptionCheck, forceVerbose, forceDebug boo if err != nil { return err } - log, err := c.Factory.Logger() - if err != nil { - return err - } - log.Debugf("Configuring core modules") + slog.Debug("Configuring core modules") consoleHandler, err := c.Factory.Console() if err != nil { return err @@ -701,7 +678,7 @@ func (c *CmdRoot) Configure(disableEncryptionCheck, forceVerbose, forceDebug boo // config/env binding previousSession := cfg.GetSessionFile() if err := cfg.BindPFlag(c.Command.PersistentFlags()); err != nil { - log.Warningf("Some configuration binding failed. %s", err) + slog.Warn("Some configuration binding failed", "err", err) } if c.SessionFile != "" { @@ -711,21 +688,21 @@ func (c *CmdRoot) Configure(disableEncryptionCheck, forceVerbose, forceDebug boo // re-load config if they are using the session argument currentSession := cfg.GetSessionFile() if previousSession != currentSession { - log.Infof("Session file has changed from %s to %s. Reading new session", previousSession, currentSession) + slog.Info(fmt.Sprintf("Session file has changed from %s to %s. Reading new session", previousSession, currentSession)) if _, err := cfg.ReadConfigFiles(nil); err != nil { - log.Infof("Failed to read configuration. Trying to proceed anyway. %s", err) + slog.Info("Failed to read configuration. Trying to proceed anyway", "err", err) } } if cfg.DisableProgress() { - log.Debugf("Disabling progress bars") + slog.Debug("Disabling progress bars") c.Factory.IOStreams.SetProgress(false) } if c.SessionMode != "" { mode := config.SessionModeUnset.FromString(c.SessionMode, false) if mode != config.SessionModeUnset { - log.Infof("Overriding session mode. default=%s, value=%s", cfg.SessionMode().String(), mode.String()) + slog.Info("Overriding session mode", "default", cfg.SessionMode().String(), "value", mode.String()) cfg.SetSessionMode(mode) } } @@ -733,38 +710,25 @@ func (c *CmdRoot) Configure(disableEncryptionCheck, forceVerbose, forceDebug boo // // Update cmd factory before passing it along // - // Update logger - c.Factory.Logger = func() (*logger.Logger, error) { - c.muLog.Lock() - defer c.muLog.Unlock() - if c.log != nil { - return c.log, nil + logOptions := logger.Options{ + Level: slog.LevelWarn, + Color: !cfg.DisableColor(), + Debug: cfg.Debug(), + } + if cfg.ShowProgress() { + // Don't silence log levels completely in case of errors + // mode errors + logOptions.Silent = false + } else { + if cfg.Verbose() || forceVerbose { + logOptions.Level = slog.LevelInfo } - logOptions := logger.Options{ - Level: zapcore.WarnLevel, - Color: !cfg.DisableColor(), - Debug: cfg.Debug(), + if cfg.Debug() || forceDebug { + logOptions.Level = slog.LevelDebug } - if cfg.ShowProgress() { - // Don't silence log levels completely in case of errors - // mode errors - logOptions.Silent = false - } else { - if cfg.Verbose() || forceVerbose { - logOptions.Level = zapcore.InfoLevel - } - if cfg.Debug() || forceDebug { - logOptions.Level = zapcore.DebugLevel - } - } - - customLogger := logger.NewLogger("c8y", logOptions) - c8y.Logger = customLogger - cfg.SetLogger(customLogger) - c.log = customLogger - return customLogger, nil } + _ = logger.NewLogger("go-c8y-cli", logOptions) // Update activity logger c.Factory.ActivityLogger = func() (*activitylogger.ActivityLogger, error) { @@ -786,7 +750,6 @@ func (c *CmdRoot) Configure(disableEncryptionCheck, forceVerbose, forceDebug boo return c.dataview, nil } - l, _ := c.Factory.Logger() viewPaths := cfg.GetViewPaths() // Add extensions @@ -797,7 +760,7 @@ func (c *CmdRoot) Configure(disableEncryptionCheck, forceVerbose, forceDebug boo } } - dv, err := dataview.NewDataView(".*", ".json", l, viewPaths...) + dv, err := dataview.NewDataView(".*", ".json", viewPaths...) c.dataview = dv return dv, err } @@ -820,15 +783,9 @@ func (c *CmdRoot) Configure(disableEncryptionCheck, forceVerbose, forceDebug boo if client != nil { if c.SessionUsername != "" || c.SessionPassword != "" { client.SetUsernamePassword(c.SessionUsername, c.SessionPassword) - c.log.Debug("Forcing basic authentication as user provided username/password") + slog.Debug("Forcing basic authentication as user provided username/password") } } - - if c.log != nil { - c8y.Logger = c.log - } else { - c8y.Logger = logger.NewDummyLogger("c8y") - } c.client = client return client, err } @@ -836,10 +793,6 @@ func (c *CmdRoot) Configure(disableEncryptionCheck, forceVerbose, forceDebug boo } func (c *CmdRoot) checkSessionExists(cmd *cobra.Command, args []string) error { - log, err := c.Factory.Logger() - if err != nil { - return err - } cfg, err := c.Factory.Config() if err != nil { return err @@ -860,22 +813,22 @@ func (c *CmdRoot) checkSessionExists(cmd *cobra.Command, args []string) error { // print log information sessionFile := cfg.GetSessionFile() if sessionFile != "" { - log.Infof("Loaded session: %s", cfg.HideSensitiveInformationIfActive(client, sessionFile)) + slog.Info("Loaded session", "file", cfg.HideSensitiveInformationIfActive(client, sessionFile)) if _, err := os.Stat(sessionFile); err != nil { if c8ysession.IsSessionFilePath(sessionFile) { - log.Warnf("Failed to verify session file. %s", err) + slog.Warn("Failed to verify session file", "err", err) } } } if cfg.DisableStdin() { // Note: Stdin is disabled elsewhere - log.Info("Disabling reading from stdin (does not accept piped data)") + slog.Info("Disabling reading from stdin (does not accept piped data)") } - log.Debugf("command str: %s", cmdStr) - log.Infof("command: c8y %s", utilities.GetCommandLineArgs()) - log.Debugf("output format: %s", cfg.GetOutputFormat().String()) + slog.Debug("command str", "value", cmdStr) + slog.Info(fmt.Sprintf("command: c8y %s", utilities.GetCommandLineArgs())) + slog.Debug("output format", "value", cfg.GetOutputFormat().String()) // print examples if cmd.Flags().Changed("examples") { @@ -931,14 +884,14 @@ func (c *CmdRoot) configureActivityLog(cfg *config.Config) (*activitylogger.Acti activitylog, err := activitylogger.NewActivityLogger(options) if err != nil { - cfg.Logger.Errorf("Failed to load activity logger. %s", err) + slog.Error("Failed to load activity logger", "err", err) return nil, err } if disabled { - cfg.Logger.Info("activityLog is disabled") + slog.Info("activityLog is disabled") } else { - cfg.Logger.Infof("activityLog path: %s", activitylog.GetPath()) + slog.Info("activityLog", "path", activitylog.GetPath()) } return activitylog, nil } diff --git a/pkg/cmd/sessions/create/create.manual.go b/pkg/cmd/sessions/create/create.manual.go index 2fd5db75d..f147bef0b 100644 --- a/pkg/cmd/sessions/create/create.manual.go +++ b/pkg/cmd/sessions/create/create.manual.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "log/slog" "net/url" "os" "path" @@ -20,17 +21,15 @@ import ( "github.com/reubenmiller/go-c8y-cli/v2/pkg/config" "github.com/reubenmiller/go-c8y-cli/v2/pkg/fileutilities" "github.com/reubenmiller/go-c8y-cli/v2/pkg/flags" - "github.com/reubenmiller/go-c8y-cli/v2/pkg/logger" "github.com/reubenmiller/go-c8y-cli/v2/pkg/prompt" "github.com/reubenmiller/go-c8y/pkg/c8y" "github.com/spf13/cobra" "github.com/spf13/viper" ) -func NewCumulocitySessionFromFile(filePath string, log *logger.Logger, cfg *config.Config) (*c8ysession.CumulocitySession, error) { +func NewCumulocitySessionFromFile(filePath string, cfg *config.Config) (*c8ysession.CumulocitySession, error) { session := &c8ysession.CumulocitySession{ Config: cfg, - Logger: log, } sessionConfig := viper.New() @@ -170,11 +169,7 @@ func (n *CmdCreate) promptArgs(cmd *cobra.Command, args []string) error { if err != nil { return err } - log, err := n.factory.Logger() - if err != nil { - return err - } - prompter := prompt.NewPrompt(log) + prompter := prompt.NewPrompt() if !cmd.Flags().Changed("host") { v, err := prompter.Input("Enter host", "", true, false) @@ -269,10 +264,6 @@ func (n *CmdCreate) RunE(cmd *cobra.Command, args []string) error { if err != nil { return err } - log, err := n.factory.Logger() - if err != nil { - return err - } session := &c8ysession.CumulocitySession{ Schema: "https://raw.githubusercontent.com/reubenmiller/go-c8y-cli/v2/tools/schema/session.schema.json", Host: n.host, @@ -281,7 +272,6 @@ func (n *CmdCreate) RunE(cmd *cobra.Command, args []string) error { Description: n.description, UseTenantPrefix: !n.noTenantPrefix, Config: cfg, - Logger: log, } session.MicroserviceAliases = make(map[string]string) @@ -390,10 +380,6 @@ func (n *CmdCreate) formatFilename(name string) string { } func (n *CmdCreate) writeSessionFile(outputDir, outputFile string, session c8ysession.CumulocitySession) error { - log, err := n.factory.Logger() - if err != nil { - return err - } data, err := json.MarshalIndent(session, "", " ") if err != nil { @@ -404,11 +390,11 @@ func (n *CmdCreate) writeSessionFile(outputDir, outputFile string, session c8yse if outputDir != "" { if err := fileutilities.CreateDirs(outputDir); err != nil { - log.Errorf("failed to create folder. folder=%s, err=%s", outputDir, err) + slog.Error("failed to create folder", "folder", outputDir, "err", err) return err } } - log.Debugf("output file: %s", outputPath) + slog.Debug("output file", "value", outputPath) if err := os.WriteFile(path.Join(outputDir, outputFile), data, 0600); err != nil { return errors.Wrap(err, "failed to write to file") diff --git a/pkg/cmd/sessions/decrypttext/decryptText.manual.go b/pkg/cmd/sessions/decrypttext/decryptText.manual.go index 73373190d..fddff3a97 100644 --- a/pkg/cmd/sessions/decrypttext/decryptText.manual.go +++ b/pkg/cmd/sessions/decrypttext/decryptText.manual.go @@ -62,16 +62,11 @@ func (n *CmdDecryptText) RunE(cmd *cobra.Command, args []string) error { if err != nil { return err } - log, err := n.factory.Logger() - if err != nil { - return err - } inputIterators, err := cmdutil.NewRequestInputIterators(cmd, cfg) if err != nil { return err } - _ = log var iter iterator.Iterator _, input, err := flags.WithPipelineIterator(&flags.PipelineOptions{ diff --git a/pkg/cmd/sessions/encrypttext/encryptText.manual.go b/pkg/cmd/sessions/encrypttext/encryptText.manual.go index b3c74dc70..9ba5e6b75 100644 --- a/pkg/cmd/sessions/encrypttext/encryptText.manual.go +++ b/pkg/cmd/sessions/encrypttext/encryptText.manual.go @@ -2,6 +2,7 @@ package encrypttext import ( "fmt" + "log/slog" "github.com/reubenmiller/go-c8y-cli/v2/pkg/cmd/subcommand" "github.com/reubenmiller/go-c8y-cli/v2/pkg/cmdutil" @@ -59,10 +60,6 @@ func (n *CmdEncryptText) RunE(cmd *cobra.Command, args []string) error { if err != nil { return err } - log, err := n.factory.Logger() - if err != nil { - return err - } if n.passphrase == "" { inputPassphrase, err := cfg.PromptPassphrase() if err != nil { @@ -108,7 +105,7 @@ func (n *CmdEncryptText) RunE(cmd *cobra.Command, args []string) error { } encryptedText = []byte(data) } else { - log.Info("Text is already encrypted") + slog.Info("Text is already encrypted") } err = n.factory.WriteOutputWithoutPropertyGuess(encryptedText, cmdutil.OutputContext{}) diff --git a/pkg/cmd/sessions/list/list.manual.go b/pkg/cmd/sessions/list/list.manual.go index b52f46e37..86236a3ce 100644 --- a/pkg/cmd/sessions/list/list.manual.go +++ b/pkg/cmd/sessions/list/list.manual.go @@ -53,12 +53,8 @@ func (n *CmdList) RunE(cmd *cobra.Command, args []string) error { if err != nil { return err } - log, err := n.factory.Logger() - if err != nil { - return err - } - sessionFile, err := selectsession.SelectSession(n.factory.IOStreams, cfg, log, n.sessionFilter) + sessionFile, err := selectsession.SelectSession(n.factory.IOStreams, cfg, n.sessionFilter) if err != nil { return err diff --git a/pkg/cmd/sessions/login/login.manual.go b/pkg/cmd/sessions/login/login.manual.go index 8bf70e76d..b9b835d59 100644 --- a/pkg/cmd/sessions/login/login.manual.go +++ b/pkg/cmd/sessions/login/login.manual.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "io" + "log/slog" "os" "os/exec" "strings" @@ -191,12 +192,7 @@ func (n *CmdLogin) FromEnv() (*c8ysession.CumulocitySession, error) { } func (n *CmdLogin) FromInteractive(cmd *cobra.Command) (*c8ysession.CumulocitySession, error) { - log, err := n.factory.Logger() - if err != nil { - return nil, err - } - - prompter := prompt.NewPrompt(log) + prompter := prompt.NewPrompt() session := &c8ysession.CumulocitySession{ Host: n.Host, @@ -238,10 +234,6 @@ func (n *CmdLogin) FromExternalProvider(args []string) (*c8ysession.CumulocitySe if err != nil { return nil, err } - log, err := n.factory.Logger() - if err != nil { - return nil, err - } // add secrets when executing the environment in case // if the external command requires extra authentication @@ -251,10 +243,10 @@ func (n *CmdLogin) FromExternalProvider(args []string) (*c8ysession.CumulocitySe secret, secretErr := cfg.PromptSecret(key) if secretErr != nil { if !errors.Is(secretErr, prompt.ErrNoPrompter) { - cfg.Logger.Warnf("Could not get secret. key=%s, err=%s", key, secretErr) + slog.Warn("Could not get secret", "key", key, "err", secretErr) } } else { - cfg.Logger.Debugf("Setting env variable secret for external provider. %s", key) + slog.Debug("Setting env variable secret for external provider", "key", key) env = append(env, fmt.Sprintf("%s=%s", key, secret)) } } @@ -293,7 +285,7 @@ func (n *CmdLogin) FromExternalProvider(args []string) (*c8ysession.CumulocitySe cmd.Stdin = os.Stdin cmd.Stderr = os.Stderr - log.Infof("Executing session provider: %s", shellquote.Join(providerCommand...)) + slog.Info("Executing session provider", "value", shellquote.Join(providerCommand...)) output, err := cmd.Output() if err != nil { return nil, err @@ -303,14 +295,14 @@ func (n *CmdLogin) FromExternalProvider(args []string) (*c8ysession.CumulocitySe // Try to detect the format if jsonUtilities.IsJSONObject(output) { n.Format = "json" - log.Infof("Detected input format: %s", n.Format) + slog.Info("Detected input format", "value", n.Format) } else { n.Format = "dotenv" - log.Infof("Guessing input format: %s", n.Format) + slog.Info("Guessing input format", "value", n.Format) } } - log.Infof("Parsing session provider output: %s", output) + slog.Info("Parsing session provider output", "value", output) return n.FromReader(bytes.NewReader(output), n.Format) } @@ -397,10 +389,6 @@ func (n *CmdLogin) RunE(cmd *cobra.Command, args []string) error { if err != nil { return err } - log, err := n.factory.Logger() - if err != nil { - return err - } canChangeActiveSession := true // Warn users if they try to use this command directly @@ -430,7 +418,7 @@ func (n *CmdLogin) RunE(cmd *cobra.Command, args []string) error { // Set defaults from config if values from flags aren't provided if !cmd.Flags().Changed("provider") { n.Provider = cfg.SessionProvider() - cfg.Logger.Debugf("Using session provider from configuration. type=%s", n.Provider) + slog.Debug("Using session provider from configuration", "type", n.Provider) } n.Exec = cfg.SessionProviderCommand() @@ -501,7 +489,7 @@ func (n *CmdLogin) RunE(cmd *cobra.Command, args []string) error { } if sessionContents, err := json.Marshal(session); err == nil { - cfg.Logger.Infof("Received session from external source:\n%s\n", sessionContents) + slog.Info("Received session from external source", "value", sessionContents) } if session.SessionUri != "" { @@ -524,7 +512,7 @@ func (n *CmdLogin) RunE(cmd *cobra.Command, args []string) error { if n.LoginType != "" { loginType = strings.ToUpper(n.LoginType) } - log.Infof("User flag login type: %s", loginType) + slog.Info("User flag login type", "value", loginType) // Set default auth mode based on login type switch loginType { @@ -537,7 +525,7 @@ func (n *CmdLogin) RunE(cmd *cobra.Command, args []string) error { } // SSO providers are in control of the token, so it should just be used as is - if !n.ClearToken && c8ysession.ShouldReuseToken(cfg, log, session.Token, loginType) { + if !n.ClearToken && c8ysession.ShouldReuseToken(cfg, session.Token, loginType) { client.SetToken(session.Token) } else { client.ClearToken() @@ -557,16 +545,15 @@ func (n *CmdLogin) RunE(cmd *cobra.Command, args []string) error { handler.SSO.Audience = n.SSOAudience - log.Infof("User preference for login type: %s", handler.LoginType) + slog.Info("User preference for login type", "value", handler.LoginType) handler.TFACode = session.TOTP if n.TFACode == "" { if code, err := cfg.GetTOTP(time.Now()); err == nil { - cfg.Logger.Infof("Setting totp code: %s", code) + slog.Info("Setting totp code", "value", code) n.TFACode = code } } - handler.SetLogger(log) err = handler.Run() if err != nil { return err diff --git a/pkg/cmd/sessions/selectsession/selectsession.go b/pkg/cmd/sessions/selectsession/selectsession.go index 0a14a52d1..2fa0938f9 100644 --- a/pkg/cmd/sessions/selectsession/selectsession.go +++ b/pkg/cmd/sessions/selectsession/selectsession.go @@ -2,6 +2,7 @@ package selectsession import ( "fmt" + "log/slog" "os" "path/filepath" "strings" @@ -12,7 +13,6 @@ import ( createCmd "github.com/reubenmiller/go-c8y-cli/v2/pkg/cmd/sessions/create" "github.com/reubenmiller/go-c8y-cli/v2/pkg/config" "github.com/reubenmiller/go-c8y-cli/v2/pkg/iostreams" - "github.com/reubenmiller/go-c8y-cli/v2/pkg/logger" "github.com/reubenmiller/go-c8y-cli/v2/pkg/utilities/bellskipper" ) @@ -54,7 +54,7 @@ func matchSession(session c8ysession.CumulocitySession, input string) bool { } // SelectSession select a Cumulocity session interactively -func SelectSession(io *iostreams.IOStreams, cfg *config.Config, log *logger.Logger, filter string) (sessionFile string, err error) { +func SelectSession(io *iostreams.IOStreams, cfg *config.Config, filter string) (sessionFile string, err error) { sessions := &c8ysession.CumulocitySessions{} sessions.Sessions = make([]c8ysession.CumulocitySession, 0) @@ -63,29 +63,29 @@ func SelectSession(io *iostreams.IOStreams, cfg *config.Config, log *logger.Logg files := make([]string, 0) srcdir := cfg.GetSessionHomeDir() - log.Infof("using c8y session folder: %s", srcdir) + slog.Info("using c8y session folder", "path", srcdir) err = filepath.Walk(srcdir, func(path string, info os.FileInfo, err error) error { if err != nil { - log.Printf("Prevent panic by handling failure accessing a path %q: %v", path, err) + slog.Info("Prevent panic by handling failure accessing a path", "path", path, "err", err) return err } if info.IsDir() && strings.Contains(subDirToSkip, ":"+strings.ToLower(info.Name())+":") { - log.Printf("Ignoring dir: %+v", info.Name()) + slog.Info("Ignoring dir", "path", info.Name()) return filepath.SkipDir } if info.IsDir() && info.Name() == ".git" { - log.Printf("Ignoring dir: %+v", info.Name()) + slog.Info("Ignoring dir", "path", info.Name()) return filepath.SkipDir } if info.IsDir() && path == cfg.ExtensionsDataDir() { - log.Printf("Ignoring extensions dir: %+v", info.Name()) + slog.Info("Ignoring extensions dir", "path", info.Name()) return filepath.SkipDir } // extensions is a reserved word (in case the user has older extensions folder which is not the current setting, but left overs from a previous location) if info.IsDir() && strings.EqualFold(info.Name(), "extensions") { - log.Printf("Ignoring reserved dir names: %+v", info.Name()) + slog.Info("Ignoring reserved dir names", "path", info.Name()) return filepath.SkipDir } @@ -98,13 +98,13 @@ func SelectSession(io *iostreams.IOStreams, cfg *config.Config, log *logger.Logg return nil } - log.Infof("Walking folder/file: %s", path) + slog.Info("Walking folder/file", "path", path) files = append(files, path) - if session, err := createCmd.NewCumulocitySessionFromFile(path, log, cfg); err == nil { + if session, err := createCmd.NewCumulocitySessionFromFile(path, cfg); err == nil { sessions.Sessions = append(sessions.Sessions, *session) } else { - log.Infof("Failed to read file: file=%s, err=%s", path, err) + slog.Info("Failed to read file", "path", path, "err", err) } return nil }) @@ -205,7 +205,7 @@ func SelectSession(io *iostreams.IOStreams, cfg *config.Config, log *logger.Logg case 0: return "", fmt.Errorf("no sessions found") case 1: - log.Info("Only 1 session found. Selecting it automatically") + slog.Info("Only 1 session found. Selecting it automatically") idx = 0 result = filteredSessions[0].Path err = nil @@ -219,7 +219,7 @@ func SelectSession(io *iostreams.IOStreams, cfg *config.Config, log *logger.Logg } if err != nil { - log.Warnf("Prompt failed %v\n", err) + slog.Warn("Prompt failed", "err", err) return "", err } diff --git a/pkg/cmd/sessions/set/set.manual.go b/pkg/cmd/sessions/set/set.manual.go index 78ec30d59..a356874b9 100644 --- a/pkg/cmd/sessions/set/set.manual.go +++ b/pkg/cmd/sessions/set/set.manual.go @@ -2,6 +2,7 @@ package login import ( "fmt" + "log/slog" "os" "slices" "strings" @@ -88,10 +89,8 @@ func NewCmdSet(f *cmdutil.Factory) *CmdSet { func (n *CmdSet) onSave(client *c8y.Client) { cfg, _ := n.factory.Config() - log, _ := n.factory.Logger() - if err := cfg.SaveClientConfig(client); err != nil { - log.Errorf("Saving file error. %s", err) + slog.Error("Saving file error", "err", err) } } @@ -100,10 +99,6 @@ func (n *CmdSet) RunE(cmd *cobra.Command, args []string) error { if err != nil { return err } - log, err := n.factory.Logger() - if err != nil { - return err - } // Ensure session values from previously read configuration is not transferred cfg.Set("settings.session.mode", nil) @@ -137,13 +132,13 @@ func (n *CmdSet) RunE(cmd *cobra.Command, args []string) error { } if sessionFile == "" { - sessionFile, err = selectsession.SelectSession(n.factory.IOStreams, cfg, log, strings.Join(append(args, n.sessionFilter), " ")) + sessionFile, err = selectsession.SelectSession(n.factory.IOStreams, cfg, strings.Join(append(args, n.sessionFilter), " ")) if err != nil { return err } } - cfg.Logger.Debugf("selected session file: %s", sessionFile) + slog.Debug("selected session file", "file", sessionFile) if sessionFile != "" { // Note: Ignore any environment variables as the session should take precedence because // the user is most likely switching session so does not want to inherit any environment variables @@ -193,7 +188,7 @@ func (n *CmdSet) RunE(cmd *cobra.Command, args []string) error { n.LoginType = cfg.GetLoginTypeWithDefault() } else { if v, err := c8y.ParseLoginType(n.LoginType); err != nil { - log.Warnf("Could not parse auth method: value=%s", err) + slog.Warn("Could not parse auth method", "err", err) } else { n.LoginType = v } @@ -218,7 +213,7 @@ func (n *CmdSet) RunE(cmd *cobra.Command, args []string) error { if n.ClearToken { client.ClearToken() cfg.ClearToken() - } else if c8ysession.ShouldReuseToken(cfg, log, token, n.LoginType) { + } else if c8ysession.ShouldReuseToken(cfg, token, n.LoginType) { client.SetToken(token) } else { client.ClearToken() @@ -228,7 +223,7 @@ func (n *CmdSet) RunE(cmd *cobra.Command, args []string) error { // If the password is not encrypted, then save it (which will apply the encryption) if !cfg.IsPasswordEncrypted() { if cfg.EncryptionEnabled() { - log.Infof("Password is unencrypted. enforcing encryption") + slog.Info("Password is unencrypted. enforcing encryption") n.onSave(nil) } } @@ -240,19 +235,18 @@ func (n *CmdSet) RunE(cmd *cobra.Command, args []string) error { handler.SSO.DiscoveryURL = cfg.SSODiscoveryUrl() handler.SSO.Scopes = cfg.SSOScopes() if handler.LoginType == "" { - log.Infof("User preference for login type: %s", "not-set") + slog.Info("User preference for login type", "value", "not-set") } else { - log.Infof("User preference for login type: %s", handler.LoginType) + slog.Info("User preference for login type", "value", handler.LoginType) } if n.TFACode == "" { if code, err := cfg.GetTOTP(time.Now()); err == nil { - cfg.Logger.Infof("Setting totp code: %s", code) + slog.Info("Setting totp code", "value", code) n.TFACode = code } } handler.TFACode = n.TFACode - handler.SetLogger(log) err = handler.Run() if err != nil { @@ -263,11 +257,11 @@ func (n *CmdSet) RunE(cmd *cobra.Command, args []string) error { // Respect session mode using the legacy format (e.g. set via individual create,update,delete values) if legacyMode, legacyValueExists := config.HasLegacySessionMode(cfg.Persistent); legacyValueExists { mode = legacyMode.String() - cfg.Logger.Debugf("Detected legacy mode. mode=%s", mode) + slog.Debug("Detected legacy mode", "mode", mode) } if hasChanged(handler.C8Yclient, cfg) { - log.Infof("Saving tenant name") + slog.Info("Saving tenant name") n.onSave(handler.C8Yclient) } @@ -319,7 +313,7 @@ func (n *CmdSet) RunE(cmd *cobra.Command, args []string) error { } // Write session details to stdout (for machines) - cfg.Logger.Infof("c8y sessions set: isAuthorized: %v", session.IsAuthorized()) + slog.Info("c8y sessions set", "isAuthorized", session.IsAuthorized()) return c8ysession.WriteOutput(n.GetCommand().OutOrStdout(), client, cfg, session, outputFormat) } diff --git a/pkg/cmd/settings/list/list.manual.go b/pkg/cmd/settings/list/list.manual.go index f4493120c..94575af19 100644 --- a/pkg/cmd/settings/list/list.manual.go +++ b/pkg/cmd/settings/list/list.manual.go @@ -2,6 +2,7 @@ package list import ( "encoding/json" + "log/slog" "github.com/MakeNowJust/heredoc/v2" "github.com/reubenmiller/go-c8y-cli/v2/pkg/cmd/subcommand" @@ -71,19 +72,19 @@ func (n *CmdList) listSettings(cmd *cobra.Command, args []string) error { // add additional settings err = allSettings.Set("settings.session.home", cfg.GetSessionHomeDir()) if err != nil { - cfg.Logger.Warnf("Could not get home session directory. %s", err) + slog.Warn("Could not get home session directory", "err", err) } if activitylog != nil { err := allSettings.Set("settings.activitylog.currentPath", activitylog.GetPath()) if err != nil { - cfg.Logger.Warnf("Could not get activity logger path. %s", err) + slog.Warn("Could not get activity logger path", "err", err) } } err = allSettings.Set("settings.session.file", cfg.GetSessionFile()) if err != nil { - cfg.Logger.Warnf("Could not get session file. %s", err) + slog.Warn("Could not get session file", "err", err) } responseText, err = json.Marshal(allSettings) diff --git a/pkg/cmd/settings/update/update.manual.go b/pkg/cmd/settings/update/update.manual.go index 1db0b8aa2..670dbdf1f 100644 --- a/pkg/cmd/settings/update/update.manual.go +++ b/pkg/cmd/settings/update/update.manual.go @@ -2,6 +2,7 @@ package update import ( "fmt" + "log/slog" "strconv" "strings" @@ -549,7 +550,7 @@ func (n *UpdateSettingsCmd) RunE(cmd *cobra.Command, args []string) error { switch name { case "mode": if err := config.SetMode(v, config.SessionModeProduction.FromString(value, false)); err != nil { - cfg.Logger.Warn(err) + slog.Warn("Could not set session mode", "err", err) } default: if handler, ok := updateSettingsOptions[name]; ok { diff --git a/pkg/cmd/ui/applications/plugins/list/list.manual.go b/pkg/cmd/ui/applications/plugins/list/list.manual.go index a2fd8207a..1580177f3 100644 --- a/pkg/cmd/ui/applications/plugins/list/list.manual.go +++ b/pkg/cmd/ui/applications/plugins/list/list.manual.go @@ -3,6 +3,7 @@ package list import ( "context" "encoding/json" + "log/slog" "strings" "github.com/MakeNowJust/heredoc/v2" @@ -103,10 +104,6 @@ func (n *CmdList) RunE(cmd *cobra.Command, args []string) error { if err != nil { return err } - log, err := n.factory.Logger() - if err != nil { - return err - } matches, err := c8yfetcher.FindHostedApplications(n.factory, []string{n.Application}, true, "", false) if err != nil { @@ -182,7 +179,7 @@ func (n *CmdList) RunE(cmd *cobra.Command, args []string) error { reference, referenceErr = NewPluginReference(sharedPlugins.Items[i], version, pluginModules[contextVersion]) } else { if !dryRun { - log.Warnf("could not find plugin. contextPath=%s, version=%s", contextPath, version) + slog.Warn("could not find plugin", "contextPath", contextPath, "version", version) } } diff --git a/pkg/cmd/ui/plugins/create/create.manual.go b/pkg/cmd/ui/plugins/create/create.manual.go index 131c7fcc7..95e9a3acc 100644 --- a/pkg/cmd/ui/plugins/create/create.manual.go +++ b/pkg/cmd/ui/plugins/create/create.manual.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "io" + "log/slog" "net/url" "os" "path/filepath" @@ -18,7 +19,6 @@ import ( "github.com/reubenmiller/go-c8y-cli/v2/pkg/completion" "github.com/reubenmiller/go-c8y-cli/v2/pkg/fileutilities" "github.com/reubenmiller/go-c8y-cli/v2/pkg/flags" - "github.com/reubenmiller/go-c8y-cli/v2/pkg/logger" "github.com/reubenmiller/go-c8y/pkg/c8y" "github.com/spf13/cobra" ) @@ -104,7 +104,7 @@ func NewCmdCreate(f *cmdutil.Factory) *CmdCreate { return ccmd } -func (n *CmdCreate) getApplicationDetails(client *c8y.Client, log *logger.Logger) (*c8y.UIExtension, error) { +func (n *CmdCreate) getApplicationDetails(client *c8y.Client) (*c8y.UIExtension, error) { // set default name to the file name appNameFromFile := artifact.ParseName(n.file) @@ -172,7 +172,7 @@ func ShouldDownload(v string) bool { return strings.HasPrefix(v, "http://") || strings.HasPrefix(v, "https://") } -func DownloadFile(u string, log *logger.Logger) (string, error) { +func DownloadFile(u string) (string, error) { fileURL, urlErr := url.Parse(u) if urlErr != nil { return "", fmt.Errorf("invalid url format. %w", urlErr) @@ -182,7 +182,7 @@ func DownloadFile(u string, log *logger.Logger) (string, error) { tmpFilename = tmpFilename + ".zip" } tmpFile := filepath.Join(os.TempDir(), tmpFilename) - log.Debugf("Downloading %s to %s", fileURL.String(), tmpFile) + slog.Debug("Downloading file", "url", fileURL.String(), "path", tmpFile) fTmpFile, fileErr := os.OpenFile(tmpFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if fileErr != nil { return "", fileErr @@ -203,10 +203,6 @@ func (n *CmdCreate) RunE(cmd *cobra.Command, args []string) error { if err != nil { return err } - log, err := n.factory.Logger() - if err != nil { - return err - } commonOptions, err := cfg.GetOutputCommonOptions(cmd) if err != nil { @@ -223,16 +219,16 @@ func (n *CmdCreate) RunE(cmd *cobra.Command, args []string) error { if cfg.DryRun() { fmt.Fprintf(n.factory.IOStreams.ErrOut, "DRY: Downloading plugin from url: %s\n", n.file) } else { - localFile, downloadErr := DownloadFile(n.file, log) + localFile, downloadErr := DownloadFile(n.file) if downloadErr != nil { return fmt.Errorf("could not download plugin. %w", downloadErr) } - log.Infof("Downloaded plugin to %s", localFile) + slog.Info("Downloaded plugin", "path", localFile) n.file = localFile defer func() { if err := os.Remove(localFile); err != nil { - log.Warnf("could not delete downloaded file. %w", err) + slog.Warn("could not delete downloaded file", "err", err) } }() } @@ -240,7 +236,7 @@ func (n *CmdCreate) RunE(cmd *cobra.Command, args []string) error { } dryRun := cfg.ShouldUseDryRun(cmd.CommandPath()) - application, err := n.getApplicationDetails(client, log) + application, err := n.getApplicationDetails(client) if err != nil { return err } @@ -251,7 +247,7 @@ func (n *CmdCreate) RunE(cmd *cobra.Command, args []string) error { filename := filepath.Base(n.file) if !IsValidFilename(filename) { tmpFile := filepath.Join(os.TempDir(), strings.ReplaceAll(filename, " ", "_")) - log.Warnf("Plugin file contains a space, so creating a temp file without a space to avoid being rejected by the server. tmpfile=%s", tmpFile) + slog.Warn("Plugin file contains a space, so creating a temp file without a space to avoid being rejected by the server", "tmpfile", tmpFile) if err := fileutilities.CopyFile(tmpFile, n.file); err != nil { return fmt.Errorf("could not copy file to tmp directory. %w", err) diff --git a/pkg/cmd/util/repeat/repeat.manual.go b/pkg/cmd/util/repeat/repeat.manual.go index 584f67d9d..d3509d851 100644 --- a/pkg/cmd/util/repeat/repeat.manual.go +++ b/pkg/cmd/util/repeat/repeat.manual.go @@ -3,6 +3,7 @@ package repeat import ( "fmt" "io" + "log/slog" "math/rand" "strconv" "strings" @@ -192,7 +193,7 @@ func (n *CmdRepeat) newTemplate(cmd *cobra.Command, args []string) error { includeRowNum = true } - cfg.Logger.Infof("repeat format string: %s", formatString) + slog.Info("repeat format string", "value", formatString) firstRow := int64(0) if n.skip > 0 { @@ -219,12 +220,12 @@ func (n *CmdRepeat) newTemplate(cmd *cobra.Command, args []string) error { } if firstRow != 0 && row <= firstRow { - cfg.Logger.Debugf("Skipping row: %d", row) + slog.Debug("Skipping row", "row", row) continue } if totalRows != 0 && rowCount >= totalRows { - cfg.Logger.Debugf("Found first %d rows", rowCount) + slog.Debug("Found first rows", "count", rowCount) break } @@ -232,7 +233,7 @@ func (n *CmdRepeat) newTemplate(cmd *cobra.Command, args []string) error { // randomly skip a row. 1 = always skip, 0 = never skip randValue := rand.Float32() if randValue <= n.randomSkip { - cfg.Logger.Debugf("Skipping random row: %d. value=%f, limit=%f", row, randValue, n.randomSkip) + slog.Debug("Skipping random row", "row", row, "value", randValue, "limit", n.randomSkip) continue } } @@ -266,7 +267,7 @@ func (n *CmdRepeat) newTemplate(cmd *cobra.Command, args []string) error { currentDelay := randomDelayFunc(delay) if currentDelay > 0 { - cfg.Logger.Infof("Waiting %v before printing next value", currentDelay) + slog.Info(fmt.Sprintf("Waiting %v before printing next value", currentDelay)) time.Sleep(currentDelay) } outputCount++ diff --git a/pkg/cmd/util/repeatcsv/repeatcsv.manual.go b/pkg/cmd/util/repeatcsv/repeatcsv.manual.go index 3151a56f6..19d7a8074 100644 --- a/pkg/cmd/util/repeatcsv/repeatcsv.manual.go +++ b/pkg/cmd/util/repeatcsv/repeatcsv.manual.go @@ -2,6 +2,7 @@ package fromcsv import ( "fmt" + "log/slog" "os" "time" @@ -105,7 +106,7 @@ func (n *CmdRepeatCsvFile) newTemplate(cmd *cobra.Command, args []string) error hasInvalidPaths := false for _, file := range args { if _, err := os.Stat(file); err != nil { - cfg.Logger.Errorf("file does not exist. path=%s. error=%s", file, err) + slog.Error("file does not exist", "path", file, "err", err) hasInvalidPaths = true } } @@ -120,7 +121,7 @@ func (n *CmdRepeatCsvFile) newTemplate(cmd *cobra.Command, args []string) error outputHandler := func(output []byte) error { if err := n.factory.WriteOutputWithoutPropertyGuess(output, cmdutil.OutputContext{}); err != nil { - cfg.Logger.Warnf("Could not process line. only json lines are accepted. %s", err) + slog.Warn("Could not process line. only json lines are accepted", "err", err) } return nil } @@ -135,7 +136,7 @@ func (n *CmdRepeatCsvFile) newTemplate(cmd *cobra.Command, args []string) error repeatRange.Max = &n.times_max } - return cmdutil.ExecuteFileIterator(n.GetCommand().OutOrStdout(), cfg.Logger, files, iterFactory, cmdutil.FileIteratorOptions{ + return cmdutil.ExecuteFileIterator(n.GetCommand().OutOrStdout(), files, iterFactory, cmdutil.FileIteratorOptions{ Infinite: n.infinite, Times: repeatRange, FirstNRows: n.first, diff --git a/pkg/cmd/util/repeatfile/repeatfile.manual.go b/pkg/cmd/util/repeatfile/repeatfile.manual.go index 7d6abf92b..3dddceb47 100644 --- a/pkg/cmd/util/repeatfile/repeatfile.manual.go +++ b/pkg/cmd/util/repeatfile/repeatfile.manual.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "io" + "log/slog" "math/rand" "os" "strconv" @@ -142,7 +143,7 @@ func (n *CmdRepeatFile) newTemplate(cmd *cobra.Command, args []string) error { hasInvalidPaths := false for _, file := range args { if _, err := os.Stat(file); err != nil { - cfg.Logger.Errorf("file does not exist. path=%s. error=%s", file, err) + slog.Error("file does not exist", "path", file, "error", err) hasInvalidPaths = true } } @@ -163,7 +164,7 @@ func (n *CmdRepeatFile) newTemplate(cmd *cobra.Command, args []string) error { includeRowNum = true } - cfg.Logger.Infof("repeat format string: %s", formatString) + slog.Info("repeat format string", "value", formatString) totalRows := int64(0) if n.first > 0 { @@ -196,7 +197,7 @@ func (n *CmdRepeatFile) newTemplate(cmd *cobra.Command, args []string) error { } if totalRows != 0 && rowCount >= totalRows { - cfg.Logger.Debugf("Found first %d rows", rowCount) + slog.Debug("Found first rows", "count", rowCount) return nil } @@ -204,7 +205,7 @@ func (n *CmdRepeatFile) newTemplate(cmd *cobra.Command, args []string) error { // randomly skip a row. 1 = always skip, 0 = never skip randValue := rand.Float32() if randValue <= n.randomSkip { - cfg.Logger.Debugf("Skipping random row: %d. value=%f, limit=%f", row, randValue, n.randomSkip) + slog.Debug("Skipping random row", "row", row, "value", randValue, "limit", n.randomSkip) continue } } @@ -227,7 +228,7 @@ func (n *CmdRepeatFile) newTemplate(cmd *cobra.Command, args []string) error { currentDelay := randomDelayFunc(delay) if currentDelay > 0 { - cfg.Logger.Infof("Waiting %v before printing next value", currentDelay) + slog.Info(fmt.Sprintf("Waiting %v before printing next value", currentDelay)) time.Sleep(currentDelay) } outputCount++ diff --git a/pkg/cmd/util/show/show.manual.go b/pkg/cmd/util/show/show.manual.go index eb86c5e27..907669bed 100644 --- a/pkg/cmd/util/show/show.manual.go +++ b/pkg/cmd/util/show/show.manual.go @@ -3,6 +3,7 @@ package repeat import ( "fmt" "io" + "log/slog" "github.com/MakeNowJust/heredoc/v2" "github.com/reubenmiller/go-c8y-cli/v2/pkg/cmd/subcommand" @@ -100,12 +101,12 @@ func (n *CmdShow) RunE(cmd *cobra.Command, args []string) error { } if !jsonUtilities.IsJSONObject(responseText) { - cfg.Logger.Warnf("Could not process line. only json lines are accepted") + slog.Warn("Could not process line. only json lines are accepted") continue } if err := n.factory.WriteOutputWithoutPropertyGuess(responseText, cmdutil.OutputContext{}); err != nil { - cfg.Logger.Warnf("Could not process line. only json lines are accepted. %s", err) + slog.Warn("Could not process line. only json lines are accepted", "err", err) } if !bounded { diff --git a/pkg/cmdparser/cmdparser.go b/pkg/cmdparser/cmdparser.go index cec6d4638..ded28d3ac 100644 --- a/pkg/cmdparser/cmdparser.go +++ b/pkg/cmdparser/cmdparser.go @@ -3,6 +3,7 @@ package cmdparser import ( "fmt" "io" + "log/slog" "os" "strconv" @@ -29,11 +30,6 @@ type Command struct { func ParseCommand(r io.Reader, factory *cmdutil.Factory, rootCmd *cobra.Command) (*cobra.Command, error) { - log, err := factory.Logger() - if err != nil { - return nil, err - } - spec := &models.Specification{} b, err := io.ReadAll(r) if err != nil { @@ -60,7 +56,7 @@ func ParseCommand(r io.Reader, factory *cmdutil.Factory, rootCmd *cobra.Command) continue } - log.Debugf("Adding command. name=%s", item.Name) + slog.Debug("Adding command", "name", item.Name) subcmd := NewCommandWithOptions(&cobra.Command{ Use: item.Name, Short: item.Description, @@ -84,9 +80,9 @@ func ParseCommand(r io.Reader, factory *cmdutil.Factory, rootCmd *cobra.Command) if err := AddFlag(subcmd, ¶m, factory); err != nil { if file, ok := r.(*os.File); ok { - log.Warnf("Extension: Ignoring invalid flag. details=%s, command=%s, file=%s", err, item.Name, file.Name()) + slog.Warn("Extension: Ignoring invalid flag", "err", err, "command", item.Name, "file", file.Name()) } else { - log.Warnf("Extension: Ignoring invalid flag. details=%s, command=%s", err, item.Name) + slog.Warn("Extension: Ignoring invalid flag", "err", err, "command", item.Name) } // TODO: Is it better to be more forgiving or should it fail hard? // return nil, err @@ -235,15 +231,11 @@ func GetCompletionOptions(cmd *CmdOptions, p *models.Parameter, factory *cmdutil } func AddFlag(cmd *CmdOptions, p *models.Parameter, factory *cmdutil.Factory) error { - log, err := factory.Logger() - if err != nil { - return err - } existingFlag := cmd.Command.Flags().Lookup(p.Name) if existingFlag != nil { // TODO: Update the existing flag rather than ignoring it // TODO: Should an error be returned? - log.Debugf("Ignoring duplicated flag. name=%s", p.Name) + slog.Debug("Ignoring duplicated flag", "name", p.Name) return nil } switch p.Type { diff --git a/pkg/cmdutil/factory.go b/pkg/cmdutil/factory.go index 9ebf8b2b3..ce74b7c8b 100644 --- a/pkg/cmdutil/factory.go +++ b/pkg/cmdutil/factory.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "log" + "log/slog" "net/http" "os" "path/filepath" @@ -28,7 +29,6 @@ import ( "github.com/reubenmiller/go-c8y-cli/v2/pkg/iterator" "github.com/reubenmiller/go-c8y-cli/v2/pkg/jsonUtilities" "github.com/reubenmiller/go-c8y-cli/v2/pkg/jsonformatter" - "github.com/reubenmiller/go-c8y-cli/v2/pkg/logger" "github.com/reubenmiller/go-c8y-cli/v2/pkg/mapbuilder" "github.com/reubenmiller/go-c8y-cli/v2/pkg/mode" "github.com/reubenmiller/go-c8y-cli/v2/pkg/pathresolver" @@ -50,7 +50,6 @@ type Factory struct { Browser Browser Client func() (*c8y.Client, error) Config func() (*config.Config, error) - Logger func() (*logger.Logger, error) ActivityLogger func() (*activitylogger.ActivityLogger, error) Console func() (*console.Console, error) DataView func() (*dataview.DataView, error) @@ -238,10 +237,6 @@ func (f *Factory) GetRequestHandler() (*request.RequestHandler, error) { if err != nil { return nil, err } - log, err := f.Logger() - if err != nil { - return nil, err - } activityLogger, err := f.ActivityLogger() if err != nil { @@ -265,7 +260,6 @@ func (f *Factory) GetRequestHandler() (*request.RequestHandler, error) { IO: f.IOStreams, Client: client, Config: cfg, - Logger: log, DataView: dataview, Console: consol, ActivityLogger: activityLogger, @@ -279,10 +273,6 @@ func (f *Factory) RunWithWorkers(client *c8y.Client, cmd *cobra.Command, req *c8 if err != nil { return err } - log, err := f.Logger() - if err != nil { - return err - } activityLogger, err := f.ActivityLogger() if err != nil { @@ -302,13 +292,12 @@ func (f *Factory) RunWithWorkers(client *c8y.Client, cmd *cobra.Command, req *c8 IO: f.IOStreams, Client: client, Config: cfg, - Logger: log, DataView: dataview, Console: consol, ActivityLogger: activityLogger, HideSensitive: cfg.HideSensitiveInformationIfActive, } - w, err := worker.NewWorker(log, cfg, f.IOStreams, client, activityLogger, handler.ProcessRequestAndResponse, f.CheckPostCommandError) + w, err := worker.NewWorker(cfg, f.IOStreams, client, activityLogger, handler.ProcessRequestAndResponse, f.CheckPostCommandError) if err != nil { return err @@ -325,10 +314,6 @@ func (f *Factory) RunWithGenericWorkers(cmd *cobra.Command, inputIterators *flag if err != nil { return err } - log, err := f.Logger() - if err != nil { - return err - } // set a default run once iterator if one is not defined if iter == nil { @@ -348,7 +333,7 @@ func (f *Factory) RunWithGenericWorkers(cmd *cobra.Command, inputIterators *flag // return err // } - w, err := worker.NewGenericWorker(log, cfg, f.IOStreams, client, activityLogger, runFunc, f.CheckPostCommandError) + w, err := worker.NewGenericWorker(cfg, f.IOStreams, client, activityLogger, runFunc, f.CheckPostCommandError) if err != nil { return err @@ -365,10 +350,6 @@ func (f *Factory) RunSequentiallyWithGenericWorkers(cmd *cobra.Command, iter ite if err != nil { return err } - log, err := f.Logger() - if err != nil { - return err - } activityLogger, err := f.ActivityLogger() if err != nil { @@ -380,7 +361,7 @@ func (f *Factory) RunSequentiallyWithGenericWorkers(cmd *cobra.Command, iter ite iter = iterator.NewRunOnceIterator() } - w, err := worker.NewGenericWorker(log, cfg, f.IOStreams, client, activityLogger, runFunc, f.CheckPostCommandError) + w, err := worker.NewGenericWorker(cfg, f.IOStreams, client, activityLogger, runFunc, f.CheckPostCommandError) if err != nil { return err @@ -394,10 +375,6 @@ func (f *Factory) GetViewProperties(cfg *config.Config, cmd *cobra.Command, outp if err != nil { return nil, err } - log, err := f.Logger() - if err != nil { - return nil, err - } view := cfg.ViewOption() showRaw := cfg.RawOutput() || cfg.WithTotalPages() || cfg.WithTotalElements() @@ -418,13 +395,13 @@ func (f *Factory) GetViewProperties(cfg *config.Config, cmd *cobra.Command, outp if err != nil || len(props) == 0 { if err != nil { - log.Infof("No matching view detected. defaulting to '**'. %s", err) + slog.Info("No matching view detected. defaulting to '**'", "err", err) } else { - log.Info("No matching view detected. defaulting to '**'") + slog.Info("No matching view detected. defaulting to '**'") } viewProperties = append(viewProperties, "**") } else { - log.Infof("Detected view: %s", strings.Join(props, ", ")) + slog.Info("Detected view", "value", strings.Join(props, ",")) viewProperties = append(viewProperties, props...) } default: @@ -432,13 +409,13 @@ func (f *Factory) GetViewProperties(cfg *config.Config, cmd *cobra.Command, outp props, err := dataView.GetViewByName(view) if err != nil || len(props) == 0 { if err != nil { - cfg.Logger.Warnf("no matching view found. %s, name=%s", err, view) + slog.Warn("no matching view found", "err", err, "name", view) } else { - cfg.Logger.Warnf("no matching view found. name=%s", view) + slog.Warn("no matching view found", "name", view) } viewProperties = append(viewProperties, "**") } else { - cfg.Logger.Infof("Detected view: %s", strings.Join(props, ", ")) + slog.Info("Detected view", "value", strings.Join(props, ",")) viewProperties = append(viewProperties, props...) } } @@ -450,10 +427,6 @@ func (f *Factory) CheckPostCommandError(err error) error { if configErr != nil { log.Fatalf("Could not load configuration. %s", configErr) } - logg, logErr := f.Logger() - if logErr != nil { - log.Fatalf("Could not configure logger. %s", logErr) - } w := io.Discard if errors.Is(err, cmderrors.ErrHelp) { @@ -491,7 +464,7 @@ func (f *Factory) CheckPostCommandError(err error) error { if cErr, ok := err.(cmderrors.CommandError); ok { if cErr.StatusCode == 403 || cErr.StatusCode == 401 { - logg.Error(fmt.Sprintf("Authentication failed (statusCode=%d). Try to run set-session again, or check the password", cErr.StatusCode)) + slog.Error(fmt.Sprintf("Authentication failed (statusCode=%d). Try to run set-session again, or check the password", cErr.StatusCode)) } // format errors as json messages @@ -502,7 +475,7 @@ func (f *Factory) CheckPostCommandError(err error) error { } if !cErr.IsSilent() && !strings.Contains(silentStatusCodes, fmt.Sprintf("%d", cErr.StatusCode)) { if printLogEntries { - logg.Errorf("%s", cErr) + slog.Error(cErr.Error()) } fmt.Fprintf(w, "%s\n", cErr.JSONString()) @@ -514,9 +487,9 @@ func (f *Factory) CheckPostCommandError(err error) error { cErr := cmderrors.NewSystemErrorF("%s", err) cErr.ExitCode = cmderrors.ExitUserError if printLogEntries { - logg.Errorf("%s", cErr) + slog.Error(cErr.Error()) } - logg.Debugf("Processing unexpected error. %s, exitCode=%d", err, cErr.ExitCode) + slog.Debug("Processing unexpected error", "err", err, "exitCode", cErr.ExitCode) fmt.Fprintf(w, "%s\n", cErr.JSONString()) cErr.Processed = true outErr = cErr @@ -708,11 +681,6 @@ func (f *Factory) WriteOutputWithRows(output []byte, params OutputContext, commo return 0, err } - logg, err := f.Logger() - if err != nil { - return 0, err - } - if commonOptions == nil { if f.Command == nil { return 0, fmt.Errorf("command output options are mandatory") @@ -726,7 +694,7 @@ func (f *Factory) WriteOutputWithRows(output []byte, params OutputContext, commo if len(output) > 0 || commonOptions.HasOutputTemplate() { // estimate size based on utf8 encoding. 1 char is 1 byte if params.Response != nil { - PrintResponseSize(logg, params.Response, output) + PrintResponseSize(params.Response, output) } var responseText []byte @@ -744,7 +712,7 @@ func (f *Factory) WriteOutputWithRows(output []byte, params OutputContext, commo if v := outputJSON.Get(dataProperty); v.Exists() && v.IsArray() { unfilteredSize = len(v.Array()) - logg.Infof("Unfiltered array size. len=%d", unfilteredSize) + slog.Info("Unfiltered array size", "len", unfilteredSize) } // Trim space from json object/array output @@ -789,10 +757,10 @@ func (f *Factory) WriteOutputWithRows(output []byte, params OutputContext, commo } if cfg.RawOutput() { - logg.Infof("Raw mode active. In raw mode the following settings are forced, view=off, output=json") + slog.Info("Raw mode active. In raw mode the following settings are forced, view=off, output=json") } view := cfg.ViewOption() - logg.Infof("View mode: %s", view) + slog.Info(fmt.Sprintf("View mode: %s", view)) // Detect view (if no filters are given) if len(commonOptions.Filters.Pluck) == 0 { @@ -822,36 +790,36 @@ func (f *Factory) WriteOutputWithRows(output []byte, params OutputContext, commo if err != nil || len(props) == 0 { if err != nil { - logg.Infof("No matching view detected. defaulting to '**'. %s", err) + slog.Info("No matching view detected. defaulting to '**'", "err", err) } else { - logg.Info("No matching view detected. defaulting to '**'") + slog.Info("No matching view detected. defaulting to '**'") } commonOptions.Filters.Pluck = []string{"**"} } else { - logg.Infof("Detected view: %s", strings.Join(props, ", ")) + slog.Info("Detected view", "value", strings.Join(props, ",")) commonOptions.Filters.Pluck = props } default: props, err := dataView.GetViewByName(view) if err != nil || len(props) == 0 { if err != nil { - logg.Warnf("no matching view found. %s, name=%s", err, view) + slog.Warn("no matching view found", "err", err, "name", view) } else { - logg.Warnf("no matching view found. name=%s", view) + slog.Warn("no matching view found", "name", view) } commonOptions.Filters.Pluck = []string{"**"} } else { - logg.Infof("Detected view: %s", strings.Join(props, ", ")) + slog.Info("Detected view", "value", strings.Join(props, ",")) commonOptions.Filters.Pluck = props } } } } else { - logg.Debugf("using existing pluck values. %v", commonOptions.Filters.Pluck) + slog.Debug("using existing pluck values", "values", commonOptions.Filters.Pluck) } if filterOutput, filterErr := commonOptions.Filters.Apply(string(output), dataProperty, false, consol.SetHeaderFromInput); filterErr != nil { - logg.Warnf("filter error. %s", filterErr) + slog.Warn("filter error", "err", filterErr) responseText = filterOutput } else { responseText = filterOutput @@ -861,7 +829,7 @@ func (f *Factory) WriteOutputWithRows(output []byte, params OutputContext, commo if !showRaw { if len(responseText) == len(emptyArray) && bytes.Equal(responseText, emptyArray) { - logg.Info("No matching results found. Empty response will be omitted") + slog.Info("No matching results found. Empty response will be omitted") responseText = []byte{} } } @@ -897,11 +865,6 @@ func (f *Factory) GuessDataProperty(output gjson.Result) string { arrayProperties := []string{} totalKeys := 0 - logg, err := f.Logger() - if err != nil { - panic(err) - } - if v := output.Get("id"); !v.Exists() { // Find the property which is an array output.ForEach(func(key, value gjson.Result) bool { @@ -914,10 +877,10 @@ func (f *Factory) GuessDataProperty(output gjson.Result) string { } if len(arrayProperties) > 1 { - logg.Debugf("Could not detect property as more than 1 array like property detected: %v", arrayProperties) + slog.Debug("Could not detect property as more than 1 array like property detected", "properties", arrayProperties) return "" } - logg.Debugf("Array properties: %v", arrayProperties) + slog.Debug("Array properties", "values", arrayProperties) if len(arrayProperties) == 0 { return "" @@ -932,19 +895,19 @@ func (f *Factory) GuessDataProperty(output gjson.Result) string { } if property != "" && totalKeys < 10 { - logg.Debugf("Data property: %s", property) + slog.Debug("Data", "property", property) } return property } -func PrintResponseSize(l *logger.Logger, resp *http.Response, output []byte) { +func PrintResponseSize(resp *http.Response, output []byte) { if resp.ContentLength > -1 { - l.Infof("Response Length: %0.1fKB", float64(resp.ContentLength)/1024) + slog.Info(fmt.Sprintf("Response Length: %0.1fKB", float64(resp.ContentLength)/1024)) } else { if resp.Uncompressed { - l.Infof("Response Length: %0.1fKB (uncompressed)", float64(len(output))/1024) + slog.Info(fmt.Sprintf("Response Length: %0.1fKB (uncompressed)", float64(len(output))/1024)) } else { - l.Infof("Response Length: %0.1fKB", float64(len(output))/1024) + slog.Info(fmt.Sprintf("Response Length: %0.1fKB", float64(len(output))/1024)) } } } diff --git a/pkg/cmdutil/file_iterator.go b/pkg/cmdutil/file_iterator.go index 4e8f95764..45eed58a8 100644 --- a/pkg/cmdutil/file_iterator.go +++ b/pkg/cmdutil/file_iterator.go @@ -3,12 +3,12 @@ package cmdutil import ( "fmt" "io" + "log/slog" "math/rand" "time" "github.com/reubenmiller/go-c8y-cli/v2/pkg/flags" "github.com/reubenmiller/go-c8y-cli/v2/pkg/iterator" - "github.com/reubenmiller/go-c8y-cli/v2/pkg/logger" "github.com/reubenmiller/go-c8y-cli/v2/pkg/randdata" ) @@ -30,7 +30,7 @@ type FileIteratorOptions struct { Format func([]byte, int64) []byte } -func ExecuteFileIterator(w io.Writer, log *logger.Logger, files []string, iterFactory func(string) (iterator.Iterator, error), opt FileIteratorOptions) error { +func ExecuteFileIterator(w io.Writer, files []string, iterFactory func(string) (iterator.Iterator, error), opt FileIteratorOptions) error { totalRows := opt.FirstNRows row := int64(0) rowCount := int64(0) @@ -89,7 +89,7 @@ func ExecuteFileIterator(w io.Writer, log *logger.Logger, files []string, iterFa } if totalRows != 0 && rowCount >= totalRows { - log.Debugf("Found first %d rows", rowCount) + slog.Debug("Found first rows", "count", rowCount) return nil } @@ -97,7 +97,7 @@ func ExecuteFileIterator(w io.Writer, log *logger.Logger, files []string, iterFa // randomly skip a row. 1 = always skip, 0 = never skip randValue := rand.Float32() if randValue <= opt.RandomSkip { - log.Debugf("Skipping random row: %d. value=%f, limit=%f", row, randValue, opt.RandomSkip) + slog.Debug("Skipping random row", "row", row, "value", randValue, "limit", opt.RandomSkip) continue } } @@ -116,7 +116,7 @@ func ExecuteFileIterator(w io.Writer, log *logger.Logger, files []string, iterFa currentDelay := opt.RandomDelayFunc(opt.Delay) if currentDelay > 0 { - log.Infof("Waiting %v before printing next value", currentDelay) + slog.Info(fmt.Sprintf("Waiting %v before printing next value", currentDelay)) time.Sleep(currentDelay) } outputCount++ diff --git a/pkg/config/cliConfiguration.go b/pkg/config/cliConfiguration.go index 3b197ef4c..ee3df396e 100644 --- a/pkg/config/cliConfiguration.go +++ b/pkg/config/cliConfiguration.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "io" + "log/slog" "net/url" "os" "path" @@ -20,7 +21,6 @@ import ( "github.com/reubenmiller/go-c8y-cli/v2/pkg/encrypt" "github.com/reubenmiller/go-c8y-cli/v2/pkg/flags" "github.com/reubenmiller/go-c8y-cli/v2/pkg/jsonfilter" - "github.com/reubenmiller/go-c8y-cli/v2/pkg/logger" "github.com/reubenmiller/go-c8y-cli/v2/pkg/numbers" "github.com/reubenmiller/go-c8y-cli/v2/pkg/pathresolver" "github.com/reubenmiller/go-c8y-cli/v2/pkg/prompt" @@ -419,8 +419,6 @@ type Config struct { // SecretText used to test the encryption passphrase SecretText string - Logger *logger.Logger - sessionFile string // private caching to improve performance @@ -443,9 +441,7 @@ func NewConfig(v *viper.Viper) *Config { SecureData: encrypt.NewSecureData("{encrypted}"), Persistent: viper.New(), prompter: prompt.Prompt{}, - Logger: logger.NewDummyLogger("SecureData"), } - c.prompter.Logger = c.Logger c.bindSettings() return c } @@ -620,19 +616,13 @@ func (c *Config) bindSettings() { ) if err != nil { - c.Logger.Warnf("Could not bind settings. %s", err) + slog.Warn("Could not bind settings", "err", err) } // Set pin entry command c.prompter.PinEntry = c.PinEntry() } -// SetLogger sets the logger -func (c *Config) SetLogger(l *logger.Logger) { - c.Logger = l - c.prompter.Logger = l -} - // ReadConfig reads the given file and loads it into the persistent session config func (c *Config) ReadConfig(file string) error { c.Persistent.SetConfigFile(file) @@ -651,7 +641,7 @@ func (c *Config) CheckEncryption(encryptedText ...string) (string, error) { secretText = encryptedText[0] } - c.Logger.Infof("Checking encryption passphrase against secret text: %s", secretText) + slog.Info("Checking encryption passphrase against secret text", "value", secretText) pass, err := c.prompter.EncryptionPassphrase(secretText, EnvPassphrase, c.Passphrase, "") c.Passphrase = pass return pass, err @@ -758,7 +748,7 @@ func (c *Config) GetTOTP(t time.Time) (string, error) { // CreateKeyFile creates a file used as reference to validate encryption func (c *Config) CreateKeyFile(keyText string) error { if _, err := os.Stat(c.KeyFile()); os.IsExist(err) { - c.Logger.Infof("Key file already exists. file=%s", c.KeyFile) + slog.Info("Key file already exists", "file", c.KeyFile) return nil } key, err := os.Create(c.KeyFile()) @@ -782,7 +772,7 @@ func (c *Config) ReadKeyFile() error { // read from env variable if v := os.Getenv(EnvPassphraseText); v != "" && c.SecureData.IsEncrypted(v) == 1 { - c.Logger.Infof("Using env variable '%s' as example encryption text", EnvPassphraseText) + slog.Info("Using env variable as example encryption text", "env", EnvPassphraseText) c.SecretText = v return c.CreateKeyFile(v) } @@ -795,7 +785,7 @@ func (c *Config) ReadKeyFile() error { c.SecretText = string(contents) return nil } - c.Logger.Warningf("Key file is invalid or contains decrypted information") + slog.Warn("Key file is invalid or contains decrypted information") } // init key file @@ -895,7 +885,7 @@ func (c Config) DebugViper() { // DecryptString returns the decrypted string if the string is encrypted func (c *Config) DecryptString(value string) (string, error) { if c.SecureData.IsEncrypted(value) > 0 { - c.Logger.Infof("Decrypting data. %s", value) + slog.Info("Decrypting data", "value", value) } value, err := c.SecureData.TryDecryptString(value, c.Passphrase) return value, err @@ -921,7 +911,7 @@ func (c *Config) SetEncryptedString(key, value string) error { } if value == "" { - c.Logger.Info("Password is not set so nothing to encrypt") + slog.Info("Password is not set so nothing to encrypt") return nil } @@ -1000,7 +990,7 @@ func (c *Config) IsTokenEncrypted(ignoreEmptyValue ...bool) bool { func (c *Config) MustGetPassword() string { decryptedValue, err := c.GetPassword() if err != nil { - c.Logger.Warningf("Could not decrypt password. %s", err) + slog.Warn("Could not decrypt password", "err", err) } return decryptedValue } @@ -1010,7 +1000,7 @@ func (c *Config) MustGetToken(silent bool) string { decryptedValue, err := c.GetToken() if err != nil { if !silent { - c.Logger.Warningf("Could not decrypt token. %s", err) + slog.Warn("Could not decrypt token", "err", err) } } return decryptedValue @@ -1102,7 +1092,7 @@ func (c *Config) TokenValidFor() time.Duration { value := c.viper.GetString(SettingsSessionTokenValidFor) duration, err := flags.GetDuration(value, true, time.Second) if err != nil { - c.Logger.Warnf("Invalid duration. value=%s, err=%s", duration, err) + slog.Warn("Invalid duration", "value", duration, "err", err) return 0 } return duration @@ -1156,7 +1146,7 @@ func (c *Config) GetWorkers() int { maxWorkers := c.GetMaxWorkers() if workers > maxWorkers { workers = maxWorkers - c.Logger.Warningf("number of workers exceeds the maximum workers limit of %d. Using maximum value (%d) instead", maxWorkers, maxWorkers) + slog.Warn(fmt.Sprintf("number of workers exceeds the maximum workers limit of %d. Using maximum value (%d) instead", maxWorkers, maxWorkers)) } return workers } @@ -1255,7 +1245,7 @@ func (c *Config) HTTPRetryWaitMax() time.Duration { value := c.viper.GetString(SettingsHTTPRetryWaitMax) duration, err := flags.GetDuration(value, true, time.Second) if err != nil { - c.Logger.Warnf("Invalid duration. value=%s, err=%s", duration, err) + slog.Warn("Invalid duration", "value", duration, "err", err) return 0 } return duration @@ -1266,7 +1256,7 @@ func (c *Config) HTTPRetryWaitMin() time.Duration { value := c.viper.GetString(SettingsHTTPRetryWaitMin) duration, err := flags.GetDuration(value, true, time.Second) if err != nil { - c.Logger.Warnf("Invalid duration. value=%s, err=%s", duration, err) + slog.Warn("Invalid duration", "value", duration, "err", err) return 0 } return duration @@ -1285,16 +1275,14 @@ func (c *Config) ShouldUseDryRun(commandLine string) bool { pattern = pattern[1:] } if m, err := regexp.MatchString(pattern, commandLine); err != nil { - if c.Logger != nil { - c.Logger.Warnf("Invalid dry run pattern. pattern=%s, err=%s", commandLine, err) - } + slog.Warn("Invalid dry run pattern", "pattern", commandLine, "err", err) } else { if shouldInvert { - c.Logger.Infof("Should use dry run: pattern=%s, result=%v", pattern, !m) + slog.Info("Should use dry run", "pattern", pattern, "result", !m) return !m } - c.Logger.Infof("Should use dry run: pattern=%s, result=%v", pattern, m) + slog.Info("Should use dry run", "pattern", pattern, "result", m) return m } } @@ -1374,7 +1362,7 @@ func (c *Config) getDuration(name string) time.Duration { v := c.viper.GetString(name) duration, err := flags.GetDuration(v, true, time.Millisecond) if err != nil { - c.Logger.Warnf("Invalid duration. value=%s, err=%s", v, err) + slog.Warn("Invalid duration", "value", v, "err", err) return 0 } return duration @@ -1430,7 +1418,7 @@ func (c *Config) RequestTimeout() time.Duration { value := c.viper.GetString(SettingsTimeout) duration, err := flags.GetDuration(value, true, time.Second) if err != nil { - c.Logger.Warnf("Invalid duration. value=%s, err=%s", duration, err) + slog.Warn("Invalid duration", "value", duration, "err", err) return 0 } return duration @@ -1596,7 +1584,6 @@ func (c *Config) GetOutputFormat() OutputFormat { } format := c.viper.GetString(SettingsOutputFormat) outputFormat := OutputJSON.FromString(format) - // c.Logger.Debugf("output format: %s", outputFormat.String()) return outputFormat } @@ -1828,7 +1815,6 @@ func (c *Config) GetJSONSelect() []string { } } - // c.Logger.Debugf("json select: len=%d, values=%v", len(allitems), allitems) return allitems } @@ -1862,7 +1848,7 @@ func (c *Config) GetOutputCommonOptions(cmd *cobra.Command) (CommonCommandOption options.ResultProperty = flags.GetCollectionPropertyFromAnnotation(cmd) // Filters and selectors - filters := jsonfilter.NewJSONFilters(c.Logger) + filters := jsonfilter.NewJSONFilters() filters.AsCSV = c.IsCSVOutput() filters.AsTSV = c.IsTSVOutput() filters.AsCompletionFormat = c.IsCompletionOutput() @@ -1885,7 +1871,6 @@ func (c *Config) GetOutputCommonOptions(cmd *cobra.Command) (CommonCommandOption if options.IncludeAll { options.PageSize = c.GetIncludeAllPageSize() - // c.Logger.Debugf("Setting pageSize to maximum value to limit number of requests. value=%d", options.PageSize) } options.CurrentPage = c.GetCurrentPage() @@ -1947,7 +1932,7 @@ func (c *Config) ShouldConfirm(methods ...string) bool { useDryRun := c.ShouldUseDryRun("") if c.IsCIMode() || c.Force() || useDryRun { - c.Logger.Debugf("no confirmation required. ci_mode=%v, force=%v, dry=%v", c.IsCIMode(), c.Force(), useDryRun) + slog.Debug("no confirmation required", "ci_mode", c.IsCIMode(), "force", c.Force(), "dry", useDryRun) return false } @@ -1958,7 +1943,7 @@ func (c *Config) ShouldConfirm(methods ...string) bool { confirmMethods := strings.ToUpper(c.GetConfirmationMethods()) for _, method := range methods { if strings.Contains(confirmMethods, strings.ToUpper(method)) { - c.Logger.Debugf("confirmation required due to method=%s", method) + slog.Debug("method requires confirmation", "method", method) return true } } @@ -2001,12 +1986,12 @@ func (c *Config) BindPFlag(flags *pflag.FlagSet) error { settingsName := GetSettingsName(f.Name) if err := c.viper.BindEnv(settingsName); err != nil { - c.Logger.Warnf("Could not bind to environment variable. name=%s, err=%s", settingsName, err) + slog.Warn("Could not bind to environment variable", "name", settingsName, "err", err) lastError = err } if err := c.viper.BindPFlag(settingsName, flags.Lookup(f.Name)); err != nil { - c.Logger.Warnf("Could not set flag. name=%s, err=%s", settingsName, err) + slog.Warn("Could not set flag", "name", settingsName, "err", err) lastError = err } }) @@ -2017,9 +2002,7 @@ func (c *Config) BindPFlag(flags *pflag.FlagSet) error { func (c *Config) ExpandHomePath(path string) string { expanded, err := homedir.Expand(path) if err != nil { - if c.Logger != nil { - c.Logger.Warnf("Could not expand path to home directory. %s", err) - } + slog.Warn("Could not expand path to home directory", "err", err) expanded = path } // replace special variables @@ -2030,22 +2013,22 @@ func (c *Config) ExpandHomePath(path string) string { // LogErrorF dynamically changes where the error is logged based on the users Silent Status Codes preferences // Silent errors are only logged on the INFO level, where as non-silent errors are logged on the ERROR level -func (c *Config) LogErrorF(err error, format string, args ...interface{}) { - errorLogger := c.Logger.Infof +func (c *Config) LogErrorF(err error, msg string, args ...interface{}) { + errorLogger := slog.Info silentStatusCodes := c.GetSilentStatusCodes() if errors.Is(err, cmderrors.ErrNoMatchesFound) { if strings.Contains(silentStatusCodes, "404") { - errorLogger = c.Logger.Infof + errorLogger = slog.Info } } else if cErr, ok := err.(cmderrors.CommandError); ok { // format errors as json messages // only log users errors if strings.Contains(silentStatusCodes, fmt.Sprintf("%d", cErr.StatusCode)) { - errorLogger = c.Logger.Infof + errorLogger = slog.Info } } - errorLogger(format, args...) + errorLogger(msg, args...) } var ConfigExtensions = []string{"json", "yaml", "yml", "env", "toml", "properties"} @@ -2068,14 +2051,14 @@ func (c *Config) ClearSessionFile() { func (c *Config) SetSessionFile(path string) { if _, fileErr := os.Stat(path); fileErr != nil { home := c.GetSessionHomeDir() - c.Logger.Debugf("Resolving session %s in %s", path, home) + slog.Debug(fmt.Sprintf("Resolving session %s in %s", path, home)) matches, err := pathresolver.ResolvePaths([]string{home}, path, ConfigExtensions, "ignore") if err != nil { - c.Logger.Warnf("Failed to find session. %s", err) + slog.Warn("Failed to find session", "err", err) } if len(matches) > 0 { path = matches[0] - c.Logger.Debugf("Resolved session. %s", path) + slog.Debug("Resolved session", "path", path) } } c.sessionFile = c.ExpandHomePath(path) @@ -2105,14 +2088,14 @@ func (c *Config) GetSessionFile(overrideSession ...string) string { sessionFile = strings.TrimPrefix(sessionFile, "file://") if _, fileErr := os.Stat(sessionFile); fileErr != nil { home := c.GetSessionHomeDir() - c.Logger.Debugf("Resolving session %s in %s", sessionFile, home) + slog.Debug(fmt.Sprintf("Resolving session %s in %s", sessionFile, home)) matches, err := pathresolver.ResolvePaths([]string{home}, sessionFile, ConfigExtensions, "ignore") if err != nil { - c.Logger.Warnf("Failed to find session. %s", err) + slog.Warn("Failed to find session", "err", err) } if len(matches) > 0 { sessionFile = matches[0] - c.Logger.Debugf("Resolved session. %s", sessionFile) + slog.Debug("Resolved session", "file", sessionFile) } } @@ -2127,7 +2110,7 @@ func (c *Config) GetSessionFile(overrideSession ...string) string { // 2. load session file (by path) // 3. load session file (by name) func (c *Config) ReadConfigFiles(client *c8y.Client, ignoreSessionFile ...bool) (path string, err error) { - c.Logger.Debugf("Reading configuration files") + slog.Debug("Reading configuration files") v := c.viper v.AddConfigPath(".") v.AddConfigPath(c.GetHomeDir()) @@ -2137,7 +2120,7 @@ func (c *Config) ReadConfigFiles(client *c8y.Client, ignoreSessionFile ...bool) if err := v.ReadInConfig(); err == nil { path = v.ConfigFileUsed() - c.Logger.Infof("Loaded settings: %s", c.HideSensitiveInformationIfActive(client, path)) + slog.Debug("Loaded settings", "file", c.HideSensitiveInformationIfActive(client, path)) } // Load session @@ -2149,7 +2132,7 @@ func (c *Config) ReadConfigFiles(client *c8y.Client, ignoreSessionFile ...bool) v.SetConfigFile(sessionFile) if err := c.ReadConfig(sessionFile); err != nil { - c.Logger.Warnf("Could not read global settings file. file=%s, err=%s", sessionFile, err) + slog.Warn("Could not read global settings file", "file", sessionFile, "err", err) } } else { // Load config by name @@ -2168,7 +2151,7 @@ func (c *Config) ReadConfigFiles(client *c8y.Client, ignoreSessionFile ...bool) path = v.ConfigFileUsed() if err != nil { - c.Logger.Debugf("Failed to merge config. %s", err) + slog.Debug("Failed to merge config. %s", "err", err) } return path, err diff --git a/pkg/config/home.go b/pkg/config/home.go index b7fd84237..ccb141e12 100644 --- a/pkg/config/home.go +++ b/pkg/config/home.go @@ -1,6 +1,7 @@ package config import ( + "log/slog" "os" "path/filepath" @@ -30,8 +31,8 @@ func (c *Config) GetSessionHomeDir() string { outputDir = v if err == nil { outputDir = expandedV - } else if c.Logger != nil { - c.Logger.Warnf("Could not expand path. %s", err) + } else { + slog.Warn("Could not expand path", "err", err) } } else { // Use session directory ~/.cumulocity (separate from c8y home, as it can store sensitive information) @@ -39,16 +40,14 @@ func (c *Config) GetSessionHomeDir() string { if v, err := homedir.Dir(); err == nil { outputDir = v } else { - if c.Logger != nil { - c.Logger.Warnf("Could not find user's home directory. %s", err) - } + slog.Warn("Could not find user's home directory", "err", err) } outputDir = filepath.Join(outputDir, DefaultSessionDir) } err := fileutilities.CreateDirs(outputDir) - if err != nil && c.Logger != nil { - c.Logger.Errorf("Sessions directory check failed. path=%s, err=%s", outputDir, err) + if err != nil { + slog.Error("Sessions directory check failed", "path", outputDir, "err", err) } return outputDir } @@ -72,13 +71,13 @@ func (c *Config) GetHomeDir() string { } outputDir, err := homedir.Expand(os.ExpandEnv(outputDir)) - if err != nil && c.Logger != nil { - c.Logger.Warnf("Could not expand path. %s", err) + if err != nil { + slog.Warn("Could not expand path", "err", err) } err = fileutilities.CreateDirs(outputDir) - if err != nil && c.Logger != nil { - c.Logger.Errorf("Sessions directory check failed. path=%s, err=%s", outputDir, err) + if err != nil { + slog.Error("Sessions directory check failed", "path", outputDir, "err", err) } return outputDir } diff --git a/pkg/dataview/dataview.go b/pkg/dataview/dataview.go index d4edda802..8b1a9ca05 100644 --- a/pkg/dataview/dataview.go +++ b/pkg/dataview/dataview.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "io/fs" + "log/slog" "net/http" "os" "path/filepath" @@ -12,7 +13,6 @@ import ( "strings" "sync" - "github.com/reubenmiller/go-c8y-cli/v2/pkg/logger" "github.com/reubenmiller/go-c8y-cli/v2/pkg/matcher" "github.com/tidwall/gjson" ) @@ -54,21 +54,16 @@ type DataView struct { Extension string Pattern string Definitions []Definition - Logger *logger.Logger ActiveView *Definition } // NewDataView creates a new data view which selected a view based in json data -func NewDataView(pattern string, extension string, log *logger.Logger, paths ...string) (*DataView, error) { - if log == nil { - log = logger.NewDummyLogger("dataview") - } +func NewDataView(pattern string, extension string, paths ...string) (*DataView, error) { view := &DataView{ mu: sync.RWMutex{}, Paths: paths, Pattern: pattern, Extension: extension, - Logger: log, } return view, nil } @@ -77,7 +72,7 @@ func NewDataView(pattern string, extension string, log *logger.Logger, paths ... func (v *DataView) LoadDefinitions() error { if len(v.GetDefinitions()) > 0 { - v.Logger.Debugf("Views already loaded") + slog.Debug("Views already loaded") return nil } @@ -85,9 +80,9 @@ func (v *DataView) LoadDefinitions() error { defer v.mu.Unlock() definitions := make([]Definition, 0) - v.Logger.Debugf("Looking for definitions in: %v", v.Paths) + slog.Debug("Looking for definitions", "paths", v.Paths) for _, path := range v.Paths { - v.Logger.Debugf("Current view path: %s", path) + slog.Debug("Current view", "path", path) extName := "" if strings.Contains(path, NamespaceSeparator) { @@ -99,18 +94,18 @@ func (v *DataView) LoadDefinitions() error { if stat, err := os.Stat(path); err != nil { if extName == "" { - v.Logger.Debugf("Skipping view path because it does not exist. path=%s, error=%s", path, err) + slog.Debug("Skipping view path because it does not exist", "path", path, "err", err) } continue } else if !stat.IsDir() { - v.Logger.Debugf("Skipping view path because it is not a folder. path=%s", path) + slog.Debug("Skipping view path because it is not a folder", "path", path) continue } err := filepath.WalkDir(path, func(path string, d fs.DirEntry, err error) error { if err != nil { // do not block walking folder - v.Logger.Warnf("Failed to walk path: %s, err=%s. file will be ignored", path, err) + slog.Warn("Failed to walk path. file will be ignored", "path", path, "err", err) return nil } if !d.IsDir() { @@ -127,13 +122,13 @@ func (v *DataView) LoadDefinitions() error { } if extName != "" { - v.Logger.Debugf("Found view definition: %s | extension: %s", d.Name(), extName) + slog.Debug(fmt.Sprintf("Found view definition: %s | extension: %s", d.Name(), extName)) } else { - v.Logger.Debugf("Found view definition: %s", d.Name()) + slog.Debug(fmt.Sprintf("Found view definition: %s", d.Name())) } viewDefinition := &DefinitionCollection{} if err := json.Unmarshal(contents, &viewDefinition); err != nil { - v.Logger.Warnf("Could not load view definitions. %s", err) + slog.Warn("Could not load view definitions", "err", err) // do not prevent walking other folders return nil } @@ -147,10 +142,10 @@ func (v *DataView) LoadDefinitions() error { return nil }) if err != nil { - v.Logger.Warnf("View discovery has errors. %s", err) + slog.Warn("View discovery has errors", "err", err) return err } - v.Logger.Debugf("Loaded definitions: %d", len(definitions)) + slog.Debug("Loaded definitions", "total", len(definitions)) } // sort by priority @@ -232,7 +227,7 @@ type ViewData struct { func (v *DataView) GetView(r *ViewData) ([]string, error) { if view := v.GetActiveView(); view != nil { - v.Logger.Debugf("Using already active view") + slog.Debug("Using already active view") return view.Columns, nil } @@ -257,7 +252,6 @@ func (v *DataView) GetView(r *ViewData) ([]string, error) { for _, fragment := range definition.Fragments { if result := data.Get(fragment); !result.Exists() { - // v.Logger.Debugf("Data did not contain fragment. view=%s, fragment=%s, input=%s", definition.FileName, fragment, data.Raw) isMatch = false break } @@ -314,13 +308,13 @@ func (v *DataView) GetView(r *ViewData) ([]string, error) { } if matchingDefinition != nil { if matchingDefinition.Extension != "" { - v.Logger.Debugf("Found matching view: name=%s, extension=%s, file: %s", matchingDefinition.Name, matchingDefinition.Extension, matchingDefinition.Path) + slog.Debug("Found matching view", "name", matchingDefinition.Name, "extension", matchingDefinition.Extension, "file", matchingDefinition.Path) } else { - v.Logger.Debugf("Found matching view: name=%s, file: %s", matchingDefinition.Name, matchingDefinition.Path) + slog.Debug("Found matching view", "name", matchingDefinition.Name, "file", matchingDefinition.Path) } v.ActiveView = matchingDefinition return matchingDefinition.Columns, nil } - v.Logger.Debug("No matching view found") + slog.Debug("No matching view found") return nil, nil } diff --git a/pkg/iostreams/iostreams.go b/pkg/iostreams/iostreams.go index 98f410d71..4fdd6d115 100644 --- a/pkg/iostreams/iostreams.go +++ b/pkg/iostreams/iostreams.go @@ -15,7 +15,6 @@ import ( "github.com/mattn/go-isatty" "github.com/muesli/termenv" "github.com/reubenmiller/go-c8y-cli/v2/pkg/clio" - "github.com/reubenmiller/go-c8y-cli/v2/pkg/logger" "github.com/vbauerster/mpb/v6" "golang.org/x/term" ) @@ -40,8 +39,6 @@ type IOStreams struct { stderrTTYOverride bool stderrIsTTY bool - Logger *logger.Logger - neverPrompt bool TempFileOverride *os.File diff --git a/pkg/jsonUtilities/shorthand.go b/pkg/jsonUtilities/shorthand.go index 485e9d765..10defc5c6 100644 --- a/pkg/jsonUtilities/shorthand.go +++ b/pkg/jsonUtilities/shorthand.go @@ -3,12 +3,12 @@ package jsonUtilities import ( "encoding/json" "fmt" + "log/slog" "regexp" "strconv" "strings" "github.com/pkg/errors" - "github.com/reubenmiller/go-c8y-cli/v2/pkg/logger" "github.com/reubenmiller/go-c8y/pkg/c8y" ) @@ -17,13 +17,6 @@ const ( Separator = "." ) -// Logger is the package logger -var Logger *logger.Logger - -func init() { - Logger = logger.NewDummyLogger("jsonUtilities") -} - // MustParseJSON parses a string and returns the map structure func MustParseJSON(value string) map[string]interface{} { data := make(map[string]interface{}) @@ -112,7 +105,7 @@ func parseValue(value string) interface{} { jsonMap := make(map[string]interface{}) if err := json.Unmarshal([]byte(propValue), &jsonMap); err != nil { - Logger.Warningf("Invalid json. %s", err) + slog.Warn("Invalid json", "err", err) // Try parsing return parseValue(propValue[1 : len(propValue)-1]) @@ -122,7 +115,7 @@ func parseValue(value string) interface{} { // parse array values valueArray := []interface{}{} for _, item := range values { - Logger.Debugf("item: %s", item) + slog.Debug("item", "value", item) valueArray = append(valueArray, parseValue(item)) } return valueArray @@ -155,7 +148,7 @@ func parseShorthandJSONStructure(value string, data map[string]interface{}) erro valuePairs := strings.Split(value, "=") if len(value) > 0 { - Logger.Debugf("Input: %s", value) + slog.Debug(fmt.Sprintf("Input: %s", value)) } outputValues := []string{} @@ -194,7 +187,7 @@ func parseShorthandJSONStructure(value string, data map[string]interface{}) erro validItems++ } - Logger.Debugf("Output: %v", outputValues) + slog.Debug(fmt.Sprintf("Output: %v", outputValues)) if validItems == 0 { return fmt.Errorf("Input contains no valid shorthand data") diff --git a/pkg/jsonfilter/jsonfilter.go b/pkg/jsonfilter/jsonfilter.go index 9e42063f2..2cb97c17b 100644 --- a/pkg/jsonfilter/jsonfilter.go +++ b/pkg/jsonfilter/jsonfilter.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/json" "fmt" + "log/slog" "sort" "strconv" "strings" @@ -11,28 +12,15 @@ import ( "github.com/hashicorp/go-version" glob "github.com/obeattie/ohmyglob" "github.com/reubenmiller/go-c8y-cli/v2/pkg/flatten" - "github.com/reubenmiller/go-c8y-cli/v2/pkg/logger" "github.com/reubenmiller/go-c8y-cli/v2/pkg/matcher" "github.com/reubenmiller/go-c8y-cli/v2/pkg/sortorder" "github.com/reubenmiller/go-c8y-cli/v2/pkg/timestamp" "github.com/reubenmiller/go-c8y/pkg/c8y" "github.com/reubenmiller/gojsonq/v2" "github.com/tidwall/gjson" - "go.uber.org/zap/zapcore" ) -var Logger *logger.Logger - -func init() { - Logger = logger.NewLogger("jsonfilter", logger.Options{ - Level: zapcore.DebugLevel, - Color: true, - Silent: true, - }) -} - type JSONFilters struct { - Logger *logger.Logger Filters []JSONFilter Selectors []string Pluck []string @@ -164,9 +152,9 @@ func FilterPropertyByWildcard(jsonValue string, prefix string, patterns []string if err != nil { return nil, nil, err } - Logger.Debugf("flattening json") + slog.Debug("flattening json") flatMap, err := flatten.Flatten(rawMap, prefix, flatten.DotStyle) - Logger.Debugf("finished flattening json") + slog.Debug("finished flattening json") if err != nil { return nil, nil, err @@ -197,9 +185,9 @@ func FilterPropertyByWildcard(jsonValue string, prefix string, patterns []string } } - Logger.Debugf("running filterFlatMap") + slog.Debug("running filterFlatMap") resolvedProperties, _ := filterFlatMap(flatMap, filteredMap, compiledPatterns, aliases) - Logger.Debugf("finished filterFlatMap") + slog.Debug("finished filterFlatMap") return filteredMap, resolvedProperties, err } @@ -223,7 +211,7 @@ func filterFlatMap(src map[string]interface{}, dst map[string]interface{}, patte for i, pattern := range patterns { found := false - Logger.Debugf("filtering keys by pattern: total=%d, pattern=%s", len(sourceKeys), pattern.String()) + slog.Debug("filtering keys by pattern", "total", len(sourceKeys), "pattern", pattern.String()) for _, key := range sourceKeys { value := src[key] @@ -301,9 +289,8 @@ func filterFlatMap(src map[string]interface{}, dst map[string]interface{}, patte } // NewJSONFilters create a json filter -func NewJSONFilters(l *logger.Logger) *JSONFilters { +func NewJSONFilters() *JSONFilters { return &JSONFilters{ - Logger: l, Filters: make([]JSONFilter, 0), Selectors: make([]string, 0), } @@ -359,7 +346,7 @@ func (f JSONFilters) filterJSON(jsonValue string, property string, showHeaders b } if v.IsObject() { - Logger.Info("Converting json object to array") + slog.Info("Converting json object to array") jq = gojsonq.New().FromString("[" + v.String() + "]") convertBackFromArray = true } else if v.IsArray() { @@ -395,18 +382,18 @@ func (f JSONFilters) filterJSON(jsonValue string, property string, showHeaders b jq.Macro("version", matchVersionConstraint) for _, query := range f.Filters { - Logger.Debugf("filtering data: %s %s %s", query.Property, query.Operation, query.Value) + slog.Debug(fmt.Sprintf("filtering data: %s %s %s", query.Property, query.Operation, query.Value)) jq.Where(query.Property, query.Operation, query.Value) } if errs := jq.Errors(); len(errs) > 0 { - Logger.Warnf("filter errors. %v", errs) + slog.Warn("filter errors", "err", errs) } if len(f.Selectors) > 0 { jq.Select(f.Selectors...) } - Logger.Debugf("Pluck values: %v", f.Pluck) + slog.Debug("Pluck", "values", f.Pluck) // format values (using gjson) // skip flatten and select if a only a globstar is provided // selectAllProperties := len(f.Pluck) == 1 && f.Pluck[0] == "**" @@ -442,7 +429,7 @@ func (f JSONFilters) filterJSON(jsonValue string, property string, showHeaders b return []byte(line), formatErrors(jq.Errors()) } - Logger.Debugf("ERROR: gjson path does not exist. %v", f.Pluck) + slog.Debug("ERROR: gjson path does not exist", "err", f.Pluck) return []byte(""), formatErrors(jq.Errors()) } @@ -529,9 +516,9 @@ func (f JSONFilters) pluckJsonValues(item *gjson.Result, properties []string) (s v, err = json.Marshal(flatMap) } else { // unflatten - Logger.Debugf("running unflatten. %v", pathPatterns) + slog.Debug("running unflatten", "patterns", pathPatterns) if len(pathPatterns) == 1 && pathPatterns[0] == "**" { - Logger.Debugf("Returning all keys because globstar is being used") + slog.Debug("Returning all keys because globstar is being used") return item.Raw, flatKeys } @@ -539,22 +526,20 @@ func (f JSONFilters) pluckJsonValues(item *gjson.Result, properties []string) (s maxKeyCount := int64(10000) keyCount := int64(len(flatMap)) if keyCount > maxKeyCount { - if f.Logger != nil { - itemID := "" - if v := item.Get("id"); v.Exists() { - itemID = v.Str - } - f.Logger.Warnf("Detected json with a large number of keys, returning all data by default. Use jq for further filtering. total_keys=%d, id=%s", keyCount, itemID) + itemID := "" + if v := item.Get("id"); v.Exists() { + itemID = v.Str } + slog.Warn("Detected json with a large number of keys, returning all data by default. Use jq for further filtering", "total_keys", keyCount, "id", itemID) return item.Raw, flatKeys } v, err = flatten.UnflattenOrdered(flatMap, flatKeys) - Logger.Debugf("Finished unflatten") + slog.Debug("Finished unflatten") } if err != nil { - Logger.Warningf("failed to marshal value. err=%s", err) + slog.Warn("failed to marshal value", "err", err) } else { if v != nil { output.Write(v) @@ -580,7 +565,7 @@ func convertToCSV(flatMap map[string]interface{}, keys []string, separator strin } if value, ok := flatMap[key]; ok { if marshalledValue, err := json.Marshal(value); err != nil { - Logger.Warningf("failed to marshal value. value=%v, err=%s", value, err) + slog.Warn("failed to marshal value", "value", value, "err", err) } else { if !bytes.Contains(marshalledValue, []byte(",")) { buf.Write(bytes.Trim(marshalledValue, "\"")) @@ -606,7 +591,7 @@ func convertToLine(flatMap map[string]interface{}, keys []string) string { } if value, ok := flatMap[key]; ok { if marshalledValue, err := json.Marshal(value); err != nil { - Logger.Warningf("failed to marshal value. value=%v, err=%s", value, err) + slog.Warn("Could not marshal value", "value", value, "err", err) } else { if i != 0 { buf.WriteString(key) diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index 3c4ad161a..a8544730b 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -1,150 +1,48 @@ package logger import ( + "log/slog" "os" + "time" - "github.com/mattn/go-colorable" - "go.uber.org/zap" - "go.uber.org/zap/zapcore" + "github.com/lmittmann/tint" ) -type LoggerInterface interface { - Debugt(msg string, fields ...zapcore.Field) - Debugf(template string, args ...interface{}) - Debugw(msg string, keysAndValues ...interface{}) - Debug(msg string, keysAndValues ...interface{}) - Debugs(args ...interface{}) - - Infot(msg string, fields ...zapcore.Field) - Infof(template string, args ...interface{}) - Infow(msg string, keysAndValues ...interface{}) - Info(msg string, keysAndValues ...interface{}) - Infos(args ...interface{}) - - Warnt(msg string, fields ...zapcore.Field) - Warnf(template string, args ...interface{}) - Warnw(msg string, keysAndValues ...interface{}) - Warn(msg string, keysAndValues ...interface{}) - Warns(args ...interface{}) - - Errort(msg string, fields ...zapcore.Field) - Errorf(template string, args ...interface{}) - Errorw(msg string, keysAndValues ...interface{}) - Error(msg string, keysAndValues ...interface{}) - Errors(args ...interface{}) - - Panict(msg string, fields ...zapcore.Field) - Panicf(template string, args ...interface{}) - Panicw(msg string, keysAndValues ...interface{}) - Panic(msg string, keysAndValues ...interface{}) - Panics(args ...interface{}) - - Fatalt(msg string, fields ...zapcore.Field) - Fatalf(template string, args ...interface{}) - Fatalw(msg string, keysAndValues ...interface{}) - Fatal(msg string, keysAndValues ...interface{}) - Fatals(args ...interface{}) - - AtLevel(level zapcore.Level, msg string, fields ...zapcore.Field) *Logger -} - // Logger provides a log interface to verbose messages to the user -type Logger struct { - zLogger *zap.Logger -} - -// Printf is an alias for Infof -func (l Logger) Printf(format string, args ...interface{}) { - l.Infof(format, args...) -} - -// Println is an alias for Info -func (l Logger) Println(args ...interface{}) { - l.Info(args...) -} - -// Warningf logs a warning message with a format string -func (l Logger) Warningf(format string, args ...interface{}) { - if l.zLogger != nil { - l.zLogger.Sugar().Warnf(format, args...) - } -} - -// Warning logs a warning message -func (l Logger) Warning(args ...interface{}) { - if l.zLogger != nil { - l.zLogger.Sugar().Warn(args...) - } -} - -// Warnf logs a warning message with a format string -func (l Logger) Warnf(format string, args ...interface{}) { - l.Warningf(format, args...) -} - -// Warn logs a warning message -func (l Logger) Warn(args ...interface{}) { - l.Warning(args...) -} - -// Errorf logs an error message with a format string -func (l Logger) Errorf(format string, args ...interface{}) { - if l.zLogger != nil { - l.zLogger.Sugar().Errorf(format, args...) - } -} - -// Error logs an error message -func (l Logger) Error(args ...interface{}) { - if l.zLogger != nil { - l.zLogger.Sugar().Error(args...) - } -} - -// Debug logs a debug message -func (l Logger) Debug(args ...interface{}) { - if l.zLogger != nil { - l.zLogger.Sugar().Debug(args...) - } -} - -// Debugf logs a debug message with a format string -func (l Logger) Debugf(format string, args ...interface{}) { - if l.zLogger != nil { - l.zLogger.Sugar().Debugf(format, args...) - } -} - -// Info logs a information message -func (l Logger) Info(args ...interface{}) { - if l.zLogger != nil { - l.zLogger.Sugar().Info(args...) - } -} - -// Infof logs a information message with a format string -func (l Logger) Infof(format string, args ...interface{}) { - if l.zLogger != nil { - l.zLogger.Sugar().Infof(format, args...) - } -} - -// Fatal logs a information message -func (l Logger) Fatal(args ...interface{}) { - if l.zLogger != nil { - l.zLogger.Sugar().Fatal(args...) - } -} - -// Fatalf logs a information message with a format string -func (l Logger) Fatalf(format string, args ...interface{}) { - if l.zLogger != nil { - l.zLogger.Sugar().Fatalf(format, args...) - } -} +// type Logger struct { +// zLogger *slog.Logger +// } + +// // Warn logs a warning message +// func (l Logger) Warn(msg string, args ...interface{}) { +// if l.zLogger != nil { +// l.zLogger.Warn(msg, args...) +// } +// } + +// // Error logs an error message +// func (l Logger) Error(msg string, args ...interface{}) { +// if l.zLogger != nil { +// l.zLogger.Error(msg, args...) +// } +// } + +// // Debug logs a debug message +// func (l Logger) Debug(msg string, args ...interface{}) { +// if l.zLogger != nil { +// l.zLogger.Debug(msg, args...) +// } +// } + +// // Info logs a information message +// func (l Logger) Info(msg string, args ...interface{}) { +// if l.zLogger != nil { +// l.zLogger.Info(msg, args...) +// } +// } // NewDummyLogger create a dummy no-op logger -func NewDummyLogger(name string) *Logger { +func NewDummyLogger(name string) *slog.Logger { return NewLogger(name, Options{ Silent: true, }) @@ -162,54 +60,42 @@ type Options struct { Debug bool // Level default log level - Level zapcore.Level + Level slog.Level } // NewLogger create a logger with given options -func NewLogger(name string, options Options) *Logger { +func NewLogger(name string, options Options) *slog.Logger { // new logger - consoleEncCfg := zapcore.EncoderConfig{ - // Keys can be anything except the empty string. - TimeKey: "T", - LevelKey: "L", - NameKey: "N", - CallerKey: "C", - FunctionKey: zapcore.OmitKey, - MessageKey: "M", - StacktraceKey: "S", - LineEnding: zapcore.DefaultLineEnding, - EncodeLevel: zapcore.CapitalColorLevelEncoder, - EncodeTime: zapcore.ISO8601TimeEncoder, - EncodeDuration: zapcore.StringDurationEncoder, - EncodeCaller: zapcore.ShortCallerEncoder, - } - - consoleLevel := options.Level - if options.Debug { - consoleLevel = zapcore.DebugLevel - } - consoleLevelEnabler := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { - return lvl >= consoleLevel - }) - - consoleEncoder := zapcore.NewConsoleEncoder(consoleEncCfg) - - var cores []zapcore.Core + lvl := new(slog.LevelVar) + var logger *slog.Logger + w := os.Stderr if options.Silent { - cores = append(cores, zapcore.NewNopCore()) + logger = slog.New(slog.DiscardHandler) } else { - output := colorable.NewNonColorable(os.Stderr) - if options.Color { - output = colorable.NewColorableStderr() - } - cores = append(cores, zapcore.NewCore(consoleEncoder, zapcore.Lock(zapcore.AddSync(output)), consoleLevelEnabler)) + logger = slog.New( + tint.NewHandler(w, &tint.Options{ + Level: lvl, + TimeFormat: time.RFC3339, + NoColor: !options.Color, + ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { + if a.Value.Kind() == slog.KindAny { + if _, ok := a.Value.Any().(error); ok { + return tint.Attr(9, a) + } + } + return a + }, + }), + ) } - core := zapcore.NewTee(cores...) - unsugared := zap.New(core) - - return &Logger{ - zLogger: unsugared, + lvl.Set(options.Level) + if options.Debug { + lvl.Set(slog.LevelDebug) + } else { + lvl.Set(options.Level) } + slog.SetDefault(logger) + return logger } diff --git a/pkg/mapbuilder/mapbuilder.go b/pkg/mapbuilder/mapbuilder.go index 909eded1f..0c1a77672 100644 --- a/pkg/mapbuilder/mapbuilder.go +++ b/pkg/mapbuilder/mapbuilder.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "log" + "log/slog" "math/rand" "net/url" "os" @@ -22,28 +23,16 @@ import ( "github.com/reubenmiller/go-c8y-cli/v2/pkg/iterator" "github.com/reubenmiller/go-c8y-cli/v2/pkg/jsonUtilities" "github.com/reubenmiller/go-c8y-cli/v2/pkg/jsonfilter" - "github.com/reubenmiller/go-c8y-cli/v2/pkg/logger" "github.com/reubenmiller/go-c8y-cli/v2/pkg/randdata" "github.com/reubenmiller/go-c8y-cli/v2/pkg/timestamp" "github.com/tidwall/gjson" "github.com/tidwall/sjson" - "go.uber.org/zap/zapcore" ) const ( timeFormatRFC3339Micro = "2006-01-02T15:04:05.999Z07:00" ) -var Logger *logger.Logger - -func init() { - Logger = logger.NewLogger("mapbuilder", logger.Options{ - Level: zapcore.DebugLevel, - Color: true, - Silent: true, - }) -} - func registerNativeFunctions(vm *jsonnet.VM) { vm.NativeFunction(&jsonnet.NativeFunction{ Name: "Name", @@ -904,7 +893,7 @@ func (b *MapBuilder) getTemplateVariablesJsonnet(existingJSON []byte, input []by externalInput = fmt.Sprintf("{value: \"%s\" }", escapeDoubleQuotes(string(input))) } } - Logger.Debugf("externalInput: %s", externalInput) + slog.Debug(fmt.Sprintf("externalInput: %s", externalInput)) inputHelper := fmt.Sprintf(`local input = {index: %d} + %s + %s;`, indexInt, @@ -945,7 +934,7 @@ func (b *MapBuilder) ClearMap() { func (b *MapBuilder) ApplyMap(body map[string]interface{}) { out, err := json.Marshal(body) if err != nil { - Logger.Warningf("Failed to convert map to json. %s", err) + slog.Warn("Failed to convert map to json", "err", err) } else { b.BodyRaw = out } @@ -972,7 +961,7 @@ func (b *MapBuilder) GetMap() map[string]interface{} { func (b *MapBuilder) GetFileContents() *os.File { file, err := os.Open(b.file) if err != nil { - Logger.Errorf("failed to open file. %s", err) + slog.Error("failed to open file", "path", b.file, "err", err) return nil } return file @@ -1107,7 +1096,7 @@ func (b *MapBuilder) MarshalJSONObject() (body []byte, err error) { for _, it := range b.bodyIterators { value, input, itErr := it.Value.GetNext() - Logger.Debugf("body iterator. path=%s, value=%s", it.Path, value) + slog.Debug("body iterator", "path", it.Path, "value", value) if itErr != nil { err = itErr @@ -1117,7 +1106,7 @@ func (b *MapBuilder) MarshalJSONObject() (body []byte, err error) { case []byte: b.externalInput = extInput } - Logger.Debugf("setting externalInput: %s", b.externalInput) + slog.Debug("setting externalInput", "value", b.externalInput) // NOTE: Do not overwrite existing values if non empty if len(value) > 0 { @@ -1128,7 +1117,7 @@ func (b *MapBuilder) MarshalJSONObject() (body []byte, err error) { if !(valueObj.IsObject() || valueObj.IsArray()) { bodyTemp, bErr := sjson.SetBytes(body, it.Path, value) if bErr != nil { - Logger.Warningf("Could not set bytes. Ignoring value: path=%s, value=%s, err=%", it.Path, value, bErr) + slog.Warn("Could not set bytes. Ignoring value", "path", it.Path, "value", value, "err", bErr) continue } body = bodyTemp @@ -1148,7 +1137,7 @@ func (b *MapBuilder) MarshalJSONObject() (body []byte, err error) { body = bodyTemp } - Logger.Debugf("Body (pre templating)\nbody:\t%s\n\texternalInput:\t%s", body, b.externalInput) + slog.Debug(fmt.Sprintf("Body (pre templating)\nbody:\t%s\n\texternalInput:\t%s", body, b.externalInput)) if b.autoApplyTemplate && len(b.templates) > 0 { body, err = b.ApplyTemplates(body, b.externalInput, b.appendTemplate) @@ -1197,7 +1186,7 @@ func (b *MapBuilder) Set(path string, value interface{}) error { // store iterators separately so we can intercept the raw value which is otherwise lost during json marshalling if it, ok := value.(iterator.Iterator); ok { b.bodyIterators = append(b.bodyIterators, IteratorReference{path, it}) - Logger.Debugf("DEBUG: Found iterator. path=%s", path) + slog.Debug("Found iterator", "path", path) return nil } @@ -1213,7 +1202,7 @@ func (b *MapBuilder) SetTuple(path string, values ...interface{}) error { // store iterators separately so we can intercept the raw value which is otherwise lost during json marshalling if it, ok := value.(iterator.Iterator); ok { b.bodyIterators = append(b.bodyIterators, IteratorReference{path, it}) - Logger.Debugf("DEBUG: Found iterator. path=%s", path) + slog.Debug("Found iterator", "path", path) return nil } diff --git a/pkg/prompt/prompt.go b/pkg/prompt/prompt.go index e1adcbb6c..aa0d2e76b 100644 --- a/pkg/prompt/prompt.go +++ b/pkg/prompt/prompt.go @@ -5,6 +5,7 @@ import ( "context" "errors" "fmt" + "log/slog" "os" "os/exec" "strings" @@ -14,7 +15,6 @@ import ( "github.com/kballard/go-shellquote" "github.com/manifoldco/promptui" "github.com/reubenmiller/go-c8y-cli/v2/pkg/encrypt" - "github.com/reubenmiller/go-c8y-cli/v2/pkg/logger" "github.com/reubenmiller/go-c8y/pkg/c8y" ) @@ -176,24 +176,20 @@ type Validate func(string) error // Prompt used to provide various interactive prompts which can be used // within the cli type Prompt struct { - Logger *logger.Logger ShowValueAfter bool PinEntry string } // NewPrompt returns a new Prompt which can be used to prompt the user for // different information -func NewPrompt(l *logger.Logger) *Prompt { - return &Prompt{ - Logger: l, - } +func NewPrompt() *Prompt { + return &Prompt{} } // EncryptionPassphrase prompt for the encryption passphrase, and test the // passphrase against the encrypted content to see if it is valid func (p *Prompt) EncryptionPassphrase(encryptedData string, key string, initPassphrase string, message string) (string, error) { prompter, err := p.WithPrompters( - p.Logger, WithExternalPrompt(p.PinEntry, key), WithCommandLinePrompt("Session is encrypted"), ) @@ -221,11 +217,11 @@ func (p *Prompt) EncryptionPassphrase(encryptedData string, key string, initPass return promptWrapper.Run() } -type UserPrompter func(*logger.Logger) Prompter +type UserPrompter func() Prompter -func (p *Prompt) WithPrompters(l *logger.Logger, opts ...UserPrompter) (Prompter, error) { +func (p *Prompt) WithPrompters(opts ...UserPrompter) (Prompter, error) { for _, prompter := range opts { - curPrompter := prompter(l) + curPrompter := prompter() if curPrompter != nil { return curPrompter, nil } @@ -235,7 +231,6 @@ func (p *Prompt) WithPrompters(l *logger.Logger, opts ...UserPrompter) (Prompter func (p *Prompt) GetPassphrasePrompter(key string) (Prompter, error) { return p.WithPrompters( - p.Logger, WithExternalPrompt(p.PinEntry, key), WithCommandLinePrompt(""), ) @@ -243,13 +238,12 @@ func (p *Prompt) GetPassphrasePrompter(key string) (Prompter, error) { func (p *Prompt) GetExternalPrompter(key string) (Prompter, error) { return p.WithPrompters( - p.Logger, WithExternalPrompt(p.PinEntry, key), ) } func WithCommandLinePrompt(userPrompt string) UserPrompter { - return func(l *logger.Logger) Prompter { + return func() Prompter { label := "enter passphrase 🔒 [input is hidden]" if userPrompt != "" { label = strings.Join([]string{userPrompt, label}, ", ") @@ -271,7 +265,7 @@ func WithCommandLinePrompt(userPrompt string) UserPrompter { } func WithExternalPrompt(command string, key string) UserPrompter { - return func(l *logger.Logger) Prompter { + return func() Prompter { if command == "" { return nil } @@ -292,10 +286,10 @@ func WithExternalPrompt(command string, key string) UserPrompter { Args: externalCommandArgs, } if _, promptErr := pinEntryPrompter.Exists(); promptErr != nil { - l.Warnf("user defined pin entry command (%s) does not exist. The default will be used instead. error=%s", command, promptErr) + slog.Warn(fmt.Sprintf("user defined pin entry command (%s) does not exist. The default will be used instead. error=%s", command, promptErr)) return nil } - l.Infof("Using external pin entry command. %s %s", pinEntryCommand[0], strings.Join(externalCommandArgs, " ")) + slog.Info("Using external pin entry command", "command", fmt.Sprintf("%s %s", pinEntryCommand[0], strings.Join(externalCommandArgs, " "))) return pinEntryPrompter } } @@ -389,7 +383,7 @@ func (p *Prompt) TOTPCode(host, username string, code string, client *c8y.Client code = input if err := client.LoginUsingOAuth2(ctx, initRequest); err != nil { - p.Logger.Errorf("OAuth2 failed. %s", err) + slog.Error("OAuth2 failed", "err", err) return err } return nil diff --git a/pkg/request/request.go b/pkg/request/request.go index 87a20d76a..73f41429a 100644 --- a/pkg/request/request.go +++ b/pkg/request/request.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "io" + "log/slog" "mime" "net/http" "net/http/httputil" @@ -30,7 +31,6 @@ import ( "github.com/reubenmiller/go-c8y-cli/v2/pkg/iostreams" "github.com/reubenmiller/go-c8y-cli/v2/pkg/jsonUtilities" "github.com/reubenmiller/go-c8y-cli/v2/pkg/jsonformatter" - "github.com/reubenmiller/go-c8y-cli/v2/pkg/logger" "github.com/reubenmiller/go-c8y-cli/v2/pkg/mapbuilder" "github.com/reubenmiller/go-c8y/pkg/c8y" "github.com/tidwall/gjson" @@ -49,7 +49,6 @@ type RequestHandler struct { IO *iostreams.IOStreams Client *c8y.Client Config *config.Config - Logger *logger.Logger DataView *dataview.DataView ActivityLogger *activitylogger.ActivityLogger HideSensitive func(*c8y.Client, string) string @@ -77,7 +76,7 @@ func (r *RequestHandler) ProcessRequestAndResponse(requests []c8y.RequestOptions tempURL, _ := url.Parse("https://dummy.com?" + req.Query.(string)) tempURL = optimizeManagedObjectsURL(tempURL, "0") req.Query = tempURL.RawQuery - r.Logger.Infof("Optimizing inventory query. %v", req.Query) + slog.Info("Optimizing inventory query", "value", req.Query) } } @@ -119,7 +118,7 @@ func (r *RequestHandler) ProcessRequestAndResponse(requests []c8y.RequestOptions if !isDryRun && resp != nil { durationMS := resp.Duration().Milliseconds() - r.Logger.Infof("Response time: %dms", durationMS) + slog.Info(fmt.Sprintf("Response time: %dms", durationMS)) if r.ActivityLogger != nil && resp != nil { r.ActivityLogger.LogRequest(resp.Response, resp.JSON(), durationMS) @@ -127,13 +126,13 @@ func (r *RequestHandler) ProcessRequestAndResponse(requests []c8y.RequestOptions } if ctx.Err() != nil { - r.Logger.Errorf("request timed out after %s", r.Config.RequestTimeout()) + slog.Error("request timed out", "timeout", r.Config.RequestTimeout()) } if commonOptions.IncludeAll || commonOptions.TotalPages > 1 { if isInventoryQuery(&req) { // TODO: Optimize implementation for inventory managed object queries to use the following - r.Logger.Info("Using inventory optimized query") + slog.Info("Using inventory optimized query") if err := r.fetchAllInventoryQueryResults(req, resp, input, commonOptions); err != nil { return nil, err } @@ -192,7 +191,7 @@ func (r *RequestHandler) DryRunHandler(iostream *iostreams.IOStreams, options *c return } if req == nil { - r.Logger.Warn("Response is nil") + slog.Warn("Response is nil") return } r.PrintRequestDetails(iostream.Out, options, req) @@ -242,7 +241,7 @@ func (r *RequestHandler) PrintRequestDetails(w io.Writer, requestOptions *c8y.Re body, err = io.ReadAll(peekBody) if err != nil { - r.Logger.Warnf("Could not read body. %s", err) + slog.Warn("Could not read body", "err", err) return } @@ -261,12 +260,12 @@ func (r *RequestHandler) PrintRequestDetails(w io.Writer, requestOptions *c8y.Re if err := jsonUtilities.ParseJSON(string(body), bodyMap); err == nil { requestBody = bodyMap } else { - r.Logger.Debugf("Error parsing json object in dry run. %s", err) + slog.Debug("Error parsing json object in dry run", "err", err) requestBody = string(body) isJSON = false } } else { - r.Logger.Debugf("Using non-json body. %s", err) + slog.Debug("Using non-json body. %s", "err", err) requestBody = string(body) isJSON = false } @@ -388,7 +387,7 @@ func (r *RequestHandler) GetCurlCommands(requestOptions *c8y.RequestOptions, req curlCmd, dummyFiles, err := curly.ToCurl(req) if err != nil { - r.Logger.Warningf("failed to get curl command. %s", err) + slog.Warn("failed to get curl command", "err", err) return } curlCmd = strings.ReplaceAll(curlCmd, "\n", "") @@ -459,7 +458,7 @@ func (r *RequestHandler) fetchAllResults(req c8y.RequestOptions, resp *c8y.Respo baseURL, _ := url.Parse(nextURI) - r.Logger.Infof("Fetching next page (%d): %s?%s", currentPage, baseURL.Path, baseURL.RawQuery) + slog.Info(fmt.Sprintf("Fetching next page (%d): %s?%s", currentPage, baseURL.Path, baseURL.RawQuery)) curReq := c8y.RequestOptions{ Method: "GET", @@ -478,7 +477,7 @@ func (r *RequestHandler) fetchAllResults(req c8y.RequestOptions, resp *c8y.Respo // save result if resp != nil { durationMS := int64(time.Since(start) / time.Millisecond) - r.Logger.Infof("Response time: %dms", durationMS) + slog.Info(fmt.Sprintf("Response time: %dms", durationMS)) r.ActivityLogger.LogRequest(resp.Response, resp.JSON(), durationMS) totalItems, processErr = r.ProcessResponse(resp, err, input, commonOptions) @@ -491,17 +490,17 @@ func (r *RequestHandler) fetchAllResults(req c8y.RequestOptions, resp *c8y.Respo // Check if total results is less than the pagesize, as this saves one request if totalItems < commonOptions.PageSize { - r.Logger.Info("Found last page") + slog.Info("Found last page") break } if totalPages != 0 && currentPage >= totalPages { - r.Logger.Infof("Max pagination reached. max pages=%d", totalPages) + slog.Info("Max pagination reached", "max_pages", totalPages) break } if delayMS > 0 { - r.Logger.Infof("Pausing %d ms before next request.", delayMS) + slog.Info(fmt.Sprintf("Pausing %d ms before next request.", delayMS)) time.Sleep(time.Duration(delayMS) * time.Millisecond) } } @@ -580,7 +579,7 @@ func (r *RequestHandler) fetchAllInventoryQueryResults(req c8y.RequestOptions, r baseURL, _ := url.Parse(originalURI) baseURL = optimizeManagedObjectsURL(baseURL, lastID) - r.Logger.Infof("Fetching next page (%d): %s?%s", currentPage, baseURL.Path, baseURL.RawQuery) + slog.Info(fmt.Sprintf("Fetching next page (%d): %s?%s", currentPage, baseURL.Path, baseURL.RawQuery)) curReq := c8y.RequestOptions{ Method: "GET", @@ -599,7 +598,7 @@ func (r *RequestHandler) fetchAllInventoryQueryResults(req c8y.RequestOptions, r // save result if resp != nil { durationMS := int64(time.Since(start) / time.Millisecond) - r.Logger.Infof("Response time: %dms", durationMS) + slog.Info(fmt.Sprintf("Response time: %dms", durationMS)) r.ActivityLogger.LogRequest(resp.Response, resp.JSON(), durationMS) totalItems, processErr = r.ProcessResponse(resp, err, input, commonOptions) @@ -613,17 +612,17 @@ func (r *RequestHandler) fetchAllInventoryQueryResults(req c8y.RequestOptions, r // Check if total results is less than the pagesize, as this saves one request if totalItems < commonOptions.PageSize { - r.Logger.Info("Found last page") + slog.Info("Found last page") break } if totalPages != 0 && currentPage >= totalPages { - r.Logger.Infof("Max pagination reached. max pages=%d", totalPages) + slog.Info("Max pagination reached", "max_pages", totalPages) break } if delayMS > 0 { - r.Logger.Infof("Pausing %d ms before next request.", delayMS) + slog.Info(fmt.Sprintf("Pausing %d ms before next request.", delayMS)) time.Sleep(time.Duration(delayMS) * time.Millisecond) } } @@ -734,22 +733,22 @@ func ExecuteTemplate(responseText []byte, resp *http.Response, input any, common return out, nil } -func printResponseSize(l *logger.Logger, resp *c8y.Response) { +func printResponseSize(resp *c8y.Response) { if resp.Response.ContentLength > -1 { - l.Infof("Response Length: %0.1fKB", float64(resp.Response.ContentLength)/1024) + slog.Info(fmt.Sprintf("Response Length: %0.1fKB", float64(resp.Response.ContentLength)/1024)) } else { if resp.Response.Uncompressed { - l.Infof("Response Length: %0.1fKB (uncompressed)", float64(len(resp.Body()))/1024) + slog.Info(fmt.Sprintf("Response Length: %0.1fKB (uncompressed)", float64(len(resp.Body()))/1024)) } else { - l.Infof("Response Length: %0.1fKB", float64(len(resp.Body()))/1024) + slog.Info(fmt.Sprintf("Response Length: %0.1fKB", float64(len(resp.Body()))/1024)) } } } func (r *RequestHandler) ProcessResponse(resp *c8y.Response, respError error, input any, commonOptions config.CommonCommandOptions) (int, error) { if resp != nil && resp.StatusCode() != 0 { - r.Logger.Infof("Response Content-Type: %s", resp.Response.Header.Get("Content-Type")) - r.Logger.Debugf("Response Headers: %v", resp.Header()) + slog.Info(fmt.Sprintf("Response Content-Type: %s", resp.Response.Header.Get("Content-Type"))) + slog.Debug(fmt.Sprintf("Response Headers: %v", resp.Header())) } // Note: An output template will affect the handling of the response @@ -804,15 +803,15 @@ func (r *RequestHandler) ProcessResponse(resp *c8y.Response, respError error, in return 0, cmderrors.NewSystemError("write to file failed", err) } - r.Logger.Infof("Saved response: %s", fullFilePath) + slog.Info("Saved response", "file", fullFilePath) } } if resp != nil && respError == nil && !hasOutputTemplate && (r.Config.IsResponseOutput() || resp.Response.Header.Get("Content-Type") == "application/octet-stream") && len(resp.Body()) > 0 { // estimate size based on utf8 encoding. 1 char is 1 byte - r.Logger.Debugf("Writing https response output") + slog.Debug("Writing https response output") - printResponseSize(r.Logger, resp) + printResponseSize(resp) outputEOL := "" if r.IsTerminal { @@ -845,7 +844,7 @@ func (r *RequestHandler) ProcessResponse(resp *c8y.Response, respError error, in if resp != nil && (len(resp.Body()) > 0 || hasOutputTemplate) { // estimate size based on utf8 encoding. 1 char is 1 byte - printResponseSize(r.Logger, resp) + printResponseSize(resp) var responseText []byte isJSONResponse := jsonUtilities.IsValidJSON(resp.Body()) @@ -862,7 +861,7 @@ func (r *RequestHandler) ProcessResponse(resp *c8y.Response, respError error, in if v := resp.JSON(dataProperty); v.Exists() && v.IsArray() { unfilteredSize = len(v.Array()) - r.Logger.Infof("Unfiltered array size. len=%d", unfilteredSize) + slog.Info("Unfiltered array size", "len", unfilteredSize) } // Apply output template (before the data is processed as the template can transform text to json or other way around) @@ -906,10 +905,10 @@ func (r *RequestHandler) ProcessResponse(resp *c8y.Response, respError error, in } if r.Config.RawOutput() { - r.Logger.Infof("Raw mode active. In raw mode the following settings are forced, view=off, output=json") + slog.Info("Raw mode active. In raw mode the following settings are forced, view=off, output=json") } view := r.Config.ViewOption() - r.Logger.Infof("View mode: %s", view) + slog.Info(fmt.Sprintf("View mode: %s", view)) // Detect view (if no filters are given) if len(commonOptions.Filters.Pluck) == 0 { @@ -938,36 +937,36 @@ func (r *RequestHandler) ProcessResponse(resp *c8y.Response, respError error, in if err != nil || len(props) == 0 { if err != nil { - r.Logger.Infof("No matching view detected. defaulting to '**'. %s", err) + slog.Info("No matching view detected. defaulting to '**'", "err", err) } else { - r.Logger.Info("No matching view detected. defaulting to '**'") + slog.Info("No matching view detected. defaulting to '**'") } commonOptions.Filters.Pluck = []string{"**"} } else { - r.Logger.Infof("Detected view: %s", strings.Join(props, ", ")) + slog.Info("Detected view", "value", strings.Join(props, ",")) commonOptions.Filters.Pluck = props } default: props, err := r.DataView.GetViewByName(view) if err != nil || len(props) == 0 { if err != nil { - r.Logger.Warnf("no matching view found. %s, name=%s", err, view) + slog.Warn("no matching view found", "err", err, "name", view) } else { - r.Logger.Warnf("no matching view found. name=%s", view) + slog.Warn("no matching view found", "name", view) } commonOptions.Filters.Pluck = []string{"**"} } else { - r.Logger.Infof("Detected view: %s", strings.Join(props, ", ")) + slog.Info("Detected view", "value", strings.Join(props, ",")) commonOptions.Filters.Pluck = props } } } } else { - r.Logger.Debugf("using existing pluck values. %v", commonOptions.Filters.Pluck) + slog.Debug("using existing pluck values", "values", commonOptions.Filters.Pluck) } if filterOutput, filterErr := commonOptions.Filters.Apply(string(resp.Body()), dataProperty, false, r.Console.SetHeaderFromInput); filterErr != nil { - r.Logger.Warnf("filter error. %s", filterErr) + slog.Warn("filter error", "err", filterErr) responseText = filterOutput } else { responseText = filterOutput @@ -977,7 +976,7 @@ func (r *RequestHandler) ProcessResponse(resp *c8y.Response, respError error, in if !showRaw { if len(responseText) == len(emptyArray) && bytes.Equal(responseText, emptyArray) { - r.Logger.Info("No matching results found. Empty response will be omitted") + slog.Info("No matching results found. Empty response will be omitted") responseText = []byte{} } } @@ -1041,10 +1040,10 @@ func (r *RequestHandler) guessDataProperty(resp *c8y.Response) string { } if len(arrayProperties) > 1 { - r.Logger.Debugf("Could not detect property as more than 1 array like property detected: %v", arrayProperties) + slog.Debug("Could not detect property as more than 1 array like property detected", "values", arrayProperties) return "" } - r.Logger.Debugf("Array properties: %v", arrayProperties) + slog.Debug("Array properties", "values", arrayProperties) if len(arrayProperties) == 0 { return "" @@ -1059,7 +1058,7 @@ func (r *RequestHandler) guessDataProperty(resp *c8y.Response) string { } if property != "" && totalKeys < 10 { - r.Logger.Debugf("Data property: %s", property) + slog.Debug("Data property", "value", property) } return property } @@ -1088,18 +1087,18 @@ func (r *RequestHandler) saveResponseToFile(resp *c8y.Response, filename string, if strings.Contains(filename, "{id}") { if resp.Response.Request != nil { - r.Logger.Infof("Request: %s", resp.Response.Request.URL.Path) + slog.Info("Request", "path", resp.Response.Request.URL.Path) urlParts := strings.Split(resp.Response.Request.URL.Path, "/") for _, part := range urlParts { if part != "" && c8y.IsID(part) { - r.Logger.Debugf("Found id like value. Substituting {id} for %s", part) + slog.Debug(fmt.Sprintf("Found id like value. Substituting {id} for %s", part)) filename = strings.ReplaceAll(filename, "{id}", part) break } } } else { - r.Logger.Infof("Request is nill") + slog.Info("Request is nill") } } } @@ -1117,7 +1116,7 @@ func (r *RequestHandler) saveResponseToFile(resp *c8y.Response, filename string, } if err != nil { - return "", fmt.Errorf("Could not create file. %s", err) + return "", fmt.Errorf("could not create file. %s", err) } defer out.Close() @@ -1131,7 +1130,7 @@ func (r *RequestHandler) saveResponseToFile(resp *c8y.Response, filename string, } // Writer the body to file - r.Logger.Printf("header: %v", resp.Header()) + slog.Info(fmt.Sprintf("header: %v", resp.Header())) fmt.Fprintf(out, "%s", resp.Body()) if err != nil { diff --git a/pkg/requestiterator/requestiterator.go b/pkg/requestiterator/requestiterator.go index 8dbe43b2c..a6fadd58f 100644 --- a/pkg/requestiterator/requestiterator.go +++ b/pkg/requestiterator/requestiterator.go @@ -3,6 +3,7 @@ package requestiterator import ( "bytes" "errors" + "log/slog" "net/url" "os" "reflect" @@ -13,19 +14,14 @@ import ( "github.com/reubenmiller/go-c8y-cli/v2/pkg/flags" "github.com/reubenmiller/go-c8y-cli/v2/pkg/iterator" "github.com/reubenmiller/go-c8y-cli/v2/pkg/jsonUtilities" - "github.com/reubenmiller/go-c8y-cli/v2/pkg/logger" "github.com/reubenmiller/go-c8y-cli/v2/pkg/mapbuilder" "github.com/reubenmiller/go-c8y-cli/v2/pkg/request" "github.com/reubenmiller/go-c8y/pkg/c8y" ) // NewRequestIterator returns an iterator that can be used to send multiple requests until the give iterators in the path/body are exhausted -func NewRequestIterator(customLogger *logger.Logger, r c8y.RequestOptions, path iterator.Iterator, query iterator.Iterator, body interface{}) *RequestIterator { - if customLogger == nil { - customLogger = logger.NewDummyLogger("requestiterator") - } +func NewRequestIterator(r c8y.RequestOptions, path iterator.Iterator, query iterator.Iterator, body interface{}) *RequestIterator { reqIter := &RequestIterator{ - Logger: customLogger, Request: r, Path: path, Query: query, @@ -36,7 +32,6 @@ func NewRequestIterator(customLogger *logger.Logger, r c8y.RequestOptions, path // RequestIterator iterates through a c8y rest request with given request options and path iterators type RequestIterator struct { - Logger *logger.Logger Request c8y.RequestOptions Path iterator.Iterator Query iterator.Iterator @@ -107,7 +102,7 @@ func (r *RequestIterator) GetNext() (*c8y.RequestOptions, interface{}, error) { if u, err := parseUrl(req.Path); err == nil { if u.Host != "" { - r.Logger.Warningf("Parsing url in request. %s", u.Host) + slog.Warn("Parsing url in request", "host", u.Host) // TODO: Check if this will break anything else req.Host = u.Scheme + "://" + u.Host } @@ -137,7 +132,7 @@ func (r *RequestIterator) GetNext() (*c8y.RequestOptions, interface{}, error) { req.Query = strings.Join(queryParts, "&") } - r.Logger.Debugf("Input line: %s", inputLine) + slog.Debug("Input", "line", inputLine) // apply body iterator if r.Body != nil && !reflect.ValueOf(r.Body).IsNil() && request.RequestSupportsBody(req.Method) { diff --git a/pkg/utilities/utilities.go b/pkg/utilities/utilities.go index 067a95e75..fcb2ac43d 100644 --- a/pkg/utilities/utilities.go +++ b/pkg/utilities/utilities.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "io" + "log/slog" "net/http" "os" "runtime" @@ -140,7 +141,7 @@ func CheckEncryption(IO *iostreams.IOStreams, cfg *config.Config, client *c8y.Cl encryptionEnabled := cfg.IsEncryptionEnabled() decryptSession := false if !encryptionEnabled && cfg.IsPasswordEncrypted() { - cfg.Logger.Infof("Encryption has been disabled but detected a encrypted session") + slog.Info("Encryption has been disabled but detected a encrypted session") decryptSession = true } if encryptionEnabled || (cfg.IsPasswordEncrypted(true) || cfg.IsTokenEncrypted(true)) { diff --git a/pkg/worker/generic_worker.go b/pkg/worker/generic_worker.go index 07cd93c69..bb27d74e6 100644 --- a/pkg/worker/generic_worker.go +++ b/pkg/worker/generic_worker.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "io" + "log/slog" "os" "strings" "sync" @@ -16,7 +17,6 @@ import ( "github.com/reubenmiller/go-c8y-cli/v2/pkg/flags" "github.com/reubenmiller/go-c8y-cli/v2/pkg/iostreams" "github.com/reubenmiller/go-c8y-cli/v2/pkg/iterator" - "github.com/reubenmiller/go-c8y-cli/v2/pkg/logger" "github.com/reubenmiller/go-c8y-cli/v2/pkg/progressbar" "github.com/reubenmiller/go-c8y-cli/v2/pkg/prompt" "github.com/reubenmiller/go-c8y/pkg/c8y" @@ -25,10 +25,9 @@ import ( type Runner func(Job) (any, error) -func NewGenericWorker(log *logger.Logger, cfg *config.Config, iostream *iostreams.IOStreams, client *c8y.Client, activityLog *activitylogger.ActivityLogger, runFunc Runner, checkError func(error) error) (*GenericWorker, error) { +func NewGenericWorker(cfg *config.Config, iostream *iostreams.IOStreams, client *c8y.Client, activityLog *activitylogger.ActivityLogger, runFunc Runner, checkError func(error) error) (*GenericWorker, error) { return &GenericWorker{ Config: cfg, - Logger: log, IO: iostream, ActivityLogger: activityLog, Client: client, @@ -40,7 +39,6 @@ func NewGenericWorker(log *logger.Logger, cfg *config.Config, iostream *iostream type GenericWorker struct { Config *config.Config IO *iostreams.IOStreams - Logger *logger.Logger Client *c8y.Client ActivityLogger *activitylogger.ActivityLogger CheckError func(error) error @@ -161,7 +159,7 @@ func (w *GenericWorker) run(iter iterator.Iterator, commonOptions config.CommonC progbar := progressbar.NewMultiProgressBar(w.IO.ErrOut, 1, batchOptions.TotalWorkers, "requests", w.Config.ShowProgress()) for iWork := 1; iWork <= batchOptions.TotalWorkers; iWork++ { - w.Logger.Debugf("starting worker: %d", iWork) + slog.Debug("starting worker", "id", iWork) workers.Add(1) go w.StartWorker(iWork, jobs, results, progbar, &workers) } @@ -184,7 +182,7 @@ func (w *GenericWorker) run(iter iterator.Iterator, commonOptions config.CommonC targetInfo = fmt.Sprintf("tenant %s", tenantName) } } - w.Logger.Infof("Max jobs: %d", maxJobs) + slog.Info("Max jobs", "value", maxJobs) // add jobs async go func() { @@ -192,7 +190,7 @@ func (w *GenericWorker) run(iter iterator.Iterator, commonOptions config.CommonC jobInputErrors := int64(0) for { jobID++ - w.Logger.Debugf("checking job iterator: %d", jobID) + slog.Debug("checking job iterator", "id", jobID) // check if iterator is exhausted value, input, err := iter.GetNext() @@ -204,7 +202,7 @@ func (w *GenericWorker) run(iter iterator.Iterator, commonOptions config.CommonC } if maxJobs != 0 && jobID > maxJobs { - w.Logger.Infof("maximum jobs reached: limit=%d", maxJobs) + slog.Info("maximum jobs reached", "limit", maxJobs) break } @@ -222,7 +220,7 @@ func (w *GenericWorker) run(iter iterator.Iterator, commonOptions config.CommonC rootCauseErr = parentErr } - w.Config.LogErrorF(rootCauseErr, "skipping job: %d. %s", jobID, rootCauseErr) + w.Config.LogErrorF(rootCauseErr, "skipping job", "id", jobID, "err", rootCauseErr) results <- err // Note: stop adding jobs if total errors are exceeded @@ -235,7 +233,7 @@ func (w *GenericWorker) run(iter iterator.Iterator, commonOptions config.CommonC // move to next job continue } - w.Logger.Debugf("adding job: %d", jobID) + slog.Debug("adding job", "id", jobID) if value != nil { if batchOptions.SemanticMethod != "" { @@ -268,7 +266,7 @@ func (w *GenericWorker) run(iter iterator.Iterator, commonOptions config.CommonC case prompt.ConfirmYes: // confirmed case prompt.ConfirmNo: - w.Logger.Warningf("skipping job: %d. %s", jobID, err) + slog.Warn("skipping job", "id", jobID, "err", err) if w.ActivityLogger != nil { // TODO: Let batching control custom log message // w.ActivityLogger.LogCustom(err.Error() + ". " + request.Path) @@ -276,12 +274,12 @@ func (w *GenericWorker) run(iter iterator.Iterator, commonOptions config.CommonC results <- err continue case prompt.ConfirmNoToAll: - w.Logger.Infof("skipping job: %d. %s", jobID, err) + slog.Info("skipping job", "id", jobID, "err", err) if w.ActivityLogger != nil { // TODO: Let batching control custom log message // w.ActivityLogger.LogCustom(err.Error() + ". " + request.Path) } - w.Logger.Infof("cancelling all remaining jobs") + slog.Info("cancelling all remaining jobs") results <- err } if confirmResult == prompt.ConfirmNoToAll { @@ -305,7 +303,7 @@ func (w *GenericWorker) run(iter iterator.Iterator, commonOptions config.CommonC } } - w.Logger.Debugf("finished adding jobs. lastJobID=%d", jobID) + slog.Debug("finished adding jobs", "lastJobID", jobID) }() // collect all the results of the work. @@ -325,9 +323,9 @@ func (w *GenericWorker) run(iter iterator.Iterator, commonOptions config.CommonC for err := range results { if err == nil { - w.Logger.Debugf("job successful") + slog.Debug("job successful") } else { - w.Logger.Infof("job error. %s", err) + slog.Info("job error", "err", err) } if err != nil && err != io.EOF { @@ -390,19 +388,19 @@ func (w *GenericWorker) StartWorker(id int, jobs <-chan Job, results chan<- erro workerStart := prog.StartJob(id, total) if job.Options.DelayBefore > 0 { - w.Logger.Infof("worker %d: sleeping %s before starting job", id, job.Options.DelayBefore) + slog.Info(fmt.Sprintf("worker %d: sleeping %s before starting job", id, job.Options.DelayBefore)) time.Sleep(job.Options.DelayBefore) } if !onStartup { if !errors.Is(err, io.EOF) && job.Options.Delay > 0 { - w.Logger.Infof("worker %d: sleeping %s before fetching next job", id, job.Options.Delay) + slog.Info(fmt.Sprintf("worker %d: sleeping %s before fetching next job", id, job.Options.Delay)) time.Sleep(job.Options.Delay) } } onStartup = false - w.Logger.Infof("worker %d: started job %d", id, job.ID) + slog.Info(fmt.Sprintf("worker %d: started job %d", id, job.ID)) startTime := time.Now().UnixNano() result, resultErr := w.Execute(job) @@ -410,11 +408,11 @@ func (w *GenericWorker) StartWorker(id int, jobs <-chan Job, results chan<- erro // and stop actions if an error is encountered if resultErr == nil { for i, action := range job.Options.PostActions { - w.Logger.Debugf("Executing action: %d", i) + slog.Debug("Executing action", "index", i) runOutput, runErr := action.Run(result) if runErr != nil { resultErr = runErr - w.Logger.Warningf("Action failed. output=%#v, err=%s", runOutput, runErr) + slog.Warn("Action failed", "output", runOutput, "err", runErr) break } } @@ -422,7 +420,7 @@ func (w *GenericWorker) StartWorker(id int, jobs <-chan Job, results chan<- erro elapsedMS := (time.Now().UnixNano() - startTime) / 1000.0 / 1000.0 - w.Logger.Infof("worker %d: finished job %d in %dms", id, job.ID, elapsedMS) + slog.Info(fmt.Sprintf("worker %d: finished job %d in %dms", id, job.ID, elapsedMS)) prog.FinishedJob(id, workerStart) // return result before delay, so errors can be handled before the sleep diff --git a/pkg/worker/worker.go b/pkg/worker/worker.go index 50164f9a5..a7a32ad2b 100644 --- a/pkg/worker/worker.go +++ b/pkg/worker/worker.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "io" + "log/slog" "os" "reflect" "strings" @@ -19,7 +20,6 @@ import ( "github.com/reubenmiller/go-c8y-cli/v2/pkg/flags" "github.com/reubenmiller/go-c8y-cli/v2/pkg/iostreams" "github.com/reubenmiller/go-c8y-cli/v2/pkg/iterator" - "github.com/reubenmiller/go-c8y-cli/v2/pkg/logger" "github.com/reubenmiller/go-c8y-cli/v2/pkg/progressbar" "github.com/reubenmiller/go-c8y-cli/v2/pkg/prompt" "github.com/reubenmiller/go-c8y-cli/v2/pkg/requestiterator" @@ -78,10 +78,9 @@ func (b *BatchOptions) useInputData() bool { return b.InputData != nil && len(b.InputData) > 0 } -func NewWorker(log *logger.Logger, cfg *config.Config, iostream *iostreams.IOStreams, client *c8y.Client, activityLog *activitylogger.ActivityLogger, reqHandlerFunc RequestHandler, checkError func(error) error) (*Worker, error) { +func NewWorker(cfg *config.Config, iostream *iostreams.IOStreams, client *c8y.Client, activityLog *activitylogger.ActivityLogger, reqHandlerFunc RequestHandler, checkError func(error) error) (*Worker, error) { return &Worker{ config: cfg, - logger: log, io: iostream, activityLogger: activityLog, client: client, @@ -95,7 +94,6 @@ type RequestHandler func(requests []c8y.RequestOptions, input any, commonOptions type Worker struct { config *config.Config io *iostreams.IOStreams - logger *logger.Logger client *c8y.Client activityLogger *activitylogger.ActivityLogger checkError func(error) error @@ -170,7 +168,7 @@ func (w *Worker) ProcessRequestAndResponse(cmd *cobra.Command, r *c8y.RequestOpt pathIter = iterator.NewRepeatIterator(r.Path, 1) } // Note: Body accepts iterator types, so no need for special handling here - requestIter := requestiterator.NewRequestIterator(w.logger, *r, pathIter, inputIterators.Query, r.Body) + requestIter := requestiterator.NewRequestIterator(*r, pathIter, inputIterators.Query, r.Body) // get common options and batch settings commonOptions, err := w.config.GetOutputCommonOptions(cmd) @@ -206,7 +204,7 @@ func (w *Worker) runBatched(requestIterator *requestiterator.RequestIterator, co progbar := progressbar.NewMultiProgressBar(w.io.ErrOut, 1, batchOptions.TotalWorkers, "requests", w.config.ShowProgress()) for iWork := 1; iWork <= batchOptions.TotalWorkers; iWork++ { - w.logger.Debugf("starting worker: %d", iWork) + slog.Debug("starting worker", "id", iWork) workers.Add(1) go w.batchWorker(iWork, jobs, results, progbar, &workers) } @@ -229,7 +227,7 @@ func (w *Worker) runBatched(requestIterator *requestiterator.RequestIterator, co targetInfo = fmt.Sprintf("tenant %s", tenantName) } } - w.logger.Infof("Max jobs: %d", maxJobs) + slog.Info(fmt.Sprintf("Max jobs: %d", maxJobs)) // add jobs async go func() { @@ -237,7 +235,7 @@ func (w *Worker) runBatched(requestIterator *requestiterator.RequestIterator, co jobInputErrors := int64(0) for { jobID++ - w.logger.Debugf("checking job iterator: %d", jobID) + slog.Debug("checking job iterator", "id", jobID) // check if iterator is exhausted request, input, err := requestIterator.GetNext() @@ -249,7 +247,7 @@ func (w *Worker) runBatched(requestIterator *requestiterator.RequestIterator, co } if maxJobs != 0 && jobID > maxJobs { - w.logger.Infof("maximum jobs reached: limit=%d", maxJobs) + slog.Info("maximum jobs reached", "limit", maxJobs) break } @@ -267,7 +265,7 @@ func (w *Worker) runBatched(requestIterator *requestiterator.RequestIterator, co rootCauseErr = parentErr } - w.config.LogErrorF(rootCauseErr, "skipping job: %d. %s", jobID, rootCauseErr) + w.config.LogErrorF(rootCauseErr, "skipping job", "id", jobID, "err", rootCauseErr) results <- err // Note: stop adding jobs if total errors are exceeded @@ -280,7 +278,7 @@ func (w *Worker) runBatched(requestIterator *requestiterator.RequestIterator, co // move to next job continue } - w.logger.Debugf("adding job: %d", jobID) + slog.Debug("adding job", "id", jobID) if request != nil { if batchOptions.SemanticMethod != "" { @@ -312,18 +310,18 @@ func (w *Worker) runBatched(requestIterator *requestiterator.RequestIterator, co case prompt.ConfirmYes: // confirmed case prompt.ConfirmNo: - w.logger.Warningf("skipping job: %d. %s", jobID, err) + slog.Warn("skipping job", "id", jobID, "err", err) if w.activityLogger != nil { w.activityLogger.LogCustom(err.Error() + ". " + request.Path) } results <- err continue case prompt.ConfirmNoToAll: - w.logger.Infof("skipping job: %d. %s", jobID, err) + slog.Info("skipping job", "id", jobID, "err", err) if w.activityLogger != nil { w.activityLogger.LogCustom(err.Error() + ". " + request.Path) } - w.logger.Infof("cancelling all remaining jobs") + slog.Info("cancelling all remaining jobs") results <- err } if confirmResult == prompt.ConfirmNoToAll { @@ -347,7 +345,7 @@ func (w *Worker) runBatched(requestIterator *requestiterator.RequestIterator, co } } - w.logger.Debugf("finished adding jobs. lastJobID=%d", jobID) + slog.Debug("finished adding jobs", "lastJobID", jobID) }() // collect all the results of the work. @@ -367,9 +365,9 @@ func (w *Worker) runBatched(requestIterator *requestiterator.RequestIterator, co for err := range results { if err == nil { - w.logger.Debugf("job successful") + slog.Debug("job successful") } else { - w.logger.Infof("job error. %s", err) + slog.Info("job error", "err", err) } if err != nil && err != io.EOF { @@ -433,19 +431,19 @@ func (w *Worker) batchWorker(id int, jobs <-chan batchArgument, results chan<- e var resp *c8y.Response if job.batchOptions.DelayBefore > 0 { - w.logger.Infof("worker %d: sleeping %s before starting job", id, job.batchOptions.DelayBefore) + slog.Info(fmt.Sprintf("worker %d: sleeping %s before starting job", id, job.batchOptions.DelayBefore)) time.Sleep(job.batchOptions.DelayBefore) } if !onStartup { if !errors.Is(err, io.EOF) && job.batchOptions.Delay > 0 { - w.logger.Infof("worker %d: sleeping %s before fetching next job", id, job.batchOptions.Delay) + slog.Info(fmt.Sprintf("worker %d: sleeping %s before fetching next job", id, job.batchOptions.Delay)) time.Sleep(job.batchOptions.Delay) } } onStartup = false - w.logger.Infof("worker %d: started job %d", id, job.id) + slog.Info(fmt.Sprintf("worker %d: started job %d", id, job.id)) startTime := time.Now().UnixNano() resp, err = w.requestHandler([]c8y.RequestOptions{job.request}, job.input, job.commonOptions) @@ -454,11 +452,11 @@ func (w *Worker) batchWorker(id int, jobs <-chan batchArgument, results chan<- e // and stop actions if an error is encountered if err == nil { for i, action := range job.batchOptions.PostActions { - w.logger.Debugf("Executing action: %d", i) + slog.Debug("Executing action: %d", "index", i) runOutput, runErr := action.Run(resp) if runErr != nil { err = runErr - w.logger.Warningf("Action failed. output=%#v, err=%s", runOutput, runErr) + slog.Warn("Action failed", "output", runOutput, "err", runErr) break } } @@ -466,7 +464,7 @@ func (w *Worker) batchWorker(id int, jobs <-chan batchArgument, results chan<- e elapsedMS := (time.Now().UnixNano() - startTime) / 1000.0 / 1000.0 - w.logger.Infof("worker %d: finished job %d in %dms", id, job.id, elapsedMS) + slog.Info(fmt.Sprintf("worker %d: finished job %d in %dms", id, job.id, elapsedMS)) prog.FinishedJob(id, workerStart) // return result before delay, so errors can be handled before the sleep @@ -479,8 +477,8 @@ func (w *Worker) getConfirmationMessage(prefix string, request *c8y.RequestOptio name := "" id := "" if input != nil { - w.logger.Infof("input: %s", input) - w.logger.Infof("input type: %s", reflect.TypeOf(input)) + slog.Info(fmt.Sprintf("input: %s", input)) + slog.Info(fmt.Sprintf("input type: %s", reflect.TypeOf(input))) switch v := input.(type) { case []byte: @@ -519,13 +517,13 @@ func (w *Worker) getConfirmationMessage(prefix string, request *c8y.RequestOptio devicePaths := []string{"source.id", "deviceId", "id"} for _, path := range devicePaths { if device := gjson.ParseBytes(jsonText).Get(path); device.Exists() { - w.logger.Infof("device: %s", device.Str) + slog.Info(fmt.Sprintf("device: %s", device.Str)) id = device.Str break } } } else { - w.logger.Debugf("json error: %s", err) + slog.Debug("json error", "err", err) } } } @@ -539,7 +537,7 @@ func (w *Worker) getConfirmationMessage(prefix string, request *c8y.RequestOptio target += ", name=" + name } - w.logger.Infof("target: [%s]", target) + slog.Info(fmt.Sprintf("target: [%s]", target)) if target != "" { return fmt.Sprintf("%s [%s]", prefix, target), nil