-
Notifications
You must be signed in to change notification settings - Fork 652
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
Use LogRecord.getMessage
to get OTLP body
#4216
Use LogRecord.getMessage
to get OTLP body
#4216
Conversation
6a71720
to
39a77ea
Compare
Some extra notes: In my opinion, the most compatible thing to do would be to always call Note that this isn't just an issue with the Those search results also include cases where |
LogRecord.getMessage
to get OLTP bodyLogRecord.getMessage
to get OTLP body
6c32dc7
to
00fd1f4
Compare
@pR0Ps Please add a changelog entry |
00fd1f4
to
54fddfc
Compare
@xrmx done! Let me know if you want me to change it in some way |
I'm curious as why you think this is the case? According to
I'm not sure if this behavior that we want since we want to preserve the original |
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.
Question about getMessage
That documentation states that using a user-defined class as the message can define a custom format string, not a custom message. This means that the expected behavior if you call More concretely, this means that the following two log messages should both be just "test" (which they are without being instrumented by open telemetry): class CustomFormat:
def __str__(self):
return "%s"
logging.error(CustomFormat(), "test")
logging.error("%s", "test") As currently written, open telemetry sets
I don't know if there's a good way around that since a log's message is basically always expected to be a string due to the fact that the logging functions expect to be provided a string to format the data into in order to produce the message. From
Since However, since the docs also mention that no formatting is performed when no args are provided, the following might be a good middle-ground where it still dumps the if record.args:
body = record.getMessage()
else:
body = record.msg This would still break in the case of using a user-defined class as a format string with no args (ie. |
I think this is a good middle ground. We should preserve original body as much as possible unless if it defies what users "commonly" expect out of Python logging library (formatting is expected to return string).
Could you be more specific about what would break? Are you referring to the otlp exporter encoding types that are not supported? If so, I believe that this would be expected behavior to error out since it is technically not following the typing of AnyValue of log body/otlp log body as defined by the spec. The only thing that we probably should change is to catch the exception and log a warning instead (since we do not want to crash the application during runtime for a misuse of the sdk). |
I don't know for sure, but I would hope that most users would expect that the logs are going to be strings. Only if you are using the logging system improperly is the As an example of this, consider: >>> logging.error(["contains", "a", "%s"])
ERROR:root:['contains', 'a', '%s'] What's actually happening is the first arg is stringified to a format string due to:
Then, the rule:
is applied so nothing else happens to it, resulting in a message of You can see this more clearly if you slightly modify the example to add an argument: >>> logging.error(["contains", "a", "%s"], "string")
ERROR:root:['contains', 'a', 'string']
It's definitely the case that there's code that just dumps objects into logging calls without a format string (ie. presumably the author of #3343 does this) which is why I proposed the middle-ground solution I mentioned above, but I don't think it's an intended way to use the logging system.
The following code will break with the middle-ground solution I mentioned above: class CustomFormat:
def __str__(self):
return "%s"
logging.error(CustomFormat()) This is because it would set Honestly, given all the nuance, the potential for breakage, and the semi-random "this body is an object because there are no args, this body is a formatted string because there were args" aspects of the above, personally I would avoid trying to be fancy and just revert #3343 . It's supporting a use-case that, in my reading of the docs, just isn't valid. According to the docs, the |
@lzchen I have applied the middle ground proposal I mentioned above including a comment explaining it and link back to this PR. I would still recommend reverting #3343 instead, but if it's a requirement that the use-case of not stringifying objects passed via EDIT: I've pushed a commit to another branch that implements the "always call |
Hmm, this might need a longer discussion. Would you be able to join the weekly Python SIG meeting and bring up this topic? We meet every Thursday 9am PST. |
@lzchen I've sent you an email to follow up |
I'm not sure what email you've sent it to and I don't usually respond to emails sent directly. Feel free to ping me on slack if you'd like to discuss further. It is also preferred to bring this topic up in the Python SIG, as there are other people who might have opinions and useful perspectives on this. |
fd9bd56
to
6f28b09
Compare
Thanks for the contribution. Would it be possible to add some tests? |
This improves compatibility with logging libraries that use `logging.setLogRecordFactory()` or patching to customize the message formatting of the created `LogRecord`s. It does this by using the processed `LogRecord`'s `getMessage()` method to get the body text instead of using `record.msg % record.args`. Also adds "getMessage" to the list of reserved attributes so if the customization is done by patching the `getMessage` function on the `LogRecord` directly (instead of using a subclass), it's not accidentally treated as an attribute.
…improve compatibility
6f28b09
to
1a2d984
Compare
@lzchen Added a test for the version that only skips calling I'll submit the version that always calls |
Description
This improves compatibility with logging libraries that use
logging.setLogRecordFactory()
or patching to customize the message formatting of the createdLogRecord
s. It does this by using the processedLogRecord
'sgetMessage()
method to get the body text instead of usingrecord.msg % record.args
.Also adds
"getMessage"
to the list of reserved attributes so if the customization is done by patching thegetMessage
function on theLogRecord
directly (instead of using a subclass), it's not accidentally treated as an attribute.Related to: #3343
Type of change
How Has This Been Tested?
I've tested this change using the
bracelogger
library, which implements custom formatting by patchinggetMessage()
. I made sure that logs produced from bothbracelogger
and the stdlib produced correct OTLP logs.To verify:
myapp.py
Produces:
Previously the same command would cause the error:
Does This PR Require a Contrib Repo Change?
Checklist: