github.com/kat-co/cmd@v0.0.0-20140616103059-5da365f9d57e/logging.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package cmd
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"os"
    10  	"time"
    11  
    12  	"github.com/juju/loggo"
    13  	"launchpad.net/gnuflag"
    14  )
    15  
    16  // WriterFactory defines the single method to create a new
    17  // logging writer for a specified output target.
    18  type WriterFactory interface {
    19  	NewWriter(target io.Writer) loggo.Writer
    20  }
    21  
    22  // Log supplies the necessary functionality for Commands that wish to set up
    23  // logging.
    24  type Log struct {
    25  	// If DefaultConfig is set, it will be used for the
    26  	// default logging configuration.
    27  	DefaultConfig string
    28  	Path    string
    29  	Verbose bool
    30  	Quiet   bool
    31  	Debug   bool
    32  	ShowLog bool
    33  	Config  string
    34  	Factory WriterFactory
    35  }
    36  
    37  // GetLogWriter returns a logging writer for the specified target.
    38  func (l *Log) GetLogWriter(target io.Writer) loggo.Writer {
    39  	if l.Factory != nil {
    40  		return l.Factory.NewWriter(target)
    41  	}
    42  	return loggo.NewSimpleWriter(target, &loggo.DefaultFormatter{})
    43  }
    44  
    45  // AddFlags adds appropriate flags to f.
    46  func (l *Log) AddFlags(f *gnuflag.FlagSet) {
    47  	f.StringVar(&l.Path, "log-file", "", "path to write log to")
    48  	f.BoolVar(&l.Verbose, "v", false, "show more verbose output")
    49  	f.BoolVar(&l.Verbose, "verbose", false, "show more verbose output")
    50  	f.BoolVar(&l.Quiet, "q", false, "show no informational output")
    51  	f.BoolVar(&l.Quiet, "quiet", false, "show no informational output")
    52  	f.BoolVar(&l.Debug, "debug", false, "equivalent to --show-log --log-config=<root>=DEBUG")
    53  	f.StringVar(&l.Config, "logging-config", l.DefaultConfig, "specify log levels for modules")
    54  	f.BoolVar(&l.ShowLog, "show-log", false, "if set, write the log file to stderr")
    55  }
    56  
    57  // Start starts logging using the given Context.
    58  func (log *Log) Start(ctx *Context) error {
    59  	if log.Verbose && log.Quiet {
    60  		return fmt.Errorf(`"verbose" and "quiet" flags clash, please use one or the other, not both`)
    61  	}
    62  	ctx.quiet = log.Quiet
    63  	ctx.verbose = log.Verbose
    64  	if log.Path != "" {
    65  		path := ctx.AbsPath(log.Path)
    66  		target, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
    67  		if err != nil {
    68  			return err
    69  		}
    70  		writer := log.GetLogWriter(target)
    71  		err = loggo.RegisterWriter("logfile", writer, loggo.TRACE)
    72  		if err != nil {
    73  			return err
    74  		}
    75  	}
    76  	level := loggo.WARNING
    77  	if log.ShowLog {
    78  		level = loggo.INFO
    79  	}
    80  	if log.Debug {
    81  		log.ShowLog = true
    82  		level = loggo.DEBUG
    83  		// override quiet or verbose if set, this way all the information goes
    84  		// to the log file.
    85  		ctx.quiet = true
    86  		ctx.verbose = false
    87  	}
    88  
    89  	if log.ShowLog {
    90  		// We replace the default writer to use ctx.Stderr rather than os.Stderr.
    91  		writer := log.GetLogWriter(ctx.Stderr)
    92  		_, err := loggo.ReplaceDefaultWriter(writer)
    93  		if err != nil {
    94  			return err
    95  		}
    96  	} else {
    97  		loggo.RemoveWriter("default")
    98  		// Create a simple writer that doesn't show filenames, or timestamps,
    99  		// and only shows warning or above.
   100  		writer := loggo.NewSimpleWriter(ctx.Stderr, &warningFormatter{})
   101  		err := loggo.RegisterWriter("warning", writer, loggo.WARNING)
   102  		if err != nil {
   103  			return err
   104  		}
   105  	}
   106  	// Set the level on the root logger.
   107  	loggo.GetLogger("").SetLogLevel(level)
   108  	// Override the logging config with specified logging config.
   109  	loggo.ConfigureLoggers(log.Config)
   110  	return nil
   111  }
   112  
   113  // warningFormatter is a simple loggo formatter that produces something like:
   114  //   WARNING The message...
   115  type warningFormatter struct{}
   116  
   117  func (*warningFormatter) Format(level loggo.Level, _, _ string, _ int, _ time.Time, message string) string {
   118  	return fmt.Sprintf("%s %s", level, message)
   119  }
   120  
   121  // NewCommandLogWriter creates a loggo writer for registration
   122  // by the callers of a command. This way the logged output can also
   123  // be displayed otherwise, e.g. on the screen.
   124  func NewCommandLogWriter(name string, out, err io.Writer) loggo.Writer {
   125  	return &commandLogWriter{name, out, err}
   126  }
   127  
   128  // commandLogWriter filters the log messages for name.
   129  type commandLogWriter struct {
   130  	name string
   131  	out  io.Writer
   132  	err  io.Writer
   133  }
   134  
   135  // Write implements loggo's Writer interface.
   136  func (s *commandLogWriter) Write(level loggo.Level, name, filename string, line int, timestamp time.Time, message string) {
   137  	if name == s.name {
   138  		if level <= loggo.INFO {
   139  			fmt.Fprintf(s.out, "%s\n", message)
   140  		} else {
   141  			fmt.Fprintf(s.err, "%s\n", message)
   142  		}
   143  	}
   144  }