Codebase list golang-github-go-kit-kit / ad435dc
Add SyncWriter and SyncLogger. Chris Hines 7 years ago
5 changed file(s) with 177 addition(s) and 81 deletion(s). Raw diff Collapse all Expand all
00 package log_test
11
22 import (
3 "net/url"
34 "os"
45
56 "github.com/go-kit/kit/log"
67 )
78
9 func Example_stdout() {
10 w := log.NewSyncWriter(os.Stdout)
11 logger := log.NewLogfmtLogger(w)
12
13 reqUrl := &url.URL{
14 Scheme: "https",
15 Host: "github.com",
16 Path: "/go-kit/kit",
17 }
18
19 logger.Log("method", "GET", "url", reqUrl)
20
21 // Output:
22 // method=GET url=https://github.com/go-kit/kit
23 }
24
825 func ExampleContext() {
9 logger := log.NewLogfmtLogger(os.Stdout)
26 w := log.NewSyncWriter(os.Stdout)
27 logger := log.NewLogfmtLogger(w)
1028 logger.Log("foo", 123)
1129 ctx := log.NewContext(logger).With("level", "info")
1230 ctx.Log()
11 //
22 // The fundamental interface is Logger. Loggers create log events from
33 // key/value data.
4 //
5 // Concurrent Safety
6 //
7 // Applications with multiple goroutines want each log event written to the
8 // same logger to remain separate from other log events. Package log provides
9 // multiple solutions for concurrent safe logging.
410 package log
511
6 import (
7 "errors"
8 "sync/atomic"
9 )
12 import "errors"
1013
1114 // Logger is the fundamental interface for all log operations. Log creates a
1215 // log event from keyvals, a variadic sequence of alternating keys and values.
148151 func (f LoggerFunc) Log(keyvals ...interface{}) error {
149152 return f(keyvals...)
150153 }
151
152 // SwapLogger wraps another logger that may be safely replaced while other
153 // goroutines use the SwapLogger concurrently. The zero value for a SwapLogger
154 // will discard all log events without error.
155 //
156 // SwapLogger serves well as a package global logger that can be changed by
157 // importers.
158 type SwapLogger struct {
159 logger atomic.Value
160 }
161
162 type loggerStruct struct {
163 Logger
164 }
165
166 // Log implements the Logger interface by forwarding keyvals to the currently
167 // wrapped logger. It does not log anything if the wrapped logger is nil.
168 func (l *SwapLogger) Log(keyvals ...interface{}) error {
169 s, ok := l.logger.Load().(loggerStruct)
170 if !ok || s.Logger == nil {
171 return nil
172 }
173 return s.Log(keyvals...)
174 }
175
176 // Swap replaces the currently wrapped logger with logger. Swap may be called
177 // concurrently with calls to Log from other goroutines.
178 func (l *SwapLogger) Swap(logger Logger) {
179 l.logger.Store(loggerStruct{logger})
180 }
206206 lc.Log("k", "v")
207207 }
208208 }
209
210 func TestSwapLogger(t *testing.T) {
211 var logger log.SwapLogger
212
213 // Zero value does not panic or error.
214 err := logger.Log("k", "v")
215 if got, want := err, error(nil); got != want {
216 t.Errorf("got %v, want %v", got, want)
217 }
218
219 buf := &bytes.Buffer{}
220 json := log.NewJSONLogger(buf)
221 logger.Swap(json)
222
223 if err := logger.Log("k", "v"); err != nil {
224 t.Error(err)
225 }
226 if got, want := buf.String(), `{"k":"v"}`+"\n"; got != want {
227 t.Errorf("got %v, want %v", got, want)
228 }
229
230 buf.Reset()
231 prefix := log.NewLogfmtLogger(buf)
232 logger.Swap(prefix)
233
234 if err := logger.Log("k", "v"); err != nil {
235 t.Error(err)
236 }
237 if got, want := buf.String(), "k=v\n"; got != want {
238 t.Errorf("got %v, want %v", got, want)
239 }
240
241 buf.Reset()
242 logger.Swap(nil)
243
244 if err := logger.Log("k", "v"); err != nil {
245 t.Error(err)
246 }
247 if got, want := buf.String(), ""; got != want {
248 t.Errorf("got %v, want %v", got, want)
249 }
250 }
251
252 func TestSwapLoggerConcurrency(t *testing.T) {
253 testConcurrency(t, &log.SwapLogger{})
254 }
0 package log
1
2 import (
3 "io"
4 "sync"
5 "sync/atomic"
6 )
7
8 // SwapLogger wraps another logger that may be safely replaced while other
9 // goroutines use the SwapLogger concurrently. The zero value for a SwapLogger
10 // will discard all log events without error.
11 //
12 // SwapLogger serves well as a package global logger that can be changed by
13 // importers.
14 type SwapLogger struct {
15 logger atomic.Value
16 }
17
18 type loggerStruct struct {
19 Logger
20 }
21
22 // Log implements the Logger interface by forwarding keyvals to the currently
23 // wrapped logger. It does not log anything if the wrapped logger is nil.
24 func (l *SwapLogger) Log(keyvals ...interface{}) error {
25 s, ok := l.logger.Load().(loggerStruct)
26 if !ok || s.Logger == nil {
27 return nil
28 }
29 return s.Log(keyvals...)
30 }
31
32 // Swap replaces the currently wrapped logger with logger. Swap may be called
33 // concurrently with calls to Log from other goroutines.
34 func (l *SwapLogger) Swap(logger Logger) {
35 l.logger.Store(loggerStruct{logger})
36 }
37
38 // SyncWriter synchronizes concurrent writes to an io.Writer.
39 type SyncWriter struct {
40 mu sync.Mutex
41 w io.Writer
42 }
43
44 // NewSyncWriter returns a new SyncWriter. The returned writer is safe for
45 // concurrent use by multiple goroutines.
46 func NewSyncWriter(w io.Writer) *SyncWriter {
47 return &SyncWriter{w: w}
48 }
49
50 // Write writes p to the underlying io.Writer. If another write is already in
51 // progress, the calling goroutine blocks until the SyncWriter is available.
52 func (w *SyncWriter) Write(p []byte) (n int, err error) {
53 w.mu.Lock()
54 n, err = w.w.Write(p)
55 w.mu.Unlock()
56 return n, err
57 }
58
59 // syncLogger provides concurrent safe logging for another Logger.
60 type syncLogger struct {
61 mu sync.Mutex
62 logger Logger
63 }
64
65 // NewSyncLogger returns a logger that synchronizes concurrent use of the
66 // wrapped logger. When multiple goroutines use the SyncLogger concurrently
67 // only one goroutine will be allowed to log to the wrapped logger at a time.
68 // The other goroutines will block until the logger is available.
69 func NewSyncLogger(logger Logger) Logger {
70 return &syncLogger{logger: logger}
71 }
72
73 // Log logs keyvals to the underlying Logger. If another log is already in
74 // progress, the calling goroutine blocks until the syncLogger is available.
75 func (l *syncLogger) Log(keyvals ...interface{}) error {
76 l.mu.Lock()
77 err := l.logger.Log(keyvals...)
78 l.mu.Unlock()
79 return err
80 }
0 package log_test
1
2 import (
3 "bytes"
4 "io"
5 "testing"
6
7 "github.com/go-kit/kit/log"
8 )
9
10 func TestSwapLogger(t *testing.T) {
11 var logger log.SwapLogger
12
13 // Zero value does not panic or error.
14 err := logger.Log("k", "v")
15 if got, want := err, error(nil); got != want {
16 t.Errorf("got %v, want %v", got, want)
17 }
18
19 buf := &bytes.Buffer{}
20 json := log.NewJSONLogger(buf)
21 logger.Swap(json)
22
23 if err := logger.Log("k", "v"); err != nil {
24 t.Error(err)
25 }
26 if got, want := buf.String(), `{"k":"v"}`+"\n"; got != want {
27 t.Errorf("got %v, want %v", got, want)
28 }
29
30 buf.Reset()
31 prefix := log.NewLogfmtLogger(buf)
32 logger.Swap(prefix)
33
34 if err := logger.Log("k", "v"); err != nil {
35 t.Error(err)
36 }
37 if got, want := buf.String(), "k=v\n"; got != want {
38 t.Errorf("got %v, want %v", got, want)
39 }
40
41 buf.Reset()
42 logger.Swap(nil)
43
44 if err := logger.Log("k", "v"); err != nil {
45 t.Error(err)
46 }
47 if got, want := buf.String(), ""; got != want {
48 t.Errorf("got %v, want %v", got, want)
49 }
50 }
51
52 func TestSwapLoggerConcurrency(t *testing.T) {
53 testConcurrency(t, &log.SwapLogger{})
54 }
55
56 func TestSyncLoggerConcurrency(t *testing.T) {
57 var w io.Writer
58 w = &bytes.Buffer{}
59 logger := log.NewLogfmtLogger(w)
60 logger = log.NewSyncLogger(logger)
61 testConcurrency(t, logger)
62 }
63
64 func TestSyncWriterConcurrency(t *testing.T) {
65 var w io.Writer
66 w = &bytes.Buffer{}
67 w = log.NewSyncWriter(w)
68 testConcurrency(t, log.NewLogfmtLogger(w))
69 }