[ 
https://issues.apache.org/jira/browse/LOG4J2-3680?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Peter De Maeyer updated LOG4J2-3680:
------------------------------------
    Description: 
When we updated from 2.20.0 to 2.22.1 (I had to pick 2.22.0 in the *Affects 
Version/s* because 2.22.1 does not exists in the Jira project), we noticed a 
regression in the serilialization of log events. The below test succeeds with 
2.20.0, but fails with 2.22.1. I had a look at the source code, and I think 
it's because of changes in the {{ObjectMessage.read/writeObject}} 
implementation.

{code:java}
import static org.apache.logging.log4j.LogManager.getLogger;
import static org.junit.jupiter.api.Assertions.assertEquals;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.ErrorHandler;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.Logger;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class Log4jSerializationTest {

    private final Logger logger = (Logger) 
getLogger(Log4jSerializationTest.class);

    private final RecordingAppender appender = new 
RecordingAppender(Log4jSerializationTest.class.getName());

    @BeforeEach
    void addRecordingAppender() {
        appender.start();
        logger.addAppender(appender);
    }

   @AfterEach
    void removeRecordingAppender() {
        logger.removeAppender(appender);
        appender.stop();
    }

    @Test
    void logEventSerialization() {
        RuntimeException exception = new RuntimeException("induced by test");
        logger.error(exception, exception);
        LogEvent event = appender.getEvents().get(0);
        assertEquals(RuntimeException.class.getName() + ": induced by test", 
event.getMessage().getFormattedMessage());
    }

    static class RecordingAppender implements Appender {

        private final List<LogEvent> events = new ArrayList();
        private State state = State.STOPPED;
        private final String name;
        private ErrorHandler handler;

        public RecordingAppender(String name) {
            this.name = name;
        }

        @Override
        public State getState() {
            return state;
        }

        @Override
        public void initialize() {
            state = State.INITIALIZED;
        }

        @Override
        public boolean isStarted() {
            return state == State.STARTED;
        }

        @Override
        public boolean isStopped() {
            return !isStarted();
        }

        @Override
        public void start() {
            events.clear();
            state = State.STARTED;
        }

        @Override
        public void stop() {
            events.clear();
            state = State.STOPPED;
        }

        @Override
        public synchronized void append(LogEvent event) {
            // The event is a mutable one, Log4j2 emits the same event instance 
but changes the message each time.
            // If we want to hold on to the event details, we need to clone the 
event.
            // LogEvent is not an instance of Cloneable, but it IS an instance 
of Serializable.
            // So we clone by serializing and deserializing.
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            try (ObjectOutputStream objectOut = new ObjectOutputStream(out)) {
                objectOut.writeObject(event);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            LogEvent clone;
            try (ObjectInputStream objectInput = new ObjectInputStream(new 
ByteArrayInputStream(out.toByteArray()))) {
                clone = (LogEvent) objectInput.readObject();
            } catch (IOException | ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
            events.add(clone);
        }

        @Override
        public ErrorHandler getHandler() {
            return handler;
        }

        @Override
        public Layout<? extends Serializable> getLayout() {
            return null;
        }

        @Override
        public String getName() {
            return name;
        }

        @Override
        public boolean ignoreExceptions() {
            return true;
        }

        @Override
        public void setHandler(ErrorHandler handler) {
            this.handler = handler;
        }

        public synchronized List<LogEvent> getEvents() {
            return events;
        }
    }
}
{code}

  was:
When we updated from 2.20.0 to 2.22.1 (I had to pick 2.22.0 in the *Affects 
Version/s* because 2.22.1 does not exists in the Jira project), we noticed a 
regression in the serilialization of log events. The following test succeeds 
with 2.20.0, but fails with 2.22.1. I had a look at the source code, and I 
think it's because of changes in the {{ObjectMessage.read/writeObject}} 
implementation.

{code:java}
import static org.apache.logging.log4j.LogManager.getLogger;
import static org.junit.jupiter.api.Assertions.assertEquals;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.ErrorHandler;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.Logger;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class Log4jSerializationTest {

    private final Logger logger = (Logger) 
getLogger(Log4jSerializationTest.class);

    private final RecordingAppender appender = new 
RecordingAppender(Log4jSerializationTest.class.getName());

    @BeforeEach
    void addRecordingAppender() {
        appender.start();
        logger.addAppender(appender);
    }

   @AfterEach
    void removeRecordingAppender() {
        logger.removeAppender(appender);
        appender.stop();
    }

    @Test
    void logEventSerialization() {
        RuntimeException exception = new RuntimeException("induced by test");
        logger.error(exception, exception);
        LogEvent event = appender.getEvents().get(0);
        assertEquals(RuntimeException.class.getName() + ": induced by test", 
event.getMessage().getFormattedMessage());
    }

    static class RecordingAppender implements Appender {

        private final List<LogEvent> events = new ArrayList();
        private State state = State.STOPPED;
        private final String name;
        private ErrorHandler handler;

        public RecordingAppender(String name) {
            this.name = name;
        }

        @Override
        public State getState() {
            return state;
        }

        @Override
        public void initialize() {
            state = State.INITIALIZED;
        }

        @Override
        public boolean isStarted() {
            return state == State.STARTED;
        }

        @Override
        public boolean isStopped() {
            return !isStarted();
        }

        @Override
        public void start() {
            events.clear();
            state = State.STARTED;
        }

        @Override
        public void stop() {
            events.clear();
            state = State.STOPPED;
        }

        @Override
        public synchronized void append(LogEvent event) {
            // The event is a mutable one, Log4j2 emits the same event instance 
but changes the message each time.
            // If we want to hold on to the event details, we need to clone the 
event.
            // LogEvent is not an instance of Cloneable, but it IS an instance 
of Serializable.
            // So we clone by serializing and deserializing.
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            try (ObjectOutputStream objectOut = new ObjectOutputStream(out)) {
                objectOut.writeObject(event);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            LogEvent clone;
            try (ObjectInputStream objectInput = new ObjectInputStream(new 
ByteArrayInputStream(out.toByteArray()))) {
                clone = (LogEvent) objectInput.readObject();
            } catch (IOException | ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
            events.add(clone);
        }

        @Override
        public ErrorHandler getHandler() {
            return handler;
        }

        @Override
        public Layout<? extends Serializable> getLayout() {
            return null;
        }

        @Override
        public String getName() {
            return name;
        }

        @Override
        public boolean ignoreExceptions() {
            return true;
        }

        @Override
        public void setHandler(ErrorHandler handler) {
            this.handler = handler;
        }

        public synchronized List<LogEvent> getEvents() {
            return events;
        }
    }
}
{code}


> Some log events are no longer serialized correctly
> --------------------------------------------------
>
>                 Key: LOG4J2-3680
>                 URL: https://issues.apache.org/jira/browse/LOG4J2-3680
>             Project: Log4j 2
>          Issue Type: Bug
>          Components: Core
>    Affects Versions: 2.22.0
>            Reporter: Peter De Maeyer
>            Priority: Major
>
> When we updated from 2.20.0 to 2.22.1 (I had to pick 2.22.0 in the *Affects 
> Version/s* because 2.22.1 does not exists in the Jira project), we noticed a 
> regression in the serilialization of log events. The below test succeeds with 
> 2.20.0, but fails with 2.22.1. I had a look at the source code, and I think 
> it's because of changes in the {{ObjectMessage.read/writeObject}} 
> implementation.
> {code:java}
> import static org.apache.logging.log4j.LogManager.getLogger;
> import static org.junit.jupiter.api.Assertions.assertEquals;
> import java.io.ByteArrayInputStream;
> import java.io.ByteArrayOutputStream;
> import java.io.IOException;
> import java.io.ObjectInputStream;
> import java.io.ObjectOutputStream;
> import java.io.Serializable;
> import java.util.ArrayList;
> import java.util.List;
> import org.apache.logging.log4j.core.Appender;
> import org.apache.logging.log4j.core.ErrorHandler;
> import org.apache.logging.log4j.core.Layout;
> import org.apache.logging.log4j.core.LogEvent;
> import org.apache.logging.log4j.core.Logger;
> import org.junit.jupiter.api.AfterEach;
> import org.junit.jupiter.api.BeforeEach;
> import org.junit.jupiter.api.Test;
> class Log4jSerializationTest {
>     private final Logger logger = (Logger) 
> getLogger(Log4jSerializationTest.class);
>     private final RecordingAppender appender = new 
> RecordingAppender(Log4jSerializationTest.class.getName());
>     @BeforeEach
>     void addRecordingAppender() {
>         appender.start();
>         logger.addAppender(appender);
>     }
>    @AfterEach
>     void removeRecordingAppender() {
>         logger.removeAppender(appender);
>         appender.stop();
>     }
>     @Test
>     void logEventSerialization() {
>         RuntimeException exception = new RuntimeException("induced by test");
>         logger.error(exception, exception);
>         LogEvent event = appender.getEvents().get(0);
>         assertEquals(RuntimeException.class.getName() + ": induced by test", 
> event.getMessage().getFormattedMessage());
>     }
>     static class RecordingAppender implements Appender {
>         private final List<LogEvent> events = new ArrayList();
>         private State state = State.STOPPED;
>         private final String name;
>         private ErrorHandler handler;
>         public RecordingAppender(String name) {
>             this.name = name;
>         }
>         @Override
>         public State getState() {
>             return state;
>         }
>         @Override
>         public void initialize() {
>             state = State.INITIALIZED;
>         }
>         @Override
>         public boolean isStarted() {
>             return state == State.STARTED;
>         }
>         @Override
>         public boolean isStopped() {
>             return !isStarted();
>         }
>         @Override
>         public void start() {
>             events.clear();
>             state = State.STARTED;
>         }
>         @Override
>         public void stop() {
>             events.clear();
>             state = State.STOPPED;
>         }
>         @Override
>         public synchronized void append(LogEvent event) {
>             // The event is a mutable one, Log4j2 emits the same event 
> instance but changes the message each time.
>             // If we want to hold on to the event details, we need to clone 
> the event.
>             // LogEvent is not an instance of Cloneable, but it IS an 
> instance of Serializable.
>             // So we clone by serializing and deserializing.
>             ByteArrayOutputStream out = new ByteArrayOutputStream();
>             try (ObjectOutputStream objectOut = new ObjectOutputStream(out)) {
>                 objectOut.writeObject(event);
>             } catch (IOException e) {
>                 throw new RuntimeException(e);
>             }
>             LogEvent clone;
>             try (ObjectInputStream objectInput = new ObjectInputStream(new 
> ByteArrayInputStream(out.toByteArray()))) {
>                 clone = (LogEvent) objectInput.readObject();
>             } catch (IOException | ClassNotFoundException e) {
>                 throw new RuntimeException(e);
>             }
>             events.add(clone);
>         }
>         @Override
>         public ErrorHandler getHandler() {
>             return handler;
>         }
>         @Override
>         public Layout<? extends Serializable> getLayout() {
>             return null;
>         }
>         @Override
>         public String getName() {
>             return name;
>         }
>         @Override
>         public boolean ignoreExceptions() {
>             return true;
>         }
>         @Override
>         public void setHandler(ErrorHandler handler) {
>             this.handler = handler;
>         }
>         public synchronized List<LogEvent> getEvents() {
>             return events;
>         }
>     }
> }
> {code}



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to