Index: log4j-api/src/test/java/org/apache/logging/log4j/spi/LoggerWriterTest.java =================================================================== --- log4j-api/src/test/java/org/apache/logging/log4j/spi/LoggerWriterTest.java (revision 0) +++ log4j-api/src/test/java/org/apache/logging/log4j/spi/LoggerWriterTest.java (revision 0) @@ -0,0 +1,140 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.spi; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.TestLogger; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import static org.hamcrest.core.StringStartsWith.startsWith; +import static org.junit.Assert.*; + +@RunWith(Parameterized.class) +public class LoggerWriterTest { + private List results; + private LoggerWriter writer; + private Level level; + private String logMessage; + + @Parameterized.Parameters + public static Collection data() { + return Arrays.asList(new Object[][] { { Level.DEBUG, "debug log string test" }, { Level.INFO, "info log string test" }, { Level.WARN, "DANGER ZONE" }, + { Level.ERROR, "MAYDAY! MAYDAY!" }, { Level.FATAL, "ABANDON SHIP!" } }); + } + + public LoggerWriterTest(final Level level, final String logMessage) { + this.level = level; + this.logMessage = logMessage; + } + + @Before + public void setUp() throws Exception { + TestLogger logger = (TestLogger) LogManager.getLogger(); + results = logger.getEntries(); + assertEmpty(); + writer = new LoggerWriter(logger, null, level); + assertEmpty(); + } + + @After + public void tearDown() throws Exception { + results.clear(); + } + + private void assertEmpty() { + assertTrue("There should be no results yet.", results.isEmpty()); + } + + private void assertNumResults(int numResults) { + assertEquals("Unexpected number of results.", numResults, results.size()); + } + + private void assertMessages(final String... messages) { + assertNumResults(messages.length); + for (int i = 0; i < messages.length; i++) { + final String start = ' ' + level.name() + ' ' + messages[i]; + assertThat(results.get(i), startsWith(start)); + } + } + + @Test + public void testWrite_CharArray() throws Exception { + final char[] chars = logMessage.toCharArray(); + writer.write(chars); + assertEmpty(); + writer.write('\n'); + assertMessages(logMessage); + } + + @Test + public void testWrite_CharArray_Offset_Length() throws Exception { + final char[] chars = logMessage.toCharArray(); + int middle = chars.length / 2; + int length = chars.length - middle; + final String right = new String(chars, middle, length); + writer.write(chars, middle, length); + assertEmpty(); + writer.write('\n'); + assertMessages(right); + } + + @Test + public void testWrite_Character() throws Exception { + for (char c : logMessage.toCharArray()) { + writer.write(c); + assertEmpty(); + } + writer.write('\n'); + assertMessages(logMessage); + } + + @Test + public void testWrite_IgnoresWindowsNewline() throws IOException { + writer.write(logMessage + "\r\n"); + assertMessages(logMessage); + } + + @Test + public void testWrite_MultipleLines() throws IOException { + writer.write(logMessage + '\n' + logMessage + '\n'); + assertMessages(logMessage, logMessage); + } + + @Test + public void testClose_NoRemainingData() throws IOException { + writer.close(); + assertEmpty(); + } + + @Test + public void testClose_HasRemainingData() throws IOException { + writer.write(logMessage); + assertEmpty(); + writer.close(); + assertMessages(logMessage); + } +} Index: log4j-api/src/test/java/org/apache/logging/log4j/LoggerTest.java =================================================================== --- log4j-api/src/test/java/org/apache/logging/log4j/LoggerTest.java (revision 1573208) +++ log4j-api/src/test/java/org/apache/logging/log4j/LoggerTest.java (working copy) @@ -19,6 +19,7 @@ import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; +import java.io.PrintWriter; import java.util.Date; import java.util.List; import java.util.Locale; @@ -26,7 +27,6 @@ import org.apache.logging.log4j.message.ParameterizedMessageFactory; import org.apache.logging.log4j.message.StringFormatterMessageFactory; import org.apache.logging.log4j.message.StructuredDataMessage; -import org.apache.logging.log4j.spi.LoggerStream; import org.junit.Before; import org.junit.Test; @@ -273,31 +273,38 @@ @Test public void getStream() { - final LoggerStream stream = logger.getStream(Level.DEBUG); - stream.println("Debug message 1"); - stream.print("Debug message 2"); + final PrintWriter stream = logger.printWriter(Level.DEBUG); + stream.println("println"); + stream.print("print followed by println"); stream.println(); + stream.println("multiple\nlines"); stream.println(); // verify blank log message - stream.print("Debug message 3\n"); + stream.print("print embedded newline\n"); stream.print("\r\n"); // verify windows EOL works - assertEquals(5, results.size()); - assertThat("Incorrect message", results.get(0), startsWith(" DEBUG Debug message 1")); - assertThat("Incorrect message", results.get(1), startsWith(" DEBUG Debug message 2")); - assertEquals("Message should be blank-ish", " DEBUG ", results.get(2)); - assertThat("Incorrect message", results.get(3), startsWith(" DEBUG Debug message 3")); - assertEquals("Message should be blank-ish", " DEBUG ", results.get(4)); + stream.print("Last Line without newline"); + stream.close(); + assertEquals(8, results.size()); + assertEquals("msg 1", " DEBUG println", results.get(0)); + assertEquals("msg 2", " DEBUG print followed by println", results.get(1)); + assertEquals("msg 3", " DEBUG multiple", results.get(2)); + assertEquals("msg 4", " DEBUG lines", results.get(3)); + assertEquals("msg 5 should be blank-ish", " DEBUG ", results.get(4)); + assertEquals("msg 6", " DEBUG print embedded newline", results.get(5)); + assertEquals("msg 7 should be blank-ish", " DEBUG ", results.get(6)); + assertEquals("msg 8 Last line", " DEBUG Last Line without newline", results.get(7)); } @Test public void getStream_Marker() { - final LoggerStream stream = logger.getStream(MarkerManager.getMarker("HI"), Level.INFO); - stream.println("Hello, world!"); - stream.print("How about this?\n"); - stream.println("Is this thing on?"); + final PrintWriter stream = logger.printWriter(MarkerManager.getMarker("HI"), Level.INFO); + stream.println("println"); + stream.print("print with embedded newline\n"); + stream.println("last line"); + stream.close(); assertEquals(3, results.size()); - assertThat("Incorrect message.", results.get(0), startsWith("HI INFO Hello")); - assertThat("Incorrect message.", results.get(1), startsWith("HI INFO How about")); - assertThat("Incorrect message.", results.get(2), startsWith("HI INFO Is this")); + assertEquals("println 1", "HI INFO println", results.get(0)); + assertEquals("print with embedded newline", "HI INFO print with embedded newline", results.get(1)); + assertEquals("println 2", "HI INFO last line", results.get(2)); } @Test Index: log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java =================================================================== --- log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java (revision 1573208) +++ log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java (working copy) @@ -16,6 +16,9 @@ */ package org.apache.logging.log4j.spi; +import java.io.PrintWriter; +import java.io.Serializable; + import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Marker; @@ -26,8 +29,6 @@ import org.apache.logging.log4j.message.StringFormattedMessage; import org.apache.logging.log4j.status.StatusLogger; -import java.io.Serializable; - /** * Base implementation of a Logger. It is highly recommended that any Logger implementation extend this class. */ @@ -856,8 +857,8 @@ * @return print stream that logs printed lines to this logger. */ @Override - public LoggerStream getStream(final Level level) { - return new LoggerStream(this, level); + public PrintWriter printWriter(final Level level) { + return new PrintWriter(new LoggerWriter(this, null, level), true); } /** @@ -868,8 +869,8 @@ * @return print stream that logs printed lines to this logger. */ @Override - public LoggerStream getStream(Marker marker, Level level) { - return new LoggerStream(this, marker, level); + public PrintWriter printWriter(Marker marker, Level level) { + return new PrintWriter(new LoggerWriter(this, marker, level), true); } /** Index: log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerWriter.java =================================================================== --- log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerWriter.java (revision 0) +++ log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerWriter.java (revision 0) @@ -0,0 +1,62 @@ +package org.apache.logging.log4j.spi; + +import java.io.IOException; +import java.io.Writer; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.message.Message; + +public class LoggerWriter extends Writer { + private static final String FQCN = LoggerWriter.class.getName(); + private final AbstractLogger logger; + private final Level level; + private final Marker marker; + private final StringBuilder buf = new StringBuilder(); + + public LoggerWriter(AbstractLogger logger, Marker marker, Level level) { + this.logger = logger; + this.marker = marker; + this.level = level; + } + + @Override + public void close() throws IOException { + // don't log a blank message if the last character was a newline + if (buf.length() > 0) { + log(); + } + } + + @Override + public void flush() throws IOException { + // flushing automatically happens when a newline is encountered + } + + @Override + public void write(char[] cbuf, int off, int len) throws IOException { + int currOff = off; + synchronized (buf) { + for (int pos = off; pos < off + len; pos++) { + switch (cbuf[pos]) { + case '\r': + buf.append(cbuf, currOff, pos - currOff); + currOff = pos + 1; + break; + case '\n': + buf.append(cbuf, currOff, pos - currOff); + currOff = pos + 1; + log(); + break; + } + } + buf.append(cbuf, currOff, len - (currOff - off)); + } + } + + private void log() { + final Message message = logger.getMessageFactory().newMessage(buf.toString()); + buf.setLength(0); + logger.log(marker, FQCN, level, message, null); + } +} Index: log4j-api/src/main/java/org/apache/logging/log4j/Logger.java =================================================================== --- log4j-api/src/main/java/org/apache/logging/log4j/Logger.java (revision 1573208) +++ log4j-api/src/main/java/org/apache/logging/log4j/Logger.java (working copy) @@ -16,9 +16,10 @@ */ package org.apache.logging.log4j; +import java.io.PrintWriter; + import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.message.MessageFactory; -import org.apache.logging.log4j.spi.LoggerStream; /** * This is the central interface in the log4j package. Most logging operations, except configuration, are done through @@ -469,7 +470,7 @@ * @param level the logging level * @return print stream that logs printed lines to this logger. */ - LoggerStream getStream(Level level); + PrintWriter printWriter(Level level); /** * Gets a marked print stream that logs lines to this logger. @@ -478,7 +479,7 @@ * @param level the logging level * @return print stream that logs printed lines to this logger. */ - LoggerStream getStream(Marker marker, Level level); + PrintWriter printWriter(Marker marker, Level level); /** * Logs a message with the specific Marker at the {@link Level#INFO INFO} level.