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  }