github.com/rogpeppe/juju@v0.0.0-20140613142852-6337964b789e/cmd/juju/debuglog.go (about)

     1  // Copyright 2013, 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package main
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  
    10  	"github.com/juju/cmd"
    11  	"github.com/juju/errors"
    12  	"github.com/juju/loggo"
    13  	"launchpad.net/gnuflag"
    14  
    15  	"github.com/juju/juju/cmd/envcmd"
    16  	"github.com/juju/juju/juju"
    17  	"github.com/juju/juju/state/api"
    18  )
    19  
    20  type DebugLogCommand struct {
    21  	envcmd.EnvCommandBase
    22  
    23  	level  string
    24  	params api.DebugLogParams
    25  }
    26  
    27  var DefaultLogLocation = "/var/log/juju/all-machines.log"
    28  
    29  // defaultLineCount is the default number of lines to
    30  // display, from the end of the consolidated log.
    31  const defaultLineCount = 10
    32  
    33  const debuglogDoc = `
    34  Stream the consolidated debug log file. This file contains the log messages
    35  from all nodes in the environment.
    36  `
    37  
    38  func (c *DebugLogCommand) Info() *cmd.Info {
    39  	return &cmd.Info{
    40  		Name:    "debug-log",
    41  		Purpose: "display the consolidated log file",
    42  		Doc:     debuglogDoc,
    43  	}
    44  }
    45  
    46  func (c *DebugLogCommand) SetFlags(f *gnuflag.FlagSet) {
    47  	f.Var(cmd.NewAppendStringsValue(&c.params.IncludeEntity), "i", "only show log messages for these entities")
    48  	f.Var(cmd.NewAppendStringsValue(&c.params.IncludeEntity), "include", "only show log messages for these entities")
    49  	f.Var(cmd.NewAppendStringsValue(&c.params.ExcludeEntity), "x", "do not show log messages for these entities")
    50  	f.Var(cmd.NewAppendStringsValue(&c.params.ExcludeEntity), "exclude", "do not show log messages for these entities")
    51  	f.Var(cmd.NewAppendStringsValue(&c.params.IncludeModule), "include-module", "only show log messages for these logging modules")
    52  	f.Var(cmd.NewAppendStringsValue(&c.params.ExcludeModule), "exclude-module", "do not show log messages for these logging modules")
    53  
    54  	f.StringVar(&c.level, "l", "", "log level to show, one of [TRACE, DEBUG, INFO, WARNING, ERROR]")
    55  	f.StringVar(&c.level, "level", "", "")
    56  
    57  	f.UintVar(&c.params.Backlog, "n", defaultLineCount, "go back this many lines from the end before starting to filter")
    58  	f.UintVar(&c.params.Backlog, "lines", defaultLineCount, "")
    59  	f.UintVar(&c.params.Limit, "limit", 0, "show at most this many lines")
    60  	f.BoolVar(&c.params.Replay, "replay", false, "start filtering from the start")
    61  }
    62  
    63  func (c *DebugLogCommand) Init(args []string) error {
    64  	if c.level != "" {
    65  		level, ok := loggo.ParseLevel(c.level)
    66  		if !ok || level < loggo.TRACE || level > loggo.ERROR {
    67  			return fmt.Errorf("level value %q is not one of %q, %q, %q, %q, %q",
    68  				c.level, loggo.TRACE, loggo.DEBUG, loggo.INFO, loggo.WARNING, loggo.ERROR)
    69  		}
    70  		c.params.Level = level
    71  	}
    72  	return cmd.CheckEmpty(args)
    73  }
    74  
    75  type DebugLogAPI interface {
    76  	WatchDebugLog(params api.DebugLogParams) (io.ReadCloser, error)
    77  	Close() error
    78  }
    79  
    80  var getDebugLogAPI = func(envName string) (DebugLogAPI, error) {
    81  	return juju.NewAPIClientFromName(envName)
    82  }
    83  
    84  // Run retrieves the debug log via the API.
    85  func (c *DebugLogCommand) Run(ctx *cmd.Context) (err error) {
    86  	client, err := getDebugLogAPI(c.EnvName)
    87  	if err != nil {
    88  		return err
    89  	}
    90  	defer client.Close()
    91  	debugLog, err := client.WatchDebugLog(c.params)
    92  	if err != nil {
    93  		if errors.IsNotSupported(err) {
    94  			return c.watchDebugLog1dot18(ctx)
    95  		}
    96  		return err
    97  	}
    98  	defer debugLog.Close()
    99  	_, err = io.Copy(ctx.Stdout, debugLog)
   100  	return err
   101  }
   102  
   103  var runSSHCommand = func(sshCmd *SSHCommand, ctx *cmd.Context) error {
   104  	return sshCmd.Run(ctx)
   105  }
   106  
   107  // watchDebugLog1dot18 runs in case of an older API server and uses ssh
   108  // but with server-side grep.
   109  func (c *DebugLogCommand) watchDebugLog1dot18(ctx *cmd.Context) error {
   110  	ctx.Infof("Server does not support new stream log, falling back to tail")
   111  	ctx.Verbosef("filters are not supported with tail")
   112  	sshCmd := &SSHCommand{}
   113  	tailCmd := fmt.Sprintf("tail -n -%d -f %s", c.params.Backlog, DefaultLogLocation)
   114  	// If the api doesn't support WatchDebugLog, then it won't be running in
   115  	// HA either, so machine 0 is where it is all at.
   116  	args := []string{"0", tailCmd}
   117  	err := sshCmd.Init(args)
   118  	if err != nil {
   119  		return err
   120  	}
   121  	sshCmd.EnvName = c.EnvName
   122  	return runSSHCommand(sshCmd, ctx)
   123  }