Skip to content
Permalink
Browse files

Add option to redirect standard "log" to vlog. (#40)

  • Loading branch information...
gauthamt committed Jun 26, 2019
1 parent 8e961b5 commit 160f5abd6b74b7b350a6c54a29b94519e87fe8e7
Showing with 113 additions and 6 deletions.
  1. +51 −5 llog/glog.go
  2. +14 −0 llog/glog_test.go
  3. +11 −0 vlog/log.go
  4. +37 −1 vlog/log_test.go
@@ -97,6 +97,7 @@ import (
"errors"
"fmt"
"io"
stdLog "log"
"os"
"path/filepath"
"regexp"
@@ -108,7 +109,7 @@ import (
"time"
)

// severity identifies the sort of log: info, warning etc. It also implements
// Severity identifies the sort of log: info, warning etc. It also implements
// the flag.Value interface. The -stderrthreshold flag is of type severity and
// should be modified only through the flag.Value interface. The values match
// the corresponding constants in C++.
@@ -552,7 +553,7 @@ func (l *Log) String() string {
l.name, l.logDirs, l.toStderr, l.alsoToStderr, l.maxStackBufSize, l.verbosity, &l.stderrThreshold, &l.vmodule, &l.vfilepath, &l.traceLocation)
}

// logDir if non-empty, write log files to this directory.
// SetLogDir sets logDir if non-empty, write log files to this directory.
func (l *Log) SetLogDir(logDir string) {
if logDir != "" {
l.mu.Lock()
@@ -587,23 +588,23 @@ func (l *Log) SetStderrThreshold(s Severity) {
l.stderrThreshold.set(s)
}

// SetModuleSpec sets the comma-separated list of pattern=N settings for
// SetVModule sets the comma-separated list of pattern=N settings for
// file-filtered logging
func (l *Log) SetVModule(spec ModuleSpec) {
l.mu.Lock()
defer l.mu.Unlock()
l.setVState(l.verbosity, spec.filter, nil, true)
}

// SetModuleSpec sets the comma-separated list of pattern=N settings for
// SetVFilepath sets the comma-separated list of pattern=N settings for
// file-filtered logging
func (l *Log) SetVFilepath(spec FilepathSpec) {
l.mu.Lock()
defer l.mu.Unlock()
l.setVState(l.verbosity, nil, spec.filter, true)
}

// SetTaceLocation sets the location, file:N, which when encountered will cause logging to emit a stack trace
// SetTraceLocation sets the location, file:N, which when encountered will cause logging to emit a stack trace
func (l *Log) SetTraceLocation(location TraceLocation) {
l.mu.Lock()
defer l.mu.Unlock()
@@ -1159,3 +1160,48 @@ func (l *Log) V(level Level) bool {
func (l *Log) Stats() Stats {
return *l.stats
}

// CopyStandardLogTo arranges for messages written to the Go "log" package's
// default logs to also appear in the Google logs for the named and lower
// severities.
func (l *Log) CopyStandardLogTo(name string) {
sev, ok := severityByName(name)
if !ok {
panic(fmt.Sprintf("log.CopyStandardLogTo(%q): unrecognized severity name", name))
}
// Set a log format that captures the user's file and line:
// d.go:23: message
stdLog.SetFlags(stdLog.Lshortfile)
stdLog.SetOutput(logBridge{severity: sev, log: l})
}

// logBridge provides the Write method that enables CopyStandardLogTo to connect
// Go's standard logs to the logs provided by this package.
type logBridge struct {
severity Severity
log *Log
}

// Write parses the standard logging line and passes its components to the
// logger for severity(lb).
func (lb logBridge) Write(b []byte) (n int, err error) {
var (
file = "???"
line = 1
)
buf := lb.log.getBuffer()
// Split "d.go:23: message" into "d.go", "23", and "message".
if parts := bytes.SplitN(b, []byte{':'}, 3); len(parts) != 3 || len(parts[0]) < 1 || len(parts[2]) < 1 {
fmt.Fprint(buf, fmt.Sprintf("bad log format: %s", b))
} else {
file = string(parts[0])
line, err = strconv.Atoi(string(parts[1]))
fmt.Fprint(buf, string(b))
if err != nil {
fmt.Fprint(buf, fmt.Sprintf("bad line number: %s", b))
line = 1
}
}
lb.log.output(Severity(lb.severity), buf, file, line)
return len(b), nil
}
@@ -19,6 +19,7 @@ package llog
import (
"bytes"
"fmt"
"log"
"path/filepath"
"runtime"
"strconv"
@@ -444,3 +445,16 @@ func TestHeader1(t *testing.T) {
l.Print(InfoLog, "hello")
fmt.Println(l.contents(InfoLog))
}

func TestCopyStandardLogTo(t *testing.T) {
l := newLogger(t)
l.CopyStandardLogTo("INFO")
log.Print("hello world")
log.Print("foo bar")
l.Print(InfoLog, "wombats")
for _, line := range []string{"hello world", "foo bar", "wombats"} {
if !l.contains(InfoLog, line, t) {
t.Errorf("Failed to find line %q in contents:%q", line, l.contents(InfoLog))
}
}
}
@@ -166,6 +166,17 @@ func (l *Logger) Configure(opts ...LoggingOpts) error {
return nil
}

// CopyStandardLogTo arranges for messages written to the Go "log" package's
// default logs to also appear in the Google logs for the named and lower
// severities. Subsequent changes to the standard log's default output location
// or format may break this behavior.
//
// Valid names are "INFO", "WARNING", "ERROR", and "FATAL". If the name is not
// recognized, CopyStandardLogTo panics.
func (l *Logger) CopyStandardLogTo(name string) {
l.log.CopyStandardLogTo(name)
}

// LogDir returns the directory where the log files are written.
func (l *Logger) LogDir() string {
if len(l.logDir) != 0 {
@@ -7,9 +7,11 @@ package vlog_test
import (
"bufio"
"io/ioutil"
"log"
"os"
"path/filepath"
"regexp"
"strings"
"testing"

"v.io/x/lib/vlog"
@@ -32,6 +34,9 @@ func ExampleError() {
}

func readLogFiles(dir string) ([]string, error) {
return readLogLines(dir, false /* do not include header lines */)
}
func readLogLines(dir string, includeHeader bool) ([]string, error) {
files, err := ioutil.ReadDir(dir)
if err != nil {
return nil, err
@@ -48,7 +53,7 @@ func readLogFiles(dir string) ([]string, error) {
}
scanner := bufio.NewScanner(file)
for scanner.Scan() {
if line := scanner.Text(); len(line) > 0 && line[0] == 'I' {
if line := scanner.Text(); len(line) > 0 && (includeHeader || line[0] == 'I') {
contents = append(contents, line)
}
}
@@ -89,6 +94,37 @@ func TestHeaders(t *testing.T) {
}
}

func TestCopyStandardLogTo(t *testing.T) {
dir, err := ioutil.TempDir("", "logtest")
defer os.RemoveAll(dir)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
logger := vlog.NewLogger("testStandardLogTo")
logger.Configure(vlog.LogDir(dir))
logger.CopyStandardLogTo("INFO")
log.Print("hello world")
log.Print("foo bar")
logger.Info("wombats")
logger.FlushLog()
expectedLines := []string{"hello world", "foo bar", "wombats"}
contents, err := readLogLines(dir, true /* include header */)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
headerNum := 4 /* number of header lines */
if want, got := len(expectedLines), len(contents)-headerNum; want != got {
t.Errorf("Expected %d info lines, got %d instead", want, got)
} else {
contents = contents[headerNum:]
for i, line := range expectedLines {
if !strings.Contains(contents[i], line) {
t.Errorf("Failed to find line %q in contents:%q", line, contents)
}
}
}
}

func TestDepth(t *testing.T) {
dir, err := ioutil.TempDir("", "logtest")
defer os.RemoveAll(dir)

0 comments on commit 160f5ab

Please sign in to comment.
You can’t perform that action at this time.