From 716471581010dd8ead88171cf660e1fbc69c51d7 Mon Sep 17 00:00:00 2001 From: Jimmy Zelinskie Date: Wed, 18 Aug 2021 18:17:27 -0400 Subject: [PATCH] add grpc, otel, metrics helpers --- cobrautil.go | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++-- go.mod | 3 ++ go.sum | 12 +++++++ 3 files changed, 102 insertions(+), 2 deletions(-) diff --git a/cobrautil.go b/cobrautil.go index 680cb06..4fe8c14 100644 --- a/cobrautil.go +++ b/cobrautil.go @@ -2,8 +2,14 @@ package cobrautil import ( "fmt" + "net/http" + "net/http/pprof" + "os" "strings" + "time" + "github.com/mattn/go-isatty" + "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/rs/zerolog" "github.com/rs/zerolog/log" "github.com/spf13/cobra" @@ -15,6 +21,9 @@ import ( "go.opentelemetry.io/otel/sdk/resource" "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.4.0" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/keepalive" ) // SyncViperPreRunE returns a Cobra run func that synchronizes Viper environment @@ -24,6 +33,10 @@ import ( func SyncViperPreRunE(prefix string) func(cmd *cobra.Command, args []string) error { prefix = strings.ReplaceAll(strings.ToUpper(prefix), "-", "_") return func(cmd *cobra.Command, args []string) error { + if cmd.Use == "help [command]" { + return nil // No-op the help command + } + v := viper.New() viper.SetEnvPrefix(prefix) @@ -58,7 +71,8 @@ func CommandStack(cmdfns ...CobraRunFunc) CobraRunFunc { // RegisterZeroLogFlags adds a "log-level" flag for use in with ZeroLogPreRunE. func RegisterZeroLogFlags(flags *pflag.FlagSet) { - flags.String("log-level", "info", "verbosity of logging (trace, debug, info, warn, error, fatal, panic)") + flags.String("log-level", "info", `verbosity of logging ("trace", "debug", "info", "warn", "error")`) + flags.String("log-format", "auto", `format of logs ("auto", "human", "json")`) } // ZeroLogPreRunE reads the provided command's flags and configures the @@ -67,6 +81,15 @@ func RegisterZeroLogFlags(flags *pflag.FlagSet) { // // This function exits with log.Fatal on failure. func ZeroLogPreRunE(cmd *cobra.Command, args []string) error { + if cmd.Use == "help [command]" { + return nil // No-op the help command + } + + format := MustGetString(cmd, "log-format") + if format == "human" || (format == "auto" && isatty.IsTerminal(os.Stdout.Fd())) { + log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stdout}) + } + level := strings.ToLower(MustGetString(cmd, "log-level")) switch level { case "trace": @@ -97,7 +120,7 @@ func ZeroLogPreRunE(cmd *cobra.Command, args []string) error { // - "otel-jaeger-endpoint" // - "otel-jaeger-service-name" func RegisterOpenTelemetryFlags(flags *pflag.FlagSet, serviceName string) { - flags.String("otel-provider", "none", "opentelemetry provider for tracing (none, jaeger)") + flags.String("otel-provider", "none", `opentelemetry provider for tracing ("none", "jaeger")`) flags.String("otel-jaeger-endpoint", "http://jaeger:14268/api/traces", "jaeger collector endpoint") flags.String("otel-jaeger-service-name", serviceName, "jaeger service name for trace data") } @@ -106,6 +129,10 @@ func RegisterOpenTelemetryFlags(flags *pflag.FlagSet, serviceName string) { // corresponding tracing provider. The required flags can be added to a command // by using RegisterTracingPersistentFlags(). func OpenTelemetryPreRunE(cmd *cobra.Command, args []string) error { + if cmd.Use == "help [command]" { + return nil // No-op the help command + } + provider := strings.ToLower(MustGetString(cmd, "otel-provider")) switch provider { case "none": @@ -144,3 +171,61 @@ func initJaegerTracer(endpoint, serviceName string) error { otel.SetTextMapPropagator(propagation.TraceContext{}) return nil } + +// RegisterGrpcServerFlags adds the following flags for use with +// GrpcServerFromFlags: +// - "grpc-addr" +// - "grpc-no-tls" +// - "grpc-cert-path" +// - "grpc-key-path" +// - "grpc-max-conn-age" +func RegisterGrpcServerFlags(flags *pflag.FlagSet) { + flags.String("grpc-addr", ":50051", "address to listen on for serving gRPC services") + flags.String("grpc-cert-path", "", "local path to the TLS certificate used to serve gRPC services") + flags.String("grpc-key-path", "", "local path to the TLS key used to serve gRPC services") + flags.Bool("grpc-no-tls", false, "serve unencrypted gRPC services") + flags.Duration("grpc-max-conn-age", 30*time.Second, "how long a connection should be able to live") +} + +func GrpcServerFromFlags(cmd *cobra.Command, opts ...grpc.ServerOption) (*grpc.Server, error) { + opts = append(opts, grpc.KeepaliveParams(keepalive.ServerParameters{ + MaxConnectionAge: MustGetDuration(cmd, "grpc-max-conn-age"), + })) + + if MustGetBool(cmd, "grpc-no-tls") { + return grpc.NewServer(opts...), nil + } + + certPath := MustGetStringExpanded(cmd, "grpc-cert-path") + keyPath := MustGetStringExpanded(cmd, "grpc-key-path") + if certPath == "" || keyPath == "" { + return nil, fmt.Errorf("failed to start gRPC server: must provide either --grpc-no-tls or --grpc-cert-path and --grpc-key-path") + } + + creds, err := credentials.NewServerTLSFromFile(certPath, keyPath) + if err != nil { + return nil, err + } + opts = append(opts, grpc.Creds(creds)) + + return grpc.NewServer(opts...), nil +} + +func RegisterMetricsServerFlags(flags *pflag.FlagSet) { + flags.String("metrics-addr", ":9090", "address on which to serve metrics and runtime profiles") +} + +func MetricsServerFromFlags(cmd *cobra.Command) *http.Server { + mux := http.NewServeMux() + mux.Handle("/metrics", promhttp.Handler()) + mux.HandleFunc("/debug/pprof/", pprof.Index) + mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) + mux.HandleFunc("/debug/pprof/profile", pprof.Profile) + mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) + mux.HandleFunc("/debug/pprof/trace", pprof.Trace) + + return &http.Server{ + Addr: MustGetString(cmd, "metrics-addr"), + Handler: mux, + } +} diff --git a/go.mod b/go.mod index a98d5ff..4267581 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,8 @@ module github.com/jzelinskie/cobrautil go 1.16 require ( + github.com/mattn/go-isatty v0.0.3 + github.com/prometheus/client_golang v0.9.3 github.com/rs/zerolog v1.23.0 github.com/spf13/cobra v1.1.3 github.com/spf13/pflag v1.0.5 @@ -10,4 +12,5 @@ require ( go.opentelemetry.io/otel v1.0.0-RC2 go.opentelemetry.io/otel/exporters/jaeger v1.0.0-RC2 go.opentelemetry.io/otel/sdk v1.0.0-RC2 + google.golang.org/grpc v1.21.1 ) diff --git a/go.sum b/go.sum index 166b4d9..108862a 100644 --- a/go.sum +++ b/go.sum @@ -21,6 +21,7 @@ github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hC github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= @@ -50,6 +51,7 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -57,6 +59,7 @@ github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -117,7 +120,9 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= @@ -143,12 +148,16 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= @@ -247,6 +256,7 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -321,9 +331,11 @@ google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a h1:Ob5/580gVHBJZgXnff1cZDbG+xLtMVE5mDRTe+nIsX4= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=