diff options
Diffstat (limited to 'src/pkg/log')
-rw-r--r-- | src/pkg/log/Makefile | 2 | ||||
-rw-r--r-- | src/pkg/log/log.go | 283 | ||||
-rw-r--r-- | src/pkg/log/log_test.go | 70 |
3 files changed, 228 insertions, 127 deletions
diff --git a/src/pkg/log/Makefile b/src/pkg/log/Makefile index 402be03bc..da72216af 100644 --- a/src/pkg/log/Makefile +++ b/src/pkg/log/Makefile @@ -2,7 +2,7 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. -include ../../Make.$(GOARCH) +include ../../Make.inc TARG=log GOFILES=\ diff --git a/src/pkg/log/log.go b/src/pkg/log/log.go index 28d6204eb..d34af9e5e 100644 --- a/src/pkg/log/log.go +++ b/src/pkg/log/log.go @@ -2,29 +2,28 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Rudimentary logging package. Defines a type, Logger, with simple -// methods for formatting output to one or two destinations. Also has -// predefined Loggers accessible through helper functions Stdout[f], -// Stderr[f], Exit[f], and Crash[f], which are easier to use than creating -// a Logger manually. -// Exit exits when written to. -// Crash causes a crash when written to. +// Simple logging package. It defines a type, Logger, with methods +// for formatting output. It also has a predefined 'standard' Logger +// accessible through helper functions Print[f|ln], Exit[f|ln], and +// Panic[f|ln], which are easier to use than creating a Logger manually. +// That logger writes to standard error and prints the date and time +// of each logged message. +// The Exit functions call os.Exit(1) after writing the log message. +// The Panic functions call panic after writing the log message. package log import ( + "bytes" "fmt" "io" "runtime" "os" "time" + "sync" ) -// These flags define the properties of the Logger and the output they produce. +// These flags define which text to prefix to each log entry generated by the Logger. const ( - // Flags - Lok = iota - Lexit // terminate execution when written - Lcrash // crash (panic) when written // Bits or'ed together to control what's printed. There is no control over the // order they appear (the order listed here) or the format they present (as // described in the comments). A colon appears after these items: @@ -34,39 +33,36 @@ 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 - lAllBits = Ldate | Ltime | Lmicroseconds | Llongfile | Lshortfile ) -// Logger represents an active logging object. +// A Logger represents an active logging object that generates lines of +// output to an io.Writer. Each logging operation makes a single call to +// 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 { - out0 io.Writer // first destination for output - out1 io.Writer // second destination for output; may be nil - prefix string // prefix to write at beginning of each line - flag int // properties + 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 } -// New creates a new Logger. The out0 and out1 variables set the -// destinations to which log data will be written; out1 may be nil. +// New creates a new Logger. The out variable sets the +// destination to which log data will be written. // The prefix appears at the beginning of each generated log line. // The flag argument defines the logging properties. -func New(out0, out1 io.Writer, prefix string, flag int) *Logger { - return &Logger{out0, out1, prefix, flag} +func New(out io.Writer, prefix string, flag int) *Logger { + return &Logger{out: out, prefix: prefix, flag: flag} } -var ( - stdout = New(os.Stdout, nil, "", Lok|Ldate|Ltime) - stderr = New(os.Stderr, nil, "", Lok|Ldate|Ltime) - exit = New(os.Stderr, nil, "", Lexit|Ldate|Ltime) - crash = New(os.Stderr, nil, "", Lcrash|Ldate|Ltime) -) - -var shortnames = make(map[string]string) // cache of short names to avoid allocation. +var std = New(os.Stderr, "", Ldate|Ltime) -// Cheap integer to fixed-width decimal ASCII. Use a negative width to avoid zero-padding -func itoa(i int, wid int) string { +// Cheap integer to fixed-width decimal ASCII. Give a negative width to avoid zero-padding. +// Knows the buffer has capacity. +func itoa(buf *bytes.Buffer, i int, wid int) { var u uint = uint(i) if u == 0 && wid <= 1 { - return "0" + buf.WriteByte('0') + return } // Assemble decimal in reverse order. @@ -78,38 +74,48 @@ func itoa(i int, wid int) string { b[bp] = byte(u%10) + '0' } - return string(b[bp:]) + // avoid slicing b to avoid an allocation. + for bp < len(b) { + buf.WriteByte(b[bp]) + bp++ + } } -func (l *Logger) formatHeader(ns int64, calldepth int) string { - h := l.prefix +func (l *Logger) formatHeader(buf *bytes.Buffer, ns int64, calldepth int) { + buf.WriteString(l.prefix) if l.flag&(Ldate|Ltime|Lmicroseconds) != 0 { t := time.SecondsToLocalTime(ns / 1e9) - if l.flag&(Ldate) != 0 { - h += itoa(int(t.Year), 4) + "/" + itoa(t.Month, 2) + "/" + itoa(t.Day, 2) + " " + if l.flag&Ldate != 0 { + itoa(buf, int(t.Year), 4) + buf.WriteByte('/') + itoa(buf, int(t.Month), 2) + buf.WriteByte('/') + itoa(buf, int(t.Day), 2) + buf.WriteByte(' ') } if l.flag&(Ltime|Lmicroseconds) != 0 { - h += itoa(t.Hour, 2) + ":" + itoa(t.Minute, 2) + ":" + itoa(t.Second, 2) + itoa(buf, int(t.Hour), 2) + buf.WriteByte(':') + itoa(buf, int(t.Minute), 2) + buf.WriteByte(':') + itoa(buf, int(t.Second), 2) if l.flag&Lmicroseconds != 0 { - h += "." + itoa(int(ns%1e9)/1e3, 6) + buf.WriteByte('.') + itoa(buf, int(ns%1e9)/1e3, 6) } - h += " " + buf.WriteByte(' ') } } if l.flag&(Lshortfile|Llongfile) != 0 { _, file, line, ok := runtime.Caller(calldepth) if ok { if l.flag&Lshortfile != 0 { - short, ok := shortnames[file] - if !ok { - short = file - for i := len(file) - 1; i > 0; i-- { - if file[i] == '/' { - short = file[i+1:] - break - } + short := file + for i := len(file) - 1; i > 0; i-- { + if file[i] == '/' { + short = file[i+1:] + break } - shortnames[file] = short } file = short } @@ -117,65 +123,156 @@ func (l *Logger) formatHeader(ns int64, calldepth int) string { file = "???" line = 0 } - h += file + ":" + itoa(line, -1) + ": " + buf.WriteString(file) + buf.WriteByte(':') + itoa(buf, line, -1) + buf.WriteString(": ") } - return h } -// Output writes the output for a logging event. The string s contains the text to print after -// the time stamp; calldepth is used to recover the PC. It is provided for generality, although -// at the moment on all pre-defined paths it will be 2. +// Output writes the output for a logging event. The string s contains +// the text to print after the prefix specified by the flags of the +// Logger. A newline is appended if the last character of s is not +// already a newline. Calldepth is used to recover the PC and is +// provided for generality, although at the moment on all pre-defined +// paths it will be 2. func (l *Logger) Output(calldepth int, s string) os.Error { now := time.Nanoseconds() // get this early. - newline := "\n" - if len(s) > 0 && s[len(s)-1] == '\n' { - newline = "" - } - s = l.formatHeader(now, calldepth+1) + s + newline - _, err := io.WriteString(l.out0, s) - if l.out1 != nil { - _, err1 := io.WriteString(l.out1, s) - if err == nil && err1 != nil { - err = err1 - } - } - switch l.flag & ^lAllBits { - case Lcrash: - panic("log: fatal error") - case Lexit: - os.Exit(1) + 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') } + l.mu.Lock() + defer l.mu.Unlock() + _, err := l.out.Write(buf.Bytes()) return err } -// Logf is analogous to Printf() for a Logger. -func (l *Logger) Logf(format string, v ...interface{}) { - l.Output(2, fmt.Sprintf(format, v)) +// Printf calls l.Output to print to the logger. +// Arguments are handled in the manner of fmt.Printf. +func (l *Logger) Printf(format string, v ...interface{}) { + l.Output(2, fmt.Sprintf(format, v...)) +} + +// Print calls l.Output to print to the logger. +// Arguments are handled in the manner of fmt.Print. +func (l *Logger) Print(v ...interface{}) { l.Output(2, fmt.Sprint(v...)) } + +// Println calls l.Output to print to the logger. +// Arguments are handled in the manner of fmt.Println. +func (l *Logger) Println(v ...interface{}) { l.Output(2, fmt.Sprintln(v...)) } + +// Exit is equivalent to l.Print() followed by a call to os.Exit(1). +func (l *Logger) Exit(v ...interface{}) { + l.Output(2, fmt.Sprint(v...)) + os.Exit(1) +} + +// Exitf is equivalent to l.Printf() followed by a call to os.Exit(1). +func (l *Logger) Exitf(format string, v ...interface{}) { + l.Output(2, fmt.Sprintf(format, v...)) + os.Exit(1) +} + +// Exitln is equivalent to l.Println() followed by a call to os.Exit(1). +func (l *Logger) Exitln(v ...interface{}) { + l.Output(2, fmt.Sprintln(v...)) + os.Exit(1) +} + +// Panic is equivalent to l.Print() followed by a call to panic(). +func (l *Logger) Panic(v ...interface{}) { + s := fmt.Sprint(v...) + l.Output(2, s) + panic(s) +} + +// Panicf is equivalent to l.Printf() followed by a call to panic(). +func (l *Logger) Panicf(format string, v ...interface{}) { + s := fmt.Sprintf(format, v...) + l.Output(2, s) + panic(s) } -// Log is analogous to Print() for a Logger. -func (l *Logger) Log(v ...interface{}) { l.Output(2, fmt.Sprintln(v)) } +// Panicln is equivalent to l.Println() followed by a call to panic(). +func (l *Logger) Panicln(v ...interface{}) { + s := fmt.Sprintln(v...) + l.Output(2, s) + panic(s) +} + +// SetOutput sets the output destination for the standard logger. +func SetOutput(w io.Writer) { + std.out = w +} + +// SetFlags sets the output flags for the standard logger. +func SetFlags(flag int) { + std.flag = flag +} + +// SetPrefix sets the output prefix for the standard logger. +func SetPrefix(prefix string) { + std.prefix = prefix +} + +// These functions write to the standard logger. + +// Print calls Output to print to the standard logger. +// Arguments are handled in the manner of fmt.Print. +func Print(v ...interface{}) { + std.Output(2, fmt.Sprint(v...)) +} -// Stdout is a helper function for easy logging to stdout. It is analogous to Print(). -func Stdout(v ...interface{}) { stdout.Output(2, fmt.Sprint(v)) } +// Printf calls Output to print to the standard logger. +// Arguments are handled in the manner of fmt.Printf. +func Printf(format string, v ...interface{}) { + std.Output(2, fmt.Sprintf(format, v...)) +} -// Stderr is a helper function for easy logging to stderr. It is analogous to Fprint(os.Stderr). -func Stderr(v ...interface{}) { stderr.Output(2, fmt.Sprintln(v)) } +// Println calls Output to print to the standard logger. +// Arguments are handled in the manner of fmt.Println. +func Println(v ...interface{}) { + std.Output(2, fmt.Sprintln(v...)) +} -// Stdoutf is a helper functions for easy formatted logging to stdout. It is analogous to Printf(). -func Stdoutf(format string, v ...interface{}) { stdout.Output(2, fmt.Sprintf(format, v)) } +// Exit is equivalent to Print() followed by a call to os.Exit(1). +func Exit(v ...interface{}) { + std.Output(2, fmt.Sprint(v...)) + os.Exit(1) +} -// Stderrf is a helper function for easy formatted logging to stderr. It is analogous to Fprintf(os.Stderr). -func Stderrf(format string, v ...interface{}) { stderr.Output(2, fmt.Sprintf(format, v)) } +// Exitf is equivalent to Printf() followed by a call to os.Exit(1). +func Exitf(format string, v ...interface{}) { + std.Output(2, fmt.Sprintf(format, v...)) + os.Exit(1) +} -// Exit is equivalent to Stderr() followed by a call to os.Exit(1). -func Exit(v ...interface{}) { exit.Output(2, fmt.Sprintln(v)) } +// Exitln is equivalent to Println() followed by a call to os.Exit(1). +func Exitln(v ...interface{}) { + std.Output(2, fmt.Sprintln(v...)) + os.Exit(1) +} -// Exitf is equivalent to Stderrf() followed by a call to os.Exit(1). -func Exitf(format string, v ...interface{}) { exit.Output(2, fmt.Sprintf(format, v)) } +// Panic is equivalent to Print() followed by a call to panic(). +func Panic(v ...interface{}) { + s := fmt.Sprint(v...) + std.Output(2, s) + panic(s) +} -// Crash is equivalent to Stderr() followed by a call to panic(). -func Crash(v ...interface{}) { crash.Output(2, fmt.Sprintln(v)) } +// Panicf is equivalent to Printf() followed by a call to panic(). +func Panicf(format string, v ...interface{}) { + s := fmt.Sprintf(format, v...) + std.Output(2, s) + panic(s) +} -// Crashf is equivalent to Stderrf() followed by a call to panic(). -func Crashf(format string, v ...interface{}) { crash.Output(2, fmt.Sprintf(format, v)) } +// Panicln is equivalent to Println() followed by a call to panic(). +func Panicln(v ...interface{}) { + s := fmt.Sprintln(v...) + std.Output(2, s) + panic(s) +} diff --git a/src/pkg/log/log_test.go b/src/pkg/log/log_test.go index eb4b69a2e..f99070afb 100644 --- a/src/pkg/log/log_test.go +++ b/src/pkg/log/log_test.go @@ -7,7 +7,7 @@ package log // These tests are too simple. import ( - "bufio" + "bytes" "os" "regexp" "testing" @@ -17,7 +17,7 @@ const ( Rdate = `[0-9][0-9][0-9][0-9]/[0-9][0-9]/[0-9][0-9]` Rtime = `[0-9][0-9]:[0-9][0-9]:[0-9][0-9]` Rmicroseconds = `\.[0-9][0-9][0-9][0-9][0-9][0-9]` - Rline = `(58|60):` // must update if the calls to l.Logf / l.Log below move + Rline = `(54|56):` // must update if the calls to l.Printf / l.Print below move Rlongfile = `.*/[A-Za-z0-9_\-]+\.go:` + Rline Rshortfile = `[A-Za-z0-9_\-]+\.go:` + Rline ) @@ -30,39 +30,32 @@ type tester struct { var tests = []tester{ // individual pieces: - tester{0, "", ""}, - tester{0, "XXX", "XXX"}, - tester{Lok | Ldate, "", Rdate + " "}, - tester{Lok | Ltime, "", Rtime + " "}, - tester{Lok | Ltime | Lmicroseconds, "", Rtime + Rmicroseconds + " "}, - tester{Lok | Lmicroseconds, "", Rtime + Rmicroseconds + " "}, // microsec implies time - tester{Lok | Llongfile, "", Rlongfile + " "}, - tester{Lok | Lshortfile, "", Rshortfile + " "}, - tester{Lok | Llongfile | Lshortfile, "", Rshortfile + " "}, // shortfile overrides longfile + {0, "", ""}, + {0, "XXX", "XXX"}, + {Ldate, "", Rdate + " "}, + {Ltime, "", Rtime + " "}, + {Ltime | Lmicroseconds, "", Rtime + Rmicroseconds + " "}, + {Lmicroseconds, "", Rtime + Rmicroseconds + " "}, // microsec implies time + {Llongfile, "", Rlongfile + " "}, + {Lshortfile, "", Rshortfile + " "}, + {Llongfile | Lshortfile, "", Rshortfile + " "}, // shortfile overrides longfile // everything at once: - tester{Lok | Ldate | Ltime | Lmicroseconds | Llongfile, "XXX", "XXX" + Rdate + " " + Rtime + Rmicroseconds + " " + Rlongfile + " "}, - tester{Lok | Ldate | Ltime | Lmicroseconds | Lshortfile, "XXX", "XXX" + Rdate + " " + Rtime + Rmicroseconds + " " + Rshortfile + " "}, + {Ldate | Ltime | Lmicroseconds | Llongfile, "XXX", "XXX" + Rdate + " " + Rtime + Rmicroseconds + " " + Rlongfile + " "}, + {Ldate | Ltime | Lmicroseconds | Lshortfile, "XXX", "XXX" + Rdate + " " + Rtime + Rmicroseconds + " " + Rshortfile + " "}, } -// Test using Log("hello", 23, "world") or using Logf("hello %d world", 23) -func testLog(t *testing.T, flag int, prefix string, pattern string, useLogf bool) { - r, w, err1 := os.Pipe() - if err1 != nil { - t.Fatal("pipe", err1) - } - defer r.Close() - defer w.Close() - buf := bufio.NewReader(r) - l := New(w, nil, prefix, flag) - if useLogf { - l.Logf("hello %d world", 23) +// Test using Println("hello", 23, "world") or using Printf("hello %d world", 23) +func testPrint(t *testing.T, flag int, prefix string, pattern string, useFormat bool) { + buf := new(bytes.Buffer) + SetOutput(buf) + SetFlags(flag) + SetPrefix(prefix) + if useFormat { + Printf("hello %d world", 23) } else { - l.Log("hello", 23, "world") - } - line, err3 := buf.ReadString('\n') - if err3 != nil { - t.Fatal("log error", err3) + Println("hello", 23, "world") } + line := buf.String() line = line[0 : len(line)-1] pattern = "^" + pattern + "hello 23 world$" matched, err4 := regexp.MatchString(pattern, line) @@ -72,11 +65,22 @@ func testLog(t *testing.T, flag int, prefix string, pattern string, useLogf bool if !matched { t.Errorf("log output should match %q is %q", pattern, line) } + SetOutput(os.Stderr) } -func TestAllLog(t *testing.T) { +func TestAll(t *testing.T) { for _, testcase := range tests { - testLog(t, testcase.flag, testcase.prefix, testcase.pattern, false) - testLog(t, testcase.flag, testcase.prefix, testcase.pattern, true) + testPrint(t, testcase.flag, testcase.prefix, testcase.pattern, false) + testPrint(t, testcase.flag, testcase.prefix, testcase.pattern, true) + } +} + +func TestOutput(t *testing.T) { + const testString = "test" + var b bytes.Buffer + l := New(&b, "", 0) + l.Println(testString) + if expect := testString + "\n"; b.String() != expect { + t.Errorf("log output should match %q is %q", expect, b.String()) } } |