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())
+}