-
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
Ability to build logger with custom Sink #572
Ability to build logger with custom Sink #572
Conversation
925dfda
to
e41e067
Compare
Codecov Report
@@ Coverage Diff @@
## master #572 +/- ##
==========================================
- Coverage 97.47% 97.23% -0.24%
==========================================
Files 39 40 +1
Lines 2017 2063 +46
==========================================
+ Hits 1966 2006 +40
- Misses 43 49 +6
Partials 8 8
Continue to review full report at Codecov.
|
d38dd8c
to
1bc0592
Compare
1bc0592
to
5398132
Compare
The core of the change can be found here https://github.com/uber-go/zap/pull/572/files#diff-dc03dbc53655fac7a4c8d16b1b4144c1L59 |
5398132
to
9b29574
Compare
3db4940
to
08c8e44
Compare
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.
Thank you for this PR, @dimroc! I'd like to discuss high-level direction with a few of the other maintainers before getting into the details of the code.
config.go
Outdated
// passed options. For example, the option "stdout" will construct a logger | ||
// that routes output to the stdout sink, but "/filepath" will fallback | ||
// to writing to file. | ||
func (cfg Config) BuildWithSinks(sm map[string]Sink, opts ...Option) (*Logger, 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.
This is a nice idea - I'd like to make this portion of zap more extensible. I also like that this approach avoids adding global state.
However, this works very differently from extending encoders: for that, we use a global registry and a top-level RegisterEncoder
function. For consistency, I'd actually prefer to add another global registry for sinks. (Generally, I much prefer dependency injection, but I think consistency is more important here.)
@prashantv and @abhinav, what do you think?
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 think I'd prefer consistency as well here, and have a global registry for both encoders and sinks.
If we want users to be able to specify the exact registries, we'll likely want to add a single method that accepts both registries rather than a method for each.
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 the feedback @akshayjshah @prashantv. I never saw RegisterEncoder
but after seeing it, your requests make complete sense. I have since added a commit with RegisterSink
to mimc that functionality. If @abhinav feels strongly about reverting back to the dependency injection style, no worries, we can just revert one commit.
writer.go
Outdated
// Don't close standard out. | ||
if sink, err := NewSink(path); err == nil { | ||
writers = append(writers, sink) | ||
closers = append(closers, sink) |
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 kept the two lists, writers
and closers
, because I didn't want to be too aggressive with the refactor and change the return value of
func open(paths []string) ([]zapcore.WriteSyncer, func(), error)
to
func open(paths []string) ([]Sink, func(), error)
It would have rippled out to affect a few other funcs and we would have needed an array of []zapcore.WriteSyncer
for CombineWriteSyncers
anyways.
1773000
to
e2b8087
Compare
writer.go
Outdated
// Don't close standard out. | ||
if sink, err := newSink(path); err == nil { | ||
writers = append(writers, sink) | ||
closers = append(closers, sink) |
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 kept the two lists, writers
and closers
, because I didn't want to be too aggressive with the refactor and change the return value of
func open(paths []string) ([]zapcore.WriteSyncer, func(), error)
to
func open(paths []string) ([]Sink, func(), error)
It would have rippled out to affect a few other funcs and we would have needed an array of []zapcore.WriteSyncer
for CombineWriteSyncers
anyways.
Akin to RegisterEncoder
e2b8087
to
de223e7
Compare
@akshayjshah |
I'd also like to see this merged. Any update? |
Would love to see this merged, it'd be very helpful for us. What's left to be done? |
This is great. I'm going to push a commit tomorrow to un-export a few of the helper structs, but the core of this is excellent - thanks for the work and the refactoring, @dimroc. To everyone waiting on this, thanks for your patience. Work and fatherhood distracted me for the past few months. Expect a release with this functionality by the end of the week. |
@dimroc did all the work for this feature already. This commit un-exports NopCloserSink (since we're only using it in tests), adds an unexported function to reset the global registry of sinks, and includes the user-supplied key in the no-such-sink error message.
Merged! I'm going to open PRs to prep a release tomorrow. We use this package extensively internally; for my own sanity and family life, I'm not going to cut a release on a Friday just in case something goes horribly wrong. I'll tag a new relase first thing monday morning. |
} | ||
|
||
func resetSinkRegistry() { | ||
_sinkMutex.Lock() |
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.
Should we have this mutex? It seems like we'd be masking a race where loggers might fail to build because the scheme isn't recognized by the sink?
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.
Certainly possible. We lock around the encoder registry; IMO, consistency with that API is important here.
|
||
// RegisterSink adds a Sink at the given key so it can be referenced | ||
// in config OutputPaths. | ||
func RegisterSink(key string, sinkFactory func() (Sink, error)) 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.
This will work well for static sinks, but I think we might need something more dynamic (e.g., a prefix match, or a URI scheme match). E.g., even for something like syslog, we'd probably need more info like port. I'd prefer to have less APIs exposed, so one that is more flexible might be better
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.
That's a good point - I hadn't thought about this use case.
I'd prefer to avoid a URI-based scheme here, since the current special-cased sinks don't use file://
. Prefix match would work, as long as we ensure that we don't get any conflicting rules.
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.
Actually, URIs with special cases for stdout
and stderr
might be best 🤔
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.
// in config OutputPaths. | ||
func RegisterSink(key string, sinkFactory func() (Sink, error)) error { | ||
_sinkMutex.Lock() | ||
defer _sinkMutex.Unlock() |
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.
optional nit: blank after defer
to separate it from the rest of the logic (same for newSink
)
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.
👍 will do.
I'm either the first or the least intelligent person to attempt to use this custom sink stuff. Took me quite some time and digging through issues/code to figure this out. To anyone wondering why the example doesn't work, it has changed slightly due to #606:
|
Using the same global-registry pattern that we're using for encoders, let users register new sinks (like the built-in `stdout` and `stderr`).
Introduce the ability to pass in
Sink
s enabling one to customize a logger'sWriter
(technicallyWriteSyncer
) andCloser
.syslog
logic likesyslog.Dial
outside ofwriter.go#Open
(syslog writer #506)writer.go#Open
Sample usage: