You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by sn...@apache.org on 2015/06/24 08:11:48 UTC
cassandra git commit: Improve log output from unit tests
Repository: cassandra
Updated Branches:
refs/heads/trunk b7e72e1ee -> c8d3cc149
Improve log output from unit tests
patch by Ariel Weisberg; reviewed by Robert Stupp for CASSANDRA-9528
Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo
Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/c8d3cc14
Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/c8d3cc14
Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/c8d3cc14
Branch: refs/heads/trunk
Commit: c8d3cc1493a0ca47fa34e88d9a113440611dce3b
Parents: b7e72e1
Author: Ariel Weisberg <ar...@weisberg.ws>
Authored: Wed Jun 24 08:09:52 2015 +0200
Committer: Robert Stupp <sn...@snazy.de>
Committed: Wed Jun 24 08:09:52 2015 +0200
----------------------------------------------------------------------
CHANGES.txt | 1 +
build.xml | 25 +-
test/conf/logback-test.xml | 45 +-
.../CassandraBriefJUnitResultFormatter.java | 13 +-
.../CassandraXMLJUnitResultFormatter.java | 11 +
.../org/apache/cassandra/ConsoleAppender.java | 81 ++++
.../apache/cassandra/LogbackStatusListener.java | 454 +++++++++++++++++++
.../org/apache/cassandra/TeeingAppender.java | 79 ++++
8 files changed, 680 insertions(+), 29 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cassandra/blob/c8d3cc14/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index 33869cb..d2d1d5f 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
3.0:
+ * Improve log output from unit tests (CASSANDRA-9528)
* Add algorithmic token allocation (CASSANDRA-7032)
* Add nodetool command to replay batchlog (CASSANDRA-9547)
* Make file buffer cache independent of paths being read (CASSANDRA-8897)
http://git-wip-us.apache.org/repos/asf/cassandra/blob/c8d3cc14/build.xml
----------------------------------------------------------------------
diff --git a/build.xml b/build.xml
index 1fbc2fa..3d83ee6 100644
--- a/build.xml
+++ b/build.xml
@@ -132,6 +132,20 @@
<format property="YEAR" pattern="yyyy"/>
</tstamp>
+ <!-- Check if all tests are being run or just one. If it's all tests don't spam the console with test output.
+ If it's an individual test print the output from the test under the assumption someone is debugging the test
+ and wants to know what is going on without having to context switch to the log file that is generated.
+ Debug level output still needs to be retrieved from the log file. -->
+ <script language="javascript">
+ if (project.getProperty("cassandra.keepBriefBrief") == null)
+ {
+ if (project.getProperty("test.name").equals("*Test"))
+ project.setProperty("cassandra.keepBriefBrief", "true");
+ else
+ project.setProperty("cassandra.keepBriefBrief", "false");
+ }
+ </script>
+
<!--
Add all the dependencies.
-->
@@ -149,7 +163,7 @@
<exclude name="**/*-sources.jar"/>
</fileset>
</path>
-
+
<path id="cobertura.classpath">
<pathelement location="${cobertura.classes.dir}"/>
</path>
@@ -709,7 +723,7 @@
description="Run in test mode. Not for production use!">
<java classname="org.apache.cassandra.service.CassandraDaemon" fork="true">
<classpath>
- <path refid="cassandra.classpath"/>
+ <path refid="cassandra.classpath"/>
<pathelement location="${test.conf}"/>
</classpath>
<jvmarg value="-Dstorage-config=${test.conf}"/>
@@ -1131,8 +1145,8 @@
<attribute name="filelist" default="" />
<attribute name="poffset" default="0"/>
<attribute name="testtag" default=""/>
-
<attribute name="usejacoco" default="no"/>
+
<sequential>
<condition property="additionalagent"
value="-javaagent:${build.dir.lib}/jars/jacocoagent.jar=destfile=${jacoco.execfile}"
@@ -1157,7 +1171,8 @@
<jvmarg value="-Dcassandra.test.use_prepared=${cassandra.test.use_prepared}"/>
<jvmarg value="-Dcassandra.test.offsetseed=@{poffset}"/>
<jvmarg value="-Dcassandra.test.sstableformatdevelopment=true"/>
- <jvmarg value="-Dcassandra.testtag=@{testtag}"/>
+ <jvmarg value="-Dcassandra.testtag=@{testtag}"/>
+ <jvmarg value="-Dcassandra.keepBriefBrief=${cassandra.keepBriefBrief}" />
<optjvmargs/>
<classpath>
<path refid="cassandra.classpath" />
@@ -1989,7 +2004,7 @@
<arg value="-properties" />
<arg value="${ecj.properties}" />
<arg value="-cp" />
- <arg value="${toString:cassandra.classpath}" />
+ <arg value="${toString:cassandra.build.classpath}" />
<arg value="${build.src.java}" />
</java>
</target>
http://git-wip-us.apache.org/repos/asf/cassandra/blob/c8d3cc14/test/conf/logback-test.xml
----------------------------------------------------------------------
diff --git a/test/conf/logback-test.xml b/test/conf/logback-test.xml
index b503f04..8cb2d6f 100644
--- a/test/conf/logback-test.xml
+++ b/test/conf/logback-test.xml
@@ -18,12 +18,17 @@
-->
<configuration debug="false">
+ <!-- Shutdown hook ensures that async appender flushes -->
<shutdownHook class="ch.qos.logback.core.hook.DelayingShutdownHook"/>
+ <!-- Status listener is used to wrap stdout/stderr and tee to log file -->
+ <statusListener class="org.apache.cassandra.LogbackStatusListener" />
+
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
- <file>./build/test/logs/system.log</file>
+
+ <file>./build/test/output/${cassandra.testtag}/TEST-${suitename}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
- <fileNamePattern>./build/test/logs/system.log.%i.zip</fileNamePattern>
+ <fileNamePattern>./build/test/logs/${cassandra.testtag}/TEST-${suitename}.log.%i.gz</fileNamePattern>
<minIndex>1</minIndex>
<maxIndex>20</maxIndex>
</rollingPolicy>
@@ -31,41 +36,39 @@
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>20MB</maxFileSize>
</triggeringPolicy>
+
<encoder>
<pattern>%-5level [%thread] %date{ISO8601} %msg%n</pattern>
<immediateFlush>false</immediateFlush>
</encoder>
</appender>
- <appender name="ASYNCFILE" class="ch.qos.logback.classic.AsyncAppender">
- <discardingThreshold>0</discardingThreshold>
- <maxFlushTime>0</maxFlushTime>
- <queueSize>1024</queueSize>
- <appender-ref ref="FILE"/>
- </appender>
-
- <appender name="STDERR" target="System.err" class="ch.qos.logback.core.ConsoleAppender">
+ <appender name="STDOUT" target="System.out" class="org.apache.cassandra.ConsoleAppender">
<encoder>
<pattern>%-5level %date{HH:mm:ss,SSS} %msg%n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
- <level>WARN</level>
+ <level>INFO</level>
</filter>
</appender>
-
- <appender name="STDOUT" target="System.out" class="ch.qos.logback.core.ConsoleAppender">
- <encoder>
- <pattern>%-5level %date{HH:mm:ss,SSS} %msg%n</pattern>
- </encoder>
- <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
- <level>WARN</level>
- </filter>
+
+ <appender name="TEE" class="org.apache.cassandra.TeeingAppender">
+ <appender-ref ref="FILE"/>
+ <appender-ref ref="STDOUT"/>
</appender>
<logger name="org.apache.hadoop" level="WARN"/>
+ <!-- Do not change the name of this appender. LogbackStatusListener uses the thread name
+ tied to the appender name to know when to write to real stdout/stderr vs forwarding to logback -->
+ <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
+ <discardingThreshold>0</discardingThreshold>
+ <maxFlushTime>0</maxFlushTime>
+ <queueSize>1024</queueSize>
+ <appender-ref ref="TEE"/>
+ </appender>
+
<root level="DEBUG">
- <appender-ref ref="ASYNCFILE" />
- <appender-ref ref="STDOUT" />
+ <appender-ref ref="ASYNC" />
</root>
</configuration>
http://git-wip-us.apache.org/repos/asf/cassandra/blob/c8d3cc14/test/unit/org/apache/cassandra/CassandraBriefJUnitResultFormatter.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/CassandraBriefJUnitResultFormatter.java b/test/unit/org/apache/cassandra/CassandraBriefJUnitResultFormatter.java
index 084858f..a6c5997 100644
--- a/test/unit/org/apache/cassandra/CassandraBriefJUnitResultFormatter.java
+++ b/test/unit/org/apache/cassandra/CassandraBriefJUnitResultFormatter.java
@@ -49,6 +49,8 @@ public class CassandraBriefJUnitResultFormatter implements JUnitResultFormatter,
private static final String tag = System.getProperty("cassandra.testtag", "");
+ private static final Boolean keepBriefBrief = Boolean.getBoolean("cassandra.keepBriefBrief");
+
/**
* Where to write the log to.
*/
@@ -145,7 +147,12 @@ public class CassandraBriefJUnitResultFormatter implements JUnitResultFormatter,
* @param suite the test suite
*/
public void endTestSuite(JUnitTest suite) {
- StringBuffer sb = new StringBuffer("Tests run: ");
+ StringBuffer sb = new StringBuffer("Testsuite: ");
+ String n = suite.getName();
+ if (n != null && !tag.isEmpty())
+ n = n + "-" + tag;
+ sb.append(n);
+ sb.append(" Tests run: ");
sb.append(suite.runCount());
sb.append(", Failures: ");
sb.append(suite.failureCount());
@@ -160,7 +167,7 @@ public class CassandraBriefJUnitResultFormatter implements JUnitResultFormatter,
sb.append(StringUtils.LINE_SEP);
// append the err and output streams to the log
- if (systemOutput != null && systemOutput.length() > 0) {
+ if (!keepBriefBrief && systemOutput != null && systemOutput.length() > 0) {
sb.append("------------- Standard Output ---------------")
.append(StringUtils.LINE_SEP)
.append(systemOutput)
@@ -168,7 +175,7 @@ public class CassandraBriefJUnitResultFormatter implements JUnitResultFormatter,
.append(StringUtils.LINE_SEP);
}
- if (systemError != null && systemError.length() > 0) {
+ if (!keepBriefBrief && systemError != null && systemError.length() > 0) {
sb.append("------------- Standard Error -----------------")
.append(StringUtils.LINE_SEP)
.append(systemError)
http://git-wip-us.apache.org/repos/asf/cassandra/blob/c8d3cc14/test/unit/org/apache/cassandra/CassandraXMLJUnitResultFormatter.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/CassandraXMLJUnitResultFormatter.java b/test/unit/org/apache/cassandra/CassandraXMLJUnitResultFormatter.java
index 066315d..b342b45 100644
--- a/test/unit/org/apache/cassandra/CassandraXMLJUnitResultFormatter.java
+++ b/test/unit/org/apache/cassandra/CassandraXMLJUnitResultFormatter.java
@@ -74,6 +74,17 @@ public class CassandraXMLJUnitResultFormatter implements JUnitResultFormatter, X
private static final String tag = System.getProperty("cassandra.testtag", "");
+ /*
+ * Set the property for the test suite name so that log configuration can pick it up
+ * and log to a file specific to this test suite
+ */
+ static
+ {
+ String command = System.getProperty("sun.java.command");
+ String args[] = command.split(" ");
+ System.setProperty("suitename", args[1]);
+ }
+
/**
* The XML document.
*/
http://git-wip-us.apache.org/repos/asf/cassandra/blob/c8d3cc14/test/unit/org/apache/cassandra/ConsoleAppender.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/ConsoleAppender.java b/test/unit/org/apache/cassandra/ConsoleAppender.java
new file mode 100644
index 0000000..aa8af1e
--- /dev/null
+++ b/test/unit/org/apache/cassandra/ConsoleAppender.java
@@ -0,0 +1,81 @@
+/*
+ * 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.cassandra;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+
+import ch.qos.logback.core.OutputStreamAppender;
+import ch.qos.logback.core.status.Status;
+import ch.qos.logback.core.status.WarnStatus;
+
+public class ConsoleAppender<E> extends OutputStreamAppender<E>
+{
+ private String target = "System.out";
+
+ public void setTarget(String target)
+ {
+ if(!(target.equals("System.out") || target.equals("System.err")))
+ {
+ Status status = new WarnStatus("[" + target + "] should be one of System.out or System.err", this);
+ status.add(new WarnStatus("Using default target System.out", this));
+ addStatus(status);
+ return;
+ }
+ this.target = target;
+ }
+
+ public String getTarget()
+ {
+ return target;
+ }
+
+ @Override
+ public void start()
+ {
+ @SuppressWarnings("resource")
+ final PrintStream targetStream = target.equals("System.out") ? LogbackStatusListener.originalOut : LogbackStatusListener.originalErr;
+ setOutputStream(new OutputStream() {
+ @Override
+ public void write(int b)
+ {
+ targetStream.write(b);
+ }
+
+ @Override
+ public void write(byte[] b) throws IOException
+ {
+ targetStream.write(b);
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len)
+ {
+ targetStream.write(b, off, len);
+ }
+
+ @Override
+ public void flush()
+ {
+ targetStream.flush();
+ }
+ });
+ super.start();
+ }
+}
http://git-wip-us.apache.org/repos/asf/cassandra/blob/c8d3cc14/test/unit/org/apache/cassandra/LogbackStatusListener.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/LogbackStatusListener.java b/test/unit/org/apache/cassandra/LogbackStatusListener.java
new file mode 100644
index 0000000..756f7eb
--- /dev/null
+++ b/test/unit/org/apache/cassandra/LogbackStatusListener.java
@@ -0,0 +1,454 @@
+/*
+ * 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.cassandra;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.Locale;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import ch.qos.logback.core.status.Status;
+import ch.qos.logback.core.status.StatusListener;
+
+/*
+ * Listen for logback readiness and then redirect stdout/stderr to logback
+ */
+public class LogbackStatusListener implements StatusListener
+{
+
+ public static final PrintStream originalOut = System.out;
+
+ public static final PrintStream originalErr = System.err;
+
+ private boolean hadError = false;
+
+ private PrintStream replacementOut;
+
+ private PrintStream replacementErr;
+
+ @Override
+ public void addStatusEvent(Status s)
+ {
+ if (s.getLevel() != 0 || s.getEffectiveLevel() != 0)
+ hadError = true;
+
+ if (!hadError && s.getMessage().equals("Registering current configuration as safe fallback point"))
+ {
+ try
+ {
+ installReplacementStreams();
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ if (s.getMessage().equals("Logback context being closed via shutdown hook"))
+ {
+ if (replacementOut != null) replacementOut.flush();
+ if (replacementErr != null) replacementErr.flush();
+ System.setErr(originalErr);
+ System.setOut(originalOut);
+ }
+ }
+
+ private void installReplacementStreams() throws Exception
+ {
+ Logger stdoutLogger = LoggerFactory.getLogger("stdout");
+ Logger stderrLogger = LoggerFactory.getLogger("stderr");
+
+ replacementOut = wrapLogger(stdoutLogger, originalOut, "sun.stdout.encoding", false);
+ System.setOut(replacementOut);
+ replacementErr = wrapLogger(stderrLogger, originalErr, "sun.stderr.encoding", true);
+ System.setErr(replacementErr);
+ }
+
+ private static PrintStream wrapLogger(final Logger logger, final PrintStream original, String encodingProperty, boolean error) throws Exception
+ {
+ final String encoding = System.getProperty(encodingProperty);
+ OutputStream os = new OutputStream()
+ {
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ @Override
+ public void write(int b) throws IOException
+ {
+ baos.write(b);
+ }
+
+ @Override
+ public void write(byte[] b, int offset, int length)
+ {
+ baos.write(b, offset, length);
+ }
+
+ @Override
+ public void write(byte[] b)
+ {
+ write(b, 0, b.length);
+ }
+
+ @Override
+ public void flush() throws IOException
+ {
+ try
+ {
+ //Filter out stupid PrintStream empty flushes
+ if (baos.size() == 0) return;
+
+ //Filter out newlines, log framework provides its own
+ if (baos.size() == 1)
+ {
+ byte[] bytes = baos.toByteArray();
+ if (bytes[0] == 0xA)
+ return;
+ }
+
+ //Filter out Windows newline
+ if (baos.size() == 2)
+ {
+ byte[] bytes = baos.toByteArray();
+ if (bytes[0] == 0xD && bytes[1] == 0xA)
+ return;
+ }
+
+ String statement;
+ if (encoding != null)
+ statement = new String(baos.toByteArray(), encoding);
+ else
+ statement = new String(baos.toByteArray());
+
+ if (error)
+ logger.error(statement);
+ else
+ logger.info(statement);
+ }
+ finally
+ {
+ baos.reset();
+ }
+ }
+ };
+
+ if (encoding != null)
+ return new PrintStream(os, true, encoding);
+ return new PrintStream(os, true)
+ {
+
+ private long asyncAppenderThreadId = Long.MIN_VALUE;
+
+ /*
+ * Long and the short of it is that we don't want to serve logback a fake System.out/err.
+ * ConsoleAppender is replaced so it always goes to the real System.out/err, but logback itself
+ * will at times try to log to System.out/err when it has issues.
+ *
+ * Now here is the problem. There is a deadlock if a thread logs to System.out, blocks on the async
+ * appender queue, and the async appender thread tries to log to System.out directly as part of some
+ * internal logback issue.
+ *
+ * So to prevent this we have to exhaustively check before locking in the PrintStream and forward
+ * to real System.out/err if it is the async appender
+ */
+ private boolean isAsyncAppender()
+ {
+ //Set the thread id based on the name
+ if (asyncAppenderThreadId == Long.MIN_VALUE)
+ asyncAppenderThreadId = Thread.currentThread().getName().equals("AsyncAppender-Worker-ASYNC") ? Thread.currentThread().getId() : asyncAppenderThreadId;
+ if (Thread.currentThread().getId() == asyncAppenderThreadId)
+ original.println("Was in async appender");
+ return Thread.currentThread().getId() == asyncAppenderThreadId;
+ }
+
+ @Override
+ public void flush()
+ {
+ if (isAsyncAppender())
+ original.flush();
+ else
+ super.flush();
+ }
+
+ @Override
+ public void close()
+ {
+ if (isAsyncAppender())
+ original.close();
+ else
+ super.flush();
+ }
+
+ @Override
+ public void write(int b)
+ {
+ if (isAsyncAppender())
+ original.write(b);
+ else
+ super.write(b);
+ }
+
+ @Override
+ public void write(byte[] buf, int off, int len)
+ {
+ if (isAsyncAppender())
+ original.write(buf, off, len);
+ else
+ super.write(buf, off, len);
+ }
+
+ @Override
+ public void print(boolean b)
+ {
+ if (isAsyncAppender())
+ original.print(b);
+ else
+ super.print(b);
+ }
+
+ @Override
+ public void print(char c)
+ {
+ if (isAsyncAppender())
+ original.print(c);
+ else
+ super.print(c);
+ }
+
+ @Override
+ public void print(int i)
+ {
+ if (isAsyncAppender())
+ original.print(i);
+ else
+ super.print(i);
+ }
+
+ @Override
+ public void print(long l)
+ {
+ if (isAsyncAppender())
+ original.print(l);
+ else
+ super.print(l);
+ }
+
+ @Override
+ public void print(float f)
+ {
+ if (isAsyncAppender())
+ original.print(f);
+ else
+ super.print(f);
+ }
+
+ @Override
+ public void print(double d)
+ {
+ if (isAsyncAppender())
+ original.print(d);
+ else
+ super.print(d);
+ }
+
+ @Override
+ public void print(char[] s)
+ {
+ if(isAsyncAppender())
+ original.println(s);
+ else
+ super.print(s);
+ }
+
+ @Override
+ public void print(String s)
+ {
+ if (isAsyncAppender())
+ original.print(s);
+ else
+ super.print(s);
+ }
+
+ @Override
+ public void print(Object obj)
+ {
+ if (isAsyncAppender())
+ original.print(obj);
+ else
+ super.print(obj);
+ }
+
+ @Override
+ public void println()
+ {
+ if (isAsyncAppender())
+ original.println();
+ else
+ super.println();
+ }
+
+ @Override
+ public void println(boolean v)
+ {
+ if (isAsyncAppender())
+ original.println(v);
+ else
+ super.println(v);
+ }
+
+ @Override
+ public void println(char v)
+ {
+ if (isAsyncAppender())
+ original.println(v);
+ else
+ super.println(v);
+ }
+
+ @Override
+ public void println(int v)
+ {
+ if (isAsyncAppender())
+ original.println(v);
+ else
+ super.println(v);
+ }
+
+ @Override
+ public void println(long v)
+ {
+ if (isAsyncAppender())
+ original.println(v);
+ else
+ super.println(v);
+ }
+
+ @Override
+ public void println(float v)
+ {
+ if (isAsyncAppender())
+ original.println(v);
+ else
+ super.println(v);
+ }
+
+ @Override
+ public void println(double v)
+ {
+ if (isAsyncAppender())
+ original.println(v);
+ else
+ super.println(v);
+ }
+
+ @Override
+ public void println(char[] v)
+ {
+ if (isAsyncAppender())
+ original.println(v);
+ else
+ super.println(v);
+ }
+
+ @Override
+ public void println(String v)
+ {
+ if (isAsyncAppender())
+ original.println(v);
+ else
+ super.println(v);
+ }
+
+ @Override
+ public void println(Object v)
+ {
+ if (isAsyncAppender())
+ original.println(v);
+ else
+ super.println(v);
+ }
+
+ @Override
+ public PrintStream printf(String format, Object... args)
+ {
+ if (isAsyncAppender())
+ return original.printf(format, args);
+ else
+ return super.printf(format, args);
+ }
+
+ @Override
+ public PrintStream printf(Locale l, String format, Object... args)
+ {
+ if (isAsyncAppender())
+ return original.printf(l, format, args);
+ else
+ return super.printf(l, format, args);
+ }
+
+ @Override
+ public PrintStream format(String format, Object... args)
+ {
+ if (isAsyncAppender())
+ return original.format(format, args);
+ else
+ return super.format(format, args);
+ }
+
+ @Override
+ public PrintStream format(Locale l, String format, Object... args)
+ {
+ if (isAsyncAppender())
+ return original.format(l, format, args);
+ else
+ return super.format(l, format, args);
+ }
+
+ @Override
+ public PrintStream append(CharSequence csq)
+ {
+ if (isAsyncAppender())
+ return original.append(csq);
+ else
+ return super.append(csq);
+ }
+
+ @Override
+ public PrintStream append(CharSequence csq, int start, int end)
+ {
+ if (isAsyncAppender())
+ return original.append(csq, start, end);
+ else
+ return super.append(csq, start, end);
+ }
+
+ @Override
+ public PrintStream append(char c)
+ {
+ if (isAsyncAppender())
+ return original.append(c);
+ else
+ return super.append(c);
+ }
+ };
+ }
+}
http://git-wip-us.apache.org/repos/asf/cassandra/blob/c8d3cc14/test/unit/org/apache/cassandra/TeeingAppender.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/TeeingAppender.java b/test/unit/org/apache/cassandra/TeeingAppender.java
new file mode 100644
index 0000000..4e3735e
--- /dev/null
+++ b/test/unit/org/apache/cassandra/TeeingAppender.java
@@ -0,0 +1,79 @@
+/*
+ * 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.cassandra;
+
+import java.util.Iterator;
+
+import ch.qos.logback.core.Appender;
+import ch.qos.logback.core.UnsynchronizedAppenderBase;
+import ch.qos.logback.core.spi.AppenderAttachable;
+import ch.qos.logback.core.spi.AppenderAttachableImpl;
+
+public class TeeingAppender<E> extends UnsynchronizedAppenderBase<E> implements AppenderAttachable<E>
+{
+ AppenderAttachableImpl<E> aai = new AppenderAttachableImpl<>();
+
+ @Override
+ protected void append(E e)
+ {
+ aai.appendLoopOnAppenders(e);
+ }
+
+ @Override
+ public void addAppender(Appender<E> appender)
+ {
+ aai.addAppender(appender);
+ }
+
+ @Override
+ public void detachAndStopAllAppenders()
+ {
+ aai.detachAndStopAllAppenders();
+ }
+
+ @Override
+ public boolean detachAppender(Appender<E> appender)
+ {
+ return aai.detachAppender(appender);
+ }
+
+ @Override
+ public boolean detachAppender(String name)
+ {
+ return aai.detachAppender(name);
+ }
+
+ @Override
+ public Appender<E> getAppender(String name)
+ {
+ return aai.getAppender(name);
+ }
+
+ @Override
+ public boolean isAttached(Appender<E> appender)
+ {
+ return aai.isAttached(appender);
+ }
+
+ @Override
+ public Iterator<Appender<E>> iteratorForAppenders()
+ {
+ return aai.iteratorForAppenders();
+ }
+
+}