diff --git a/cmd/root.go b/cmd/root.go index 78d467b..0748d28 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -5,11 +5,13 @@ import ( "fmt" "os" "strings" + "time" "github.com/fsnotify/fsnotify" "github.com/rs/zerolog" "github.com/rs/zerolog/log" "github.com/spf13/cobra" + "github.com/spf13/pflag" "github.com/spf13/viper" "go.xsfx.dev/schnutibox/internal/config" "go.xsfx.dev/schnutibox/pkg/prepare" @@ -67,16 +69,23 @@ func init() { // Timer. rootCmd.AddCommand(timerCmd) + timerCmd.Flags().String("hostname", "localhost", "Hostname of schnutibox") + timerCmd.Flags().Int("port", 6600, "Port of schnutibox") + timerCmd.Flags().DurationP("duration", "d", time.Minute, "Duration until the timer stops the playback") + + if err := timerCmd.MarkFlagRequired("duration"); err != nil { + log.Fatal().Err(err).Msg("missing flag") + } // Defaults. viper.SetDefault("box.hostname", "localhost") viper.SetDefault("box.port", 9999) - viper.SetDefault("box.grpc", 9998) viper.SetDefault("mpd.hostname", "localhost") viper.SetDefault("mpd.port", 6600) viper.SetDefault("reader.dev", "/dev/hidraw0") viper.SetDefault("reader.ignore", false) - viper.SetDefault("pprof", false) + viper.SetDefault("debug.pprof", false) + viper.SetDefault("timer.duration", time.Minute) // Environment handling. viper.SetEnvPrefix("SCHNUTIBOX") @@ -84,16 +93,17 @@ func init() { viper.AutomaticEnv() // Flags. - if err := viper.BindPFlag("pprof", rootCmd.PersistentFlags().Lookup("pprof")); err != nil { - log.Fatal().Err(err).Msg("could not bind flag") - } - - if err := viper.BindPFlag("reader.dev", prepareCmd.Flags().Lookup("rfid-reader")); err != nil { - log.Fatal().Err(err).Msg("could not bind flag") - } - - if err := viper.BindPFlag("reader.ignore", runCmd.Flags().Lookup("ignore-reader")); err != nil { - log.Fatal().Err(err).Msg("could not bind flag") + for k, v := range map[string]*pflag.Flag{ + "debug.pprof": rootCmd.PersistentFlags().Lookup("pprof"), + "reader.dev": prepareCmd.Flags().Lookup("rfid-reader"), + "reader.ignore": runCmd.Flags().Lookup("ignore-reader"), + "box.hostname": timerCmd.Flags().Lookup("hostname"), + "box.port": timerCmd.Flags().Lookup("port"), + "timer.duration": timerCmd.Flags().Lookup("duration"), + } { + if err := viper.BindPFlag(k, v); err != nil { + log.Fatal().Err(err).Msg("could not bind flag") + } } } @@ -103,18 +113,25 @@ func initConfig(fatal bool) { logger := log.With().Str("config", cfgFile).Logger() // Parse config file. - if cfgFile != "" { - viper.SetConfigFile(cfgFile) - parseConfig(logger, fatal) - } else { + if cfgFile == "" && fatal { logger.Fatal().Msg("missing config file") + } else { + logger.Warn().Msg("missing config file") } - viper.WatchConfig() - viper.OnConfigChange(func(e fsnotify.Event) { - logger.Info().Msg("config file changed") - parseConfig(logger, false) - }) + // Dont mind if there is no config file... viper also should populate + // flags and environment variables. + viper.SetConfigFile(cfgFile) + parseConfig(logger, fatal) + + // Configfile changes watch only enabled if there is a config file. + if cfgFile != "" { + viper.WatchConfig() + viper.OnConfigChange(func(e fsnotify.Event) { + logger.Info().Msg("config file changed") + parseConfig(logger, false) + }) + } } // parseConfig parses the config and does some tests if required fields are there. @@ -126,8 +143,6 @@ func parseConfig(logger zerolog.Logger, fatal bool) { } logger.Error().Err(err).Msg("error loading config file") - - return } if err := viper.Unmarshal(&config.Cfg); err != nil { @@ -136,16 +151,22 @@ func parseConfig(logger zerolog.Logger, fatal bool) { } logger.Error().Err(err).Msg("could not unmarshal config") - - return } - if err := config.Cfg.Require(); err != nil { - if fatal { - logger.Fatal().Err(err).Msg("missing config parts") - } + // Disabling require check if no config is set. + // Not sure about this! + if cfgFile != "" { + if err := config.Cfg.Require(); err != nil { + if fatal { + logger.Fatal().Err(err).Msg("missing config parts") + } - logger.Error().Err(err).Msg("missing config parts") + logger.Error().Err(err).Msg("missing config parts") + + return + } + } else { + logger.Warn().Msg("doesnt do a config requirement check") return } diff --git a/cmd/timer.go b/cmd/timer.go index 66277f7..341bf69 100644 --- a/cmd/timer.go +++ b/cmd/timer.go @@ -1,6 +1,10 @@ package cmd import ( + "os" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" "github.com/spf13/cobra" "go.xsfx.dev/schnutibox/pkg/timer" ) @@ -10,4 +14,8 @@ var timerCmd = &cobra.Command{ Use: "timer", Short: "Handling timer", Run: timer.Run, + PreRun: func(cmd *cobra.Command, args []string) { + log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) + initConfig(false) + }, } diff --git a/go.mod b/go.mod index dd296f8..f39728e 100644 --- a/go.mod +++ b/go.mod @@ -8,14 +8,17 @@ require ( github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e // indirect github.com/fhs/gompd/v2 v2.2.0 github.com/fsnotify/fsnotify v1.4.7 + github.com/golang/protobuf v1.5.2 github.com/grpc-ecosystem/grpc-gateway/v2 v2.4.0 github.com/helloyi/go-sshclient v1.0.0 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect github.com/ory/dockertest/v3 v3.6.3 + github.com/philip-bui/grpc-zerolog v1.0.1 github.com/prometheus/client_golang v0.9.3 github.com/rs/zerolog v1.22.0 github.com/spf13/cobra v1.1.3 + github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.7.0 github.com/tmc/scp v0.0.0-20170824174625-f7b48647feef go.xsfx.dev/don v1.0.0 diff --git a/go.sum b/go.sum index a7308bf..853e396 100644 --- a/go.sum +++ b/go.sum @@ -274,6 +274,8 @@ github.com/ory/dockertest/v3 v3.6.3/go.mod h1:EFLcVUOl8qCwp9NyDAcCDtq/QviLtYswW/ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/philip-bui/grpc-zerolog v1.0.1 h1:EMacvLRUd2O1K0eWod27ZP5CY1iTNkhBDLSN+Q4JEvA= +github.com/philip-bui/grpc-zerolog v1.0.1/go.mod h1:qXbiq/2X4ZUMMshsqlWyTHOcw7ns+GZmlqZZN05ZHcQ= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -336,6 +338,7 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= diff --git a/internal/config/config.go b/internal/config/config.go index ccef878..ef48b73 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -11,6 +11,10 @@ import ( var Cfg Config type Config struct { + Debug struct { + PPROF bool `mapstructure:"PPROF"` + } + // Reader is used to configure the RFID Reader. Reader struct { Dev string `mapstructure:"Dev"` diff --git a/internal/grpcclient/grpcclient.go b/internal/grpcclient/grpcclient.go new file mode 100644 index 0000000..664bb91 --- /dev/null +++ b/internal/grpcclient/grpcclient.go @@ -0,0 +1,18 @@ +package grpcclient + +import ( + "fmt" + + "google.golang.org/grpc" +) + +func Conn(hostname string, port int) (*grpc.ClientConn, error) { + var conn *grpc.ClientConn + + conn, err := grpc.Dial(fmt.Sprintf("%s:%d", hostname, port), grpc.WithInsecure()) + if err != nil { + return nil, fmt.Errorf("could not connect: %w", err) + } + + return conn, nil +} diff --git a/pkg/timer/timer.go b/pkg/timer/timer.go index 2b2aa51..75e293e 100644 --- a/pkg/timer/timer.go +++ b/pkg/timer/timer.go @@ -1,45 +1,77 @@ package timer import ( + "context" + "github.com/golang/protobuf/ptypes/duration" "github.com/rs/zerolog/log" "github.com/spf13/cobra" + "github.com/spf13/viper" + "go.xsfx.dev/schnutibox/internal/config" + "go.xsfx.dev/schnutibox/internal/grpcclient" api "go.xsfx.dev/schnutibox/pkg/api/v1" + "go.xsfx.dev/schnutibox/pkg/mpc" + "google.golang.org/protobuf/types/known/durationpb" ) // nolint:gochecknoglobals -var T = &api.Timer{} +var T = &Timer{} -func Timer() { - if T.Duration != nil { +type Timer struct { + Req *api.Timer +} + +func (t *Timer) Handle() { + if t.Req != nil { // Initialize the current object. - if T.Current == nil { - T.Current = &duration.Duration{} - T.Current.Seconds = T.Duration.Seconds + if t.Req.Current == nil { + t.Req.Current = &duration.Duration{} + t.Req.Current.Seconds = t.Req.Duration.Seconds } switch { // There is some timing going on. - case T.Duration.Seconds != 0 && T.Current.Seconds != 0: + case t.Req.Duration.Seconds != 0 && t.Req.Current.Seconds != 0: log.Debug(). - Int64("current", T.Current.Seconds). - Int64("duration", T.Duration.Seconds). + Int64("current", t.Req.Current.Seconds). + Int64("duration", t.Req.Duration.Seconds). Msg("timer is running") - if T.Current.Seconds > 0 { - T.Current.Seconds -= 1 + if t.Req.Current.Seconds > 0 { + t.Req.Current.Seconds -= 1 return } // No timer is running... so setting the duration to 0. - // TODO: Needs to do something actually! - case T.Current.Seconds == 0 && T.Duration.Seconds != 0: + case t.Req.Current.Seconds == 0 && t.Req.Duration.Seconds != 0: log.Debug().Msg("stoping timer") - T.Duration.Seconds = 0 + if err := mpc.Stop(log.Logger); err != nil { + log.Error().Err(err).Msg("could not stop") + } + + t.Req.Duration.Seconds = 0 } } } -func Run(cmd *cobra.Command, args []string) {} +// Run is the command line interface for triggering the timer. +func Run(cmd *cobra.Command, args []string) { + conn, err := grpcclient.Conn(config.Cfg.Box.Hostname, config.Cfg.Box.Port) + if err != nil { + log.Fatal().Err(err).Msg("could not connect") + } + defer conn.Close() + + c := api.NewTimerServiceClient(conn) + + d := durationpb.New(viper.GetDuration("timer.duration")) + + _, err = c.Create(context.Background(), &api.Timer{Duration: d}) + if err != nil { + log.Fatal().Err(err).Msg("could not create timer") + } + + log.Info().Msg("added timer") +} diff --git a/pkg/watcher/watcher.go b/pkg/watcher/watcher.go index 6ea32ca..b90a989 100644 --- a/pkg/watcher/watcher.go +++ b/pkg/watcher/watcher.go @@ -23,19 +23,20 @@ func Run() { <-ticker.C // Timer. - go timer.Timer() + go timer.T.Handle() // Metrics. go func() { m, err := mpc.Conn() if err != nil { - // log.Error().Err(err).Msg("could not connect") + log.Error().Err(err).Msg("could not connect") + return } uris, err := mpc.PlaylistURIS() if err != nil { - // log.Error().Err(err).Msg("could not get playlist uris") + log.Error().Err(err).Msg("could not get playlist uris") metrics.BoxErrors.Inc() return @@ -44,7 +45,7 @@ func Run() { // Gettings MPD state. s, err := m.Status() if err != nil { - // log.Error().Err(err).Msg("could not get status") + log.Error().Err(err).Msg("could not get status") metrics.BoxErrors.Inc() return diff --git a/pkg/web/web.go b/pkg/web/web.go index 9cb29e1..60c7959 100644 --- a/pkg/web/web.go +++ b/pkg/web/web.go @@ -9,10 +9,10 @@ import ( "strings" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + zerolog "github.com/philip-bui/grpc-zerolog" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/rs/zerolog/log" "github.com/spf13/cobra" - "github.com/spf13/viper" "go.xsfx.dev/logginghandler" assets "go.xsfx.dev/schnutibox/assets/web" "go.xsfx.dev/schnutibox/internal/config" @@ -81,14 +81,14 @@ func (i identifyServer) Identify(ctx context.Context, in *api.IdentifyRequest) ( type timerServer struct{} func (t timerServer) Create(ctx context.Context, req *api.Timer) (*api.Timer, error) { - timer.T = req + timer.T.Req = req - return timer.T, nil + return timer.T.Req, nil } // Get just returns the status of the timer. func (t timerServer) Get(ctx context.Context, req *api.TimerEmpty) (*api.Timer, error) { - return timer.T, nil + return timer.T.Req, nil } func gw(s *grpc.Server, conn string) *runtime.ServeMux { @@ -114,7 +114,9 @@ func Run(command *cobra.Command, args []string) { lh := fmt.Sprintf("%s:%d", config.Cfg.Box.Hostname, config.Cfg.Box.Port) // Create grpc server. - grpcServer := grpc.NewServer() + grpcServer := grpc.NewServer( + zerolog.UnaryInterceptor(), + ) // Define http handlers. mux := http.NewServeMux() @@ -138,7 +140,7 @@ func Run(command *cobra.Command, args []string) { mux.Handle("/api/", http.StripPrefix("/api", gw(grpcServer, lh))) // PPROF. - if viper.GetBool("pprof") { + if config.Cfg.Debug.PPROF { mux.HandleFunc("/debug/pprof/", pprof.Index) } diff --git a/vendor/github.com/philip-bui/grpc-zerolog/.gitignore b/vendor/github.com/philip-bui/grpc-zerolog/.gitignore new file mode 100644 index 0000000..d97c9f4 --- /dev/null +++ b/vendor/github.com/philip-bui/grpc-zerolog/.gitignore @@ -0,0 +1,16 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +/vendor +grpc-zerolog +coverage.txt diff --git a/vendor/github.com/philip-bui/grpc-zerolog/.travis.yml b/vendor/github.com/philip-bui/grpc-zerolog/.travis.yml new file mode 100644 index 0000000..5d04e60 --- /dev/null +++ b/vendor/github.com/philip-bui/grpc-zerolog/.travis.yml @@ -0,0 +1,14 @@ +language: go + +go: + - tip + +before_install: + - curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh + - dep ensure + +script: + - go test -race -coverprofile=coverage.txt -covermode=atomic + +after_success: + - bash <(curl -s https://codecov.io/bash) diff --git a/vendor/github.com/philip-bui/grpc-zerolog/Gopkg.lock b/vendor/github.com/philip-bui/grpc-zerolog/Gopkg.lock new file mode 100644 index 0000000..d1ed4d0 --- /dev/null +++ b/vendor/github.com/philip-bui/grpc-zerolog/Gopkg.lock @@ -0,0 +1,259 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + digest = "1:a2c1d0e43bd3baaa071d1b9ed72c27d78169b2b269f71c105ac4ba34b1be4a39" + name = "github.com/davecgh/go-spew" + packages = ["spew"] + pruneopts = "UT" + revision = "346938d642f2ec3594ed81d874461961cd0faa76" + version = "v1.1.0" + +[[projects]] + digest = "1:4ba8096f8ea6ea9f7aefb98e189e0128324883f0b4e839800ce4b37c90230c5c" + name = "github.com/golang/protobuf" + packages = [ + "jsonpb", + "proto", + "ptypes", + "ptypes/any", + "ptypes/duration", + "ptypes/struct", + "ptypes/timestamp", + ] + pruneopts = "UT" + revision = "d04d7b157bb510b1e0c10132224b616ac0e26b17" + version = "v1.4.2" + +[[projects]] + digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe" + name = "github.com/pmezard/go-difflib" + packages = ["difflib"] + pruneopts = "UT" + revision = "792786c7400a136282c1664665ae0a8db921c6c2" + version = "v1.0.0" + +[[projects]] + digest = "1:342baf1b227dbc8829a60dc06cd7a5e70d3de350b501e2184ec8f19fe4418d4a" + name = "github.com/rs/zerolog" + packages = [ + ".", + "internal/cbor", + "internal/json", + "log", + ] + pruneopts = "UT" + revision = "7825d863376faee2723fc99c061c538bd80812c8" + version = "v1.19.0" + +[[projects]] + digest = "1:ac83cf90d08b63ad5f7e020ef480d319ae890c208f8524622a2f3136e2686b02" + name = "github.com/stretchr/objx" + packages = ["."] + pruneopts = "UT" + revision = "477a77ecc69700c7cdeb1fa9e129548e1c1c393c" + version = "v0.1.1" + +[[projects]] + digest = "1:5201127841a78d84d0ca68a2e564c08e3882c0fb9321a75997ce87926e0d63ea" + name = "github.com/stretchr/testify" + packages = [ + "assert", + "mock", + "require", + "suite", + ] + pruneopts = "UT" + revision = "f654a9112bbeac49ca2cd45bfbe11533c4666cf8" + version = "v1.6.1" + +[[projects]] + branch = "master" + digest = "1:dc1b5a4f3a84b5c8503036630dd0f245de44d9c22414ddff0e5e473849139fd3" + name = "golang.org/x/net" + packages = [ + "context", + "http/httpguts", + "http2", + "http2/hpack", + "idna", + "internal/timeseries", + "trace", + ] + pruneopts = "UT" + revision = "ed29d75add3d7c4bf7ca65aac0c6df3d1420216f" + +[[projects]] + digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18" + name = "golang.org/x/text" + packages = [ + "collate", + "collate/build", + "internal/colltab", + "internal/gen", + "internal/tag", + "internal/triegen", + "internal/ucd", + "language", + "secure/bidirule", + "transform", + "unicode/bidi", + "unicode/cldr", + "unicode/norm", + "unicode/rangetable", + ] + pruneopts = "UT" + revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0" + version = "v0.3.0" + +[[projects]] + branch = "master" + digest = "1:601e63e7d4577f907118bec825902505291918859d223bce015539e79f1160e3" + name = "google.golang.org/genproto" + packages = ["googleapis/rpc/status"] + pruneopts = "UT" + revision = "ff3583edef7de132f219f0efc00e097cabcc0ec0" + +[[projects]] + digest = "1:2dab32a43451e320e49608ff4542fdfc653c95dcc35d0065ec9c6c3dd540ed74" + name = "google.golang.org/grpc" + packages = [ + ".", + "balancer", + "balancer/base", + "balancer/roundrobin", + "codes", + "connectivity", + "credentials", + "encoding", + "encoding/proto", + "grpclog", + "internal", + "internal/backoff", + "internal/channelz", + "internal/grpcrand", + "keepalive", + "metadata", + "naming", + "peer", + "resolver", + "resolver/dns", + "resolver/passthrough", + "stats", + "status", + "tap", + "transport", + ] + pruneopts = "UT" + revision = "168a6198bcb0ef175f7dacec0b8691fc141dc9b8" + version = "v1.13.0" + +[[projects]] + digest = "1:6a351cb031761dcd668e5aca9a10bd6d1675aa5427ac1483a61c3a49b2d64962" + name = "google.golang.org/protobuf" + packages = [ + "encoding/protojson", + "encoding/prototext", + "encoding/protowire", + "internal/descfmt", + "internal/descopts", + "internal/detrand", + "internal/encoding/defval", + "internal/encoding/json", + "internal/encoding/messageset", + "internal/encoding/tag", + "internal/encoding/text", + "internal/errors", + "internal/fieldsort", + "internal/filedesc", + "internal/filetype", + "internal/flags", + "internal/genid", + "internal/impl", + "internal/mapsort", + "internal/pragma", + "internal/set", + "internal/strs", + "internal/version", + "proto", + "reflect/protoreflect", + "reflect/protoregistry", + "runtime/protoiface", + "runtime/protoimpl", + "types/known/anypb", + "types/known/durationpb", + "types/known/structpb", + "types/known/timestamppb", + ] + pruneopts = "UT" + revision = "3f7a61f89bb6813f89d981d1870ed68da0b3c3f1" + version = "v1.25.0" + +[[projects]] + branch = "v3" + digest = "1:229cb0f6192914f518cc1241ede6d6f1f458b31debfa18bf3a5c9e4f7b01e24b" + name = "gopkg.in/yaml.v3" + packages = ["."] + pruneopts = "UT" + revision = "eeeca48fe7764f320e4870d231902bf9c1be2c08" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + input-imports = [ + "github.com/davecgh/go-spew/spew", + "github.com/golang/protobuf/jsonpb", + "github.com/golang/protobuf/proto", + "github.com/golang/protobuf/ptypes", + "github.com/golang/protobuf/ptypes/any", + "github.com/golang/protobuf/ptypes/duration", + "github.com/golang/protobuf/ptypes/struct", + "github.com/golang/protobuf/ptypes/timestamp", + "github.com/pmezard/go-difflib/difflib", + "github.com/rs/zerolog", + "github.com/rs/zerolog/log", + "github.com/stretchr/objx", + "github.com/stretchr/testify/assert", + "github.com/stretchr/testify/mock", + "github.com/stretchr/testify/require", + "github.com/stretchr/testify/suite", + "golang.org/x/net/context", + "golang.org/x/net/http/httpguts", + "golang.org/x/net/http2", + "golang.org/x/net/http2/hpack", + "golang.org/x/net/idna", + "golang.org/x/net/trace", + "golang.org/x/text/collate", + "golang.org/x/text/collate/build", + "golang.org/x/text/language", + "golang.org/x/text/secure/bidirule", + "golang.org/x/text/transform", + "golang.org/x/text/unicode/bidi", + "golang.org/x/text/unicode/cldr", + "golang.org/x/text/unicode/norm", + "golang.org/x/text/unicode/rangetable", + "google.golang.org/genproto/googleapis/rpc/status", + "google.golang.org/grpc", + "google.golang.org/grpc/balancer", + "google.golang.org/grpc/balancer/base", + "google.golang.org/grpc/balancer/roundrobin", + "google.golang.org/grpc/codes", + "google.golang.org/grpc/connectivity", + "google.golang.org/grpc/credentials", + "google.golang.org/grpc/encoding", + "google.golang.org/grpc/encoding/proto", + "google.golang.org/grpc/grpclog", + "google.golang.org/grpc/keepalive", + "google.golang.org/grpc/metadata", + "google.golang.org/grpc/naming", + "google.golang.org/grpc/peer", + "google.golang.org/grpc/resolver", + "google.golang.org/grpc/resolver/dns", + "google.golang.org/grpc/resolver/passthrough", + "google.golang.org/grpc/stats", + "google.golang.org/grpc/status", + "google.golang.org/grpc/tap", + "google.golang.org/grpc/transport", + ] + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/vendor/github.com/philip-bui/grpc-zerolog/Gopkg.toml b/vendor/github.com/philip-bui/grpc-zerolog/Gopkg.toml new file mode 100644 index 0000000..db95009 --- /dev/null +++ b/vendor/github.com/philip-bui/grpc-zerolog/Gopkg.toml @@ -0,0 +1,46 @@ +# Gopkg.toml example +# +# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" +# +# [prune] +# non-go = false +# go-tests = true +# unused-packages = true + + +[[constraint]] + name = "github.com/golang/protobuf" + version = "1.1.0" + +[[constraint]] + name = "github.com/rs/zerolog" + version = "1.8.0" + +[[constraint]] + name = "google.golang.org/grpc" + version = "1.13.0" + +[prune] + go-tests = true + unused-packages = true + +[[constraint]] + name = "github.com/stretchr/testify" + version = "1.2.2" diff --git a/vendor/github.com/philip-bui/grpc-zerolog/LICENSE b/vendor/github.com/philip-bui/grpc-zerolog/LICENSE new file mode 100644 index 0000000..9e59ffc --- /dev/null +++ b/vendor/github.com/philip-bui/grpc-zerolog/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Philip Bui + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/philip-bui/grpc-zerolog/Makefile b/vendor/github.com/philip-bui/grpc-zerolog/Makefile new file mode 100644 index 0000000..7b139e0 --- /dev/null +++ b/vendor/github.com/philip-bui/grpc-zerolog/Makefile @@ -0,0 +1,17 @@ +PORT?=8000 +PACKAGE:=github.com/philip-bui/grpc-zerolog +COVERAGE:=coverage.txt +proto: + protoc -I protos/ protos/*.proto --go_out=plugins=grpc:protos + +godoc: + echo "localhost:${PORT}/pkg/${PACKAGE}" + godoc -http=:${PORT} + +.PHONY: test + +test: + go test -race -coverprofile=${COVERAGE} -covermode=atomic + +coverage: + go tool cover -html=${COVERAGE} diff --git a/vendor/github.com/philip-bui/grpc-zerolog/README.md b/vendor/github.com/philip-bui/grpc-zerolog/README.md new file mode 100644 index 0000000..b88db28 --- /dev/null +++ b/vendor/github.com/philip-bui/grpc-zerolog/README.md @@ -0,0 +1,49 @@ +# gRPC Zerolog + +[![BuildStatus Widget]][BuildStatus Result] +[![CodeCov Widget]][CodeCov Result] +[![GoReport Widget]][GoReport Status] +[![GoDoc Widget]][GoDoc] + +[BuildStatus Result]: https://travis-ci.org/philip-bui/grpc-zerolog +[BuildStatus Widget]: https://travis-ci.org/philip-bui/grpc-zerolog.svg?branch=master + +[CodeCov Result]: https://codecov.io/gh/philip-bui/grpc-zerolog +[CodeCov Widget]: https://codecov.io/gh/philip-bui/grpc-zerolog/branch/master/graph/badge.svg + +[GoReport Status]: https://goreportcard.com/report/github.com/philip-bui/grpc-zerolog +[GoReport Widget]: https://goreportcard.com/badge/github.com/philip-bui/grpc-zerolog + +[GoDoc]: https://godoc.org/github.com/philip-bui/grpc-zerolog +[GoDoc Widget]: https://godoc.org/github.com/philip-bui/grpc-zerolog?status.svg + +Implementation of gRPC Logging Middleware, integrating [Zerolog](https://github.com/rs/zerolog) as a gRPC [Interceptor](https://github.com/grpc-ecosystem/go-grpc-middleware) to log the following fields: + +- Request Protobufs as JSON. +- Response Protobufs as JSON, or Errors. +- Status Code, Duration, Timestamp, Service Name, Service Method, IP, Metadata Fields and User Agent. + +## Usage + +```go +import ( + "github.com/philip-bui/grpc-zerolog" +) + +func main() { + // With global Zerolog logger. + grpc.NewServer( + zerolog.UnaryInterceptor(), + ) + + // With custom Zerolog instance. + log := zerolog.New(os.Stdout) + grpc.NewServer( + zerolog.UnaryInterceptorWithLogger(&log), + ) +} +``` + +## License + +gRPC Zerolog is available under the MIT license. [See LICENSE](https://github.com/philip-bui/grpc-zerolog/blob/master/LICENSE) for details. diff --git a/vendor/github.com/philip-bui/grpc-zerolog/unary_interceptor.go b/vendor/github.com/philip-bui/grpc-zerolog/unary_interceptor.go new file mode 100644 index 0000000..ba5d76d --- /dev/null +++ b/vendor/github.com/philip-bui/grpc-zerolog/unary_interceptor.go @@ -0,0 +1,66 @@ +package zerolog + +import ( + "context" + "time" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "google.golang.org/grpc" +) + +// UnaryInterceptor is a gRPC Server Option that uses NewUnaryServerInterceptor() to log gRPC Requests. +func UnaryInterceptor() grpc.ServerOption { + return grpc.UnaryInterceptor(NewUnaryServerInterceptor()) +} + +func UnaryInterceptorWithLogger(log *zerolog.Logger) grpc.ServerOption { + return grpc.UnaryInterceptor(NewUnaryServerInterceptorWithLogger(log)) +} + +// NewUnaryServerInterceptor that logs gRPC Requests using Zerolog. +// { +// ServiceField: "ExampleService", +// MethodField: "ExampleMethod", +// DurationField: 1.00 +// +// IpField: "127.0.0.1", +// +// MetadataField: {}, +// +// UserAgentField: "ExampleClientUserAgent", +// ReqField: {}, // JSON representation of Request Protobuf +// +// Err: "An unexpected error occurred", +// CodeField: "Unknown", +// MsgField: "Error message returned from the server", +// DetailsField: [Errors], +// +// RespField: {}, // JSON representation of Response Protobuf +// +// ZerologMessageField: "UnaryMessageDefault", +// } +func NewUnaryServerInterceptor() grpc.UnaryServerInterceptor { + return NewUnaryServerInterceptorWithLogger(&log.Logger) +} + +func NewUnaryServerInterceptorWithLogger(log *zerolog.Logger) grpc.UnaryServerInterceptor { + return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + now := time.Now() + resp, err := handler(ctx, req) + if log.Error().Enabled() { + if err != nil { + logger := log.Error() + LogIncomingCall(ctx, logger, info.FullMethod, now, req) + LogStatusError(logger, err) + logger.Msg(UnaryMessageDefault) + } else if log.Info().Enabled() { + logger := log.Info() + LogIncomingCall(ctx, logger, info.FullMethod, now, req) + LogResponse(logger, resp) + logger.Msg(UnaryMessageDefault) + } + } + return resp, err + } +} diff --git a/vendor/github.com/philip-bui/grpc-zerolog/util.go b/vendor/github.com/philip-bui/grpc-zerolog/util.go new file mode 100644 index 0000000..4935c0e --- /dev/null +++ b/vendor/github.com/philip-bui/grpc-zerolog/util.go @@ -0,0 +1,224 @@ +package zerolog + +import ( + "bytes" + "context" + "path" + "strings" + "time" + + "github.com/golang/protobuf/jsonpb" + "github.com/golang/protobuf/proto" + "github.com/rs/zerolog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/peer" + "google.golang.org/grpc/status" +) + +var ( + // Marshaller of Protobuf to JSON + Marshaller = &jsonpb.Marshaler{} + // TimestampLog call start. + TimestampLog = true + // ServiceField key. + ServiceField = "service" + // ServiceLog gRPC service name. + ServiceLog = true + // MethodField key. + MethodField = "method" + // MethodLog gRPC method name. + MethodLog = true + // DurationField key. + DurationField = "dur" + // DurationLog gRPC call duration. + DurationLog = true + // IPField key. + IPField = "ip" + // IPLog gRPC client IP. + IPLog = true + // MetadataField key. + MetadataField = "md" + // MetadataLog gRPC call metadata. + MetadataLog = true + // UserAgentField key. + UserAgentField = "ua" + // UserAgentLog gRPC client User Agent. + UserAgentLog = true + // ReqField key. + ReqField = "req" + // ReqLog gRPC request body. + ReqLog = true + // RespField key. + RespField = "resp" + // RespLog gRPC response body. + RespLog = true + // MaxSize to log gRPC bodies. + MaxSize = 2048000 + // CodeField gRPC status code response. + CodeField = "code" + // MsgField gRPC response message. + MsgField = "msg" + // DetailsField gRPC response errors. + DetailsField = "details" + // UnaryMessageDefault of logging messages from unary. + UnaryMessageDefault = "unary" +) + +// LogIncomingCall of gRPC method. +// { +// ServiceField: ExampleService, +// MethodField: ExampleMethod, +// DurationField: 1.00, +// } +func LogIncomingCall(ctx context.Context, logger *zerolog.Event, method string, t time.Time, req interface{}) { + LogTimestamp(logger, t) + LogService(logger, method) + LogMethod(logger, method) + LogDuration(logger, t) + LogRequest(logger, req) + LogIncomingMetadata(ctx, logger) +} + +// LogTimestamp of call. +// { +// TimestampField: Timestamp, +// } +func LogTimestamp(logger *zerolog.Event, t time.Time) { + if TimestampLog { + *logger = *logger.Time(zerolog.TimestampFieldName, t) + } +} + +// LogService of gRPC name. +// { +// ServiceField: gRPCServiceName, +// } +func LogService(logger *zerolog.Event, method string) { + if ServiceLog { + *logger = *logger.Str(ServiceField, path.Dir(method)[1:]) + } +} + +// LogMethod of gRPC call. +// { +// MethodField: gRPCMethodName, +// } +func LogMethod(logger *zerolog.Event, method string) { + if MethodLog { + *logger = *logger.Str(MethodField, path.Base(method)) + } +} + +// LogDuration in seconds of gRPC call. +// { +// DurationField: Timestamp, +// } +func LogDuration(logger *zerolog.Event, t time.Time) { + if DurationLog { + *logger = *logger.Dur(DurationField, time.Since(t)) + } +} + +// LogIP address of gRPC client, if assigned. +// { +// IpField: 127.0.0.1 +// } +func LogIP(ctx context.Context, logger *zerolog.Event) { + if IPLog { + if p, ok := peer.FromContext(ctx); ok { + *logger = *logger.Str(IPField, p.Addr.String()) + } + } +} + +// LogRequest in JSON of gRPC Call, given Request is smaller than MaxSize (Default=2MB). +// { +// ReqField: {} +// } +func LogRequest(e *zerolog.Event, req interface{}) { + if ReqLog { + if b := GetRawJSON(req); b != nil { + *e = *e.RawJSON(ReqField, b.Bytes()) + } + } +} + +// LogResponse in JSON of gRPC Call, given Response is smaller than MaxSize (Default=2MB). +// { +// RespField: {} +// } +func LogResponse(e *zerolog.Event, resp interface{}) { + if RespLog { + if b := GetRawJSON(resp); b != nil { + *e = *e.RawJSON(RespField, b.Bytes()) + } + } +} + +// GetRawJSON converts a Protobuf message to JSON bytes if less than MaxSize. +func GetRawJSON(i interface{}) *bytes.Buffer { + if pb, ok := i.(proto.Message); ok { + b := &bytes.Buffer{} + if err := Marshaller.Marshal(b, pb); err == nil && b.Len() < MaxSize { + return b + } + } + return nil +} + +// LogIncomingMetadata or UserAgent field of incoming gRPC Request, if assigned. +// { +// MetadataField: { +// MetadataKey1: MetadataValue1, +// } +// } +// +// { +// UserAgentField: "Client-assigned User-Agent", +// } +func LogIncomingMetadata(ctx context.Context, e *zerolog.Event) { + if md, ok := metadata.FromIncomingContext(ctx); ok { + if MetadataLog { + *e = *e.Dict(MetadataField, LogMetadata(&md)) + return + } else if UserAgentLog { + LogUserAgent(e, &md) + } + } +} + +// LogMetadata of gRPC Request +// { +// MetadataField: { +// MetadataKey1: MetadataValue1, +// } +// } +func LogMetadata(md *metadata.MD) *zerolog.Event { + dict := zerolog.Dict() + for i := range *md { + dict = dict.Str(i, strings.Join(md.Get(i), ",")) + } + return dict +} + +// LogUserAgent of gRPC Client, if assigned. +// { +// UserAgentField: "Client-assigned User-Agent", +// } +func LogUserAgent(logger *zerolog.Event, md *metadata.MD) { + if ua := strings.Join(md.Get("user-agent"), ""); ua != "" { + *logger = *logger.Str(UserAgentField, ua) + } +} + +// LogStatusError of gRPC Error Response. +// { +// Err: "An unexpected error occurred", +// CodeField: "Unknown", +// MsgField: "Error message returned from the server", +// DetailsField: [Errors], +// } +func LogStatusError(logger *zerolog.Event, err error) { + statusErr := status.Convert(err) + *logger = *logger.Err(err).Str(CodeField, statusErr.Code().String()).Str(MsgField, statusErr.Message()).Interface(DetailsField, statusErr.Details()) +} diff --git a/vendor/github.com/philip-bui/grpc-zerolog/zerolog.go b/vendor/github.com/philip-bui/grpc-zerolog/zerolog.go new file mode 100644 index 0000000..b7a8fb9 --- /dev/null +++ b/vendor/github.com/philip-bui/grpc-zerolog/zerolog.go @@ -0,0 +1,119 @@ +package zerolog + +import ( + "fmt" + + "github.com/rs/zerolog" + "google.golang.org/grpc/grpclog" +) + +// GrpcLogSetNewZeroLogger sets grpclog to a new GrpcZeroLogger. +func GrpcLogSetNewZeroLogger() { + grpclog.SetLoggerV2(NewGrpcZeroLogger(zerolog.Logger{})) +} + +// GrpcLogSetZeroLogger sets grpclog to a GrpcZeroLogger. +func GrpcLogSetZeroLogger(logger GrpcZeroLogger) { + grpclog.SetLoggerV2(logger) +} + +// GrpcZeroLogger transforms grpc log calls to Zerolog logger. +type GrpcZeroLogger struct { + log zerolog.Logger +} + +// NewGrpcZeroLogger creates a new GrpcZeroLogger +func NewGrpcZeroLogger(logger zerolog.Logger) GrpcZeroLogger { + return GrpcZeroLogger{log: logger} +} + +// Fatal fatals arguments. +func (l GrpcZeroLogger) Fatal(args ...interface{}) { + l.log.Fatal().Msg(fmt.Sprint(args...)) +} + +// Fatalf fatals formatted string with arguments. +func (l GrpcZeroLogger) Fatalf(format string, args ...interface{}) { + l.log.Fatal().Msgf(format, args...) +} + +// Fatalln fatals and new line. +func (l GrpcZeroLogger) Fatalln(args ...interface{}) { + l.Fatal(args...) +} + +// Error errors arguments. +func (l GrpcZeroLogger) Error(args ...interface{}) { + l.log.Error().Msg(fmt.Sprint(args...)) +} + +// Errorf errors formatted string with arguments. +func (l GrpcZeroLogger) Errorf(format string, args ...interface{}) { + l.log.Error().Msgf(format, args...) +} + +// Errorln errors and new line. +func (l GrpcZeroLogger) Errorln(args ...interface{}) { + l.Error(args...) +} + +// Info infos arguments. +func (l GrpcZeroLogger) Info(args ...interface{}) { + l.log.Info().Msg(fmt.Sprint(args...)) +} + +// Infof infos formatted string with arguments. +func (l GrpcZeroLogger) Infof(format string, args ...interface{}) { + l.log.Info().Msgf(format, args...) +} + +// Infoln infos and new line. +func (l GrpcZeroLogger) Infoln(args ...interface{}) { + l.Info(args...) +} + +// Warning warns arguments. +func (l GrpcZeroLogger) Warning(args ...interface{}) { + l.log.Warn().Msg(fmt.Sprint(args...)) +} + +// Warningf warns formatted string with arguments. +func (l GrpcZeroLogger) Warningf(format string, args ...interface{}) { + l.log.Warn().Msgf(format, args...) +} + +// Warningln warns and new line. +func (l GrpcZeroLogger) Warningln(args ...interface{}) { + l.Warning(args...) +} + +// Print logs arguments. +func (l GrpcZeroLogger) Print(args ...interface{}) { + l.Info(args...) +} + +// Printf logs formatted string with arguments. +func (l GrpcZeroLogger) Printf(format string, args ...interface{}) { + l.Infof(format, args...) +} + +// Println logs with new line. +func (l GrpcZeroLogger) Println(args ...interface{}) { + l.Infoln(args...) +} + +// V determines Verbosity Level. +func (l GrpcZeroLogger) V(level int) bool { + switch level { + case 0: + return zerolog.InfoLevel <= zerolog.GlobalLevel() + case 1: + return zerolog.WarnLevel <= zerolog.GlobalLevel() + case 2: + return zerolog.ErrorLevel <= zerolog.GlobalLevel() + case 3: + return zerolog.FatalLevel <= zerolog.GlobalLevel() + default: + panic("unhandled gRPC logger level") + } +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 8f37017..4ee5a03 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -156,6 +156,7 @@ github.com/golang/glog # github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e github.com/golang/groupcache/lru # github.com/golang/protobuf v1.5.2 +## explicit github.com/golang/protobuf/descriptor github.com/golang/protobuf/jsonpb github.com/golang/protobuf/proto @@ -268,6 +269,9 @@ github.com/ory/dockertest/v3/docker/types/strslice github.com/ory/dockertest/v3/docker/types/versions # github.com/pelletier/go-toml v1.2.0 github.com/pelletier/go-toml +# github.com/philip-bui/grpc-zerolog v1.0.1 +## explicit +github.com/philip-bui/grpc-zerolog # github.com/pkg/errors v0.9.1 github.com/pkg/errors # github.com/pkg/profile v1.5.0 @@ -306,6 +310,7 @@ github.com/spf13/cobra # github.com/spf13/jwalterweatherman v1.0.0 github.com/spf13/jwalterweatherman # github.com/spf13/pflag v1.0.5 +## explicit github.com/spf13/pflag # github.com/spf13/viper v1.7.0 ## explicit