[ 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)