lib: moving vlog from release.go.v23 to release.go.x.lib
MultiPart: 5/9
Change-Id: I1e57972b68c1c76a6bae756d198b20201b693ec6
diff --git a/vlog/GO.PACKAGE b/vlog/GO.PACKAGE
new file mode 100644
index 0000000..0d3f6ae
--- /dev/null
+++ b/vlog/GO.PACKAGE
@@ -0,0 +1,7 @@
+{
+ "dependencies": {
+ "outgoing": [
+ {"allow": "github.com/cosmosnicolaou/llog"}
+ ]
+ }
+}
diff --git a/vlog/doc.go b/vlog/doc.go
new file mode 100644
index 0000000..cf0fb3d
--- /dev/null
+++ b/vlog/doc.go
@@ -0,0 +1,69 @@
+// Package vlog defines and implements the veyron2 logging interfaces and
+// command line parsing. vlog is modeled on google3 and glog;
+// the differences from glog are:
+//
+// - interfaces are used to allow for multiple implementations and instances.
+// In particular, application and runtime logging can be separated.
+// We also expect to stream log messages to external log collectors rather
+// to local storage.
+// - the Warn family of methods are not provided; their main use within
+// google3 is to avoid the flush that's implicit in the Error routines
+// rather than any semantic difference between warnings and errors.
+// - Info logging and Event logging is separated with the former expected
+// to be somewhat spammy and the latter to be used sparingly. An error
+// message that occurs during execution of a test should be treated as
+// a failure of that test regardless of whether the test code itself
+// passes. TODO(cnicolaou,toddw): implement this.
+// - Event logging includes methods for unconditionally (i.e. regardless
+// of any command line options) logging the current goroutine's stack
+// or the stacks of all goroutines.
+// - The use of interfaces and encapsulated state means that a single
+// function (V) can no longer be used for 'if guarded' and 'chained' logging.
+// That is:
+// if vlog.V(1) { ... } and vlog.V(1).Infof( ... )
+// become
+// if logger.V(1) { ... } and logger.VI(1).Infof( ... )
+//
+// vlog also creates a global instance of the Logger (vlog.Log) and
+// provides command line flags (see flags.go). Parsing of these flags is
+// performed by calling one of ConfigureLibraryLoggerFromFlags or
+// ConfigureLoggerFromFlags .
+//
+// The supported flags are:
+//
+// -logtostderr=false
+// Logs are written to standard error instead of to files.
+// -alsologtostderr=false
+// Logs are written to standard error as well as to files.
+// -stderrthreshold=ERROR
+// Log events at or above this severity are logged to standard
+// error as well as to files.
+// -log_dir=""
+// Log files will be written to this directory instead of the
+// default temporary directory.
+//
+// Other flags provide aids to debugging.
+//
+// -log_backtrace_at=""
+// When set to a file and line number holding a logging statement,
+// such as
+// -log_backtrace_at=gopherflakes.go:234
+// a stack trace will be written to the Info log whenever execution
+// hits that statement. (Unlike with -vmodule, the ".go" must be
+// present.)
+// -v=0
+// Enable V-leveled logging at the specified level.
+// -vmodule=""
+// The syntax of the argument is a comma-separated list of pattern=N,
+// where pattern is a literal file name (minus the ".go" suffix) or
+// "glob" pattern and N is a V level. For instance,
+// -vmodule=gopher*=3
+// sets the V level to 3 in all Go files whose names begin "gopher".
+// -max_stack_buf_size=<size in bytes>
+// Set the max size (bytes) of the byte buffer to use for stack
+// traces. The default max is 4M; use powers of 2 since the
+// stack size will be grown exponentially until it exceeds the max.
+// A min of 128K is enforced and any attempts to reduce this will
+// be silently ignored.
+//
+package vlog
diff --git a/vlog/flags.go b/vlog/flags.go
new file mode 100644
index 0000000..8ca854e
--- /dev/null
+++ b/vlog/flags.go
@@ -0,0 +1,109 @@
+package vlog
+
+import (
+ "flag"
+ "fmt"
+
+ "github.com/cosmosnicolaou/llog"
+)
+
+var (
+ toStderr bool
+ alsoToStderr bool
+ logDir string
+ verbosity Level
+ stderrThreshold StderrThreshold = StderrThreshold(llog.ErrorLog)
+ vmodule ModuleSpec
+ traceLocation TraceLocation
+ maxStackBufSize int
+)
+
+var flagDefs = []struct {
+ name string
+ variable interface{}
+ defaultValue interface{}
+ description string
+}{
+ {"log_dir", &logDir, "", "if non-empty, write log files to this directory"},
+ {"logtostderr", &toStderr, false, "log to standard error instead of files"},
+ {"alsologtostderr", &alsoToStderr, true, "log to standard error as well as files"},
+ {"max_stack_buf_size", &maxStackBufSize, 4192 * 1024, "max size in bytes of the buffer to use for logging stack traces"},
+ {"v", &verbosity, nil, "log level for V logs"},
+ {"stderrthreshold", &stderrThreshold, nil, "logs at or above this threshold go to stderr"},
+ {"vmodule", &vmodule, nil, "comma-separated list of pattern=N settings for file-filtered logging"},
+ {"log_backtrace_at", &traceLocation, nil, "when logging hits line file:N, emit a stack trace"},
+}
+
+func init() {
+ istest := false
+ if flag.CommandLine.Lookup("test.v") != nil {
+ istest = true
+ }
+ for _, flagDef := range flagDefs {
+ if istest && flagDef.name == "v" {
+ continue
+ }
+ switch v := flagDef.variable.(type) {
+ case *string:
+ flag.StringVar(v, flagDef.name,
+ flagDef.defaultValue.(string), flagDef.description)
+ case *bool:
+ flag.BoolVar(v, flagDef.name,
+ flagDef.defaultValue.(bool), flagDef.description)
+ case *int:
+ flag.IntVar(v, flagDef.name,
+ flagDef.defaultValue.(int), flagDef.description)
+ case flag.Value:
+ if flagDef.defaultValue != nil {
+ panic(fmt.Sprintf("default value not supported for flag %s", flagDef.name))
+ }
+ flag.Var(v, flagDef.name, flagDef.description)
+ default:
+ panic("invalid flag type")
+ }
+ }
+}
+
+// ConfigureLibraryLoggerFromFlags will configure the internal global logger
+// using command line flags. It assumes that flag.Parse() has already been
+// called to initialize the flag variables.
+func ConfigureLibraryLoggerFromFlags() error {
+ return ConfigureLoggerFromFlags(Log)
+}
+
+// ConfigureLoggerFromLogs will configure the supplied logger using
+// command line flags.
+func ConfigureLoggerFromFlags(l Logger) error {
+ return l.ConfigureLogger(
+ LogToStderr(toStderr),
+ AlsoLogToStderr(alsoToStderr),
+ LogDir(logDir),
+ Level(verbosity),
+ StderrThreshold(stderrThreshold),
+ ModuleSpec(vmodule),
+ TraceLocation(traceLocation),
+ MaxStackBufSize(maxStackBufSize),
+ )
+}
+
+func (l *logger) String() string {
+ return l.log.String()
+}
+
+// ExplicitlySetFlags returns a map of the logging command line flags and their
+// values formatted as strings. Only the flags that were explicitly set are
+// returned. This is intended for use when an application needs to know what
+// value the flags were set to, for example when creating subprocesses.
+func (l *logger) ExplicitlySetFlags() map[string]string {
+ logFlagNames := make(map[string]bool)
+ for _, flagDef := range flagDefs {
+ logFlagNames[flagDef.name] = true
+ }
+ args := make(map[string]string)
+ flag.Visit(func(f *flag.Flag) {
+ if logFlagNames[f.Name] {
+ args[f.Name] = f.Value.String()
+ }
+ })
+ return args
+}
diff --git a/vlog/flags_test.go b/vlog/flags_test.go
new file mode 100644
index 0000000..5de15f6
--- /dev/null
+++ b/vlog/flags_test.go
@@ -0,0 +1,57 @@
+package vlog_test
+
+import (
+ "flag"
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
+ "testing"
+
+ "v.io/core/veyron/lib/modules"
+
+ "v.io/x/lib/vlog"
+)
+
+//go:generate v23 test generate
+
+func child(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+ tmp := filepath.Join(os.TempDir(), "foo")
+ flag.Set("log_dir", tmp)
+ flag.Set("vmodule", "foo=2")
+ flags := vlog.Log.ExplicitlySetFlags()
+ if v, ok := flags["log_dir"]; !ok || v != tmp {
+ return fmt.Errorf("log_dir was supposed to be %v", tmp)
+ }
+ if v, ok := flags["vmodule"]; !ok || v != "foo=2" {
+ return fmt.Errorf("vmodule was supposed to be foo=2")
+ }
+ if f := flag.Lookup("max_stack_buf_size"); f == nil {
+ return fmt.Errorf("max_stack_buf_size is not a flag")
+ }
+ maxStackBufSizeSet := false
+ flag.Visit(func(f *flag.Flag) {
+ if f.Name == "max_stack_buf_size" {
+ maxStackBufSizeSet = true
+ }
+ })
+ if v, ok := flags["max_stack_buf_size"]; ok && !maxStackBufSizeSet {
+ return fmt.Errorf("max_stack_buf_size unexpectedly set to %v", v)
+ }
+ return nil
+}
+
+func TestFlags(t *testing.T) {
+ sh, err := modules.NewShell(nil, nil)
+ if err != nil {
+ t.Fatalf("unexpected error: %s", err)
+ }
+ defer sh.Cleanup(nil, nil)
+ h, err := sh.Start("child", nil)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if err = h.Shutdown(nil, os.Stderr); err != nil {
+ t.Errorf("unexpected error: %v", err)
+ }
+}
diff --git a/vlog/funcs.go b/vlog/funcs.go
new file mode 100644
index 0000000..952fb6c
--- /dev/null
+++ b/vlog/funcs.go
@@ -0,0 +1,97 @@
+package vlog
+
+import (
+ "github.com/cosmosnicolaou/llog"
+)
+
+// Info logs to the INFO log.
+// Arguments are handled in the manner of fmt.Print; a newline is appended if missing.
+func Info(args ...interface{}) {
+ Log.log.Print(llog.InfoLog, args...)
+ Log.maybeFlush()
+}
+
+// Infof logs to the INFO log.
+// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing.
+func Infof(format string, args ...interface{}) {
+ Log.log.Printf(llog.InfoLog, format, args...)
+ Log.maybeFlush()
+}
+
+// InfoStack logs the current goroutine's stack if the all parameter
+// is false, or the stacks of all goroutines if it's true.
+func InfoStack(all bool) {
+ infoStack(Log, all)
+}
+
+// V returns true if the configured logging level is greater than or equal to its parameter
+func V(level Level) bool {
+ return Log.log.V(llog.Level(level))
+}
+
+// VI is like V, except that it returns an instance of the Info
+// interface that will either log (if level >= the configured level)
+// or discard its parameters. This allows for logger.VI(2).Info
+// style usage.
+func VI(level Level) InfoLog {
+ if Log.log.V(llog.Level(level)) {
+ return Log
+ }
+ return &discardInfo{}
+}
+
+// Flush flushes all pending log I/O.
+func FlushLog() {
+ Log.FlushLog()
+}
+
+// Error logs to the ERROR and INFO logs.
+// Arguments are handled in the manner of fmt.Print; a newline is appended if missing.
+func Error(args ...interface{}) {
+ Log.log.Print(llog.ErrorLog, args...)
+ Log.maybeFlush()
+}
+
+// Errorf logs to the ERROR and INFO logs.
+// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing.
+func Errorf(format string, args ...interface{}) {
+ Log.log.Printf(llog.ErrorLog, format, args...)
+ Log.maybeFlush()
+}
+
+// Fatal logs to the FATAL, ERROR and INFO logs,
+// including a stack trace of all running goroutines, then calls os.Exit(255).
+// Arguments are handled in the manner of fmt.Print; a newline is appended if missing.
+func Fatal(args ...interface{}) {
+ Log.log.Print(llog.FatalLog, args...)
+}
+
+// Fatalf logs to the FATAL, ERROR and INFO logs,
+// including a stack trace of all running goroutines, then calls os.Exit(255).
+// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing.
+func Fatalf(format string, args ...interface{}) {
+ Log.log.Printf(llog.FatalLog, format, args...)
+}
+
+// ConfigureLogging configures all future logging. Some options
+// may not be usable if ConfigureLogging is called from an init function,
+// in which case an error will be returned.
+func ConfigureLogger(opts ...LoggingOpts) error {
+ return Log.ConfigureLogger(opts...)
+}
+
+// Stats returns stats on how many lines/bytes haven been written to
+// this set of logs.
+func Stats() LevelStats {
+ return Log.Stats()
+}
+
+// Panic is equivalent to Error() followed by a call to panic().
+func Panic(args ...interface{}) {
+ Log.Panic(args...)
+}
+
+// Panicf is equivalent to Errorf() followed by a call to panic().
+func Panicf(format string, args ...interface{}) {
+ Log.Panicf(format, args...)
+}
diff --git a/vlog/log.go b/vlog/log.go
new file mode 100644
index 0000000..5d5f93d
--- /dev/null
+++ b/vlog/log.go
@@ -0,0 +1,193 @@
+package vlog
+
+import (
+ "fmt"
+ "os"
+ "runtime"
+
+ "github.com/cosmosnicolaou/llog"
+)
+
+const (
+ initialMaxStackBufSize = 128 * 1024
+)
+
+type logger struct {
+ log *llog.Log
+ autoFlush bool
+ maxStackBufSize int
+ logDir string
+}
+
+func (l *logger) maybeFlush() {
+ if l.autoFlush {
+ l.log.Flush()
+ }
+}
+
+var (
+ Log *logger
+)
+
+const stackSkip = 1
+
+func init() {
+ Log = &logger{log: llog.NewLogger("veyron", stackSkip)}
+}
+
+// NewLogger creates a new instance of the logging interface.
+func NewLogger(name string, opts ...LoggingOpts) (Logger, error) {
+ // Create an instance of the runtime with just logging enabled.
+ nl := &logger{log: llog.NewLogger(name, stackSkip)}
+ if err := nl.ConfigureLogger(opts...); err != nil {
+ return nil, err
+ }
+ return nl, nil
+}
+
+// ConfigureLogging configures all future logging. Some options
+// may not be usable if ConfigureLogging
+// is called from an init function, in which case an error will
+// be returned.
+func (l *logger) ConfigureLogger(opts ...LoggingOpts) error {
+ for _, o := range opts {
+ switch v := o.(type) {
+ case AlsoLogToStderr:
+ l.log.SetAlsoLogToStderr(bool(v))
+ case Level:
+ l.log.SetV(llog.Level(v))
+ case LogDir:
+ l.logDir = string(v)
+ l.log.SetLogDir(l.logDir)
+ case LogToStderr:
+ l.log.SetLogToStderr(bool(v))
+ case MaxStackBufSize:
+ sz := int(v)
+ if sz > initialMaxStackBufSize {
+ l.maxStackBufSize = sz
+ l.log.SetMaxStackBufSize(sz)
+ }
+ case ModuleSpec:
+ l.log.SetVModule(v.ModuleSpec)
+ case TraceLocation:
+ l.log.SetTraceLocation(v.TraceLocation)
+ case StderrThreshold:
+ l.log.SetStderrThreshold(llog.Severity(v))
+ case AutoFlush:
+ l.autoFlush = bool(v)
+ }
+ }
+ return nil
+}
+
+// LogDir returns the directory where the log files are written.
+func (l *logger) LogDir() string {
+ if len(l.logDir) != 0 {
+ return l.logDir
+ }
+ return os.TempDir()
+}
+
+// Stats returns stats on how many lines/bytes haven been written to
+// this set of logs.
+func (l *logger) Stats() LevelStats {
+ return LevelStats(l.log.Stats())
+}
+
+// Info logs to the INFO log.
+// Arguments are handled in the manner of fmt.Print; a newline is appended if missing.
+func (l *logger) Info(args ...interface{}) {
+ l.log.Print(llog.InfoLog, args...)
+ l.maybeFlush()
+}
+
+// Infof logs to the INFO log.
+// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing.
+func (l *logger) Infof(format string, args ...interface{}) {
+ l.log.Printf(llog.InfoLog, format, args...)
+ l.maybeFlush()
+}
+
+func infoStack(l *logger, all bool) {
+ n := initialMaxStackBufSize
+ var trace []byte
+ for n <= l.maxStackBufSize {
+ trace = make([]byte, n)
+ nbytes := runtime.Stack(trace, all)
+ if nbytes < len(trace) {
+ l.log.Printf(llog.InfoLog, "%s", trace[:nbytes])
+ return
+ }
+ n *= 2
+ }
+ l.log.Printf(llog.InfoLog, "%s", trace)
+ l.maybeFlush()
+}
+
+// InfoStack logs the current goroutine's stack if the all parameter
+// is false, or the stacks of all goroutines if it's true.
+func (l *logger) InfoStack(all bool) {
+ infoStack(l, all)
+}
+
+func (l *logger) V(v Level) bool {
+ return l.log.V(llog.Level(v))
+}
+
+type discardInfo struct{}
+
+func (_ *discardInfo) Info(args ...interface{}) {}
+func (_ *discardInfo) Infof(format string, args ...interface{}) {}
+func (_ *discardInfo) InfoStack(all bool) {}
+
+func (l *logger) VI(v Level) InfoLog {
+ if l.log.V(llog.Level(v)) {
+ return l
+ }
+ return &discardInfo{}
+}
+
+// Flush flushes all pending log I/O.
+func (l *logger) FlushLog() {
+ l.log.Flush()
+}
+
+// Error logs to the ERROR and INFO logs.
+// Arguments are handled in the manner of fmt.Print; a newline is appended if missing.
+func (l *logger) Error(args ...interface{}) {
+ l.log.Print(llog.ErrorLog, args...)
+ l.maybeFlush()
+}
+
+// Errorf logs to the ERROR and INFO logs.
+// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing.
+func (l *logger) Errorf(format string, args ...interface{}) {
+ l.log.Printf(llog.ErrorLog, format, args...)
+ l.maybeFlush()
+}
+
+// Fatal logs to the FATAL, ERROR and INFO logs,
+// including a stack trace of all running goroutines, then calls os.Exit(255).
+// Arguments are handled in the manner of fmt.Print; a newline is appended if missing.
+func (l *logger) Fatal(args ...interface{}) {
+ l.log.Print(llog.FatalLog, args...)
+}
+
+// Fatalf logs to the FATAL, ERROR and INFO logs,
+// including a stack trace of all running goroutines, then calls os.Exit(255).
+// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing.
+func (l *logger) Fatalf(format string, args ...interface{}) {
+ l.log.Printf(llog.FatalLog, format, args...)
+}
+
+// Panic is equivalent to Error() followed by a call to panic().
+func (l *logger) Panic(args ...interface{}) {
+ l.Error(args...)
+ panic(fmt.Sprint(args...))
+}
+
+// Panicf is equivalent to Errorf() followed by a call to panic().
+func (l *logger) Panicf(format string, args ...interface{}) {
+ l.Errorf(format, args...)
+ panic(fmt.Sprintf(format, args...))
+}
diff --git a/vlog/log_test.go b/vlog/log_test.go
new file mode 100644
index 0000000..562c2e4
--- /dev/null
+++ b/vlog/log_test.go
@@ -0,0 +1,178 @@
+package vlog_test
+
+import (
+ "bufio"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "regexp"
+ "testing"
+
+ "v.io/x/lib/vlog"
+)
+
+func ExampleConfigure() {
+ vlog.ConfigureLogger()
+}
+
+func ExampleInfo() {
+ vlog.Info("hello")
+}
+
+func ExampleError() {
+ vlog.Errorf("%s", "error")
+ if vlog.V(2) {
+ vlog.Info("some spammy message")
+ }
+ vlog.VI(2).Infof("another spammy message")
+}
+
+func readLogFiles(dir string) ([]string, error) {
+ files, err := ioutil.ReadDir(dir)
+ if err != nil {
+ return nil, err
+ }
+ var contents []string
+ for _, fi := range files {
+ // Skip symlinks to avoid double-counting log lines.
+ if !fi.Mode().IsRegular() {
+ continue
+ }
+ file, err := os.Open(filepath.Join(dir, fi.Name()))
+ if err != nil {
+ return nil, err
+ }
+ scanner := bufio.NewScanner(file)
+ for scanner.Scan() {
+ if line := scanner.Text(); len(line) > 0 && line[0] == 'I' {
+ contents = append(contents, line)
+ }
+ }
+ }
+ return contents, nil
+}
+
+func TestHeaders(t *testing.T) {
+ dir, err := ioutil.TempDir("", "logtest")
+ defer os.RemoveAll(dir)
+ if err != nil {
+ t.Fatalf("unexpected error: %s", err)
+ }
+ logger, err := vlog.NewLogger("testHeader", vlog.LogDir(dir), vlog.Level(2))
+ if err != nil {
+ t.Fatalf("unexpected error: %s", err)
+ }
+ logger.Infof("abc\n")
+ logger.Infof("wombats\n")
+ logger.VI(1).Infof("wombats again\n")
+ logger.FlushLog()
+ contents, err := readLogFiles(dir)
+ if err != nil {
+ t.Fatalf("unexpected error: %s", err)
+ }
+ fileRE := regexp.MustCompile(`\S+ \S+ \S+ (.*):.*`)
+ for _, line := range contents {
+ name := fileRE.FindStringSubmatch(line)
+ if len(name) < 2 {
+ t.Errorf("failed to find file in %s", line)
+ continue
+ }
+ if name[1] != "log_test.go" {
+ t.Errorf("unexpected file name: %s", name[1])
+ continue
+ }
+ }
+ if want, got := 3, len(contents); want != got {
+ t.Errorf("Expected %d info lines, got %d instead", want, got)
+ }
+}
+
+func myLoggedFunc() {
+ f := vlog.LogCall("entry")
+ f("exit")
+}
+
+func TestLogCall(t *testing.T) {
+ dir, err := ioutil.TempDir("", "logtest")
+ defer os.RemoveAll(dir)
+ if err != nil {
+ t.Fatalf("unexpected error: %s", err)
+ }
+ logger, err := vlog.NewLogger("testHeader", vlog.LogDir(dir), vlog.Level(2))
+ if err != nil {
+ t.Fatalf("unexpected error: %s", err)
+ }
+ saveLog := vlog.Log
+ defer vlog.SetLog(saveLog)
+ vlog.SetLog(logger)
+
+ myLoggedFunc()
+ vlog.FlushLog()
+ contents, err := readLogFiles(dir)
+ if err != nil {
+ t.Fatalf("unexpected error: %s", err)
+ }
+ logCallLineRE := regexp.MustCompile(`\S+ \S+ \S+ ([^:]*):.*(call|return)\[(\S*)`)
+ for _, line := range contents {
+ match := logCallLineRE.FindStringSubmatch(line)
+ if len(match) != 4 {
+ t.Errorf("failed to match %s", line)
+ continue
+ }
+ fileName, callType, funcName := match[1], match[2], match[3]
+ if fileName != "log_test.go" {
+ t.Errorf("unexpected file name: %s", fileName)
+ continue
+ }
+ if callType != "call" && callType != "return" {
+ t.Errorf("unexpected call type: %s", callType)
+ }
+ if funcName != "vlog_test.myLoggedFunc" {
+ t.Errorf("unexpected func name: %s", funcName)
+ }
+ }
+ if want, got := 2, len(contents); want != got {
+ t.Errorf("Expected %d info lines, got %d instead", want, got)
+ }
+}
+
+func TestVModule(t *testing.T) {
+ dir, err := ioutil.TempDir("", "logtest")
+ defer os.RemoveAll(dir)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ logger, err := vlog.NewLogger("testVmodule", vlog.LogDir(dir))
+ if logger.V(2) || logger.V(3) {
+ t.Errorf("Logging should not be enabled at levels 2 & 3")
+ }
+ spec := vlog.ModuleSpec{}
+ if err := spec.Set("*log_test=2"); err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if err := logger.ConfigureLogger(spec); err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if !logger.V(2) {
+ t.Errorf("logger.V(2) should be true")
+ }
+ if logger.V(3) {
+ t.Errorf("logger.V(3) should be false")
+ }
+ if vlog.V(2) || vlog.V(3) {
+ t.Errorf("Logging should not be enabled at levels 2 & 3")
+ }
+ vlog.Log.ConfigureLogger(spec)
+ if !vlog.V(2) {
+ t.Errorf("vlog.V(2) should be true")
+ }
+ if vlog.V(3) {
+ t.Errorf("vlog.V(3) should be false")
+ }
+ if vlog.VI(2) != vlog.Log {
+ t.Errorf("vlog.V(2) should be vlog.Log")
+ }
+ if vlog.VI(3) == vlog.Log {
+ t.Errorf("vlog.V(3) should not be vlog.Log")
+ }
+}
diff --git a/vlog/logcall.go b/vlog/logcall.go
new file mode 100644
index 0000000..cfcffd1
--- /dev/null
+++ b/vlog/logcall.go
@@ -0,0 +1,142 @@
+package vlog
+
+import (
+ "fmt"
+ "path"
+ "reflect"
+ "runtime"
+ "sync/atomic"
+
+ "github.com/cosmosnicolaou/llog"
+)
+
+// logCallLogLevel is the log level beyond which calls are logged.
+const logCallLogLevel = 1
+
+func callerFuncName() string {
+ var funcName string
+ pc, _, _, ok := runtime.Caller(stackSkip + 1)
+ if ok {
+ function := runtime.FuncForPC(pc)
+ if function != nil {
+ funcName = path.Base(function.Name())
+ }
+ }
+ return funcName
+}
+
+// LogCall logs that its caller has been called given the arguments
+// passed to it. It returns a function that is supposed to be called
+// when the caller returns, logging the caller’s return along with the
+// arguments it is provided with.
+// File name and line number of the call site and a randomly generated
+// invocation identifier is logged automatically. The path through which
+// the caller function returns will be logged automatically too.
+//
+// The canonical way to use LogCall is along the lines of the following:
+//
+// func Function(a Type1, b Type2) ReturnType {
+// defer vlog.LogCall(a, b)()
+// // ... function body ...
+// return retVal
+// }
+//
+// To log the return value as the function returns, the following
+// pattern should be used. Note that pointers to the output
+// variables should be passed to the returning function, not the
+// variables themselves:
+//
+// func Function(a Type1, b Type2) (r ReturnType) {
+// defer vlog.LogCall(a, b)(&r)
+// // ... function body ...
+// return computeReturnValue()
+// }
+//
+// Note that when using this pattern, you do not need to actually
+// assign anything to the named return variable explicitly. A regular
+// return statement would automatically do the proper return variable
+// assignments.
+//
+// The log injector tool will automatically insert a LogCall invocation
+// into all implementations of the public API it runs, unless a Valid
+// Log Construct is found. A Valid Log Construct is defined as one of
+// the following at the beginning of the function body (i.e. should not
+// be preceded by any non-whitespace or non-comment tokens):
+// 1. defer vlog.LogCall(optional arguments)(optional pointers to return values)
+// 2. defer vlog.LogCallf(argsFormat, optional arguments)(returnValuesFormat, optional pointers to return values)
+// 3. // nologcall
+//
+// The comment "// nologcall" serves as a hint to log injection and
+// checking tools to exclude the function from their consideration.
+// It is used as follows:
+//
+// func FunctionWithoutLogging(args ...interface{}) {
+// // nologcall
+// // ... function body ...
+// }
+//
+func LogCall(v ...interface{}) func(...interface{}) {
+ if !V(logCallLogLevel) {
+ return func(...interface{}) {}
+ }
+ callerFuncName := callerFuncName()
+ invocationId := newInvocationIdentifier()
+ if len(v) > 0 {
+ Log.log.Printf(llog.InfoLog, "call[%s %s]: args:%v", callerFuncName, invocationId, v)
+ } else {
+ Log.log.Printf(llog.InfoLog, "call[%s %s]", callerFuncName, invocationId)
+ }
+ return func(v ...interface{}) {
+ if len(v) > 0 {
+ Log.log.Printf(llog.InfoLog, "return[%s %s]: %v", callerFuncName, invocationId, derefSlice(v))
+ } else {
+ Log.log.Printf(llog.InfoLog, "return[%s %s]", callerFuncName, invocationId)
+ }
+ }
+}
+
+// LogCallf behaves identically to LogCall, except it lets the caller to
+// customize the log messages via format specifiers, like the following:
+//
+// func Function(a Type1, b Type2) (r, t ReturnType) {
+// defer vlog.LogCallf("a: %v, b: %v", a, b)("(r,t)=(%v,%v)", &r, &t)
+// // ... function body ...
+// return finalR, finalT
+// }
+//
+func LogCallf(format string, v ...interface{}) func(string, ...interface{}) {
+ if !V(logCallLogLevel) {
+ return func(string, ...interface{}) {}
+ }
+ callerFuncName := callerFuncName()
+ invocationId := newInvocationIdentifier()
+ Log.log.Printf(llog.InfoLog, "call[%s %s]: %s", callerFuncName, invocationId, fmt.Sprintf(format, v...))
+ return func(format string, v ...interface{}) {
+ Log.log.Printf(llog.InfoLog, "return[%s %s]: %v", callerFuncName, invocationId, fmt.Sprintf(format, derefSlice(v)...))
+ }
+}
+
+func derefSlice(slice []interface{}) []interface{} {
+ o := make([]interface{}, 0, len(slice))
+ for _, x := range slice {
+ o = append(o, reflect.Indirect(reflect.ValueOf(x)).Interface())
+ }
+ return o
+}
+
+var invocationCounter uint64 = 0
+
+// newInvocationIdentifier generates a unique identifier for a method invocation
+// to make it easier to match up log lines for the entry and exit of a function
+// when looking at a log transcript.
+func newInvocationIdentifier() string {
+ const (
+ charSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz"
+ charSetLen = uint64(len(charSet))
+ )
+ r := []byte{'@'}
+ for n := atomic.AddUint64(&invocationCounter, 1); n > 0; n /= charSetLen {
+ r = append(r, charSet[n%charSetLen])
+ }
+ return string(r)
+}
diff --git a/vlog/model.go b/vlog/model.go
new file mode 100644
index 0000000..90e5b83
--- /dev/null
+++ b/vlog/model.go
@@ -0,0 +1,152 @@
+package vlog
+
+import (
+ // TODO(cnicolaou): remove this dependency in the future. For now this
+ // saves us some code.
+ "github.com/cosmosnicolaou/llog"
+)
+
+type InfoLog interface {
+ // Info logs to the INFO log.
+ // Arguments are handled in the manner of fmt.Print; a newline is appended if missing.
+ Info(args ...interface{})
+
+ // Infoln logs to the INFO log.
+ // Arguments are handled in the manner of fmt.Printf; a newline is appended if missing.
+ Infof(format string, args ...interface{})
+
+ // InfoStack logs the current goroutine's stack if the all parameter
+ // is false, or the stacks of all goroutines if it's true.
+ InfoStack(all bool)
+}
+
+type Verbosity interface {
+ // V returns true if the configured logging level is greater than or equal to its parameter
+ V(level Level) bool
+ // VI is like V, except that it returns an instance of the Info
+ // interface that will either log (if level >= the configured level)
+ // or discard its parameters. This allows for logger.VI(2).Info
+ // style usage.
+ VI(level Level) InfoLog
+}
+
+// Level specifies a level of verbosity for V logs.
+// It can be set via the Level optional parameter to ConfigureLogger.
+// It implements the flag.Value interface to support command line option parsing.
+type Level llog.Level
+
+// Set is part of the flag.Value interface.
+func (l *Level) Set(v string) error {
+ return (*llog.Level)(l).Set(v)
+}
+
+// Get is part of the flag.Value interface.
+func (l *Level) Get(v string) interface{} {
+ return *l
+}
+
+// String is part of the flag.Value interface.
+func (l *Level) String() string {
+ return (*llog.Level)(l).String()
+}
+
+// StderrThreshold identifies the sort of log: info, warning etc.
+// The values match the corresponding constants in C++ - e.g WARNING etc.
+// It can be set via the StderrThreshold optional parameter to ConfigureLogger.
+// It implements the flag.Value interface to support command line option parsing.
+type StderrThreshold llog.Severity
+
+// Set is part of the flag.Value interface.
+func (s *StderrThreshold) Set(v string) error {
+ return (*llog.Severity)(s).Set(v)
+}
+
+// Get is part of the flag.Value interface.
+func (s *StderrThreshold) Get(v string) interface{} {
+ return *s
+}
+
+// String is part of the flag.Value interface.
+func (s *StderrThreshold) String() string {
+ return (*llog.Severity)(s).String()
+}
+
+// ModuleSpec allows for the setting of specific log levels for specific
+// modules. The syntax is recordio=2,file=1,gfs*=3
+// It can be set via the ModuleSpec optional parameter to ConfigureLogger.
+// It implements the flag.Value interface to support command line option parsing.
+type ModuleSpec struct {
+ llog.ModuleSpec
+}
+
+// TraceLocation specifies the location, file:N, which when encountered will
+// cause logging to emit a stack trace.
+// It can be set via the TraceLocation optional parameter to ConfigureLogger.
+// It implements the flag.Value interface to support command line option parsing.
+type TraceLocation struct {
+ llog.TraceLocation
+}
+
+// LevelStats tracks the number of lines of output and number of bytes
+// per severity level.
+type LevelStats llog.Stats
+
+type Logger interface {
+ InfoLog
+ Verbosity
+
+ // Flush flushes all pending log I/O.
+ FlushLog()
+
+ // Error logs to the ERROR and INFO logs.
+ // Arguments are handled in the manner of fmt.Print; a newline is appended if missing.
+ Error(args ...interface{})
+
+ // Errorf logs to the ERROR and INFO logs.
+ // Arguments are handled in the manner of fmt.Printf; a newline is appended if missing.
+ Errorf(format string, args ...interface{})
+
+ // Fatal logs to the FATAL, ERROR and INFO logs,
+ // including a stack trace of all running goroutines, then calls os.Exit(255).
+ // Arguments are handled in the manner of fmt.Print; a newline is appended if missing.
+ Fatal(args ...interface{})
+
+ // Fatalf logs to the FATAL, ERROR and INFO logs,
+ // including a stack trace of all running goroutines, then calls os.Exit(255).
+ // Arguments are handled in the manner of fmt.Printf; a newline is appended if missing.
+ Fatalf(format string, args ...interface{})
+
+ // Panic is equivalent to Error() followed by a call to panic().
+ Panic(args ...interface{})
+
+ // Panicf is equivalent to Errorf() followed by a call to panic().
+ Panicf(format string, args ...interface{})
+
+ // ConfigureLogger configures all future logging. Some options
+ // may not be usable if ConfigureLogger
+ // is called from an init function, in which case an error will
+ // be returned.
+ // Some options only take effect if they are set before the logger
+ // is used. Once anything is logged using the logger, these options
+ // will silently be ignored. For example, LogDir, LogToStderr or
+ // AlsoLogToStderr fall in this category.
+ ConfigureLogger(opts ...LoggingOpts) error
+
+ // Stats returns stats on how many lines/bytes haven been written to
+ // this set of logs per severity level.
+ Stats() LevelStats
+
+ // LogDir returns the currently configured directory for storing logs.
+ LogDir() string
+}
+
+// Runtime defines the methods that the runtime must implement.
+type Runtime interface {
+ // Logger returns the current logger, if any, in use by the Runtime.
+ // TODO(cnicolaou): remove this.
+ Logger() Logger
+
+ // NewLogger creates a new instance of the logging interface that is
+ // separate from the one provided by Runtime.
+ NewLogger(name string, opts ...LoggingOpts) (Logger, error)
+}
diff --git a/vlog/opts.go b/vlog/opts.go
new file mode 100644
index 0000000..4b38ca1
--- /dev/null
+++ b/vlog/opts.go
@@ -0,0 +1,55 @@
+package vlog
+
+type LoggingOpts interface {
+ LoggingOpt()
+}
+
+type AutoFlush bool
+type AlsoLogToStderr bool
+type LogDir string
+type LogToStderr bool
+type MaxStackBufSize int
+
+// If true, logs are written to standard error as well as to files.
+func (_ AlsoLogToStderr) LoggingOpt() {}
+
+// Enable V-leveled logging at the specified level.
+func (_ Level) LoggingOpt() {}
+
+// log files will be written to this directory instead of the
+// default temporary directory.
+func (_ LogDir) LoggingOpt() {}
+
+// If true, logs are written to standard error instead of to files.
+func (_ LogToStderr) LoggingOpt() {}
+
+// Set the max size (bytes) of the byte buffer to use for stack
+// traces. The default max is 4M; use powers of 2 since the
+// stack size will be grown exponentially until it exceeds the max.
+// A min of 128K is enforced and any attempts to reduce this will
+// be silently ignored.
+func (_ MaxStackBufSize) LoggingOpt() {}
+
+// The syntax of the argument is a comma-separated list of pattern=N,
+// where pattern is a literal file name (minus the ".go" suffix) or
+// "glob" pattern and N is a V level. For instance,
+// -gopher*=3
+// sets the V level to 3 in all Go files whose names begin "gopher".
+func (_ ModuleSpec) LoggingOpt() {}
+
+// Log events at or above this severity are logged to standard
+// error as well as to files.
+func (_ StderrThreshold) LoggingOpt() {}
+
+// When set to a file and line number holding a logging statement, such as
+// gopherflakes.go:234
+// a stack trace will be written to the Info log whenever execution
+// hits that statement. (Unlike with -vmodule, the ".go" must be
+// present.)
+func (_ TraceLocation) LoggingOpt() {}
+
+// If true, enables automatic flushing of log output on every call
+func (_ AutoFlush) LoggingOpt() {}
+
+// TODO(cnicolaou): provide options for setting a remote network
+// destination for logging.
diff --git a/vlog/util_test.go b/vlog/util_test.go
new file mode 100644
index 0000000..3b8aa44
--- /dev/null
+++ b/vlog/util_test.go
@@ -0,0 +1,6 @@
+package vlog
+
+// SetLog allows us to override the Log global for testing purposes.
+func SetLog(l Logger) {
+ Log = l.(*logger)
+}
diff --git a/vlog/v23_test.go b/vlog/v23_test.go
new file mode 100644
index 0000000..6b83078
--- /dev/null
+++ b/vlog/v23_test.go
@@ -0,0 +1,30 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This file was auto-generated via go generate.
+// DO NOT UPDATE MANUALLY
+package vlog_test
+
+import "fmt"
+import "testing"
+import "os"
+
+import "v.io/core/veyron/lib/modules"
+import "v.io/core/veyron/lib/testutil"
+
+func init() {
+ modules.RegisterChild("child", ``, child)
+}
+
+func TestMain(m *testing.M) {
+ testutil.Init()
+ if modules.IsModulesProcess() {
+ if err := modules.Dispatch(); err != nil {
+ fmt.Fprintf(os.Stderr, "modules.Dispatch failed: %v\n", err)
+ os.Exit(1)
+ }
+ return
+ }
+ os.Exit(m.Run())
+}