diff --git a/nanolog.go b/nanolog.go index 8d1003c..bd4010d 100644 --- a/nanolog.go +++ b/nanolog.go @@ -161,34 +161,64 @@ type Logger struct { Segs []string } -var ( - loggers = make([]Logger, MaxLoggers) - curLoggersIdx = new(uint32) -) +var defaultLogWriter = New() + +type LogWriter interface { + // SetWriter will set up efficient writing for the log to the output stream given. + // A raw IO stream is best. The first time SetWriter is called any logs that were + // created or posted before the call will be sent to the writer all in one go. + SetWriter(new io.Writer) error + // Flush ensures all log entries written up to this point are written to the underlying io.Writer + Flush() error + // AddLogger initializes a logger and returns a handle for future logging + AddLogger(fmt string) Handle + // Log logs to the output stream + Log(handle Handle, args ...interface{}) error +} -var ( - initBuf = &bytes.Buffer{} - w = bufio.NewWriter(initBuf) - firstSet = true -) +type logWriter struct { + initBuf *bytes.Buffer + w *bufio.Writer + firstSet bool + + writeLock sync.Locker + + loggers []Logger + curLoggersIdx *uint32 +} + +// New creates a new LogWriter +func New() LogWriter { + initBuf := &bytes.Buffer{} + return &logWriter{ + initBuf: initBuf, + w: bufio.NewWriter(initBuf), + firstSet: true, + writeLock: new(sync.Mutex), + loggers: make([]Logger, MaxLoggers), + curLoggersIdx: new(uint32), + } +} -// SetWriter will set up efficient writing for the log to the output stream given. -// A raw IO stream is best. The first time SetWriter is called any logs that were -// created or posted before the call will be sent to the writer all in one go. +// SetWriter calls LogWriter.SetWriter on the default log writer. func SetWriter(new io.Writer) error { - // grab write lock to ensure no prblems - writeLock.Lock() - defer writeLock.Unlock() + return defaultLogWriter.SetWriter(new) +} + +func (lw *logWriter) SetWriter(new io.Writer) error { + // grab write lock to ensure no problems + lw.writeLock.Lock() + defer lw.writeLock.Unlock() - if err := w.Flush(); err != nil { + if err := lw.w.Flush(); err != nil { return err } - w = bufio.NewWriter(new) + lw.w = bufio.NewWriter(new) - if firstSet { - firstSet = false - if _, err := initBuf.WriteTo(w); err != nil { + if lw.firstSet { + lw.firstSet = false + if _, err := lw.initBuf.WriteTo(lw.w); err != nil { return err } } @@ -196,28 +226,36 @@ func SetWriter(new io.Writer) error { return nil } -// Flush ensures all log entries written up to this point are written to the underlying io.Writer +// Flush calls LogWriter.Flush on the default log writer. func Flush() error { + return defaultLogWriter.Flush() +} + +func (lw *logWriter) Flush() error { // grab write lock to ensure no prblems - writeLock.Lock() - defer writeLock.Unlock() + lw.writeLock.Lock() + defer lw.writeLock.Unlock() - return w.Flush() + return lw.w.Flush() } -// AddLogger initializes a logger and returns a handle for future logging +// AddLogger calls LogWriter.AddLogger on the default log writer. func AddLogger(fmt string) Handle { + return defaultLogWriter.AddLogger(fmt) +} + +func (lw *logWriter) AddLogger(fmt string) Handle { // save some kind of string format to the file - idx := atomic.AddUint32(curLoggersIdx, 1) - 1 + idx := atomic.AddUint32(lw.curLoggersIdx, 1) - 1 if idx >= MaxLoggers { panic("Too many loggers") } l, segs := parseLogLine(fmt) - loggers[idx] = l + lw.loggers[idx] = l - writeLogDataToFile(idx, l.Kinds, segs) + lw.writeLogDataToFile(idx, l.Kinds, segs) return Handle(idx) } @@ -424,7 +462,7 @@ func next(s *string) rune { return r } -func writeLogDataToFile(idx uint32, kinds []reflect.Kind, segs []string) { +func (lw *logWriter) writeLogDataToFile(idx uint32, kinds []reflect.Kind, segs []string) { buf := &bytes.Buffer{} b := make([]byte, 4) @@ -459,7 +497,7 @@ func writeLogDataToFile(idx uint32, kinds []reflect.Kind, segs []string) { } // finally write all of it together to the output - w.Write(buf.Bytes()) + lw.w.Write(buf.Bytes()) } // helper function to have consistently formatted panics and shorter code above @@ -474,13 +512,15 @@ var ( return &temp }, } - - writeLock = new(sync.Mutex) ) -// Log logs to the output stream for the logging package +// Log calls LogWriter.Log on the default log writer. func Log(handle Handle, args ...interface{}) error { - l := loggers[handle] + return defaultLogWriter.Log(handle, args...) +} + +func (lw *logWriter) Log(handle Handle, args ...interface{}) error { + l := lw.loggers[handle] if len(l.Kinds) != len(args) { panic("Number of args does not match log line") @@ -612,9 +652,9 @@ func Log(handle Handle, args ...interface{}) error { } } - writeLock.Lock() - _, err := w.Write(*buf) - writeLock.Unlock() + lw.writeLock.Lock() + _, err := lw.w.Write(*buf) + lw.writeLock.Unlock() bufpool.Put(buf) return err diff --git a/nanolog_test.go b/nanolog_test.go index bdbd11a..94163f5 100644 --- a/nanolog_test.go +++ b/nanolog_test.go @@ -15,7 +15,6 @@ package nanolog import ( - "bufio" "bytes" "encoding/binary" "io/ioutil" @@ -45,12 +44,13 @@ func randString() string { func TestSetWriter(t *testing.T) { buf := &bytes.Buffer{} - SetWriter(buf) + lw := New() + lw.SetWriter(buf) // simulate some logging - w.WriteByte(35) + lw.(*logWriter).w.WriteByte(35) - SetWriter(&bytes.Buffer{}) + lw.SetWriter(&bytes.Buffer{}) if buf.Bytes()[0] != 35 { t.Fatalf("Expected data to be written to the underlying") @@ -62,12 +62,13 @@ func TestFlush(t *testing.T) { // test new one can be written to // probably set twice and check the middle contents buf := &bytes.Buffer{} - SetWriter(buf) + lw := New() + lw.SetWriter(buf) // simulate some logging - w.WriteByte(35) + lw.(*logWriter).w.WriteByte(35) - Flush() + lw.Flush() if buf.Bytes()[0] != 35 { t.Fatalf("Expected data to be written to the underlying") @@ -86,15 +87,14 @@ func TestAddLogger(t *testing.T) { t.Logf("Expected kinds: %v", expectedKinds) t.Logf("Expected segs: %v", expectedSegs) - // Reset to avoid running over the loggers limit - *curLoggersIdx = 0 buf := &bytes.Buffer{} - w = bufio.NewWriter(buf) - h := AddLogger(logLine) + lw := New() + lw.SetWriter(buf) + h := lw.AddLogger(logLine) //t.Log("Handle:", h) - w.Flush() + lw.Flush() out := buf.Bytes() //t.Log(string(out)) @@ -237,23 +237,19 @@ func TestAddLoggerLimit(t *testing.T) { if r := recover(); r != nil { t.Logf("Correctly got a panic: %v", r) } else { - t.Fatalf("Expected a panic but did not get one") + t.Fatal("Expected a panic but did not get one") } - - // reset so other tests can actually continue testing - *curLoggersIdx = 0 }() - t.Logf("Filling up loggers") + lw := New() + t.Log("Filling up loggers") for i := 0; i < MaxLoggers+1; i++ { - AddLogger("") + lw.AddLogger("") } } func TestParseLogLine(t *testing.T) { t.Run("Correct", func(t *testing.T) { - buf := &bytes.Buffer{} - w = bufio.NewWriter(buf) f := "foo thing bar thing %i64. Fubar %s foo. sadf %% asdf %u32 sdfasfasdfasdffds %u32." l, segs := parseLogLine(f) @@ -317,17 +313,17 @@ func TestParseLogLine(t *testing.T) { func TestLog(t *testing.T) { check := func(t *testing.T, fmtstring string, toWrite interface{}, dataLen int, checkRest func(*testing.T, []byte) bool) bool { // Reset to avoid running over the loggers limit - *curLoggersIdx = 0 buf := &bytes.Buffer{} - w = bufio.NewWriter(buf) - h := AddLogger(fmtstring) + lw := New() + lw.SetWriter(buf) + h := lw.AddLogger(fmtstring) //t.Log("Handle:", h) - w.Flush() + lw.Flush() buf.Reset() - Log(h, toWrite) + lw.Log(h, toWrite) - w.Flush() + lw.Flush() out := buf.Bytes() expectedLen := 1 + 4 + dataLen @@ -764,16 +760,12 @@ func TestLog(t *testing.T) { } }() - // Reset to avoid running over the loggers limit - *curLoggersIdx = 0 - buf := &bytes.Buffer{} - w = bufio.NewWriter(buf) - h := AddLogger("%b") + lw := New() + h := lw.AddLogger("%b") //t.Log("Handle:", h) - w.Flush() - buf.Reset() + lw.Flush() - Log(h, 42) + lw.Log(h, 42) }) t.Run("WrongNumberOfArgs", func(t *testing.T) { defer func() { @@ -784,16 +776,12 @@ func TestLog(t *testing.T) { } }() - // Reset to avoid running over the loggers limit - *curLoggersIdx = 0 - buf := &bytes.Buffer{} - w = bufio.NewWriter(buf) - h := AddLogger("%b") + lw := New() + h := lw.AddLogger("%b") //t.Log("Handle:", h) - w.Flush() - buf.Reset() + lw.Flush() - Log(h, true, 42) + lw.Log(h, true, 42) }) }) } @@ -801,11 +789,14 @@ func TestLog(t *testing.T) { var testLogHandleSink Handle func BenchmarkAddLogger(b *testing.B) { + lw := New() + + b.ResetTimer() for i := 0; i < b.N; i++ { - testLogHandleSink = AddLogger("foo thing bar thing %i64. Fubar %s foo. sadfasdf %u32 sdfasfasdfasdffds %u32.") + testLogHandleSink = lw.AddLogger("foo thing bar thing %i64. Fubar %s foo. sadfasdf %u32 sdfasfasdfasdffds %u32.") // to prevent it from overflowing the logger array - *curLoggersIdx = 0 + *lw.(*logWriter).curLoggersIdx = 0 } } @@ -815,7 +806,6 @@ var ( ) func BenchmarkParseLogLine(b *testing.B) { - w = bufio.NewWriter(ioutil.Discard) f := "The operation %s could not be completed. Wanted %u64 bar %c128 %b %{s} %{i32}" for i := 0; i < b.N; i++ { testLoggerSink, testSegmentsSink = parseLogLine(f) @@ -823,38 +813,38 @@ func BenchmarkParseLogLine(b *testing.B) { } func BenchmarkLogParallel(b *testing.B) { - w = bufio.NewWriter(ioutil.Discard) - h := AddLogger("foo thing bar thing %i64. Fubar %s foo. sadfasdf %u32 sdfasfasdfasdffds %u32.") + lw := New() + h := lw.AddLogger("foo thing bar thing %i64. Fubar %s foo. sadfasdf %u32 sdfasfasdfasdffds %u32.") args := []interface{}{int64(1), "string", uint32(2), uint32(3)} b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { - Log(h, args...) + lw.Log(h, args...) } }) } func BenchmarkLogSequential(b *testing.B) { - w = bufio.NewWriter(ioutil.Discard) - h := AddLogger("foo thing bar thing %i64. Fubar %s foo. sadfasdf %u32 sdfasfasdfasdffds %u32.") + lw := New() + h := lw.AddLogger("foo thing bar thing %i64. Fubar %s foo. sadfasdf %u32 sdfasfasdfasdffds %u32.") args := []interface{}{int64(1), "string", uint32(2), uint32(3)} b.ResetTimer() for i := 0; i < b.N; i++ { - Log(h, args...) + lw.Log(h, args...) } } func BenchmarkCompareToStdlib(b *testing.B) { b.Run("Nanolog", func(b *testing.B) { - w = bufio.NewWriter(ioutil.Discard) - h := AddLogger("foo thing bar thing %i64. Fubar %s foo. sadfasdf %u32 sdfasfasdfasdffds %u32.") + lw := New() + h := lw.AddLogger("foo thing bar thing %i64. Fubar %s foo. sadfasdf %u32 sdfasfasdfasdffds %u32.") args := []interface{}{int64(1), "string", uint32(2), uint32(3)} b.ResetTimer() for i := 0; i < b.N; i++ { - Log(h, args...) + lw.Log(h, args...) } }) b.Run("Stdlib", func(b *testing.B) { @@ -872,8 +862,8 @@ func BenchmarkInterpolations(b *testing.B) { return func(b *testing.B) { for i := 1; i <= limit; i++ { b.Run(strconv.Itoa(i), func(b *testing.B) { - w = bufio.NewWriter(ioutil.Discard) - h := AddLogger(strings.Repeat(interp, i)) + lw := New() + h := lw.AddLogger(strings.Repeat(interp, i)) args := make([]interface{}, i) for j := range args {