Add SyncWriter and SyncLogger.
Chris Hines
7 years ago
0 | 0 | package log_test |
1 | 1 | |
2 | 2 | import ( |
3 | "net/url" | |
3 | 4 | "os" |
4 | 5 | |
5 | 6 | "github.com/go-kit/kit/log" |
6 | 7 | ) |
7 | 8 | |
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 | ||
8 | 25 | func ExampleContext() { |
9 | logger := log.NewLogfmtLogger(os.Stdout) | |
26 | w := log.NewSyncWriter(os.Stdout) | |
27 | logger := log.NewLogfmtLogger(w) | |
10 | 28 | logger.Log("foo", 123) |
11 | 29 | ctx := log.NewContext(logger).With("level", "info") |
12 | 30 | ctx.Log() |
1 | 1 | // |
2 | 2 | // The fundamental interface is Logger. Loggers create log events from |
3 | 3 | // 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. | |
4 | 10 | package log |
5 | 11 | |
6 | import ( | |
7 | "errors" | |
8 | "sync/atomic" | |
9 | ) | |
12 | import "errors" | |
10 | 13 | |
11 | 14 | // Logger is the fundamental interface for all log operations. Log creates a |
12 | 15 | // log event from keyvals, a variadic sequence of alternating keys and values. |
148 | 151 | func (f LoggerFunc) Log(keyvals ...interface{}) error { |
149 | 152 | return f(keyvals...) |
150 | 153 | } |
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 | } |
206 | 206 | lc.Log("k", "v") |
207 | 207 | } |
208 | 208 | } |
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 | } |