Skip to content

Add -log-level flag for install and inject commands#239

Merged
klingerf merged 3 commits intomasterfrom
kl/logging
Feb 1, 2018
Merged

Add -log-level flag for install and inject commands#239
klingerf merged 3 commits intomasterfrom
kl/logging

Conversation

@klingerf
Copy link
Contributor

This branch updates the install and inject commands to add a --log-level flag, 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-level flag, 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:

$ conduit completion asdf
2018/01/30 18:05:54 unsupported shell type (must be bash or zsh): asdf

Whereas with this change:

$ conduit completion asdf
unsupported shell type (must be bash or zsh): asdf

Fixes #230.

@klingerf klingerf self-assigned this Jan 31, 2018
@klingerf klingerf added the review/ready Issue has a reviewable PR label Jan 31, 2018
Copy link
Member

@siggy siggy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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{}) {
Copy link
Member

@siggy siggy Jan 31, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 logs

Perhaps 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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

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")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  • trace
  • debug
  • info
  • warn
  • error

The scopes we're probably the most likely to care about are:

  • conduit_proxy: the proxy itself
  • h2, tower-h2: the libraries we use for HTTP/2
  • hyper: the library we used for HTTP/1
  • tower-grpc: the gRPC library (probably will want the h2 libs 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

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, it occurs to me that this wall of text could probably go in docs somewhere now that I think of it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

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")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar comment to above, can we use --controller-log-level?

Copy link
Contributor

@pcalcado pcalcado left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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{}) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@siggy
Copy link
Member

siggy commented Feb 1, 2018

@pcalcado i've updated #230 with more specific exit criteria for what this feature looks like.

Copy link
Member

@siggy siggy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 🌈 😢

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

curious: in this context, how do you think about the difference between:

return fmt.Errorf(...

and:

fmt.Fprintf(os.Stderr, ...
os.Exit(1)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, thanks for the explanation!

Signed-off-by: Kevin Lingerfelt <[email protected]>
@klingerf klingerf merged commit 9ff439e into master Feb 1, 2018
@klingerf klingerf removed the review/ready Issue has a reviewable PR label Feb 1, 2018
@klingerf klingerf deleted the kl/logging branch February 1, 2018 20:38
olix0r added a commit that referenced this pull request May 1, 2019
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]>
olix0r added a commit that referenced this pull request May 1, 2019
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]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

enable log-level setting via install and inject commands

4 participants