Skip to content
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 duplicated logging issue in Log4j2Logger #36

Merged
merged 3 commits into from Oct 26, 2020

Conversation

NathanQingyangXu
Copy link
Contributor

The issue was found in a ticket of Hibernate: https://hibernate.atlassian.net/browse/HHH-14139

See the ticket for details.

@@ -52,7 +52,7 @@ protected void doLog(final Level level, final String loggerClassName, final Obje
if (this.logger.isEnabled(translatedLevel)) {
try {
this.logger.logMessage(loggerClassName, translatedLevel, null,
(parameters == null || parameters.length == 0) ? this.messageFactory.newMessage(message) : this.messageFactory.newMessage(String.valueOf(message), parameters),
(parameters == null || parameters.length == 0) ? this.messageFactory.newMessage(String.valueOf(message)) : this.messageFactory.newMessage(String.valueOf(message), parameters),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without the above fix, the following newMessage() overloaded method would be chosen:

    public Message newMessage(Object message) {
        return new ObjectMessage(message);
    }

That explains why the following logging statement

log.trace("hello {}");

would end up with the following strange output:

hello hello {}

because the object per se would be chosen as the sole parameter for the placeholder (i.e. {}):

public class ObjectMessage implements Message, StringBuilderFormattable {
... ...
    public Object[] getParameters() {
        return new Object[]{this.obj};
    }
... ...

@NathanQingyangXu NathanQingyangXu changed the title fix issue in Log4j2Logger fix duplicated logging issue in Log4j2Logger Sep 3, 2020
@jamezp
Copy link
Member

jamezp commented Oct 21, 2020

I'm not able to reproduce this with log4j2. Is it only reproducible with the chain of log4j2, slf4j and logback?

@NathanQingyangXu
Copy link
Contributor Author

NathanQingyangXu commented Oct 21, 2020 via email

@jamezp
Copy link
Member

jamezp commented Oct 21, 2020

I found the issue while working on a hibernate-orm bug. Seems an obvious bug from code logic. Do you require me to attach a testing case for the PR?

No, there is no test required. I was just trying to reproduce it as I added some tests. Since it does allow objects to be passed we likely need to allow it. I just need to read about how log4j uses it.

@@ -52,7 +52,7 @@ protected void doLog(final Level level, final String loggerClassName, final Obje
if (this.logger.isEnabled(translatedLevel)) {
try {
this.logger.logMessage(loggerClassName, translatedLevel, null,
(parameters == null || parameters.length == 0) ? this.messageFactory.newMessage(message) : this.messageFactory.newMessage(String.valueOf(message), parameters),
(parameters == null || parameters.length == 0) ? this.messageFactory.newMessage(String.valueOf(message)) : this.messageFactory.newMessage(String.valueOf(message), parameters),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should do something like:

this.messageFactory.newMessage((message instanceof String) ? (String) message : message)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or actually maybe should use a CharSequence instead of a string.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the gist of this PR is to maintain consistency in the following two cases:

  • non-empty parameter list is provided;
  • empty parameter list is provided;

We solved the first case by

this.messageFactory.newMessage(String.valueOf(message), parameters)

because of the following method signature:

    public Message newMessage(String message, Object... params) {
        return new MessageFormatMessage(message, params);
    }

I think we should strive to invoke the same method in case 2. For that reason, I think the original PR makes sense.
String.valueOf() is much stronger than String casting and I do think its usage should be retained consistently.

What is the issue in this PR?
Because we wanna avoid invoking this.messageFactory.newMessage(message) for it would lead to the following statement:

public Message newMessage(Object message) {
        return new ObjectMessage(message);
    }

However, in log4j2 (not in log4j), there exists the following methods in org.apache.logging.log4j.message.ObjectMessage class:

    public Object getParameter() {
        return this.obj;
    }

    public Object[] getParameters() {
        return new Object[]{this.obj};
    }

That is the root cause of the bug. Suppose the following message is provided without any parameter:

binding parameter [someName] as [VARCHAR] - [{}]

the message per se would be treated as the parameter for the {} placeholder and the final logging statement would be:

binding parameter [someName] as [VARCHAR] - [binding parameter [someName] as [VARCHAR] - [{}]]

Thus the bug and the gist of this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another approach is the following statement:

this.messageFactory.newMessage(String.valueOf(message), parameters == null ? new String[0] : parameters),

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I adopted the above approach. It enjoys the benefit that same method would be used consistently regardless of whether parameter is empty or not.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for all the delays on this. I finally had a chance to look at what the ObjectMessage does and I think your initial approach was correct. I apologize for that. If we always use the MessageFormatMessage then it's processed through a formatter where as just using the SimpleMessage just the message will be returned and won't be processed through a formatter.

@NathanQingyangXu
Copy link
Contributor Author

I'm not able to reproduce this with log4j2. Is it only reproducible with the chain of log4j2, slf4j and logback?

Originally I faced the same issue to reproduce it. Please see the comments in https://hibernate.atlassian.net/browse/HHH-14139 on how to reproduce it.
When you can reproduce it, the bug is self-explanatory.

@NathanQingyangXu
Copy link
Contributor Author

NathanQingyangXu commented Oct 23, 2020 via email

@jamezp
Copy link
Member

jamezp commented Oct 26, 2020

Sorry for the delay again @NathanQingyangXu. Yes I think your original idea was best IMO. I really appreciate all the work and detailed descriptions on this!

@NathanQingyangXu
Copy link
Contributor Author

Sorry for the delay again @NathanQingyangXu. Yes I think your original idea was best IMO. I really appreciate all the work and detailed descriptions on this!

I reverted back the original status. My fault to create the PR based on my master branch so I cannot force push and for that reason I've to commit the third time to have it reverted back.

@jamezp jamezp merged commit ec1d26a into jboss-logging:master Oct 26, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants