Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(275)

Side by Side Diff: cmd/juju/debuglog.go

Issue 58510045: debug-log: added new debug log api and command
Patch Set: debug-log: added new debug log api and command Created 11 years, 2 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « [revision details] ('k') | cmd/juju/debuglog_test.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2013 Canonical Ltd. 1 // Copyright 2013, 2014 Canonical Ltd.
2 // Licensed under the AGPLv3, see LICENCE file for details. 2 // Licensed under the AGPLv3, see LICENCE file for details.
3 3
4 package main 4 package main
5 5
6 import ( 6 import (
7 "fmt" 7 "fmt"
8 "io"
9 "os"
10 "os/exec"
8 "strconv" 11 "strconv"
9 12
10 "launchpad.net/gnuflag" 13 "launchpad.net/gnuflag"
11 14
12 "launchpad.net/juju-core/cmd" 15 "launchpad.net/juju-core/cmd"
16 "launchpad.net/juju-core/environs"
17 "launchpad.net/juju-core/environs/configstore"
18 "launchpad.net/juju-core/juju"
19 "launchpad.net/juju-core/juju/osenv"
20 "launchpad.net/juju-core/provider"
13 ) 21 )
14 22
15 type DebugLogCommand struct { 23 type DebugLogCommand struct {
16 » cmd.CommandBase 24 » cmd.EnvCommandBase
17 » // The debug log command simply invokes juju ssh with the required argum ents. 25
18 » sshCmd cmd.Command 26 » lines int
19 » lines linesValue 27 » filter string
20 } 28 }
21 29
22 // defaultLineCount is the default number of lines to 30 // defaultLineCount is the default number of lines to
23 // display, from the end of the consolidated log. 31 // display, from the end of the consolidated log.
24 const defaultLineCount = 10 32 const defaultLineCount = 10
25 33
26 // linesValue implements gnuflag.Value, and represents
27 // a -n/--lines flag value compatible with "tail".
28 //
29 // A negative value (-K) corresponds to --lines=K,
30 // i.e. the last K lines; a positive value (+K)
31 // corresponds to --lines=+K, i.e. from line K onwards.
32 type linesValue int
33
34 func (v *linesValue) String() string {
35 if *v > 0 {
36 return fmt.Sprintf("+%d", *v)
37 }
38 return fmt.Sprint(-*v)
39 }
40
41 func (v *linesValue) Set(value string) error {
42 if len(value) > 0 {
43 sign := -1
44 if value[0] == '+' {
45 value = value[1:]
46 sign = 1
47 }
48 n, err := strconv.ParseInt(value, 10, 0)
49 if err == nil && n > 0 {
50 *v = linesValue(sign * int(n))
51 return nil
52 }
53 // err is quite verbose, and doesn't convey
54 // any additional useful information.
55 }
56 return fmt.Errorf("invalid number of lines")
57 }
58
59 const debuglogDoc = ` 34 const debuglogDoc = `
60 Launch an ssh shell on the state server machine and tail the consolidated log fi le. 35 Stream the consolidated debug log file. This file contains the log messages
61 The consolidated log file contains log messages from all nodes in the environmen t. 36 from all nodes in the environment.
62 ` 37 `
63 38
64 func (c *DebugLogCommand) Info() *cmd.Info { 39 func (c *DebugLogCommand) Info() *cmd.Info {
65 return &cmd.Info{ 40 return &cmd.Info{
66 Name: "debug-log", 41 Name: "debug-log",
67 Purpose: "display the consolidated log file", 42 Purpose: "display the consolidated log file",
68 Doc: debuglogDoc, 43 Doc: debuglogDoc,
69 } 44 }
70 } 45 }
71 46
72 func (c *DebugLogCommand) SetFlags(f *gnuflag.FlagSet) { 47 func (c *DebugLogCommand) SetFlags(f *gnuflag.FlagSet) {
73 » c.sshCmd.SetFlags(f) 48 » f.IntVar(&c.lines, "n", defaultLineCount, "output the last K lines; or u se -n +K to output lines starting with the Kth")
74 49 » f.IntVar(&c.lines, "lines", defaultLineCount, "")
75 » c.lines = -defaultLineCount 50 » f.StringVar(&c.filter, "f", "", "filter the output with a regular expres sion")
76 » f.Var(&c.lines, "n", "output the last K lines; or use -n +K to output li nes starting with the Kth") 51 » f.StringVar(&c.filter, "filter", "", "")
77 » f.Var(&c.lines, "lines", "")
78 }
79
80 func (c *DebugLogCommand) AllowInterspersedFlags() bool {
81 » return true
82 } 52 }
83 53
84 func (c *DebugLogCommand) Init(args []string) error { 54 func (c *DebugLogCommand) Init(args []string) error {
85 » tailcmd := fmt.Sprintf("tail -n %s -f /var/log/juju/all-machines.log", & c.lines) 55 » return nil
86 » args = append([]string{"0"}, args...)
87 » args = append(args, tailcmd)
88 » return c.sshCmd.Init(args)
89 } 56 }
90 57
91 // Run uses "juju ssh" to log into the state server node 58 // Run retrieves the debug log via the API.
92 // and tails the consolidated log file which captures log 59 func (c *DebugLogCommand) Run(ctx *cmd.Context) (err error) {
93 // messages from all nodes. 60 » client, err := juju.NewAPIClientFromName(c.EnvName)
94 func (c *DebugLogCommand) Run(ctx *cmd.Context) error { 61 » if err != nil {
95 » return c.sshCmd.Run(ctx) 62 » » return err
63 » }
64 » defer client.Close()
65
66 » debugLog, err := client.WatchDebugLog(c.lines, c.filter)
67 » if err != nil {
68 » » logger.Infof("WatchDebugLog not supported by the API server, " +
69 » » » "falling back to 1.16 compatibility mode using ssh")
70 » » return c.watchDebugLog1dot16(ctx)
71 » }
72 » defer debugLog.Close()
73
74 » _, err = io.Copy(os.Stdout, debugLog)
75 » return err
96 } 76 }
77
78 // watchDebugLog1dot16 runs in case of an older API server and uses ssh
79 // but with server-side grep.
80 func (c *DebugLogCommand) watchDebugLog1dot16(ctx *cmd.Context) error {
81 name, local, err := c.currentEnvironment()
82 if err != nil {
83 return err
84 }
85 // Work depending on the provider.
86 if local {
87 // Local provider tails local log file.
88 logLocation := fmt.Sprintf("%s/%s/log/all-machines.log", osenv.J ujuHomeDir(), name)
89 tailCmd := exec.Command("tail", "-n", strconv.Itoa(c.lines), "-f ", logLocation)
90 grepCmd := exec.Command("grep", c.filter)
rog 2014/02/10 16:24:50 I think you probably want grep -E here for compati
91 r, w := io.Pipe()
92
93 tailCmd.Stdout = w
94 grepCmd.Stdin = r
95 grepCmd.Stdout = os.Stdout
96 grepCmd.Stderr = os.Stderr
97
98 err := tailCmd.Start()
99 if err != nil {
100 return err
101 }
102 err = grepCmd.Start()
103 if err != nil {
104 return err
105 }
106 return grepCmd.Wait()
107 }
108 // Any other provider uses ssh with tail.
109 logLocation := "/var/log/juju/all-machines.log"
110 sshCmd := &SSHCommand{}
111 tailGrepCmd := fmt.Sprintf("tail -n %d -f %s|grep %s", c.lines, logLocat ion, c.filter)
112 args := []string{"0", tailGrepCmd}
113 err = sshCmd.Init(args)
114 if err != nil {
115 return err
116 }
117 return sshCmd.Run(ctx)
118 }
119
120 // currentEnvironment returns the name of the environment and if it is local.
121 func (c *DebugLogCommand) currentEnvironment() (string, bool, error) {
122 store, err := configstore.Default()
123 if err != nil {
124 return "", false, fmt.Errorf("cannot open environment info stora ge: %v", err)
125 }
126 environ, err := environs.NewFromName(c.EnvironName(), store)
127 if err != nil {
128 return "", false, err
129 }
130 local := environ.Config().Type() == provider.Local
131 return environ.Name(), local, nil
132 }
OLDNEW
« no previous file with comments | « [revision details] ('k') | cmd/juju/debuglog_test.go » ('j') | no next file with comments »

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b