Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 49 additions & 14 deletions log/sync.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package log
package log

import (
"io"
Expand Down Expand Up @@ -36,24 +36,59 @@ func (l *SwapLogger) Swap(logger Logger) {
l.logger.Store(loggerStruct{logger})
}

// SyncWriter synchronizes concurrent writes to an io.Writer.
type SyncWriter struct {
mu sync.Mutex
w io.Writer
// NewSyncWriter returns a new writer that is safe for concurrent use by
// multiple goroutines. Writes to the returned writer are passed on to w. If
// another write is already in progress, the calling goroutine blocks until
// the writer is available.
//
// If w implements the following interface, so does the returned writer.
//
// interface {
// Fd() uintptr
// }
func NewSyncWriter(w io.Writer) io.Writer {
switch w := w.(type) {
case fdWriter:
return &fdSyncWriter{fdWriter: w}
default:
return &syncWriter{Writer: w}
}
}

// syncWriter synchronizes concurrent writes to an io.Writer.
type syncWriter struct {
sync.Mutex
io.Writer
}

// Write writes p to the underlying io.Writer. If another write is already in
// progress, the calling goroutine blocks until the syncWriter is available.
func (w *syncWriter) Write(p []byte) (n int, err error) {
w.Lock()
n, err = w.Writer.Write(p)
w.Unlock()
return n, err
}

// fdWriter is an io.Writer that also has an Fd method. The most common
// example of an fdWriter is an *os.File.
type fdWriter interface {
io.Writer
Fd() uintptr
}

// NewSyncWriter returns a new SyncWriter. The returned writer is safe for
// concurrent use by multiple goroutines.
func NewSyncWriter(w io.Writer) *SyncWriter {
return &SyncWriter{w: w}
// fdSyncWriter synchronizes concurrent writes to an fdWriter.
type fdSyncWriter struct {
sync.Mutex
fdWriter
}

// Write writes p to the underlying io.Writer. If another write is already in
// progress, the calling goroutine blocks until the SyncWriter is available.
func (w *SyncWriter) Write(p []byte) (n int, err error) {
w.mu.Lock()
n, err = w.w.Write(p)
w.mu.Unlock()
// progress, the calling goroutine blocks until the fdSyncWriter is available.
func (w *fdSyncWriter) Write(p []byte) (n int, err error) {
w.Lock()
n, err = w.fdWriter.Write(p)
w.Unlock()
return n, err
}

Expand Down
13 changes: 12 additions & 1 deletion log/sync_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package log_test
package log_test

import (
"bytes"
"io"
"os"
"testing"

"github.com/go-kit/kit/log"
Expand Down Expand Up @@ -70,3 +71,13 @@ func TestSyncWriterConcurrency(t *testing.T) {
w = log.NewSyncWriter(w)
testConcurrency(t, log.NewLogfmtLogger(w), 10000)
}

func TestSyncWriterFd(t *testing.T) {
_, ok := log.NewSyncWriter(os.Stdout).(interface {
Fd() uintptr
})

if !ok {
t.Error("NewSyncWriter does not pass through Fd method")
}
}