-
Notifications
You must be signed in to change notification settings - Fork 2
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
TECH-4787: support progname and message block #34
TECH-4787: support progname and message block #34
Conversation
7c53219
to
aec156c
Compare
…or all 3: progname, context, message block
…progname or message block is not a String
8cb6574
to
2174d3d
Compare
8671f25
to
8058449
Compare
8058449
to
c202488
Compare
error: Logger::Severity::ERROR, | ||
fatal: Logger::Severity::FATAL, | ||
unknown: Logger::Severity::UNKNOWN | ||
}.freeze |
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 hit the breaking point where I needed to go meta to avoid copying too much code!
def unknown(message = nil, context = {}) | ||
add(Logger::Severity::UNKNOWN, message.nil? && block_given? ? yield : message, **context) | ||
LOG_LEVEL_NAMES_TO_SEVERITY.each do |method_name, log_level| | ||
class_eval(<<~EOS, __FILE__, __LINE__ + 1) |
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.
@madelynrr I made a matrix on paper and added all the test cases first. Then picked them off one at a time here. This is about the 12th refactor to get it as clean as I could.
lib/contextual_logger.rb
Outdated
add(#{log_level}, nil, context, &block) | ||
elsif block | ||
add(#{log_level}, nil, context) do | ||
"\#{ContextualLogger.normalize_message(arg)}: \#{ContextualLogger.normalize_message(block.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.
I wanted to support the full matrix (as enumerated in the CHANGELOG) but I was stymied about how to support all 3 of progname
, context
, and message
when the class add
interface only has 2 parameters after the level
. My first work-around was to glue progname
to message
just like ::Logger
does, with :
. But that wasn't exposing progname
to the formatter. So I went with a different approach of storing progname
into the context with the :progname
key. Stealing that key seems fine since our JSON formatter will wind up putting it in there anyway (in addition to prepending the progname
to the message with :
as a separator).
def add(init_severity, message = nil, init_progname = nil, **context) # Ruby will prefer to match hashes up to last ** argument | ||
severity = init_severity || UNKNOWN | ||
def add(arg_severity, arg1 = nil, arg2 = nil, **context) # Ruby will prefer to match hashes up to last ** argument | ||
severity = arg_severity || UNKNOWN |
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.
@jebentier The rewrite below avoids any variable reassignment. Each if/else branch assigns the locals message
and progname
. And I renamed the formal args to just arg1
and arg2
since it was so confusing to call one progname
when half the time it had a message in it!
if arg.nil? | ||
add(#{log_level}, nil, context, &block) | ||
elsif block | ||
add(#{log_level}, nil, context.merge(progname: arg), &block) |
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.
Why did we go with the approach here of packing and unpacking the progname
from the context when we can still pass it cleanly through arg1
? By doing this (un)packing, we're adding back the bug that the context is populated with unnecessary information that the formatter isn't aware of, leading to potential duplicate data being formatted by the formatter.
For example with the code as it is, if I have a formatter that looks like the following:
-> (severity, timestamp, progname, message_with_context) do
message_with_context.merge(log_progname: progname).to_json
end
And then I call logger.info('progname', some_context: 'hello') { 'hello world' }
, the log line will look like
{ 'message': 'hello world', 'progname': 'progname', 'some_context': 'hello', 'log_progname': 'progname' }
Is it possible to avoid this?
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.
Why did we go with the approach here of packing and unpacking the progname from the context when we can still pass it cleanly through arg1
Because we can't pass it through arg1 when there's a block. (The standard add
method only looks at the bock when arg1 is nil
.) That's the bug that caused this ticket. I listed the constraints the comments just above.
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.
And we can't call the block first, because the log level needs to be checked.
And we can't check the log level because broadcast has several of them, and they hidden from us.
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.
Ahh, I definitely got confused by which arg is which in the original implementation. I just went back to the original method to look it up. Can you add to the comment's what the original add
interface is? def add(severity, message = nil, progname = 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.
Done. 822f94c
@@ -106,7 +106,7 @@ def add(arg_severity, arg1 = nil, arg2 = nil, **context) # Ruby will prefer to | |||
if arg1.nil? | |||
if block_given? | |||
message = yield | |||
progname = arg2 || context[:progname] || @progname | |||
progname = arg2 || context.delete(:progname) || @progname |
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.
@jebentier I added this back as I had it, so that the tunneled progname
doesn't live on in the context hash.
Usually I'd avoid delete
since that mutates the context
here. But it's really succinct, and performs best...and I figured no one else but our code above would be putting a :progname
key into the context and calling add
in the block form with a context hash! LMK if you disagree.
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 this is okay for now. Maybe something to not in the README is that through this implementation you're able to specify progname
using two different method (flip it to a feature 😉 )
logger.info('request') { message }
and logger.info(progname: 'request') { message }
will pass progname down to the formatter.
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.
@jebentier Can you take a look at the README restructure here: 549e570
17b8f29
to
549e570
Compare
TECH-4787
[0.10.0] - Unreleased
Added
progname
,message
, andcontext
:including where
progname
andmessage
are types other thanString
.Fixed
logger.info(progname) { message }
was dropping the message and only showingprogname
.(this could have been slow).