-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
writer: add CombineWriteSyncers #346
Conversation
cc @peter-edge @akshayjshah |
writer.go
Outdated
// OpenWriters combines the passed io.Writer objects that are compatible with | ||
// zapcore.WriteSyncer into a locked WriteSyncer. It returns any errors | ||
// encountered. | ||
func OpenWriters(writers ...io.Writer) (zapcore.WriteSyncer, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this function should be here - it somewhat defeats the purpose of static typing. Instead, there is a function in this package that can turn a Writer into a WriteSyncer, and then a user can pass the converted Writer to OpenWriteSyncers
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. In cases where this is needed, I'd much prefer to have users deal with it explicitly.
Please remove this function.
writer.go
Outdated
// OpenWriteSyncers combines the passed set of WriteSyncer objects into a locked | ||
// WriteSyncer. It returns any error encountered | ||
func OpenWriteSyncers(writers ...zapcore.WriteSyncer) (zapcore.WriteSyncer, error) { | ||
if len(writers) == 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I might just return nil here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer to return a discarder, purely for consistency with New
.
writer.go
Outdated
if len(writers) == 1 { | ||
return zapcore.Lock(writers[0]), nil | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: there's a lot of newlines here, maybe just remove them :)
writer.go
Outdated
// OpenWriters combines the passed io.Writer objects that are compatible with | ||
// zapcore.WriteSyncer into a locked WriteSyncer. It returns any errors | ||
// encountered. | ||
func OpenWriters(writers ...io.Writer) (zapcore.WriteSyncer, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. In cases where this is needed, I'd much prefer to have users deal with it explicitly.
Please remove this function.
writer.go
Outdated
// OpenWriteSyncers combines the passed set of WriteSyncer objects into a locked | ||
// WriteSyncer. It returns any error encountered | ||
func OpenWriteSyncers(writers ...zapcore.WriteSyncer) (zapcore.WriteSyncer, error) { | ||
if len(writers) == 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer to return a discarder, purely for consistency with New
.
writer.go
Outdated
|
||
if len(writers) == 1 { | ||
return zapcore.Lock(writers[0]), nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's actually make this improvement to zapcore.NewMultiWriteSyncer
- if it's passed just one argument, it should return that WriteSyncer
unchanged. This wrapper can then rely on that behavior.
writer_test.go
Outdated
|
||
type testWriter struct { | ||
expected []byte | ||
t *testing.T |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For future extensibility, let's make this a testing.TB
.
writer_test.go
Outdated
w, err := OpenWriters(tw) | ||
if err != nil { | ||
t.Fatalf("OpenWriters failed, error: %v", err) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead, please use the require
package, which is already included as part of testify.
require.NoError(t, err, "OpenWriters failed.")
@akshayjshah ping |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for updating! There's still a little work to do here.
writer.go
Outdated
return zapcore.AddSync(ioutil.Discard), nil | ||
} | ||
return zapcore.Lock(zapcore.NewMultiWriteSyncer(writers...)), nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove this function; I'd much rather export just Open
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@akshayjshah the whole point of this PR was to get a function in the API that takes interfaces instead of strings as output file paths.
Please see my first comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ack, my fault - I confused this with the previously-present OpenWriters
.
Since the files/writers are already open, let's rename this to CombineWriteSyncers
or ComposeWriteSyncers
instead. This function can't fail, so let's also remove the error return value.
@@ -88,6 +88,9 @@ type multiWriteSyncer []WriteSyncer | |||
// NewMultiWriteSyncer creates a WriteSyncer that duplicates its writes | |||
// and sync calls, much like io.MultiWriter. | |||
func NewMultiWriteSyncer(ws ...WriteSyncer) WriteSyncer { | |||
if len(ws) == 1 { | |||
return ws[0] | |||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needs test coverage.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added, Currently simply verifying that passing just one argument to NewMultiWriteSyncer simply returns the argument back.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✅ Excellent.
Allowing functions to take interfaces as parameters allows for better customization. For example, to have a writer that also counts the number of writes specifically made to its instance ``` type MyWriter struct { *os.File logcount int } func (w *MyWriter) Write(data []byte) (int, error) { w.logcount++ return w.File.Write(data) } func (w *MyWriter) Sync() error { return w.File.Sync() } ... func main() { f, err := os.Create(...) if err != nil { log.Fatal(err) } defer f.Close() w := &MyWriter{f, 0} ws, err := zap.OpenWriteSyncers(os.Stdout, zapcore.AddSync(w)) if err != nil { log.Fatal(err) } core := zapcore.NewCore( zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig()), ws, zapcore.DebugLevel) logger := zap.New(core) ... fmt.Printf("logged %v times to MyWriter", w.logcount) } ```
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Small nits remaining, but generally looks good.
Please don't amend your existing commits - it makes reviewing this PR more difficult. Since we always squash before merging, having lots of tiny commits in the PR won't introduce noise on master
.
writer.go
Outdated
return zapcore.AddSync(ioutil.Discard), nil | ||
} | ||
return zapcore.Lock(zapcore.NewMultiWriteSyncer(writers...)), nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ack, my fault - I confused this with the previously-present OpenWriters
.
Since the files/writers are already open, let's rename this to CombineWriteSyncers
or ComposeWriteSyncers
instead. This function can't fail, so let's also remove the error return value.
writer_test.go
Outdated
w, err := OpenWriteSyncers(tw) | ||
if err != nil { | ||
require.NoError(t, err, "OpenWriters Failed.") | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need for the conditional.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
writer_test.go
Outdated
} | ||
|
||
func (w *testWriter) Write(actual []byte) (int, error) { | ||
assert.Equal(w.t, w.expected, actual, "expected writer to write %v, wrote %v", string(w.expected), string(actual)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The failure message automatically contains the expected and actual values, so we can simplify this to
assert.Equal(w.t, w.expected, string(actual), "Unexpected write call.")
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
writer_test.go
Outdated
@@ -81,3 +81,28 @@ func TestOpen(t *testing.T) { | |||
assert.Equal(t, tt.filenames, names, "Opened unexpected files given paths %v.", tt.paths) | |||
} | |||
} | |||
|
|||
type testWriter struct { | |||
expected []byte |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we make this a string, we'll get better output on test failures and easier initialization.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
writer_test.go
Outdated
|
||
func (w *testWriter) Write(actual []byte) (int, error) { | ||
assert.Equal(w.t, w.expected, actual, "expected writer to write %v, wrote %v", string(w.expected), string(actual)) | ||
return 0, nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's fulfill the writer contract and return the correct number of bytes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
returning the length of actual
zapcore/write_syncer_test.go
Outdated
@@ -65,6 +65,12 @@ func TestAddSyncWriter(t *testing.T) { | |||
assert.NoError(t, ws.Sync(), "Unexpected error calling a no-op Sync method.") | |||
} | |||
|
|||
func TestNewMultiWriteSyncerWorksForSingleWriter(t *testing.T) { | |||
w := AddSync(&bytes.Buffer{}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can use testutils.Buffer
here instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@@ -88,6 +88,9 @@ type multiWriteSyncer []WriteSyncer | |||
// NewMultiWriteSyncer creates a WriteSyncer that duplicates its writes | |||
// and sync calls, much like io.MultiWriter. | |||
func NewMultiWriteSyncer(ws ...WriteSyncer) WriteSyncer { | |||
if len(ws) == 1 { | |||
return ws[0] | |||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✅ Excellent.
Allowing functions to take interfaces as parameters allows for better
customization.
For example, to have a writer that also counts the number of writes
specifically made to its instance
Before opening your pull request, please make sure that you've:
make test
); and finally,make lint
).Thanks for your contribution!