-
Notifications
You must be signed in to change notification settings - Fork 111
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
NewRelicContextFormatter update to support adding a stack trace #1168
Conversation
|
newrelic/api/log.py
Outdated
def __init__(self, *args, **kwargs): | ||
super(NewRelicContextFormatter, self).__init__() | ||
def __init__(self, *args, stack_trace_limit: Optional[int] = 0, **kwargs): | ||
""" | ||
:param Optional[int] stack_trace_limit: | ||
Specifies the maximum number of frames to include for stack traces. | ||
Defaults to `0` to suppress stack traces. | ||
Setting this to `None` will make it so all available frames are included. | ||
""" | ||
super(NewRelicContextFormatter, self).__init__(*args, **kwargs) |
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.
Reviewer note: *args
and **kwargs
were not being passed through to logging.Formatter
init. This might have been a bug.
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 likely true.
newrelic/api/log.py
Outdated
formatted["error.expected"] = expected | ||
|
||
if self._stack_trace_limit is None or self._stack_trace_limit > 0: | ||
stack_trace: Optional[str] |
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.
stack_trace: Optional[str] |
We still have Python 2 wheels at the moment, so this line is a syntax issue. Should be removed.
assert filename.endswith("/test_logs_in_context.py") | ||
assert isinstance(line_number, int) | ||
assert isinstance(stack_trace, str) | ||
assert stack_trace == expected_stack_trace |
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.
assert stack_trace == expected_stack_trace | |
assert stack_trace and stack_trace == expected_stack_trace |
Same as above
assert filename.endswith("/test_logs_in_context.py") | ||
assert isinstance(line_number, int) | ||
assert isinstance(stack_trace, str) | ||
assert stack_trace == expected_stack_trace |
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.
assert stack_trace == expected_stack_trace | |
assert stack_trace and stack_trace == expected_stack_trace |
I like this strategy for checking stack traces, but would like to make sure they're not None or empty just in case.
newrelic/api/log.py
Outdated
def __init__(self, *args, **kwargs): | ||
super(NewRelicContextFormatter, self).__init__() | ||
def __init__(self, *args, stack_trace_limit: Optional[int] = 0, **kwargs): | ||
""" | ||
:param Optional[int] stack_trace_limit: | ||
Specifies the maximum number of frames to include for stack traces. | ||
Defaults to `0` to suppress stack traces. | ||
Setting this to `None` will make it so all available frames are included. | ||
""" | ||
super(NewRelicContextFormatter, self).__init__(*args, **kwargs) |
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 likely true.
newrelic/api/log.py
Outdated
|
||
@classmethod | ||
def log_record_to_dict(cls, record): | ||
expected = is_expected_error(exc_info) |
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.
An issue here, we're taking log_record_to_dict()
which is a classmethod and changing it to an instance method. This would be a breaking change to the public API.
The same goes for format_exc_info()
changing from a top level function to an instance method.
We definitely have customers who use these functions, or at least log_record_to_dict()
without instantiating a class for convenience.
I'll push a commit with my proposal, but I'd like to convert these both into class functions, and export the names to the top level of the module so they can be used like functions. I think passing in the stack trace limit as an argument to the functions should preserve the most backwards compatibility for other users.
newrelic/api/log.py
Outdated
@@ -69,11 +55,46 @@ def safe_json_encode(obj, ignore_string_types=False, **kwargs): | |||
class NewRelicContextFormatter(logging.Formatter): | |||
DEFAULT_LOG_RECORD_KEYS = frozenset(set(vars(logging.LogRecord("", 0, "", 0, "", (), None))) | {"message"}) | |||
|
|||
def __init__(self, *args, **kwargs): | |||
super(NewRelicContextFormatter, self).__init__() | |||
def __init__(self, *args, stack_trace_limit: Optional[int] = 0, **kwargs): |
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.
def __init__(self, *args, stack_trace_limit: Optional[int] = 0, **kwargs): | |
def __init__(self, *args, stack_trace_limit=0, **kwargs): |
We still have Python 2 wheels at the moment, so this line is a syntax issue. Should be removed.
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 having a default keyword argument after *args isn't supported in Python 2 at all, I'll remove it for now like we do for our trace APIs and just attempt to pop it off the kwargs list if it exists.
I've several pushed commits for both @fritzdj and our team to review. Here's the summary of my changes.
I think this will achieve the most flexible compatibility possible with existing customer uses, while making this feature possible. It's worth noting that in all cases the default behavior will remain unchanged, where no stack traces are attached. |
Thanks @TimPansino, these changes look good to me! Good catches on the breaking change to the public API and Python 2 compatibility issues. |
We extend the class OurNewRelicContextFormatter(NewRelicContextFormatter):
@classmethod
def log_record_to_dict(cls, record) -> dict[str, Any]:
log_dict = super().log_record_to_dict(record)
# ... add more stuff to log_dict here ...
return log_dict When we updated to We already fixed this from our side, by taking in |
Overview
Currently the
NewRelicContextFormatter
implementation doesn't support including the stack trace. It would be convenient if there was an option because it would allow stack frames to be included as an attribute for an log in NR (instead of them being broken up by frame, which is the default if structured logs are not used). This PR is adding opt-in support for including stack traces.Related Issue
#1169