-
Notifications
You must be signed in to change notification settings - Fork 10
/
cobrautil.go
241 lines (210 loc) · 7.62 KB
/
cobrautil.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
package cobrautil
import (
"fmt"
"net/http"
"net/http/pprof"
"os"
"strings"
"time"
"github.com/jzelinskie/stringz"
"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"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/jaeger"
"go.opentelemetry.io/otel/propagation"
"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"
)
func IsBuiltinCommand(cmd *cobra.Command) bool {
return stringz.SliceContains([]string{
"help [command]",
"completion [command]",
},
cmd.Use,
)
}
// SyncViperPreRunE returns a Cobra run func that synchronizes Viper environment
// flags prefixed with the provided argument.
//
// Thanks to Carolyn Van Slyck: https://github.com/carolynvs/stingoftheviper
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 IsBuiltinCommand(cmd) {
return nil // No-op for builtins
}
v := viper.New()
viper.SetEnvPrefix(prefix)
cmd.Flags().VisitAll(func(f *pflag.Flag) {
suffix := strings.ToUpper(strings.ReplaceAll(f.Name, "-", "_"))
v.BindEnv(f.Name, prefix+"_"+suffix)
if !f.Changed && v.IsSet(f.Name) {
val := v.Get(f.Name)
cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val))
}
})
return nil
}
}
// CobraRunFunc is the signature of cobra.Command RunFuncs.
type CobraRunFunc func(cmd *cobra.Command, args []string) error
// RunFuncStack chains together a collection of CobraCommandFuncs into one.
func CommandStack(cmdfns ...CobraRunFunc) CobraRunFunc {
return func(cmd *cobra.Command, args []string) error {
for _, cmdfn := range cmdfns {
if err := cmdfn(cmd, args); err != nil {
return err
}
}
return nil
}
}
// 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")`)
flags.String("log-format", "auto", `format of logs ("auto", "human", "json")`)
}
// ZeroLogPreRunE reads the provided command's flags and configures the
// corresponding log level. The required flags can be added to a command by
// using RegisterLoggingPersistentFlags().
//
// This function exits with log.Fatal on failure.
func ZeroLogPreRunE(cmd *cobra.Command, args []string) error {
if IsBuiltinCommand(cmd) {
return nil // No-op for builtins
}
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":
zerolog.SetGlobalLevel(zerolog.TraceLevel)
case "debug":
zerolog.SetGlobalLevel(zerolog.DebugLevel)
case "info":
zerolog.SetGlobalLevel(zerolog.InfoLevel)
case "warn":
zerolog.SetGlobalLevel(zerolog.WarnLevel)
case "error":
zerolog.SetGlobalLevel(zerolog.ErrorLevel)
case "fatal":
zerolog.SetGlobalLevel(zerolog.FatalLevel)
case "panic":
zerolog.SetGlobalLevel(zerolog.PanicLevel)
default:
return fmt.Errorf("unknown log level: %s", level)
}
log.Info().Str("new level", level).Msg("set log level")
return nil
}
// RegisterOpenTelemetryFlags adds the following flags for use with
// OpenTelemetryPreRunE:
// - "otel-provider"
// - "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-jaeger-endpoint", "http://jaeger:14268/api/traces", "jaeger collector endpoint")
flags.String("otel-jaeger-service-name", serviceName, "jaeger service name for trace data")
}
// TracingPreRun reads the provided command's flags and configures the
// corresponding tracing provider. The required flags can be added to a command
// by using RegisterTracingPersistentFlags().
func OpenTelemetryPreRunE(cmd *cobra.Command, args []string) error {
if IsBuiltinCommand(cmd) {
return nil // No-op for builtins
}
provider := strings.ToLower(MustGetString(cmd, "otel-provider"))
switch provider {
case "none":
// Nothing.
case "jaeger":
return initJaegerTracer(
MustGetString(cmd, "otel-jaeger-endpoint"),
MustGetString(cmd, "otel-jaeger-service-name"),
)
default:
return fmt.Errorf("unknown tracing provider: %s", provider)
}
log.Info().Str("new provider", provider).Msg("set tracing provider")
return nil
}
func initJaegerTracer(endpoint, serviceName string) error {
exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(endpoint)))
if err != nil {
return err
}
// Configure the global tracer as a batched, always sampling Jaeger exporter.
otel.SetTracerProvider(trace.NewTracerProvider(
trace.WithSampler(trace.AlwaysSample()),
trace.WithSpanProcessor(trace.NewBatchSpanProcessor(exp)),
trace.WithResource(resource.NewSchemaless(semconv.ServiceNameKey.String(serviceName))),
))
// Configure the global tracer to use the W3C method for propagating contexts
// across services.
//
// For low-level details see:
// https://www.w3.org/TR/trace-context/
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,
}
}