merge with latest glog

This commit is contained in:
Chris Lu 2015-07-11 01:06:58 -07:00
parent 95855da282
commit a6aa79aab2
3 changed files with 259 additions and 31 deletions

View file

@ -37,6 +37,7 @@
// //
// By default, all log statements write to files in a temporary directory. // By default, all log statements write to files in a temporary directory.
// This package provides several flags that modify this behavior. // This package provides several flags that modify this behavior.
// As a result, flag.Parse must be called before any logging is done.
// //
// -logtostderr=false // -logtostderr=false
// Logs are written to standard error instead of to files. // Logs are written to standard error instead of to files.
@ -45,6 +46,9 @@
// -stderrthreshold=ERROR // -stderrthreshold=ERROR
// Log events at or above this severity are logged to standard // Log events at or above this severity are logged to standard
// error as well as to files. // 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. // Other flags provide aids to debugging.
// //
@ -73,6 +77,7 @@ import (
"flag" "flag"
"fmt" "fmt"
"io" "io"
stdLog "log"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
@ -89,6 +94,9 @@ import (
// the corresponding constants in C++. // the corresponding constants in C++.
type severity int32 // sync/atomic int32 type severity int32 // sync/atomic int32
// These constants identify the log levels in order of increasing severity.
// A message written to a high-severity log file is also written to each
// lower-severity log file.
const ( const (
infoLog severity = iota infoLog severity = iota
warningLog warningLog
@ -307,7 +315,7 @@ func (m *moduleSpec) Set(value string) error {
// isLiteral reports whether the pattern is a literal string, that is, has no metacharacters // isLiteral reports whether the pattern is a literal string, that is, has no metacharacters
// that require filepath.Match to be called to match the pattern. // that require filepath.Match to be called to match the pattern.
func isLiteral(pattern string) bool { func isLiteral(pattern string) bool {
return !strings.ContainsAny(pattern, `*?[]\`) return !strings.ContainsAny(pattern, `\*?[]`)
} }
// traceLocation represents the setting of the -log_backtrace_at flag. // traceLocation represents the setting of the -log_backtrace_at flag.
@ -446,8 +454,7 @@ type loggingT struct {
vmodule moduleSpec // The state of the -vmodule flag. vmodule moduleSpec // The state of the -vmodule flag.
verbosity Level // V logging level, the value of the -v flag/ verbosity Level // V logging level, the value of the -v flag/
// If the logging exits, the system should still run. // added by seaweedfs
// This flag is to disable the logging if logging exited.
exited bool exited bool
} }
@ -466,7 +473,7 @@ func (l *loggingT) setVState(verbosity Level, filter []modulePat, setFilter bool
// Turn verbosity off so V will not fire while we are in transition. // Turn verbosity off so V will not fire while we are in transition.
logging.verbosity.set(0) logging.verbosity.set(0)
// Ditto for filter length. // Ditto for filter length.
logging.filterLength = 0 atomic.StoreInt32(&logging.filterLength, 0)
// Set the new filters and wipe the pc->Level map if the filter has changed. // Set the new filters and wipe the pc->Level map if the filter has changed.
if setFilter { if setFilter {
@ -513,7 +520,8 @@ var timeNow = time.Now // Stubbed out for testing.
/* /*
header formats a log header as defined by the C++ implementation. header formats a log header as defined by the C++ implementation.
It returns a buffer containing the formatted header. It returns a buffer containing the formatted header and the user's file and line number.
The depth specifies how many stack frames above lives the source line to be identified in the log message.
Log lines have this form: Log lines have this form:
Lmmdd hh:mm:ss.uuuuuu threadid file:line] msg... Lmmdd hh:mm:ss.uuuuuu threadid file:line] msg...
@ -527,10 +535,8 @@ where the fields are defined as follows:
line The line number line The line number
msg The user-supplied message msg The user-supplied message
*/ */
func (l *loggingT) header(s severity) *buffer { func (l *loggingT) header(s severity, depth int) (*buffer, string, int) {
// Lmmdd hh:mm:ss.uuuuuu threadid file:line] _, file, line, ok := runtime.Caller(3 + depth)
now := timeNow()
_, file, line, ok := runtime.Caller(3) // It's always the same number of frames to the user's call.
if !ok { if !ok {
file = "???" file = "???"
line = 1 line = 1
@ -540,6 +546,12 @@ func (l *loggingT) header(s severity) *buffer {
file = file[slash+1:] file = file[slash+1:]
} }
} }
return l.formatHeader(s, file, line), file, line
}
// formatHeader formats a log header using the provided file name and line number.
func (l *loggingT) formatHeader(s severity, file string, line int) *buffer {
now := timeNow()
if line < 0 { if line < 0 {
line = 0 // not a real line number, but acceptable to someDigits line = 0 // not a real line number, but acceptable to someDigits
} }
@ -552,6 +564,7 @@ func (l *loggingT) header(s severity) *buffer {
// It's worth about 3X. Fprintf is hard. // It's worth about 3X. Fprintf is hard.
_, month, day := now.Date() _, month, day := now.Date()
hour, minute, second := now.Clock() hour, minute, second := now.Clock()
// Lmmdd hh:mm:ss.uuuuuu threadid file:line]
buf.tmp[0] = severityChar[s] buf.tmp[0] = severityChar[s]
buf.twoDigits(1, int(month)) buf.twoDigits(1, int(month))
buf.twoDigits(3, day) buf.twoDigits(3, day)
@ -562,7 +575,7 @@ func (l *loggingT) header(s severity) *buffer {
buf.tmp[11] = ':' buf.tmp[11] = ':'
buf.twoDigits(12, second) buf.twoDigits(12, second)
buf.tmp[14] = ' ' buf.tmp[14] = ' '
buf.nDigits(5, 15, pid) // TODO: should be TID buf.nDigits(5, 15, pid, ' ') // TODO: should be TID
buf.tmp[20] = ' ' buf.tmp[20] = ' '
buf.Write(buf.tmp[:21]) buf.Write(buf.tmp[:21])
buf.WriteString(file) buf.WriteString(file)
@ -585,12 +598,18 @@ func (buf *buffer) twoDigits(i, d int) {
buf.tmp[i] = digits[d%10] buf.tmp[i] = digits[d%10]
} }
// nDigits formats a zero-prefixed n-digit integer at buf.tmp[i]. // nDigits formats an n-digit integer at buf.tmp[i],
func (buf *buffer) nDigits(n, i, d int) { // padding with pad on the left.
for j := n - 1; j >= 0; j-- { // It assumes d >= 0.
func (buf *buffer) nDigits(n, i, d int, pad byte) {
j := n - 1
for ; j >= 0 && d > 0; j-- {
buf.tmp[i+j] = digits[d%10] buf.tmp[i+j] = digits[d%10]
d /= 10 d /= 10
} }
for ; j >= 0; j-- {
buf.tmp[i+j] = pad
}
} }
// someDigits formats a zero-prefixed variable-width integer at buf.tmp[i]. // someDigits formats a zero-prefixed variable-width integer at buf.tmp[i].
@ -610,35 +629,50 @@ func (buf *buffer) someDigits(i, d int) int {
} }
func (l *loggingT) println(s severity, args ...interface{}) { func (l *loggingT) println(s severity, args ...interface{}) {
buf := l.header(s) buf, file, line := l.header(s, 0)
fmt.Fprintln(buf, args...) fmt.Fprintln(buf, args...)
l.output(s, buf) l.output(s, buf, file, line, false)
} }
func (l *loggingT) print(s severity, args ...interface{}) { func (l *loggingT) print(s severity, args ...interface{}) {
buf := l.header(s) l.printDepth(s, 1, args...)
}
func (l *loggingT) printDepth(s severity, depth int, args ...interface{}) {
buf, file, line := l.header(s, depth)
fmt.Fprint(buf, args...) fmt.Fprint(buf, args...)
if buf.Bytes()[buf.Len()-1] != '\n' { if buf.Bytes()[buf.Len()-1] != '\n' {
buf.WriteByte('\n') buf.WriteByte('\n')
} }
l.output(s, buf) l.output(s, buf, file, line, false)
} }
func (l *loggingT) printf(s severity, format string, args ...interface{}) { func (l *loggingT) printf(s severity, format string, args ...interface{}) {
buf := l.header(s) buf, file, line := l.header(s, 0)
fmt.Fprintf(buf, format, args...) fmt.Fprintf(buf, format, args...)
if buf.Bytes()[buf.Len()-1] != '\n' { if buf.Bytes()[buf.Len()-1] != '\n' {
buf.WriteByte('\n') buf.WriteByte('\n')
} }
l.output(s, buf) l.output(s, buf, file, line, false)
}
// printWithFileLine behaves like print but uses the provided file and line number. If
// alsoLogToStderr is true, the log message always appears on standard error; it
// will also appear in the log file unless --logtostderr is set.
func (l *loggingT) printWithFileLine(s severity, file string, line int, alsoToStderr bool, args ...interface{}) {
buf := l.formatHeader(s, file, line)
fmt.Fprint(buf, args...)
if buf.Bytes()[buf.Len()-1] != '\n' {
buf.WriteByte('\n')
}
l.output(s, buf, file, line, alsoToStderr)
} }
// output writes the data to the log files and releases the buffer. // output writes the data to the log files and releases the buffer.
func (l *loggingT) output(s severity, buf *buffer) { func (l *loggingT) output(s severity, buf *buffer, file string, line int, alsoToStderr bool) {
l.mu.Lock() l.mu.Lock()
if l.traceLocation.isSet() { if l.traceLocation.isSet() {
_, file, line, ok := runtime.Caller(3) // It's always the same number of frames to the user's call (same as header). if l.traceLocation.match(file, line) {
if ok && l.traceLocation.match(file, line) {
buf.Write(stacks(false)) buf.Write(stacks(false))
} }
} }
@ -646,7 +680,7 @@ func (l *loggingT) output(s severity, buf *buffer) {
if l.toStderr { if l.toStderr {
os.Stderr.Write(data) os.Stderr.Write(data)
} else { } else {
if l.alsoToStderr || s >= l.stderrThreshold.get() { if alsoToStderr || l.alsoToStderr || s >= l.stderrThreshold.get() {
os.Stderr.Write(data) os.Stderr.Write(data)
} }
if l.file[s] == nil { if l.file[s] == nil {
@ -670,7 +704,16 @@ func (l *loggingT) output(s severity, buf *buffer) {
} }
} }
if s == fatalLog { if s == fatalLog {
// Make sure we see the trace for the current goroutine on standard error. // If we got here via Exit rather than Fatal, print no stacks.
if atomic.LoadUint32(&fatalNoStacks) > 0 {
l.mu.Unlock()
timeoutFlush(10 * time.Second)
os.Exit(1)
}
// Dump all goroutine stacks before exiting.
// First, make sure we see the trace for the current goroutine on standard error.
// If -logtostderr has been specified, the loop below will do that anyway
// as the first stack in the full dump.
if !l.toStderr { if !l.toStderr {
os.Stderr.Write(stacks(false)) os.Stderr.Write(stacks(false))
} }
@ -862,6 +905,54 @@ func (l *loggingT) flushAll() {
} }
} }
// 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 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(sev))
}
// logBridge provides the Write method that enables CopyStandardLogTo to connect
// Go's standard logs to the logs provided by this package.
type logBridge severity
// 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
text string
)
// 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 {
text = fmt.Sprintf("bad log format: %s", b)
} else {
file = string(parts[0])
text = string(parts[2][1:]) // skip leading space
line, err = strconv.Atoi(string(parts[1]))
if err != nil {
text = fmt.Sprintf("bad line number: %s", b)
line = 1
}
}
// printWithFileLine with alsoToStderr=true, so standard log messages
// always appear on standard error.
logging.printWithFileLine(severity(lb), file, line, true, text)
return len(b), nil
}
// setV computes and remembers the V level for a given PC // setV computes and remembers the V level for a given PC
// when vmodule is enabled. // when vmodule is enabled.
// File pattern matching takes the basename of the file, stripped // File pattern matching takes the basename of the file, stripped
@ -965,6 +1056,12 @@ func Info(args ...interface{}) {
logging.print(infoLog, args...) logging.print(infoLog, args...)
} }
// InfoDepth acts as Info but uses depth to determine which call frame to log.
// InfoDepth(0, "msg") is the same as Info("msg").
func InfoDepth(depth int, args ...interface{}) {
logging.printDepth(infoLog, depth, args...)
}
// Infoln logs to the INFO log. // Infoln logs to the INFO log.
// Arguments are handled in the manner of fmt.Println; a newline is appended if missing. // Arguments are handled in the manner of fmt.Println; a newline is appended if missing.
func Infoln(args ...interface{}) { func Infoln(args ...interface{}) {
@ -983,6 +1080,12 @@ func Warning(args ...interface{}) {
logging.print(warningLog, args...) logging.print(warningLog, args...)
} }
// WarningDepth acts as Warning but uses depth to determine which call frame to log.
// WarningDepth(0, "msg") is the same as Warning("msg").
func WarningDepth(depth int, args ...interface{}) {
logging.printDepth(warningLog, depth, args...)
}
// Warningln logs to the WARNING and INFO logs. // Warningln logs to the WARNING and INFO logs.
// Arguments are handled in the manner of fmt.Println; a newline is appended if missing. // Arguments are handled in the manner of fmt.Println; a newline is appended if missing.
func Warningln(args ...interface{}) { func Warningln(args ...interface{}) {
@ -1001,6 +1104,12 @@ func Error(args ...interface{}) {
logging.print(errorLog, args...) logging.print(errorLog, args...)
} }
// ErrorDepth acts as Error but uses depth to determine which call frame to log.
// ErrorDepth(0, "msg") is the same as Error("msg").
func ErrorDepth(depth int, args ...interface{}) {
logging.printDepth(errorLog, depth, args...)
}
// Errorln logs to the ERROR, WARNING, and INFO logs. // Errorln logs to the ERROR, WARNING, and INFO logs.
// Arguments are handled in the manner of fmt.Println; a newline is appended if missing. // Arguments are handled in the manner of fmt.Println; a newline is appended if missing.
func Errorln(args ...interface{}) { func Errorln(args ...interface{}) {
@ -1020,6 +1129,12 @@ func Fatal(args ...interface{}) {
logging.print(fatalLog, args...) logging.print(fatalLog, args...)
} }
// FatalDepth acts as Fatal but uses depth to determine which call frame to log.
// FatalDepth(0, "msg") is the same as Fatal("msg").
func FatalDepth(depth int, args ...interface{}) {
logging.printDepth(fatalLog, depth, args...)
}
// Fatalln logs to the FATAL, ERROR, WARNING, and INFO logs, // Fatalln logs to the FATAL, ERROR, WARNING, and INFO logs,
// including a stack trace of all running goroutines, then calls os.Exit(255). // including a stack trace of all running goroutines, then calls os.Exit(255).
// Arguments are handled in the manner of fmt.Println; a newline is appended if missing. // Arguments are handled in the manner of fmt.Println; a newline is appended if missing.
@ -1033,3 +1148,34 @@ func Fatalln(args ...interface{}) {
func Fatalf(format string, args ...interface{}) { func Fatalf(format string, args ...interface{}) {
logging.printf(fatalLog, format, args...) logging.printf(fatalLog, format, args...)
} }
// fatalNoStacks is non-zero if we are to exit without dumping goroutine stacks.
// It allows Exit and relatives to use the Fatal logs.
var fatalNoStacks uint32
// Exit logs to the FATAL, ERROR, WARNING, and INFO logs, then calls os.Exit(1).
// Arguments are handled in the manner of fmt.Print; a newline is appended if missing.
func Exit(args ...interface{}) {
atomic.StoreUint32(&fatalNoStacks, 1)
logging.print(fatalLog, args...)
}
// ExitDepth acts as Exit but uses depth to determine which call frame to log.
// ExitDepth(0, "msg") is the same as Exit("msg").
func ExitDepth(depth int, args ...interface{}) {
atomic.StoreUint32(&fatalNoStacks, 1)
logging.printDepth(fatalLog, depth, args...)
}
// Exitln logs to the FATAL, ERROR, WARNING, and INFO logs, then calls os.Exit(1).
func Exitln(args ...interface{}) {
atomic.StoreUint32(&fatalNoStacks, 1)
logging.println(fatalLog, args...)
}
// Exitf logs to the FATAL, ERROR, WARNING, and INFO logs, then calls os.Exit(1).
// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing.
func Exitf(format string, args ...interface{}) {
atomic.StoreUint32(&fatalNoStacks, 1)
logging.printf(fatalLog, format, args...)
}

View file

@ -114,8 +114,8 @@ func create(tag string, t time.Time) (f *os.File, filename string, err error) {
f, err := os.Create(fname) f, err := os.Create(fname)
if err == nil { if err == nil {
symlink := filepath.Join(dir, link) symlink := filepath.Join(dir, link)
os.Remove(symlink) // ignore err os.Remove(symlink) // ignore err
os.Symlink(fname, symlink) // ignore err os.Symlink(name, symlink) // ignore err
return f, fname, nil return f, fname, nil
} }
lastErr = err lastErr = err

View file

@ -19,8 +19,10 @@ package glog
import ( import (
"bytes" "bytes"
"fmt" "fmt"
stdLog "log"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strconv"
"strings" "strings"
"testing" "testing"
"time" "time"
@ -96,20 +98,99 @@ func TestInfo(t *testing.T) {
} }
} }
func TestInfoDepth(t *testing.T) {
setFlags()
defer logging.swap(logging.newBuffers())
f := func() { InfoDepth(1, "depth-test1") }
// The next three lines must stay together
_, _, wantLine, _ := runtime.Caller(0)
InfoDepth(0, "depth-test0")
f()
msgs := strings.Split(strings.TrimSuffix(contents(infoLog), "\n"), "\n")
if len(msgs) != 2 {
t.Fatalf("Got %d lines, expected 2", len(msgs))
}
for i, m := range msgs {
if !strings.HasPrefix(m, "I") {
t.Errorf("InfoDepth[%d] has wrong character: %q", i, m)
}
w := fmt.Sprintf("depth-test%d", i)
if !strings.Contains(m, w) {
t.Errorf("InfoDepth[%d] missing %q: %q", i, w, m)
}
// pull out the line number (between : and ])
msg := m[strings.LastIndex(m, ":")+1:]
x := strings.Index(msg, "]")
if x < 0 {
t.Errorf("InfoDepth[%d]: missing ']': %q", i, m)
continue
}
line, err := strconv.Atoi(msg[:x])
if err != nil {
t.Errorf("InfoDepth[%d]: bad line number: %q", i, m)
continue
}
wantLine++
if wantLine != line {
t.Errorf("InfoDepth[%d]: got line %d, want %d", i, line, wantLine)
}
}
}
func init() {
CopyStandardLogTo("INFO")
}
// Test that CopyStandardLogTo panics on bad input.
func TestCopyStandardLogToPanic(t *testing.T) {
defer func() {
if s, ok := recover().(string); !ok || !strings.Contains(s, "LOG") {
t.Errorf(`CopyStandardLogTo("LOG") should have panicked: %v`, s)
}
}()
CopyStandardLogTo("LOG")
}
// Test that using the standard log package logs to INFO.
func TestStandardLog(t *testing.T) {
setFlags()
defer logging.swap(logging.newBuffers())
stdLog.Print("test")
if !contains(infoLog, "I", t) {
t.Errorf("Info has wrong character: %q", contents(infoLog))
}
if !contains(infoLog, "test", t) {
t.Error("Info failed")
}
}
// Test that the header has the correct format. // Test that the header has the correct format.
func TestHeader(t *testing.T) { func TestHeader(t *testing.T) {
setFlags() setFlags()
defer logging.swap(logging.newBuffers()) defer logging.swap(logging.newBuffers())
defer func(previous func() time.Time) { timeNow = previous }(timeNow) defer func(previous func() time.Time) { timeNow = previous }(timeNow)
timeNow = func() time.Time { timeNow = func() time.Time {
return time.Date(2006, 1, 2, 15, 4, 5, .678901e9, time.Local) return time.Date(2006, 1, 2, 15, 4, 5, .067890e9, time.Local)
} }
pid = 1234
Info("test") Info("test")
var line, pid int var line int
n, err := fmt.Sscanf(contents(infoLog), "I0102 15:04:05 %d glog_test.go:%d] test\n", &pid, &line) format := "I0102 15:04:05.067890 1234 glog_test.go:%d] test\n"
if n != 2 || err != nil { n, err := fmt.Sscanf(contents(infoLog), format, &line)
if n != 1 || err != nil {
t.Errorf("log format error: %d elements, error %s:\n%s", n, err, contents(infoLog)) t.Errorf("log format error: %d elements, error %s:\n%s", n, err, contents(infoLog))
} }
// Scanf treats multiple spaces as equivalent to a single space,
// so check for correct space-padding also.
want := fmt.Sprintf(format, line)
if contents(infoLog) != want {
t.Errorf("log format error: got:\n\t%q\nwant:\t%q", contents(infoLog), want)
}
} }
// Test that an Error log goes to Warning and Info. // Test that an Error log goes to Warning and Info.
@ -328,6 +409,7 @@ func TestLogBacktraceAt(t *testing.T) {
func BenchmarkHeader(b *testing.B) { func BenchmarkHeader(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
logging.putBuffer(logging.header(infoLog)) buf, _, _ := logging.header(infoLog, 0)
logging.putBuffer(buf)
} }
} }