Add -log-level flag for install and inject commands#239
Conversation
Signed-off-by: Kevin Lingerfelt <[email protected]>
siggy
left a comment
There was a problem hiding this comment.
Excited for this feature!
I left some comments that may motivate significant changes (or not). Happy to chat more about this.
cli/cmd/root.go
Outdated
| // This is equivalent to calling log.Fatalf, but unlike go's logger interface, | ||
| // it does not support any formatting of the output prior to printing, which is | ||
| // preferable for printing errors from the CLI | ||
| func logFatalf(format string, v ...interface{}) { |
There was a problem hiding this comment.
I've been meaning to address this. You're right that the way we're using logrus doesn't really fit for CLI output. I think we can continue using centralized logging interfaces and satisfy both logging and CLI use cases.
For this particular logFatalf case, we could configure a logger instance distinct from the package-level logger, to output without formatting:
type cliFmt struct{}
func (f *cliFmt) Format(entry *log.Entry) ([]byte, error) {
return []byte(entry.Message + "\n"), nil
}
func logFatalf(format string, v ...interface{}) {
var cliLog = log.New()
cliLog.Out = os.Stderr
cliLog.Formatter = new(cliFmt)
cliLog.Fatalf(format, v...)
}Taking this a step further, we could instantiate a shared cliLog logger, and use it with the same pattern that we do with the package-level log lines.
There was a problem hiding this comment.
I actually like the formatted output, but if we're going to change this then I'd prefer to have a global log object that is configured this way on root. Whatever solution we end up with, I think the important thing is that either we only use the wrapper functions or we only use the log object directly, which means that we'd need wrapped functions for all levels and formatted vs. ln versions.
There was a problem hiding this comment.
Cool, yeah this all makes sense. I've removed the logFatalf method, and changed the initializer back to setting the log level on logrus's global logger. Related to my comment above, I've also defaulted the log level to "panic" (no logging), when the --verbose flag is not set. As part of this change, I've converted all of the log.Fatal calls in this package to instead print directly to stderr (since they otherwise wouldn't be printed at the default level). This guarantees that we won't print log messages unless the --verbose flag is set, and when we do print messages, they'll be fully formatted with timestamps.
| log.Fatalf("invalid log-level: %s", logLevel) | ||
| // turn on debug logging | ||
| if verbose { | ||
| log.SetLevel(log.DebugLevel) |
There was a problem hiding this comment.
TIOLI, I prefer the granular control that log levels provide, though I may be in the minority. @pcalcado and I discussed this on a previous PR: #154 (comment)
As another example, I needed exactly this functionality today while debugging with kubectl proxy --v=10:
$ kubectl options
...
-v, --v=0: log level for V logsPerhaps this may be symptom of trying to solve two distinct use cases: 1) verbose information for users vs. 2) debugging information for developers. I think the line between the two cases (and users) are blurry enough that multiple log levels can satisfy both.
There was a problem hiding this comment.
Although I still prefer --verbose for the UX I'd be happier keeping the --log-level as it is right now. If anything, we could add a --verbose that pretty much is an alias for --log-level DEBUG later.
There was a problem hiding this comment.
Ah, sorry, I missed the discussion about this in #154. I agree with @pcalcado's comments in that PR. Exposing log levels to a CLI feels unintuitive to me, since I usually expect long-running processes to log, and I expect CLI commands to print stuff and exit, but not log in the traditional sense. That said, for the sake of debugging, I can totally see the value in adjusting the verbosity of the command, which, again, doesn't really feel like logging to me.
Put another way, under normal circumstances, a CLI command should never log. It should only return exactly the output that it's intended to return, explicitly. Outputting messages as the byproduct of log statements seems messy. If the CLI does have the capacity to log, I'd expect the default log level to be "off". In that world, the -log-level flag would only ever be used to lower the log level for the sake of debugging. Hence my preference to switch this to a boolean --verbose flag that sets log-level=debug.
cli/cmd/inject.go
Outdated
| injectCmd.PersistentFlags().UintSliceVar(&ignoreOutboundPorts, "skip-outbound-ports", nil, "outbound ports that should skip the proxy") | ||
| injectCmd.PersistentFlags().UintVar(&proxyControlPort, "control-port", 4190, "proxy port to use for control") | ||
| injectCmd.PersistentFlags().UintVar(&proxyAPIPort, "api-port", 8086, "port where the Conduit controller is running") | ||
| injectCmd.PersistentFlags().StringVar(&proxyLogLevel, "log-level", "warn,conduit_proxy=info", "log level for the proxy") |
There was a problem hiding this comment.
can we call this --proxy-log-level? with our usage of --log-level in other go processes, i'd prefer to be explicit that this something different from the log level for the process being executed.
also, could we provide some examples for valid inputs? for example, i used this this week: trace,conduit_proxy=trace,hyper=trace.
There was a problem hiding this comment.
Yeah, so the way the RUST_LOG environment variable works is that it can take a comma separated list of { level | scope | scope=level }. A bare log level will enable that level for all modules, a bare scope will enable all log levels for that scope, and scope=level will set the log level for that scope to the given level.
Valid log levels (see https://docs.rs/log/0.4.1/log/#use) are:
tracedebuginfowarnerror
The scopes we're probably the most likely to care about are:
conduit_proxy: the proxy itselfh2,tower-h2: the libraries we use for HTTP/2hyper: the library we used for HTTP/1tower-grpc: the gRPC library (probably will want theh2libs as well for debugging gRPC issues)
You can set more granular scopes as well, but this is based on the layout of the Rust modules in the proxy.
For more in depth docs, see https://doc.rust-lang.org/log/env_logger/index.html#enabling-logging
There was a problem hiding this comment.
Hmm, it occurs to me that this wall of text could probably go in docs somewhere now that I think of it.
There was a problem hiding this comment.
It seems like we might be overthinking this a bit. The help message for this flag looks like this:
--proxy-log-level string log level for the proxy (default "warn,conduit_proxy=info")
Including "warn,conduit_proxy=info" as the default gives the user enough of an indication that you can set both a global log level and a package-specific log level. Anybody who needs more control than that is likely familiar enough with the proxy to know which levels and packages to use. So I'm inclined to rename this flag to --proxy-log-level and leave it as is, if that works for you all.
cli/cmd/install.go
Outdated
| installCmd.PersistentFlags().UintVar(&webReplicas, "web-replicas", 1, "replicas of the web server to deploy") | ||
| installCmd.PersistentFlags().UintVar(&prometheusReplicas, "prometheus-replicas", 1, "replicas of prometheus to deploy") | ||
| installCmd.PersistentFlags().StringVar(&imagePullPolicy, "image-pull-policy", "IfNotPresent", "Docker image pull policy") | ||
| installCmd.PersistentFlags().StringVar(&controllerLogLevel, "log-level", "info", "log level for the controller and web components") |
There was a problem hiding this comment.
similar comment to above, can we use --controller-log-level?
pcalcado
left a comment
There was a problem hiding this comment.
I understand the possible confusion between the CLI's global --log-level (which I think should remain global to all commands) and the log level configured by inject, but I have the impression that I am trying to reverse engineer the UX from the code instead of the other way around.
Building on @hawkw 's comments, could we update #230 with acceptance criteriaspec'ng the UX for this feature a bit further?
cli/cmd/root.go
Outdated
| // This is equivalent to calling log.Fatalf, but unlike go's logger interface, | ||
| // it does not support any formatting of the output prior to printing, which is | ||
| // preferable for printing errors from the CLI | ||
| func logFatalf(format string, v ...interface{}) { |
There was a problem hiding this comment.
I actually like the formatted output, but if we're going to change this then I'd prefer to have a global log object that is configured this way on root. Whatever solution we end up with, I think the important thing is that either we only use the wrapper functions or we only use the log object directly, which means that we'd need wrapped functions for all levels and formatted vs. ln versions.
| log.Fatalf("invalid log-level: %s", logLevel) | ||
| // turn on debug logging | ||
| if verbose { | ||
| log.SetLevel(log.DebugLevel) |
There was a problem hiding this comment.
Although I still prefer --verbose for the UX I'd be happier keeping the --log-level as it is right now. If anything, we could add a --verbose that pretty much is an alias for --log-level DEBUG later.
Signed-off-by: Kevin Lingerfelt <[email protected]>
siggy
left a comment
There was a problem hiding this comment.
thanks for all these changes! 🚢
cli/cmd/root.go
Outdated
| if err != nil { | ||
| log.Fatalf("invalid log-level: %s", logLevel) | ||
| // clear log color formatting | ||
| log.SetFormatter(&log.TextFormatter{DisableColors: true}) |
There was a problem hiding this comment.
Ah, you're right -- it turns out we don't actually need to disable colors. Will remove this.
| if err != nil { | ||
| log.Fatalf("Failed to start kubectl: %v", err) | ||
| fmt.Fprintf(os.Stderr, "Failed to start kubectl: %s", err) | ||
| os.Exit(1) |
There was a problem hiding this comment.
curious: in this context, how do you think about the difference between:
return fmt.Errorf(...and:
fmt.Fprintf(os.Stderr, ...
os.Exit(1)There was a problem hiding this comment.
This is a good question. If the run function returns an error, then cobra will print the error text and the usage message for the command that's being run. In most cases that's the right behavior (e.g. like on line 23 of this file, where the --proxy-port flag is invalid). But in this case, the usage checks have passed and something else has failed, in which case I think it's preferable to just print the failure and exit.
There was a problem hiding this comment.
Makes sense, thanks for the explanation!
Signed-off-by: Kevin Lingerfelt <[email protected]>
commit 073a1beb4a7cd709c6b1eaa56a319c1829a94d11 Author: Sean McArthur <[email protected]> Date: Mon Apr 29 17:54:01 2019 -0700 tap: remove need to clone Services (#238) This refactors the tap system to not require intermediary channels to register matches and taps when a request comes through. The Dispatcher that used to exist in order to prevent tapping more requests than the limit asked for has been removed. In its place is a shared atomic counter to keep the count under the limit. The resulting behavior should be the same. There should be improved performance as tap registration doesn't need go through a second channel, and requests don't need to be delayed waiting for the dispatcher to be able to process its queue. Signed-off-by: Sean McArthur <[email protected]> commit 7a3be8c8737188e5debbc465f9a33da0d79b8b80 Author: Zahari Dichev <[email protected]> Date: Wed May 1 01:57:01 2019 +0300 Replace fixed reconnect backoff with exponential one (#237) When reconnecting to a destination, use an exponential, jittered backoff strategy. Signed-off-by: Zahari Dichev <[email protected]> commit 32b813aad4fe2fcf0252e8c2215d6835101d2337 Author: Oliver Gould <[email protected]> Date: Tue Apr 30 15:58:20 2019 -0700 Support endpoint weights (#230) This change modifies the proxy to honor weights provided by the destination service. When the destination service replies with a weight, this value is divided by 10,000 to produce a weight on [0.0, ~400000.0]. This weight is used by load the load balancer to modify load interpretation and therefore request distribution. A weight of 0.0 will cause the endpoint's load to be effectively infinite so that requests will only be sent to the endpoint when no other endpoints exists or when the other endpoints that were considered had 0-weights. commit 501802671a346250b6dbaae73f29d9be7a4c2086 Author: Sean McArthur <[email protected]> Date: Wed May 1 13:42:38 2019 -0700 Remove buffers from endpoint stacks (#239) Due to the `http::settings::router`, a `buffer` was needed in each endpoint stack. This meant that the service was always ready, even if the client were falling over (and reconnecting). In turn, this meant that the balancer would pick one of these endpoint stacks, because it was always ready! This change includes a test of a failing endpoint, that the balancer no longer assumes it is ready, and has the following functional changes: - Removed `http::settings::router`, instead the client HTTP settings are detected as part of the `DstAddr`. This means that each balancer only has endpoints with the same HTTP settings. - Removed `buffer` layer from inside the endpoint stacks. Signed-off-by: Sean McArthur <[email protected]>
commit 073a1beb4a7cd709c6b1eaa56a319c1829a94d11 Author: Sean McArthur <[email protected]> Date: Mon Apr 29 17:54:01 2019 -0700 tap: remove need to clone Services (#238) This refactors the tap system to not require intermediary channels to register matches and taps when a request comes through. The Dispatcher that used to exist in order to prevent tapping more requests than the limit asked for has been removed. In its place is a shared atomic counter to keep the count under the limit. The resulting behavior should be the same. There should be improved performance as tap registration doesn't need go through a second channel, and requests don't need to be delayed waiting for the dispatcher to be able to process its queue. Signed-off-by: Sean McArthur <[email protected]> commit 7a3be8c8737188e5debbc465f9a33da0d79b8b80 Author: Zahari Dichev <[email protected]> Date: Wed May 1 01:57:01 2019 +0300 Replace fixed reconnect backoff with exponential one (#237) When reconnecting to a destination, use an exponential, jittered backoff strategy. Signed-off-by: Zahari Dichev <[email protected]> commit 32b813aad4fe2fcf0252e8c2215d6835101d2337 Author: Oliver Gould <[email protected]> Date: Tue Apr 30 15:58:20 2019 -0700 Support endpoint weights (#230) This change modifies the proxy to honor weights provided by the destination service. When the destination service replies with a weight, this value is divided by 10,000 to produce a weight on [0.0, ~400000.0]. This weight is used by load the load balancer to modify load interpretation and therefore request distribution. A weight of 0.0 will cause the endpoint's load to be effectively infinite so that requests will only be sent to the endpoint when no other endpoints exists or when the other endpoints that were considered had 0-weights. commit 501802671a346250b6dbaae73f29d9be7a4c2086 Author: Sean McArthur <[email protected]> Date: Wed May 1 13:42:38 2019 -0700 Remove buffers from endpoint stacks (#239) Due to the `http::settings::router`, a `buffer` was needed in each endpoint stack. This meant that the service was always ready, even if the client were falling over (and reconnecting). In turn, this meant that the balancer would pick one of these endpoint stacks, because it was always ready! This change includes a test of a failing endpoint, that the balancer no longer assumes it is ready, and has the following functional changes: - Removed `http::settings::router`, instead the client HTTP settings are detected as part of the `DstAddr`. This means that each balancer only has endpoints with the same HTTP settings. - Removed `buffer` layer from inside the endpoint stacks. Signed-off-by: Sean McArthur <[email protected]>
This branch updates the install and inject commands to add a
--log-levelflag, which can be used to set the log level of the control and data plane components.Note that the CLI previously supported a global
--log-levelflag, but I've renamed that to--verbose, which can be used to turn on debug logging, as there shouldn't be any other use case for adjusting the log level of the CLI.I'm also fixing an issue where, in certain places, we were calling
log.Fatal, and that was causing the logged messages to be rendered with additional formatting, which seemed unintended. For instance, on master:Whereas with this change:
Fixes #230.