-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
⚠️ Fix some more races in the delegating logger #1361
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,11 +29,11 @@ type loggerPromise struct { | |
childPromises []*loggerPromise | ||
promisesLock sync.Mutex | ||
|
||
name *string | ||
tags []interface{} | ||
name *string | ||
tags []interface{} | ||
level int | ||
} | ||
|
||
// WithName provides a new Logger with the name appended | ||
func (p *loggerPromise) WithName(l *DelegatingLogger, name string) *loggerPromise { | ||
res := &loggerPromise{ | ||
logger: l, | ||
|
@@ -61,6 +61,19 @@ func (p *loggerPromise) WithValues(l *DelegatingLogger, tags ...interface{}) *lo | |
return res | ||
} | ||
|
||
func (p *loggerPromise) V(l *DelegatingLogger, level int) *loggerPromise { | ||
res := &loggerPromise{ | ||
logger: l, | ||
level: level, | ||
promisesLock: sync.Mutex{}, | ||
} | ||
|
||
p.promisesLock.Lock() | ||
defer p.promisesLock.Unlock() | ||
p.childPromises = append(p.childPromises, res) | ||
return res | ||
} | ||
|
||
// Fulfill instantiates the Logger with the provided logger | ||
func (p *loggerPromise) Fulfill(parentLogger logr.Logger) { | ||
var logger = parentLogger | ||
|
@@ -71,9 +84,12 @@ func (p *loggerPromise) Fulfill(parentLogger logr.Logger) { | |
if p.tags != nil { | ||
logger = logger.WithValues(p.tags...) | ||
} | ||
if p.level != 0 { | ||
logger = logger.V(p.level) | ||
} | ||
|
||
p.logger.lock.Lock() | ||
p.logger.Logger = logger | ||
p.logger.logger = logger | ||
p.logger.promise = nil | ||
p.logger.lock.Unlock() | ||
|
||
|
@@ -88,21 +104,75 @@ func (p *loggerPromise) Fulfill(parentLogger logr.Logger) { | |
// logger. It expects to have *some* logr.Logger set at all times (generally | ||
// a no-op logger before the promises are fulfilled). | ||
type DelegatingLogger struct { | ||
lock sync.Mutex | ||
logr.Logger | ||
lock sync.RWMutex | ||
logger logr.Logger | ||
promise *loggerPromise | ||
} | ||
|
||
// Enabled tests whether this Logger is enabled. For example, commandline | ||
// flags might be used to set the logging verbosity and disable some info | ||
// logs. | ||
func (l *DelegatingLogger) Enabled() bool { | ||
l.lock.RLock() | ||
defer l.lock.RUnlock() | ||
return l.logger.Enabled() | ||
} | ||
|
||
// Info logs a non-error message with the given key/value pairs as context. | ||
// | ||
// The msg argument should be used to add some constant description to | ||
// the log line. The key/value pairs can then be used to add additional | ||
// variable information. The key/value pairs should alternate string | ||
// keys and arbitrary values. | ||
func (l *DelegatingLogger) Info(msg string, keysAndValues ...interface{}) { | ||
l.lock.RLock() | ||
defer l.lock.RUnlock() | ||
Comment on lines
+128
to
+129
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't these be write locks? Isn't There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Lock protects the reference to the logger, not the method call. The actual logger implementation must also be threadsafe, but that needs to be done in that implementation, not here. Everything except There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we care about that, I can update the PR later to only use the lock to get the logger reference and not around the whole method call, which might improve performance slightly There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Up to you. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Then I'll keep it as-is, only protecting the reference would require to allocate a pointer so it might actually be worse performance-wise and is more complex |
||
l.logger.Info(msg, keysAndValues...) | ||
} | ||
|
||
// Error logs an error, with the given message and key/value pairs as context. | ||
// It functions similarly to calling Info with the "error" named value, but may | ||
// have unique behavior, and should be preferred for logging errors (see the | ||
// package documentations for more information). | ||
// | ||
// The msg field should be used to add context to any underlying error, | ||
// while the err field should be used to attach the actual error that | ||
// triggered this log line, if present. | ||
func (l *DelegatingLogger) Error(err error, msg string, keysAndValues ...interface{}) { | ||
l.lock.RLock() | ||
defer l.lock.RUnlock() | ||
l.logger.Error(err, msg, keysAndValues...) | ||
} | ||
|
||
// V returns an Logger value for a specific verbosity level, relative to | ||
// this Logger. In other words, V values are additive. V higher verbosity | ||
// level means a log message is less important. It's illegal to pass a log | ||
// level less than zero. | ||
func (l *DelegatingLogger) V(level int) logr.Logger { | ||
l.lock.RLock() | ||
defer l.lock.RUnlock() | ||
|
||
if l.promise == nil { | ||
return l.logger.V(level) | ||
} | ||
|
||
res := &DelegatingLogger{logger: l.logger} | ||
promise := l.promise.V(res, level) | ||
res.promise = promise | ||
|
||
return res | ||
} | ||
|
||
// WithName provides a new Logger with the name appended | ||
func (l *DelegatingLogger) WithName(name string) logr.Logger { | ||
l.lock.Lock() | ||
defer l.lock.Unlock() | ||
l.lock.RLock() | ||
defer l.lock.RUnlock() | ||
|
||
if l.promise == nil { | ||
return l.Logger.WithName(name) | ||
return l.logger.WithName(name) | ||
} | ||
|
||
res := &DelegatingLogger{Logger: l.Logger} | ||
res := &DelegatingLogger{logger: l.logger} | ||
promise := l.promise.WithName(res, name) | ||
res.promise = promise | ||
|
||
|
@@ -111,14 +181,14 @@ func (l *DelegatingLogger) WithName(name string) logr.Logger { | |
|
||
// WithValues provides a new Logger with the tags appended | ||
func (l *DelegatingLogger) WithValues(tags ...interface{}) logr.Logger { | ||
l.lock.Lock() | ||
defer l.lock.Unlock() | ||
l.lock.RLock() | ||
defer l.lock.RUnlock() | ||
|
||
if l.promise == nil { | ||
return l.Logger.WithValues(tags...) | ||
return l.logger.WithValues(tags...) | ||
} | ||
|
||
res := &DelegatingLogger{Logger: l.Logger} | ||
res := &DelegatingLogger{logger: l.logger} | ||
promise := l.promise.WithValues(res, tags...) | ||
res.promise = promise | ||
|
||
|
@@ -138,7 +208,7 @@ func (l *DelegatingLogger) Fulfill(actual logr.Logger) { | |
// the given logger before it's promise is fulfilled. | ||
func NewDelegatingLogger(initial logr.Logger) *DelegatingLogger { | ||
l := &DelegatingLogger{ | ||
Logger: initial, | ||
logger: initial, | ||
promise: &loggerPromise{promisesLock: sync.Mutex{}}, | ||
} | ||
l.promise.logger = l | ||
|
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.
Removing godocs?
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 guess it doesn't matter because its an interface method
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.
Err yeah sorry, I abused this to check if the linter correctly doesn't care about godocs of exported methods on unexported types (which it does). Since it is only visible from within the package it doesn't matter much IMO