Merge lp:~thumper/juju-core/default-command-info into lp:~go-bot/juju-core/trunk

Proposed by Tim Penhey
Status: Merged
Approved by: Tim Penhey
Approved revision: no longer in the source branch.
Merged at revision: 2479
Proposed branch: lp:~thumper/juju-core/default-command-info
Merge into: lp:~go-bot/juju-core/trunk
Diff against target: 323 lines (+152/-39)
6 files modified
cmd/cmd.go (+34/-4)
cmd/juju/deploy.go (+2/-3)
cmd/juju/deploy_test.go (+2/-2)
cmd/juju/main_test.go (+1/-0)
cmd/logging.go (+25/-18)
cmd/logging_test.go (+88/-12)
To merge this branch: bzr merge lp:~thumper/juju-core/default-command-info
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+194061@code.launchpad.net

Commit message

Add standard functions for command output.

Add simple function support and flags around changing how the command output
works by default. Add flag options for '-q' and '--quiet'. Also updated the text for the flags.

There are now two methods on the cmd.Context object that should be used for output:
  func (ctx *Context) Infof(format string, params ...interface{})
and
  func (ctx *Context) Verbosef(format string, params ...interface{})

If quiet is specified, both are logged instead of written out. If verbose is
specified, both are written out, if the defaults are used, Info is written
out, but verbose is logged.

Description of the change

Add standard functions for command output.

Add simple function support and flags around changing how the command output
works by default. Add flag options for '-q' and '--quiet', and provider a
short '-d' for '--debug'. Also updated the text for the flags.

There are now two methods in the cmd package that should be used for output:
  cmd.Infof(...)
and
  cmd.Verbosef(...)

If quiet is specified, both are logged instead of written out. If verbose is
specified, both are written out, if the defaults are used, Info is written
out, but verbose is logged.

Ideally I'd like to tweak loggo so we can specify the call depth of the
caller, so we can grab the appropriate line numbers etc, but this can happen
later. Also, now any use of these methods will have the 'juju.cmd' module. Not
sure if we want to really parameterize that.

The only thing I'm wondering about is Stderr vs. Stdout. Ideally I'd prefer
to use Stdout where possible, but for the format oriented commands, we still
want users to be able to go:
  'juju status --format=json | other-thing'
So we don't want to polute Stdout in those situations with output.

There are a number of options here:
 * we just write to Stdout, and have people add '-q'
 * we write the info and verbose info to Stderr so it just works

Then do we have some go to Stdout and some Stderr?

Logging now goes to Stderr, so we could just send all output there (except for
the actual formatted output).

https://codereview.appspot.com/22320043/

To post a comment you must log in.
Revision history for this message
Tim Penhey (thumper) wrote :

Reviewers: mp+194061_code.launchpad.net,

Message:
Please take a look.

Description:
Add standard functions for command output.

Add simple function support and flags around changing how the command
output
works by default. Add flag options for '-q' and '--quiet', and provider
a
short '-d' for '--debug'. Also updated the text for the flags.

There are now two methods in the cmd package that should be used for
output:
   cmd.Infof(...)
and
   cmd.Verbosef(...)

If quiet is specified, both are logged instead of written out. If
verbose is
specified, both are written out, if the defaults are used, Info is
written
out, but verbose is logged.

Ideally I'd like to tweak loggo so we can specify the call depth of the
caller, so we can grab the appropriate line numbers etc, but this can
happen
later. Also, now any use of these methods will have the 'juju.cmd'
module. Not
sure if we want to really parameterize that.

The only thing I'm wondering about is Stderr vs. Stdout. Ideally I'd
prefer
to use Stdout where possible, but for the format oriented commands, we
still
want users to be able to go:
   'juju status --format=json | other-thing'
So we don't want to polute Stdout in those situations with output.

There are a number of options here:
  * we just write to Stdout, and have people add '-q'
  * we write the info and verbose info to Stderr so it just works

Then do we have some go to Stdout and some Stderr?

Logging now goes to Stderr, so we could just send all output there
(except for
the actual formatted output).

https://code.launchpad.net/~thumper/juju-core/default-command-info/+merge/194061

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/22320043/

Affected files (+139, -28 lines):
   A [revision details]
   M cmd/export_test.go
   M cmd/logging.go
   M cmd/logging_test.go

Revision history for this message
Ian Booth (wallyworld) wrote :

A few questions.

1. -d for debug is sorta bad

There's a number of existing commands that use the -d option (eg for
specifying a directory) so I think we want to stcik with just --debug

2. Does it make sense to allow -q and --debug together?

If you want debug, then I think you would want everything else as well

3. show-log

I think if verbose is specified, that could turn on show-log. Do we
still need show-log? I'm not sure we do. For a user's perspective, I
want to see either:

i. nothing at all except errors... option = "--quiet"
ii. informational lines that signpost where the command is up to as it
executes (without timestamps etc)... the default, option = ""
iii. verbose output with timestamps and some more detail... option =
"--verbose"

As a developer, I also want:

iv. debug output with everything... option = "--debug"

I'm not sure with the above if show-log is still relevant.

As for the stdout vs stderr thing, I can see both sides. My initial view
is that stderr is for all log outout and stdout is for command results
in json/yaml etc

https://codereview.appspot.com/22320043/diff/1/cmd/logging.go
File cmd/logging.go (right):

https://codereview.appspot.com/22320043/diff/1/cmd/logging.go#newcode77
cmd/logging.go:77: return fmt.Errorf(`"verbose" and "quiet" flags
clash`)
perhaps

"verbose" and "quiet" flags clash, please use one or the other, not both

https://codereview.appspot.com/22320043/

Revision history for this message
William Reade (fwereade) wrote :

On 2013/11/06 04:04:42, thumper wrote:
> Please take a look.

I'm sure we want to keep informational messages to stderr. Still
thinking about the other points raised.

https://codereview.appspot.com/22320043/

Revision history for this message
John A Meinel (jameinel) wrote :

We have a cmd.Context object which is passed to all the commands, and is
something that we can then pass into internal code.

It seems better to have it on an object, rather than as a package level
function.

does that make sense to you?

https://codereview.appspot.com/22320043/

Revision history for this message
William Reade (fwereade) wrote :

On 2013/11/06 11:45:34, jameinel wrote:
> We have a cmd.Context object which is passed to all the commands, and
is
> something that we can then pass into internal code.

> It seems better to have it on an object, rather than as a package
level
> function.

> does that make sense to you?

That's actually starting to make more and more sense, because:

1) the value of this gets lower and lower as more functionality moves
behind the API -- it becomes more and more convenient to just write to
the cmd.Context that's right there in Run

2) bootstrap and (manual) add-machine feel like the major exceptions to
the above, but both of those need the context anyway for sudo (and given
that we probably ought not to be reading env vars directly, we probably
ought to be communicating those to bootstrap via some such context too).

What's the actual set of potential users for this code once the CLI
talks API?

https://codereview.appspot.com/22320043/

Revision history for this message
William Reade (fwereade) wrote :

WIPping -- any further thoughts, tim?

Revision history for this message
Tim Penhey (thumper) wrote :
Revision history for this message
Tim Penhey (thumper) wrote :

On 2013/11/06 04:31:25, wallyworld wrote:
> A few questions.

> 1. -d for debug is sorta bad

> There's a number of existing commands that use the -d option (eg for
specifying
> a directory) so I think we want to stcik with just --debug

Removed -d

> 2. Does it make sense to allow -q and --debug together?

> If you want debug, then I think you would want everything else as well

Yes -q and --debug is fine (IMO). In fact I force the fact, as if you
say
--debug, by forcing -q we get all the info, and verbose output as log
file
entries.

> 3. show-log

> I think if verbose is specified, that could turn on show-log. Do we
still need
> show-log? I'm not sure we do. For a user's perspective, I want to see
either:

> i. nothing at all except errors... option = "--quiet"
> ii. informational lines that signpost where the command is up to as it
executes
> (without timestamps etc)... the default, option = ""
> iii. verbose output with timestamps and some more detail... option =
"--verbose"

> As a developer, I also want:

> iv. debug output with everything... option = "--debug"

> I'm not sure with the above if show-log is still relevant.

Perhaps not... but outside the scope of this for now.

> As for the stdout vs stderr thing, I can see both sides. My initial
view is that
> stderr is for all log outout and stdout is for command results in
json/yaml etc

> https://codereview.appspot.com/22320043/diff/1/cmd/logging.go
> File cmd/logging.go (right):

https://codereview.appspot.com/22320043/diff/1/cmd/logging.go#newcode77
> cmd/logging.go:77: return fmt.Errorf(`"verbose" and "quiet" flags
clash`)
> perhaps

> "verbose" and "quiet" flags clash, please use one or the other, not
both

done

https://codereview.appspot.com/22320043/

Revision history for this message
Tim Penhey (thumper) wrote :

On 2013/11/06 10:44:31, fwereade wrote:
> On 2013/11/06 04:04:42, thumper wrote:
> > Please take a look.

> I'm sure we want to keep informational messages to stderr. Still
thinking about
> the other points raised.

kept them on stderr

https://codereview.appspot.com/22320043/

Revision history for this message
Tim Penhey (thumper) wrote :

On 2013/11/06 11:45:34, jameinel wrote:
> We have a cmd.Context object which is passed to all the commands, and
is
> something that we can then pass into internal code.

> It seems better to have it on an object, rather than as a package
level
> function.

> does that make sense to you?

moved the methods to the context object.

I had originally thought that by having them as stand alone functions,
we wouldn't have to pass the context down to the places that are needing
them.

Consider the bastardisation we have with loggers at the moment.

https://codereview.appspot.com/22320043/

Revision history for this message
Ian Booth (wallyworld) wrote :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'cmd/cmd.go'
2--- cmd/cmd.go 2014-03-24 13:23:40 +0000
3+++ cmd/cmd.go 2014-03-26 01:26:35 +0000
4@@ -90,10 +90,40 @@
5 // should interpret file names relative to Dir (see AbsPath below), and print
6 // output and errors to Stdout and Stderr respectively.
7 type Context struct {
8- Dir string
9- Stdin io.Reader
10- Stdout io.Writer
11- Stderr io.Writer
12+ Dir string
13+ Stdin io.Reader
14+ Stdout io.Writer
15+ Stderr io.Writer
16+ quiet bool
17+ verbose bool
18+}
19+
20+func (ctx *Context) write(format string, params ...interface{}) {
21+ output := fmt.Sprintf(format, params...)
22+ if !strings.HasSuffix(output, "\n") {
23+ output = output + "\n"
24+ }
25+ fmt.Fprint(ctx.Stderr, output)
26+}
27+
28+// Infof will write the formatted string to Stderr if quiet is false, but if
29+// quiet is true the message is logged.
30+func (ctx *Context) Infof(format string, params ...interface{}) {
31+ if ctx.quiet {
32+ logger.Infof(format, params...)
33+ } else {
34+ ctx.write(format, params...)
35+ }
36+}
37+
38+// Verbosef will write the formatted string to Stderr if the verbose is true,
39+// and to the logger if not.
40+func (ctx *Context) Verbosef(format string, params ...interface{}) {
41+ if ctx.verbose {
42+ ctx.write(format, params...)
43+ } else {
44+ logger.Infof(format, params...)
45+ }
46 }
47
48 // AbsPath returns an absolute representation of path, with relative paths
49
50=== modified file 'cmd/juju/deploy.go'
51--- cmd/juju/deploy.go 2014-03-18 05:08:25 +0000
52+++ cmd/juju/deploy.go 2014-03-26 01:26:35 +0000
53@@ -150,7 +150,7 @@
54 }
55
56 if c.BumpRevision {
57- ctx.Stdout.Write([]byte("--upgrade (or -u) is deprecated and ignored; charms are always deployed with a unique revision.\n"))
58+ ctx.Infof("--upgrade (or -u) is deprecated and ignored; charms are always deployed with a unique revision.")
59 }
60
61 charmInfo, err := client.CharmInfo(curl.String())
62@@ -293,7 +293,6 @@
63 default:
64 return nil, fmt.Errorf("unsupported charm URL schema: %q", curl.Schema)
65 }
66- report := fmt.Sprintf("Added charm %q to the environment.\n", curl)
67- ctx.Stdout.Write([]byte(report))
68+ ctx.Infof("Added charm %q to the environment.", curl)
69 return curl, nil
70 }
71
72=== modified file 'cmd/juju/deploy_test.go'
73--- cmd/juju/deploy_test.go 2014-03-13 07:54:56 +0000
74+++ cmd/juju/deploy_test.go 2014-03-26 01:26:35 +0000
75@@ -83,8 +83,8 @@
76 ctx, err := coretesting.RunCommand(c, &DeployCommand{}, []string{"local:dummy", "-u"})
77 c.Assert(err, gc.IsNil)
78
79- c.Assert(coretesting.Stderr(ctx), gc.Equals, "")
80- output := strings.Split(coretesting.Stdout(ctx), "\n")
81+ c.Assert(coretesting.Stdout(ctx), gc.Equals, "")
82+ output := strings.Split(coretesting.Stderr(ctx), "\n")
83 c.Check(output[0], gc.Matches, `Added charm ".*" to the environment.`)
84 c.Check(output[1], gc.Equals, "--upgrade (or -u) is deprecated and ignored; charms are always deployed with a unique revision.")
85 }
86
87=== modified file 'cmd/juju/main_test.go'
88--- cmd/juju/main_test.go 2014-02-27 05:22:46 +0000
89+++ cmd/juju/main_test.go 2014-03-26 01:26:35 +0000
90@@ -316,6 +316,7 @@
91 "-h, --help .*",
92 "--log-file .*",
93 "--logging-config .*",
94+ "-q, --quiet .*",
95 "--show-log .*",
96 "-v, --verbose .*",
97 }
98
99=== modified file 'cmd/logging.go'
100--- cmd/logging.go 2014-03-05 19:41:34 +0000
101+++ cmd/logging.go 2014-03-26 01:26:35 +0000
102@@ -26,6 +26,7 @@
103 type Log struct {
104 Path string
105 Verbose bool
106+ Quiet bool
107 Debug bool
108 ShowLog bool
109 Config string
110@@ -43,45 +44,51 @@
111 // AddFlags adds appropriate flags to f.
112 func (l *Log) AddFlags(f *gnuflag.FlagSet) {
113 f.StringVar(&l.Path, "log-file", "", "path to write log to")
114- // TODO(thumper): rename verbose to --show-log
115- f.BoolVar(&l.Verbose, "v", false, "if set, log additional messages")
116- f.BoolVar(&l.Verbose, "verbose", false, "if set, log additional messages")
117- f.BoolVar(&l.Debug, "debug", false, "if set, log debugging messages")
118+ f.BoolVar(&l.Verbose, "v", false, "show more verbose output")
119+ f.BoolVar(&l.Verbose, "verbose", false, "show more verbose output")
120+ f.BoolVar(&l.Quiet, "q", false, "show no informational output")
121+ f.BoolVar(&l.Quiet, "quiet", false, "show no informational output")
122+ f.BoolVar(&l.Debug, "debug", false, "equivalent to --show-log --log-config=<root>=DEBUG")
123 defaultLogConfig := os.Getenv(osenv.JujuLoggingConfigEnvKey)
124 f.StringVar(&l.Config, "logging-config", defaultLogConfig, "specify log levels for modules")
125 f.BoolVar(&l.ShowLog, "show-log", false, "if set, write the log file to stderr")
126 }
127
128 // Start starts logging using the given Context.
129-func (l *Log) Start(ctx *Context) error {
130- if l.Path != "" {
131- path := ctx.AbsPath(l.Path)
132+func (log *Log) Start(ctx *Context) error {
133+ if log.Verbose && log.Quiet {
134+ return fmt.Errorf(`"verbose" and "quiet" flags clash, please use one or the other, not both`)
135+ }
136+ ctx.quiet = log.Quiet
137+ ctx.verbose = log.Verbose
138+ if log.Path != "" {
139+ path := ctx.AbsPath(log.Path)
140 target, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
141 if err != nil {
142 return err
143 }
144- writer := l.GetLogWriter(target)
145+ writer := log.GetLogWriter(target)
146 err = loggo.RegisterWriter("logfile", writer, loggo.TRACE)
147 if err != nil {
148 return err
149 }
150 }
151 level := loggo.WARNING
152- if l.Verbose {
153- ctx.Stdout.Write([]byte("Flag --verbose is deprecated with the current meaning, use --show-log\n"))
154- l.ShowLog = true
155- }
156- if l.ShowLog {
157+ if log.ShowLog {
158 level = loggo.INFO
159 }
160- if l.Debug {
161- l.ShowLog = true
162+ if log.Debug {
163+ log.ShowLog = true
164 level = loggo.DEBUG
165+ // override quiet or verbose if set, this way all the information goes
166+ // to the log file.
167+ ctx.quiet = true
168+ ctx.verbose = false
169 }
170
171- if l.ShowLog {
172+ if log.ShowLog {
173 // We replace the default writer to use ctx.Stderr rather than os.Stderr.
174- writer := l.GetLogWriter(ctx.Stderr)
175+ writer := log.GetLogWriter(ctx.Stderr)
176 _, err := loggo.ReplaceDefaultWriter(writer)
177 if err != nil {
178 return err
179@@ -99,7 +106,7 @@
180 // Set the level on the root logger.
181 loggo.GetLogger("").SetLogLevel(level)
182 // Override the logging config with specified logging config.
183- loggo.ConfigureLoggers(l.Config)
184+ loggo.ConfigureLoggers(log.Config)
185 return nil
186 }
187
188
189=== modified file 'cmd/logging_test.go'
190--- cmd/logging_test.go 2014-03-18 15:43:41 +0000
191+++ cmd/logging_test.go 2014-03-26 01:26:35 +0000
192@@ -25,6 +25,7 @@
193 var _ = gc.Suite(&LogSuite{})
194
195 func (s *LogSuite) SetUpTest(c *gc.C) {
196+ s.CleanupSuite.SetUpTest(c)
197 s.PatchEnvironment(osenv.JujuLoggingConfigEnvKey, "")
198 s.AddCleanup(func(_ *gc.C) {
199 loggo.ResetLoggers()
200@@ -44,6 +45,7 @@
201 func (s *LogSuite) TestNoFlags(c *gc.C) {
202 log := newLogWithFlags(c, []string{})
203 c.Assert(log.Path, gc.Equals, "")
204+ c.Assert(log.Quiet, gc.Equals, false)
205 c.Assert(log.Verbose, gc.Equals, false)
206 c.Assert(log.Debug, gc.Equals, false)
207 c.Assert(log.Config, gc.Equals, "")
208@@ -68,17 +70,6 @@
209 c.Assert(log.Config, gc.Equals, config)
210 }
211
212-func (s *LogSuite) TestVerboseSetsLogLevel(c *gc.C) {
213- l := &cmd.Log{Verbose: true}
214- ctx := coretesting.Context(c)
215- err := l.Start(ctx)
216- c.Assert(err, gc.IsNil)
217-
218- c.Assert(loggo.GetLogger("").LogLevel(), gc.Equals, loggo.INFO)
219- c.Assert(coretesting.Stderr(ctx), gc.Equals, "")
220- c.Assert(coretesting.Stdout(ctx), gc.Equals, "Flag --verbose is deprecated with the current meaning, use --show-log\n")
221-}
222-
223 func (s *LogSuite) TestDebugSetsLogLevel(c *gc.C) {
224 l := &cmd.Log{Debug: true}
225 ctx := coretesting.Context(c)
226@@ -102,7 +93,7 @@
227 }
228
229 func (s *LogSuite) TestStderr(c *gc.C) {
230- l := &cmd.Log{Verbose: true, Config: "<root>=INFO"}
231+ l := &cmd.Log{ShowLog: true, Config: "<root>=INFO"}
232 ctx := coretesting.Context(c)
233 err := l.Start(ctx)
234 c.Assert(err, gc.IsNil)
235@@ -161,3 +152,88 @@
236 c.Assert(coretesting.Stderr(ctx), gc.Matches, `^.*WARNING a warning\n.*ERROR an error\n.*`)
237 c.Assert(coretesting.Stdout(ctx), gc.Equals, "")
238 }
239+
240+func (s *LogSuite) TestQuietAndVerbose(c *gc.C) {
241+ l := &cmd.Log{Verbose: true, Quiet: true}
242+ ctx := coretesting.Context(c)
243+ err := l.Start(ctx)
244+ c.Assert(err, gc.ErrorMatches, `"verbose" and "quiet" flags clash, please use one or the other, not both`)
245+}
246+
247+func (s *LogSuite) TestOutputDefault(c *gc.C) {
248+ l := &cmd.Log{}
249+ ctx := coretesting.Context(c)
250+ err := l.Start(ctx)
251+ c.Assert(err, gc.IsNil)
252+
253+ ctx.Infof("Writing info output")
254+ ctx.Verbosef("Writing verbose output")
255+
256+ c.Assert(coretesting.Stderr(ctx), gc.Equals, "Writing info output\n")
257+}
258+
259+func (s *LogSuite) TestOutputVerbose(c *gc.C) {
260+ l := &cmd.Log{Verbose: true}
261+ ctx := coretesting.Context(c)
262+ err := l.Start(ctx)
263+ c.Assert(err, gc.IsNil)
264+
265+ ctx.Infof("Writing info output")
266+ ctx.Verbosef("Writing verbose output")
267+
268+ c.Assert(coretesting.Stderr(ctx), gc.Equals, "Writing info output\nWriting verbose output\n")
269+}
270+
271+func (s *LogSuite) TestOutputQuiet(c *gc.C) {
272+ l := &cmd.Log{Quiet: true}
273+ ctx := coretesting.Context(c)
274+ err := l.Start(ctx)
275+ c.Assert(err, gc.IsNil)
276+
277+ ctx.Infof("Writing info output")
278+ ctx.Verbosef("Writing verbose output")
279+
280+ c.Assert(coretesting.Stderr(ctx), gc.Equals, "")
281+}
282+
283+func (s *LogSuite) TestOutputQuietLogs(c *gc.C) {
284+ l := &cmd.Log{Quiet: true, Path: "foo.log", Config: "<root>=INFO"}
285+ ctx := coretesting.Context(c)
286+ err := l.Start(ctx)
287+ c.Assert(err, gc.IsNil)
288+
289+ ctx.Infof("Writing info output")
290+ ctx.Verbosef("Writing verbose output")
291+
292+ content, err := ioutil.ReadFile(filepath.Join(ctx.Dir, "foo.log"))
293+ c.Assert(err, gc.IsNil)
294+ c.Assert(coretesting.Stderr(ctx), gc.Equals, "")
295+ c.Assert(string(content), gc.Matches, `^.*INFO .* Writing info output\n.*INFO .*Writing verbose output\n.*`)
296+}
297+
298+func (s *LogSuite) TestOutputDefaultLogsVerbose(c *gc.C) {
299+ l := &cmd.Log{Path: "foo.log", Config: "<root>=INFO"}
300+ ctx := coretesting.Context(c)
301+ err := l.Start(ctx)
302+ c.Assert(err, gc.IsNil)
303+
304+ ctx.Infof("Writing info output")
305+ ctx.Verbosef("Writing verbose output")
306+
307+ content, err := ioutil.ReadFile(filepath.Join(ctx.Dir, "foo.log"))
308+ c.Assert(err, gc.IsNil)
309+ c.Assert(coretesting.Stderr(ctx), gc.Equals, "Writing info output\n")
310+ c.Assert(string(content), gc.Matches, `^.*INFO .*Writing verbose output\n.*`)
311+}
312+
313+func (s *LogSuite) TestOutputDebugForcesQuiet(c *gc.C) {
314+ l := &cmd.Log{Verbose: true, Debug: true}
315+ ctx := coretesting.Context(c)
316+ err := l.Start(ctx)
317+ c.Assert(err, gc.IsNil)
318+
319+ ctx.Infof("Writing info output")
320+ ctx.Verbosef("Writing verbose output")
321+
322+ c.Assert(coretesting.Stderr(ctx), gc.Matches, `^.*INFO .* Writing info output\n.*INFO .*Writing verbose output\n.*`)
323+}

Subscribers

People subscribed via source and target branches

to status/vote changes: