Commit 5ee13c0d authored by Rob Pike's avatar Rob Pike

log: generalize getting and setting flags and prefix.

- used to be only for standard log, not for user-built.
- there were no getters.
Also rearrange the code a little so we can avoid allocating
a buffer on every call.  Logging is expensive but we should
avoid unnecessary cost.

This should have no effect on existing code.

R=rsc
CC=golang-dev
https://golang.org/cl/4363045
parent 41971434
......@@ -33,6 +33,7 @@ const (
Lmicroseconds // microsecond resolution: 01:23:23.123123. assumes Ltime.
Llongfile // full file name and line number: /a/b/c/d.go:23
Lshortfile // final file name element and line number: d.go:23. overrides Llongfile
LstdFlags = Ldate | Ltime // initial values for the standard logger
)
// A Logger represents an active logging object that generates lines of
......@@ -40,10 +41,11 @@ const (
// the Writer's Write method. A Logger can be used simultaneously from
// multiple goroutines; it guarantees to serialize access to the Writer.
type Logger struct {
mu sync.Mutex // ensures atomic writes
out io.Writer // destination for output
prefix string // prefix to write at beginning of each line
flag int // properties
mu sync.Mutex // ensures atomic writes; protects the following fields
out io.Writer // destination for output
buf bytes.Buffer // for accumulating text to write
}
// New creates a new Logger. The out variable sets the
......@@ -54,7 +56,7 @@ func New(out io.Writer, prefix string, flag int) *Logger {
return &Logger{out: out, prefix: prefix, flag: flag}
}
var std = New(os.Stderr, "", Ldate|Ltime)
var std = New(os.Stderr, "", LstdFlags)
// Cheap integer to fixed-width decimal ASCII. Give a negative width to avoid zero-padding.
// Knows the buffer has capacity.
......@@ -81,7 +83,7 @@ func itoa(buf *bytes.Buffer, i int, wid int) {
}
}
func (l *Logger) formatHeader(buf *bytes.Buffer, ns int64, calldepth int) {
func (l *Logger) formatHeader(buf *bytes.Buffer, ns int64, file string, line int) {
buf.WriteString(l.prefix)
if l.flag&(Ldate|Ltime|Lmicroseconds) != 0 {
t := time.SecondsToLocalTime(ns / 1e9)
......@@ -107,8 +109,6 @@ func (l *Logger) formatHeader(buf *bytes.Buffer, ns int64, calldepth int) {
}
}
if l.flag&(Lshortfile|Llongfile) != 0 {
_, file, line, ok := runtime.Caller(calldepth)
if ok {
if l.flag&Lshortfile != 0 {
short := file
for i := len(file) - 1; i > 0; i-- {
......@@ -119,10 +119,6 @@ func (l *Logger) formatHeader(buf *bytes.Buffer, ns int64, calldepth int) {
}
file = short
}
} else {
file = "???"
line = 0
}
buf.WriteString(file)
buf.WriteByte(':')
itoa(buf, line, -1)
......@@ -138,15 +134,26 @@ func (l *Logger) formatHeader(buf *bytes.Buffer, ns int64, calldepth int) {
// paths it will be 2.
func (l *Logger) Output(calldepth int, s string) os.Error {
now := time.Nanoseconds() // get this early.
buf := new(bytes.Buffer)
l.formatHeader(buf, now, calldepth+1)
buf.WriteString(s)
if len(s) > 0 && s[len(s)-1] != '\n' {
buf.WriteByte('\n')
// get caller info (if required) before locking - it's expensive.
var file string
var line int
if l.flag&(Lshortfile|Llongfile) != 0 {
var ok bool
_, file, line, ok = runtime.Caller(calldepth)
if !ok {
file = "???"
line = 0
}
}
l.mu.Lock()
defer l.mu.Unlock()
_, err := l.out.Write(buf.Bytes())
l.buf.Reset()
l.formatHeader(&l.buf, now, file, line)
l.buf.WriteString(s)
if len(s) > 0 && s[len(s)-1] != '\n' {
l.buf.WriteByte('\n')
}
_, err := l.out.Write(l.buf.Bytes())
return err
}
......@@ -203,19 +210,49 @@ func (l *Logger) Panicln(v ...interface{}) {
panic(s)
}
// Flags returns the output flags for the logger.
func (l *Logger) Flags() int {
return l.flag
}
// SetFlags sets the output flags for the logger.
func (l *Logger) SetFlags(flag int) {
l.flag = flag
}
// Prefix returns the output prefix for the logger.
func (l *Logger) Prefix() string {
return l.prefix
}
// SetPrefix sets the output prefix for the logger.
func (l *Logger) SetPrefix(prefix string) {
l.prefix = prefix
}
// SetOutput sets the output destination for the standard logger.
func SetOutput(w io.Writer) {
std.out = w
}
// Flags returns the output flags for the standard logger.
func Flags() int {
return std.Flags()
}
// SetFlags sets the output flags for the standard logger.
func SetFlags(flag int) {
std.flag = flag
std.SetFlags(flag)
}
// Prefix returns the output prefix for the standard logger.
func Prefix() string {
return std.Prefix()
}
// SetPrefix sets the output prefix for the standard logger.
func SetPrefix(prefix string) {
std.prefix = prefix
std.SetPrefix(prefix)
}
// These functions write to the standard logger.
......
......@@ -84,3 +84,36 @@ func TestOutput(t *testing.T) {
t.Errorf("log output should match %q is %q", expect, b.String())
}
}
func TestFlagAndPrefixSetting(t *testing.T) {
var b bytes.Buffer
l := New(&b, "Test:", LstdFlags)
f := l.Flags()
if f != LstdFlags {
t.Errorf("Flags 1: expected %x got %x", LstdFlags, f)
}
l.SetFlags(f | Lmicroseconds)
f = l.Flags()
if f != LstdFlags|Lmicroseconds {
t.Errorf("Flags 2: expected %x got %x", LstdFlags|Lmicroseconds, f)
}
p := l.Prefix()
if p != "Test:" {
t.Errorf(`Prefix: expected "Test:" got %q`, p)
}
l.SetPrefix("Reality:")
p = l.Prefix()
if p != "Reality:" {
t.Errorf(`Prefix: expected "Reality:" got %q`, p)
}
// Verify a log message looks right, with our prefix and microseconds present.
l.Print("hello")
pattern := "^Reality:" + Rdate + " " + Rtime + Rmicroseconds + " hello\n"
matched, err := regexp.Match(pattern, b.Bytes())
if err != nil {
t.Fatalf("pattern %q did not compile: %s", pattern, err)
}
if !matched {
t.Error("message did not match pattern")
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment