github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/cmd/juju/commands/debuglog.go (about) 1 // Copyright 2013, 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package commands 5 6 import ( 7 "fmt" 8 "io" 9 "os" 10 "time" 11 12 "github.com/juju/ansiterm" 13 "github.com/juju/cmd" 14 "github.com/juju/errors" 15 "github.com/juju/gnuflag" 16 "github.com/juju/loggo" 17 "github.com/mattn/go-isatty" 18 19 "github.com/juju/juju/api" 20 "github.com/juju/juju/cmd/modelcmd" 21 ) 22 23 // defaultLineCount is the default number of lines to 24 // display, from the end of the consolidated log. 25 const defaultLineCount = 10 26 27 var usageDebugLogSummary = ` 28 Displays log messages for a model.`[1:] 29 30 var usageDebugLogDetails = ` 31 32 This command provides access to all logged Juju activity on a per-model 33 basis. By default, the logs for the currently select model are shown. 34 35 Each log line is emitted in this format: 36 37 <entity> <timestamp> <log-level> <module>:<line-no> <message> 38 39 The "entity" is the source of the message: a machine or unit. The names for 40 machines and units can be seen in the output of `[1:] + "`juju status`" + `. 41 42 The '--include' and '--exclude' options filter by entity. A unit entity is 43 identified by prefixing 'unit-' to its corresponding unit name and replacing 44 the slash with a dash. A machine entity is identified by prefixing 'machine-' 45 to its corresponding machine id. 46 47 The '--include-module' and '--exclude-module' options filter by (dotted) 48 logging module name. The module name can be truncated such that all loggers 49 with the prefix will match. 50 51 The filtering options combine as follows: 52 * All --include options are logically ORed together. 53 * All --exclude options are logically ORed together. 54 * All --include-module options are logically ORed together. 55 * All --exclude-module options are logically ORed together. 56 * The combined --include, --exclude, --include-module and --exclude-module 57 selections are logically ANDed to form the complete filter. 58 59 Examples: 60 61 Exclude all machine 0 messages; show a maximum of 100 lines; and continue to 62 append filtered messages: 63 64 juju debug-log --exclude machine-0 --lines 100 65 66 Include only unit mysql/0 messages; show a maximum of 50 lines; and then 67 exit: 68 69 juju debug-log -T --include unit-mysql-0 --lines 50 70 71 Show all messages from unit apache2/3 or machine 1 and then exit: 72 73 juju debug-log -T --replay --include unit-apache2-3 --include machine-1 74 75 Show all juju.worker.uniter logging module messages that are also unit 76 wordpress/0 messages, and then show any new log messages which match the 77 filter: 78 79 juju debug-log --replay 80 --include-module juju.worker.uniter \ 81 --include unit-wordpress-0 82 83 Show all messages from the juju.worker.uniter module, except those sent from 84 machine-3 or machine-4, and then stop: 85 86 juju debug-log --replay --no-tail 87 --include-module juju.worker.uniter \ 88 --exclude machine-3 \ 89 --exclude machine-4 90 91 To see all WARNING and ERROR messages and then continue showing any 92 new WARNING and ERROR messages as they are logged: 93 94 juju debug-log --replay --level WARNING 95 96 See also: 97 status 98 ssh` 99 100 func (c *debugLogCommand) Info() *cmd.Info { 101 return &cmd.Info{ 102 Name: "debug-log", 103 Purpose: usageDebugLogSummary, 104 Doc: usageDebugLogDetails, 105 } 106 } 107 108 func newDebugLogCommand() cmd.Command { 109 return newDebugLogCommandTZ(time.Local) 110 } 111 112 func newDebugLogCommandTZ(tz *time.Location) cmd.Command { 113 return modelcmd.Wrap(&debugLogCommand{tz: tz}) 114 } 115 116 type debugLogCommand struct { 117 modelcmd.ModelCommandBase 118 119 level string 120 params api.DebugLogParams 121 122 utc bool 123 location bool 124 date bool 125 ms bool 126 127 tail bool 128 notail bool 129 color bool 130 131 format string 132 tz *time.Location 133 } 134 135 func (c *debugLogCommand) SetFlags(f *gnuflag.FlagSet) { 136 c.ModelCommandBase.SetFlags(f) 137 f.Var(cmd.NewAppendStringsValue(&c.params.IncludeEntity), "i", "Only show log messages for these entities") 138 f.Var(cmd.NewAppendStringsValue(&c.params.IncludeEntity), "include", "Only show log messages for these entities") 139 f.Var(cmd.NewAppendStringsValue(&c.params.ExcludeEntity), "x", "Do not show log messages for these entities") 140 f.Var(cmd.NewAppendStringsValue(&c.params.ExcludeEntity), "exclude", "Do not show log messages for these entities") 141 f.Var(cmd.NewAppendStringsValue(&c.params.IncludeModule), "include-module", "Only show log messages for these logging modules") 142 f.Var(cmd.NewAppendStringsValue(&c.params.ExcludeModule), "exclude-module", "Do not show log messages for these logging modules") 143 144 f.StringVar(&c.level, "l", "", "Log level to show, one of [TRACE, DEBUG, INFO, WARNING, ERROR]") 145 f.StringVar(&c.level, "level", "", "") 146 147 f.UintVar(&c.params.Backlog, "n", defaultLineCount, "Show this many of the most recent (possibly filtered) lines, and continue to append") 148 f.UintVar(&c.params.Backlog, "lines", defaultLineCount, "") 149 f.UintVar(&c.params.Limit, "limit", 0, "Exit once this many of the most recent (possibly filtered) lines are shown") 150 f.BoolVar(&c.params.Replay, "replay", false, "Show the entire (possibly filtered) log and continue to append") 151 152 f.BoolVar(&c.notail, "no-tail", false, "Stop after returning existing log messages") 153 f.BoolVar(&c.tail, "tail", false, "Wait for new logs") 154 f.BoolVar(&c.color, "color", false, "Force use of ANSI color codes") 155 156 f.BoolVar(&c.utc, "utc", false, "Show times in UTC") 157 f.BoolVar(&c.location, "location", false, "Show filename and line numbers") 158 f.BoolVar(&c.date, "date", false, "Show dates as well as times") 159 f.BoolVar(&c.ms, "ms", false, "Show times to millisecond precision") 160 } 161 162 func (c *debugLogCommand) Init(args []string) error { 163 if c.level != "" { 164 level, ok := loggo.ParseLevel(c.level) 165 if !ok || level < loggo.TRACE || level > loggo.ERROR { 166 return errors.Errorf("level value %q is not one of %q, %q, %q, %q, %q", 167 c.level, loggo.TRACE, loggo.DEBUG, loggo.INFO, loggo.WARNING, loggo.ERROR) 168 } 169 c.params.Level = level 170 } 171 if c.tail && c.notail { 172 return errors.NotValidf("setting --tail and --no-tail") 173 } 174 if c.utc { 175 c.tz = time.UTC 176 } 177 if c.date { 178 c.format = "2006-01-02 15:04:05" 179 } else { 180 c.format = "15:04:05" 181 } 182 if c.ms { 183 c.format = c.format + ".000" 184 } 185 return cmd.CheckEmpty(args) 186 } 187 188 type DebugLogAPI interface { 189 WatchDebugLog(params api.DebugLogParams) (<-chan api.LogMessage, error) 190 Close() error 191 } 192 193 var getDebugLogAPI = func(c *debugLogCommand) (DebugLogAPI, error) { 194 return c.NewAPIClient() 195 } 196 197 func isTerminal(out io.Writer) bool { 198 f, ok := out.(*os.File) 199 if !ok { 200 return false 201 } 202 return isatty.IsTerminal(f.Fd()) 203 } 204 205 // Run retrieves the debug log via the API. 206 func (c *debugLogCommand) Run(ctx *cmd.Context) (err error) { 207 if c.tail { 208 c.params.NoTail = false 209 } else if c.notail { 210 c.params.NoTail = true 211 } else { 212 // Set the default tail option to true if the caller is 213 // using a terminal. 214 c.params.NoTail = !isTerminal(ctx.Stdout) 215 } 216 217 client, err := getDebugLogAPI(c) 218 if err != nil { 219 return err 220 } 221 defer client.Close() 222 messages, err := client.WatchDebugLog(c.params) 223 if err != nil { 224 return err 225 } 226 writer := ansiterm.NewWriter(ctx.Stdout) 227 if c.color { 228 writer.SetColorCapable(true) 229 } 230 for { 231 msg, ok := <-messages 232 if !ok { 233 break 234 } 235 c.writeLogRecord(writer, msg) 236 } 237 238 return nil 239 } 240 241 var SeverityColor = map[string]*ansiterm.Context{ 242 "TRACE": ansiterm.Foreground(ansiterm.Default), 243 "DEBUG": ansiterm.Foreground(ansiterm.Green), 244 "INFO": ansiterm.Foreground(ansiterm.BrightBlue), 245 "WARNING": ansiterm.Foreground(ansiterm.Yellow), 246 "ERROR": ansiterm.Foreground(ansiterm.BrightRed), 247 "CRITICAL": &ansiterm.Context{ 248 Foreground: ansiterm.White, 249 Background: ansiterm.Red, 250 }, 251 } 252 253 func (c *debugLogCommand) writeLogRecord(w *ansiterm.Writer, r api.LogMessage) { 254 ts := r.Timestamp.In(c.tz).Format(c.format) 255 fmt.Fprintf(w, "%s: %s ", r.Entity, ts) 256 SeverityColor[r.Severity].Fprintf(w, r.Severity) 257 fmt.Fprintf(w, " %s ", r.Module) 258 if c.location { 259 loggo.LocationColor.Fprintf(w, "%s ", r.Location) 260 } 261 fmt.Fprintln(w, r.Message) 262 }