You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by gg...@apache.org on 2016/07/20 21:35:07 UTC

[04/16] logging-log4j2 git commit: Add ANSI style option to extended throwable converter.

Add ANSI style option to extended throwable converter.

Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo
Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/d7f9d951
Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/d7f9d951
Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/d7f9d951

Branch: refs/heads/StyledXException
Commit: d7f9d951911f5fcbc75c776a092677a005b0226e
Parents: b364a32
Author: ggregory <gg...@apache.org>
Authored: Tue Jul 12 19:50:34 2016 -0700
Committer: ggregory <gg...@apache.org>
Committed: Tue Jul 12 19:50:34 2016 -0700

----------------------------------------------------------------------
 .../log4j/core/impl/ExtendedClassInfo.java      |  23 +-
 .../core/impl/ExtendedStackTraceElement.java    |  46 +++-
 .../log4j/core/impl/ThrowableFormatOptions.java | 104 +++++--
 .../logging/log4j/core/impl/ThrowableProxy.java | 184 +++++++++----
 .../ExtendedThrowablePatternConverter.java      |   2 +-
 .../log4j/core/pattern/HtmlMessageRenderer.java |  33 ---
 .../log4j/core/pattern/HtmlTextRenderer.java    |  38 +++
 .../core/pattern/JAnsiMessageRenderer.java      | 211 ---------------
 .../log4j/core/pattern/JAnsiTextRenderer.java   | 268 +++++++++++++++++++
 .../core/pattern/MessagePatternConverter.java   |  16 +-
 .../log4j/core/pattern/MessageRenderer.java     |  32 ---
 .../log4j/core/pattern/PlainTextRenderer.java   |  40 +++
 .../log4j/core/pattern/TextRenderer.java        |  46 ++++
 .../apache/logging/log4j/core/util/Loader.java  |   5 +
 .../ConsoleAppenderJAnsiXExceptionMain.java     |  74 +++++
 .../test/resources/log4j2-console-xex-ansi.xml  |  31 +++
 16 files changed, 770 insertions(+), 383 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/d7f9d951/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ExtendedClassInfo.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ExtendedClassInfo.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ExtendedClassInfo.java
index 1c7e686..280a574 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ExtendedClassInfo.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ExtendedClassInfo.java
@@ -18,6 +18,9 @@ package org.apache.logging.log4j.core.impl;
 
 import java.io.Serializable;
 
+import org.apache.logging.log4j.core.pattern.PlainTextRenderer;
+import org.apache.logging.log4j.core.pattern.TextRenderer;
+
 /**
  * Class and package data used with a {@link StackTraceElement} in a {@link ExtendedStackTraceElement}.
  */
@@ -99,17 +102,21 @@ public final class ExtendedClassInfo implements Serializable {
         return result;
     }
 
+    public void renderOn(StringBuilder output, TextRenderer textRenderer) {
+        if (!this.exact) {
+            textRenderer.render("~", output, "ExtraClassInfo.Inexact");
+        }
+        textRenderer.render("[", output, "ExtraClassInfo.Container");
+        textRenderer.render(this.location, output, "ExtraClassInfo.Location");
+        textRenderer.render(":", output, "ExtraClassInfo.ContainerSeparator");
+        textRenderer.render(this.version, output, "ExtraClassInfo.Version");
+        textRenderer.render("]", output, "ExtraClassInfo.Container");
+    }
+
     @Override
     public String toString() {
         final StringBuilder sb = new StringBuilder();
-        if (!this.exact) {
-            sb.append('~');
-        }
-        sb.append('[');
-        sb.append(this.location);
-        sb.append(':');
-        sb.append(this.version);
-        sb.append(']');
+        renderOn(sb, PlainTextRenderer.getInstance());
         return sb.toString();
     }
 

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/d7f9d951/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ExtendedStackTraceElement.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ExtendedStackTraceElement.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ExtendedStackTraceElement.java
index 9ced720..4648ed8 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ExtendedStackTraceElement.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ExtendedStackTraceElement.java
@@ -18,6 +18,9 @@ package org.apache.logging.log4j.core.impl;
 
 import java.io.Serializable;
 
+import org.apache.logging.log4j.core.pattern.PlainTextRenderer;
+import org.apache.logging.log4j.core.pattern.TextRenderer;
+
 /**
  * Wraps and extends the concept of the JRE's final class {@link StackTraceElement} by adding more location information.
  * <p>
@@ -37,7 +40,8 @@ public final class ExtendedStackTraceElement implements Serializable {
 
     private final StackTraceElement stackTraceElement;
 
-    public ExtendedStackTraceElement(final StackTraceElement stackTraceElement, final ExtendedClassInfo extraClassInfo) {
+    public ExtendedStackTraceElement(final StackTraceElement stackTraceElement,
+            final ExtendedClassInfo extraClassInfo) {
         this.stackTraceElement = stackTraceElement;
         this.extraClassInfo = extraClassInfo;
     }
@@ -47,8 +51,8 @@ public final class ExtendedStackTraceElement implements Serializable {
      */
     public ExtendedStackTraceElement(final String declaringClass, final String methodName, final String fileName,
             final int lineNumber, final boolean exact, final String location, final String version) {
-        this(new StackTraceElement(declaringClass, methodName, fileName, lineNumber), new ExtendedClassInfo(exact,
-                location, version));
+        this(new StackTraceElement(declaringClass, methodName, fileName, lineNumber),
+                new ExtendedClassInfo(exact, location, version));
     }
 
     @Override
@@ -129,12 +133,42 @@ public final class ExtendedStackTraceElement implements Serializable {
         return this.stackTraceElement.isNativeMethod();
     }
 
+    void renderOn(StringBuilder output, TextRenderer textRenderer) {
+        render(this.stackTraceElement, output, textRenderer);
+        textRenderer.render(" ", output, "Text");
+        this.extraClassInfo.renderOn(output, textRenderer);
+    }
+
+    private void render(StackTraceElement stElement, StringBuilder output, TextRenderer textRenderer) {
+        final String fileName = stElement.getFileName();
+        final int lineNumber = stElement.getLineNumber();
+        textRenderer.render(getClassName(), output, "StackTraceElement.ClassName");
+        textRenderer.render(".", output, "StackTraceElement.ClassMethodSeparator");
+        textRenderer.render(stElement.getMethodName(), output, "StackTraceElement.MethodName");
+        if (stElement.isNativeMethod()) {
+            textRenderer.render("(Native Method)", output, "StackTraceElement.NativeMethod");
+        } else if (fileName != null && lineNumber >= 0) {
+            textRenderer.render("(", output, "StackTraceElement.Container");
+            textRenderer.render(fileName, output, "StackTraceElement.FileName");
+            textRenderer.render(":", output, "StackTraceElement.ContainerSeparator");
+            textRenderer.render(Integer.toString(lineNumber), output, "StackTraceElement.LineNumber");
+            textRenderer.render(")", output, "StackTraceElement.Container");
+        } else if (fileName != null) {
+            textRenderer.render("(", output, "StackTraceElement.Container");
+            textRenderer.render(fileName, output, "StackTraceElement.FileName");
+            textRenderer.render(")", output, "StackTraceElement.Container");
+        } else {
+            textRenderer.render("(", output, "StackTraceElement.Container");
+            textRenderer.render("Unknown Source", output, "StackTraceElement.UnknownSource");
+            textRenderer.render(")", output, "StackTraceElement.Container");
+        }
+    }
+
     @Override
     public String toString() {
         final StringBuilder sb = new StringBuilder();
-        sb.append(this.stackTraceElement);
-        sb.append(" ");
-        sb.append(this.extraClassInfo);
+        renderOn(sb, PlainTextRenderer.getInstance());
         return sb.toString();
     }
+
 }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/d7f9d951/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableFormatOptions.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableFormatOptions.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableFormatOptions.java
index d5e0285..7a10d0c 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableFormatOptions.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableFormatOptions.java
@@ -20,8 +20,13 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Scanner;
 
+import org.apache.logging.log4j.core.pattern.JAnsiTextRenderer;
+import org.apache.logging.log4j.core.pattern.TextRenderer;
+import org.apache.logging.log4j.core.pattern.PlainTextRenderer;
 import org.apache.logging.log4j.core.util.Constants;
+import org.apache.logging.log4j.core.util.Loader;
 import org.apache.logging.log4j.core.util.Patterns;
+import org.apache.logging.log4j.status.StatusLogger;
 import org.apache.logging.log4j.util.Strings;
 
 /**
@@ -52,6 +57,11 @@ public final class ThrowableFormatOptions {
     private static final String SHORT = "short";
 
     /**
+     * ANSI renderer
+     */
+    private final TextRenderer textRenderer;
+
+    /**
      * The number of lines to write.
      */
     private final int lines;
@@ -74,34 +84,45 @@ public final class ThrowableFormatOptions {
     public static final String LOCALIZED_MESSAGE = "short.localizedMessage";
 
     /**
-     * Construct the options for printing stack trace.
-     * @param lines The number of lines.
-     * @param separator The stack trace separator.
-     * @param packages The packages to filter.
+     * Constructs the options for printing stack trace.
+     * 
+     * @param lines
+     *            The number of lines.
+     * @param separator
+     *            The stack trace separator.
+     * @param packages
+     *            The packages to filter.
+     * @param textRenderer
+     *            The ANSI renderer
      */
-    protected ThrowableFormatOptions(final int lines, final String separator, final List<String> packages) {
+    protected ThrowableFormatOptions(final int lines, final String separator, final List<String> packages,
+            TextRenderer textRenderer) {
         this.lines = lines;
         this.separator = separator == null ? Constants.LINE_SEPARATOR : separator;
         this.packages = packages;
+        this.textRenderer = textRenderer == null ? PlainTextRenderer.getInstance() : textRenderer;
     }
 
     /**
-     * Construct the options for printing stack trace.
-     * @param packages The packages to filter.
+     * Constructs the options for printing stack trace.
+     * 
+     * @param packages
+     *            The packages to filter.
      */
     protected ThrowableFormatOptions(final List<String> packages) {
-        this(DEFAULT_LINES, null, packages);
+        this(DEFAULT_LINES, null, packages, null);
     }
 
     /**
-     * Construct the options for printing stack trace.
+     * Constructs the options for printing stack trace.
      */
     protected ThrowableFormatOptions() {
-        this(DEFAULT_LINES, null, null);
+        this(DEFAULT_LINES, null, null, null);
     }
 
     /**
      * Returns the number of lines to write.
+     * 
      * @return The number of lines to write.
      */
     public int getLines() {
@@ -110,6 +131,7 @@ public final class ThrowableFormatOptions {
 
     /**
      * Returns the stack trace separator.
+     * 
      * @return The stack trace separator.
      */
     public String getSeparator() {
@@ -117,7 +139,17 @@ public final class ThrowableFormatOptions {
     }
 
     /**
+     * Returns the message rendered.
+     * 
+     * @return the message rendered.
+     */
+    public TextRenderer getTextRenderer() {
+        return textRenderer;
+    }
+
+    /**
      * Returns the list of packages to filter.
+     * 
      * @return The list of packages to filter.
      */
     public List<String> getPackages() {
@@ -126,6 +158,7 @@ public final class ThrowableFormatOptions {
 
     /**
      * Determines if all lines should be printed.
+     * 
      * @return true for all lines, false otherwise.
      */
     public boolean allLines() {
@@ -134,6 +167,7 @@ public final class ThrowableFormatOptions {
 
     /**
      * Determines if any lines should be printed.
+     * 
      * @return true for any lines, false otherwise.
      */
     public boolean anyLines() {
@@ -142,7 +176,9 @@ public final class ThrowableFormatOptions {
 
     /**
      * Returns the minimum between the lines and the max lines.
-     * @param maxLines The maximum number of lines.
+     * 
+     * @param maxLines
+     *            The maximum number of lines.
      * @return The number of lines to print.
      */
     public int minLines(final int maxLines) {
@@ -151,6 +187,7 @@ public final class ThrowableFormatOptions {
 
     /**
      * Determines if there are any packages to filter.
+     * 
      * @return true if there are packages, false otherwise.
      */
     public boolean hasPackages() {
@@ -163,7 +200,9 @@ public final class ThrowableFormatOptions {
     @Override
     public String toString() {
         final StringBuilder s = new StringBuilder();
-        s.append('{').append(allLines() ? FULL : this.lines == 2 ? SHORT : anyLines() ? String.valueOf(this.lines) : NONE).append('}');
+        s.append('{')
+                .append(allLines() ? FULL : this.lines == 2 ? SHORT : anyLines() ? String.valueOf(this.lines) : NONE)
+                .append('}');
         s.append("{separator(").append(this.separator).append(")}");
         if (hasPackages()) {
             s.append("{filters(");
@@ -177,8 +216,11 @@ public final class ThrowableFormatOptions {
     }
 
     /**
-     * Create a new instance based on the array of options.
-     * @param options The array of options.
+     * Creates a new instance based on the array of options.
+     * 
+     * @param options
+     *            The array of options.
+     * @return A new initialized instance.
      */
     public static ThrowableFormatOptions newInstance(String[] options) {
         if (options == null || options.length == 0) {
@@ -187,19 +229,16 @@ public final class ThrowableFormatOptions {
         // NOTE: The following code is present for backward compatibility
         // and was copied from Extended/RootThrowablePatternConverter.
         // This supports a single option with the format:
-        //     %xEx{["none"|"short"|"full"|depth],[filters(packages)}
+        // %xEx{["none"|"short"|"full"|depth],[filters(packages)}
         // However, the convention for multiple options should be:
-        //     %xEx{["none"|"short"|"full"|depth]}[{filters(packages)}]
+        // %xEx{["none"|"short"|"full"|depth]}[{filters(packages)}]
         if (options.length == 1 && Strings.isNotEmpty(options[0])) {
             final String[] opts = options[0].split(Patterns.COMMA_SEPARATOR, 2);
             final String first = opts[0].trim();
             try (final Scanner scanner = new Scanner(first)) {
-                if (opts.length > 1
-                        && (first.equalsIgnoreCase(FULL) || first.equalsIgnoreCase(SHORT)
-                                || first.equalsIgnoreCase(NONE) || scanner.hasNextInt())) {
-                    options = new String[] {
-                            first,
-                            opts[1].trim() };
+                if (opts.length > 1 && (first.equalsIgnoreCase(FULL) || first.equalsIgnoreCase(SHORT)
+                        || first.equalsIgnoreCase(NONE) || scanner.hasNextInt())) {
+                    options = new String[] { first, opts[1].trim() };
                 }
             }
         }
@@ -207,6 +246,7 @@ public final class ThrowableFormatOptions {
         int lines = DEFAULT.lines;
         String separator = DEFAULT.separator;
         List<String> packages = DEFAULT.packages;
+        TextRenderer ansiRenderer = DEFAULT.textRenderer;
         for (final String rawOption : options) {
             if (rawOption != null) {
                 final String option = rawOption.trim();
@@ -230,16 +270,26 @@ public final class ThrowableFormatOptions {
                     }
                 } else if (option.equalsIgnoreCase(NONE)) {
                     lines = 0;
-                } else if (option.equalsIgnoreCase(SHORT) || option.equalsIgnoreCase(CLASS_NAME) ||
-                        option.equalsIgnoreCase(METHOD_NAME) || option.equalsIgnoreCase(LINE_NUMBER) ||
-                        option.equalsIgnoreCase(FILE_NAME) || option.equalsIgnoreCase(MESSAGE) ||
-                        option.equalsIgnoreCase(LOCALIZED_MESSAGE)) {
+                } else if (option.equalsIgnoreCase(SHORT) || option.equalsIgnoreCase(CLASS_NAME)
+                        || option.equalsIgnoreCase(METHOD_NAME) || option.equalsIgnoreCase(LINE_NUMBER)
+                        || option.equalsIgnoreCase(FILE_NAME) || option.equalsIgnoreCase(MESSAGE)
+                        || option.equalsIgnoreCase(LOCALIZED_MESSAGE)) {
                     lines = 2;
+                } else if (option.startsWith("ansi(") && option.endsWith(")")) {
+                    if (Loader.isJansiAvailable()) {
+                        ansiRenderer = new JAnsiTextRenderer(
+                                new String[] { null, option.substring("ansi(".length(), option.length() - 1) },
+                                JAnsiTextRenderer.DefaultExceptionStyleMap);
+                    } else {
+                        StatusLogger.getLogger()
+                                .warn("You requested ANSI exception rendering but JANSI is not on the classpath.");
+                    }
                 } else if (!option.equalsIgnoreCase(FULL)) {
                     lines = Integer.parseInt(option);
                 }
             }
         }
-        return new ThrowableFormatOptions(lines, separator, packages);
+        return new ThrowableFormatOptions(lines, separator, packages, ansiRenderer);
     }
+
 }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/d7f9d951/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxy.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxy.java
index 28f8e56..7d30a6f 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxy.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxy.java
@@ -28,6 +28,8 @@ import java.util.Map;
 import java.util.Set;
 import java.util.Stack;
 
+import org.apache.logging.log4j.core.pattern.PlainTextRenderer;
+import org.apache.logging.log4j.core.pattern.TextRenderer;
 import org.apache.logging.log4j.core.util.Loader;
 import org.apache.logging.log4j.status.StatusLogger;
 import org.apache.logging.log4j.util.ReflectionUtil;
@@ -51,7 +53,8 @@ import org.apache.logging.log4j.util.Strings;
  */
 public class ThrowableProxy implements Serializable {
 
-	private static final String CAUSED_BY_LABEL = "Caused by: ";
+	private static final String TAB = "\t";
+    private static final String CAUSED_BY_LABEL = "Caused by: ";
 	private static final String SUPPRESSED_LABEL = "Suppressed: ";
     private static final String WRAPPED_BY_LABEL = "Wrapped by: ";
 
@@ -75,6 +78,8 @@ public class ThrowableProxy implements Serializable {
 
     private static final char EOL = '\n';
 
+    private static final String EOL_STR = "\n";
+
     private static final long serialVersionUID = -2752771578252251910L;
 
     private final ThrowableProxy causeProxy;
@@ -205,76 +210,95 @@ public class ThrowableProxy implements Serializable {
         return true;
     }
 
-	private void formatCause(final StringBuilder sb, final String prefix, final ThrowableProxy cause, final List<String> ignorePackages) {
-		formatThrowableProxy(sb, prefix, CAUSED_BY_LABEL, cause, ignorePackages);
-	}
+    private void formatCause(final StringBuilder sb, final String prefix, final ThrowableProxy cause,
+            final List<String> ignorePackages, TextRenderer textRenderer) {
+        formatThrowableProxy(sb, prefix, CAUSED_BY_LABEL, cause, ignorePackages, textRenderer);
+    }
 
 	private void formatThrowableProxy(final StringBuilder sb, final String prefix, final String causeLabel,
-			final ThrowableProxy throwableProxy, final List<String> ignorePackages) {
+			final ThrowableProxy throwableProxy, final List<String> ignorePackages, TextRenderer textRenderer) {
 		if (throwableProxy == null) {
 			return;
 		}
-		sb.append(prefix).append(causeLabel).append(throwableProxy).append(EOL);
+        textRenderer.render(prefix, sb, "Prefix");
+        textRenderer.render(causeLabel, sb, "CauseLabel");
+        throwableProxy.renderOn(sb, textRenderer);
+        textRenderer.render(EOL_STR, sb, "Text");
 		this.formatElements(sb, prefix, throwableProxy.commonElementCount,
-				throwableProxy.getStackTrace(), throwableProxy.extendedStackTrace, ignorePackages);
-		this.formatSuppressed(sb, prefix + "\t", throwableProxy.suppressedProxies, ignorePackages);
-		this.formatCause(sb, prefix, throwableProxy.causeProxy, ignorePackages);
+				throwableProxy.getStackTrace(), throwableProxy.extendedStackTrace, ignorePackages, textRenderer);
+		this.formatSuppressed(sb, prefix + TAB, throwableProxy.suppressedProxies, ignorePackages, textRenderer);
+		this.formatCause(sb, prefix, throwableProxy.causeProxy, ignorePackages, textRenderer);
 	}
 
-	private void formatSuppressed(final StringBuilder sb, final String prefix, final ThrowableProxy[] suppressedProxies,
-			final List<String> ignorePackages) {
+	void renderOn(StringBuilder output, TextRenderer textRenderer) {
+        final String msg = this.message;
+        textRenderer.render(this.name, output, "Name");
+        if (msg != null) {
+            textRenderer.render(": ", output, "Text");
+            textRenderer.render(msg, output, "Message");
+        }
+    }
+
+    private void formatSuppressed(final StringBuilder sb, final String prefix, final ThrowableProxy[] suppressedProxies,
+			final List<String> ignorePackages, TextRenderer textRenderer) {
 		if (suppressedProxies == null) {
 			return;
 		}
 		for (final ThrowableProxy suppressedProxy : suppressedProxies) {
-			final ThrowableProxy cause = suppressedProxy;
-			formatThrowableProxy(sb, prefix, SUPPRESSED_LABEL, cause, ignorePackages);
+			formatThrowableProxy(sb, prefix, SUPPRESSED_LABEL, suppressedProxy, ignorePackages, textRenderer);
 		}
 	}
 
 	private void formatElements(final StringBuilder sb, final String prefix, final int commonCount,
 			final StackTraceElement[] causedTrace, final ExtendedStackTraceElement[] extStackTrace,
-			final List<String> ignorePackages) {
+			final List<String> ignorePackages, TextRenderer textRenderer) {
 		if (ignorePackages == null || ignorePackages.isEmpty()) {
 			for (final ExtendedStackTraceElement element : extStackTrace) {
-				this.formatEntry(element, sb, prefix);
+				this.formatEntry(element, sb, prefix, textRenderer);
 			}
 		} else {
 			int count = 0;
 			for (int i = 0; i < extStackTrace.length; ++i) {
 				if (!this.ignoreElement(causedTrace[i], ignorePackages)) {
 					if (count > 0) {
-						appendSuppressedCount(sb, prefix, count);
+						appendSuppressedCount(sb, prefix, count, textRenderer);
 						count = 0;
 					}
-					this.formatEntry(extStackTrace[i], sb, prefix);
+					this.formatEntry(extStackTrace[i], sb, prefix, textRenderer);
 				} else {
 					++count;
 				}
 			}
 			if (count > 0) {
-				appendSuppressedCount(sb, prefix, count);
+				appendSuppressedCount(sb, prefix, count, textRenderer);
 			}
 		}
 		if (commonCount != 0) {
-			sb.append(prefix).append("\t... ").append(commonCount).append(" more").append(EOL);
+            textRenderer.render(prefix, sb, "Prefix");
+            textRenderer.render("\t... ", sb, "Text");
+            textRenderer.render(Integer.toString(commonCount), sb, "Text");
+            textRenderer.render(" more", sb, "Text");
+            textRenderer.render(EOL_STR, sb, "Text");
 		}
 	}
 
-    private void appendSuppressedCount(final StringBuilder sb, final String prefix, final int count) {
-    	sb.append(prefix);
+    private void appendSuppressedCount(final StringBuilder sb, final String prefix, final int count, TextRenderer textRenderer) {
+        textRenderer.render(prefix, sb, "Prefix");
         if (count == 1) {
-            sb.append("\t....").append(EOL);
+            textRenderer.render("\t... ", sb, "Text");
         } else {
-            sb.append("\t... suppressed ").append(count).append(" lines").append(EOL);
+            textRenderer.render("\t... suppressed ", sb, "Text");
+            textRenderer.render(Integer.toString(count), sb, "Text");
+            textRenderer.render(" lines", sb, "Text");
         }
+        textRenderer.render(EOL_STR, sb, "Text");
     }
 
-    private void formatEntry(final ExtendedStackTraceElement extStackTraceElement, final StringBuilder sb, final String prefix) {
-        sb.append(prefix);
-        sb.append("\tat ");
-        sb.append(extStackTraceElement);
-        sb.append(EOL);
+    private void formatEntry(final ExtendedStackTraceElement extStackTraceElement, final StringBuilder sb, final String prefix, TextRenderer textRenderer) {
+        textRenderer.render(prefix, sb, "Prefix");
+        textRenderer.render("\tat ", sb, "At");
+        extStackTraceElement.renderOn(sb, textRenderer);
+        textRenderer.render(EOL_STR, sb, "Text");
     }
 
     /**
@@ -286,7 +310,7 @@ public class ThrowableProxy implements Serializable {
      *        The Throwable to format.
      */
     public void formatWrapper(final StringBuilder sb, final ThrowableProxy cause) {
-        this.formatWrapper(sb, cause, null);
+        this.formatWrapper(sb, cause, null, PlainTextRenderer.getInstance());
     }
 
     /**
@@ -296,19 +320,37 @@ public class ThrowableProxy implements Serializable {
      *        StringBuilder to contain the formatted Throwable.
      * @param cause
      *        The Throwable to format.
-     * @param packages
+     * @param ignorePackages
      *        The List of packages to be suppressed from the trace.
      */
     @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
-    public void formatWrapper(final StringBuilder sb, final ThrowableProxy cause, final List<String> packages) {
+    public void formatWrapper(final StringBuilder sb, final ThrowableProxy cause, final List<String> ignorePackages) {
+        this.formatWrapper(sb, cause, ignorePackages, PlainTextRenderer.getInstance());
+    }
+
+    /**
+     * Formats the specified Throwable.
+     * 
+     * @param sb
+     *            StringBuilder to contain the formatted Throwable.
+     * @param cause
+     *            The Throwable to format.
+     * @param ignorePackages
+     *            The List of packages to be suppressed from the trace.
+     * @param textRenderer
+     *            The text render
+     */
+    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+    public void formatWrapper(final StringBuilder sb, final ThrowableProxy cause, final List<String> ignorePackages, final TextRenderer textRenderer) {
         final Throwable caused = cause.getCauseProxy() != null ? cause.getCauseProxy().getThrowable() : null;
         if (caused != null) {
-            this.formatWrapper(sb, cause.causeProxy);
+            this.formatWrapper(sb, cause.causeProxy, ignorePackages, textRenderer);
             sb.append(WRAPPED_BY_LABEL);
         }
-        sb.append(cause).append(EOL);
-        this.formatElements(sb, "", cause.commonElementCount,
-                cause.getThrowable().getStackTrace(), cause.extendedStackTrace, packages);
+        cause.renderOn(sb, textRenderer);
+        textRenderer.render(EOL_STR, sb, "Text");
+        this.formatElements(sb, Strings.EMPTY, cause.commonElementCount,
+                cause.getThrowable().getStackTrace(), cause.extendedStackTrace, ignorePackages, textRenderer);
     }
 
     public ThrowableProxy getCauseProxy() {
@@ -316,35 +358,49 @@ public class ThrowableProxy implements Serializable {
     }
 
     /**
-     * Format the Throwable that is the cause of this Throwable.
+     * Formats the Throwable that is the cause of this Throwable.
      * 
      * @return The formatted Throwable that caused this Throwable.
      */
     public String getCauseStackTraceAsString() {
-        return this.getCauseStackTraceAsString(null);
+        return this.getCauseStackTraceAsString(null, PlainTextRenderer.getInstance());
     }
 
     /**
-     * Format the Throwable that is the cause of this Throwable.
+     * Formats the Throwable that is the cause of this Throwable.
      * 
      * @param packages
      *        The List of packages to be suppressed from the trace.
      * @return The formatted Throwable that caused this Throwable.
      */
     public String getCauseStackTraceAsString(final List<String> packages) {
+        return getCauseStackTraceAsString(null, PlainTextRenderer.getInstance());
+    }
+
+    /**
+     * Formats the Throwable that is the cause of this Throwable.
+     * 
+     * @param ignorePackages
+     *            The List of packages to be suppressed from the trace.
+     * @param textRenderer
+     *            the text renderer
+     * @return The formatted Throwable that caused this Throwable.
+     */
+    public String getCauseStackTraceAsString(final List<String> ignorePackages, final TextRenderer textRenderer) {
         final StringBuilder sb = new StringBuilder();
         if (this.causeProxy != null) {
-            this.formatWrapper(sb, this.causeProxy);
+            this.formatWrapper(sb, this.causeProxy, ignorePackages, textRenderer);
             sb.append(WRAPPED_BY_LABEL);
         }
-        sb.append(this.toString());
-        sb.append(EOL);
-        this.formatElements(sb, "", 0, this.throwable.getStackTrace(), this.extendedStackTrace, packages);
+        this.renderOn(sb, textRenderer);
+        textRenderer.render(EOL_STR, sb, "Text");
+        this.formatElements(sb, Strings.EMPTY, 0, this.throwable.getStackTrace(), this.extendedStackTrace,
+                ignorePackages, textRenderer);
         return sb.toString();
     }
 
     /**
-     * Return the number of elements that are being omitted because they are common with the parent Throwable's stack
+     * Returns the number of elements that are being omitted because they are common with the parent Throwable's stack
      * trace.
      * 
      * @return The number of elements omitted from the stack trace.
@@ -368,7 +424,7 @@ public class ThrowableProxy implements Serializable {
      * @return The formatted stack trace including packaging information.
      */
     public String getExtendedStackTraceAsString() {
-        return this.getExtendedStackTraceAsString(null);
+        return this.getExtendedStackTraceAsString(null, PlainTextRenderer.getInstance());
     }
 
     /**
@@ -379,16 +435,28 @@ public class ThrowableProxy implements Serializable {
      * @return The formatted stack trace including packaging information.
      */
     public String getExtendedStackTraceAsString(final List<String> ignorePackages) {
-        final StringBuilder sb = new StringBuilder(this.name);
-        final String msg = this.message;
-        if (msg != null) {
-            sb.append(": ").append(msg);
-        }
-        sb.append(EOL);
+        return getExtendedStackTraceAsString(ignorePackages, PlainTextRenderer.getInstance());
+    }
+
+    /**
+     * Format the stack trace including packaging information.
+     * 
+     * @param ignorePackages
+     *            List of packages to be ignored in the trace.
+     * @param textRenderer
+     *            The message renderer
+     * @return The formatted stack trace including packaging information.
+     */
+    public String getExtendedStackTraceAsString(final List<String> ignorePackages, final TextRenderer textRenderer) {
+        final StringBuilder sb = new StringBuilder(1024);
+        textRenderer.render(name, sb, "Name");
+        textRenderer.render(": ", sb, "Name");
+        textRenderer.render(this.message, sb, "Message");
+        textRenderer.render(EOL_STR, sb, "Text");
         final StackTraceElement[] causedTrace = this.throwable != null ? this.throwable.getStackTrace() : null;
-        this.formatElements(sb, "", 0, causedTrace, this.extendedStackTrace, ignorePackages);
-        this.formatSuppressed(sb, "\t", this.suppressedProxies, ignorePackages);
-        this.formatCause(sb, "", this.causeProxy, ignorePackages);
+        this.formatElements(sb, Strings.EMPTY, 0, causedTrace, this.extendedStackTrace, ignorePackages, textRenderer);
+        this.formatSuppressed(sb, TAB, this.suppressedProxies, ignorePackages, textRenderer);
+        this.formatCause(sb, Strings.EMPTY, this.causeProxy, ignorePackages, textRenderer);
         return sb.toString();
     }
 
@@ -461,10 +529,12 @@ public class ThrowableProxy implements Serializable {
     }
 
     private boolean ignoreElement(final StackTraceElement element, final List<String> ignorePackages) {
-        final String className = element.getClassName();
-        for (final String pkg : ignorePackages) {
-            if (className.startsWith(pkg)) {
-                return true;
+        if (ignorePackages != null) {
+            final String className = element.getClassName();
+            for (final String pkg : ignorePackages) {
+                if (className.startsWith(pkg)) {
+                    return true;
+                }
             }
         }
         return false;

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/d7f9d951/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ExtendedThrowablePatternConverter.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ExtendedThrowablePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ExtendedThrowablePatternConverter.java
index 8e781b6..01ec640 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ExtendedThrowablePatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ExtendedThrowablePatternConverter.java
@@ -65,7 +65,7 @@ public final class ExtendedThrowablePatternConverter extends ThrowablePatternCon
                 super.format(event, toAppendTo);
                 return;
             }
-            final String extStackTrace = proxy.getExtendedStackTraceAsString(options.getPackages());
+            final String extStackTrace = proxy.getExtendedStackTraceAsString(options.getPackages(), options.getTextRenderer());
             final int len = toAppendTo.length();
             if (len > 0 && !Character.isWhitespace(toAppendTo.charAt(len - 1))) {
                 toAppendTo.append(' ');

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/d7f9d951/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/HtmlMessageRenderer.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/HtmlMessageRenderer.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/HtmlMessageRenderer.java
deleted file mode 100644
index be62365..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/HtmlMessageRenderer.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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.core.pattern;
-
-/**
- * TODO Renders an input as HTML output.
- */
-public final class HtmlMessageRenderer implements MessageRenderer {
-
-    public HtmlMessageRenderer(String[] formats) {
-        // TODO Auto-generated constructor stub
-    }
-
-    @Override
-    public void render(StringBuilder input, StringBuilder output) {
-        // TODO Auto-generated method stub
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/d7f9d951/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/HtmlTextRenderer.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/HtmlTextRenderer.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/HtmlTextRenderer.java
new file mode 100644
index 0000000..e13bae6
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/HtmlTextRenderer.java
@@ -0,0 +1,38 @@
+/*
+ * 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.core.pattern;
+
+/**
+ * TODO Renders an input as HTML output.
+ */
+public final class HtmlTextRenderer implements TextRenderer {
+
+    public HtmlTextRenderer(String[] formats) {
+        // TODO Auto-generated constructor stub
+    }
+
+    @Override
+    public void render(String input, StringBuilder output, String styleName) {
+        // TODO Auto-generated method stub
+    }
+
+    @Override
+    public void render(StringBuilder input, StringBuilder output) {
+        // TODO Auto-generated method stub
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/d7f9d951/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/JAnsiMessageRenderer.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/JAnsiMessageRenderer.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/JAnsiMessageRenderer.java
deleted file mode 100644
index 039275a..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/JAnsiMessageRenderer.java
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * 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.core.pattern;
-
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.Map;
-
-import org.apache.logging.log4j.status.StatusLogger;
-import org.fusesource.jansi.Ansi;
-import org.fusesource.jansi.AnsiRenderer;
-import org.fusesource.jansi.AnsiRenderer.Code;
-
-/**
- * Renders an input as ANSI escaped output.
- * 
- * Uses the JAnsi rendering syntax as the default to render a message into an ANSI escaped string.
- * 
- * The default syntax for embedded ANSI codes is:
- *
- * <pre>
- *   &#64;|<em>code</em>(,<em>code</em>)* <em>text</em>|@
- * </pre>
- *
- * For example, to render the message {@code "Hello"} in green, use:
- *
- * <pre>
- *   &#64;|green Hello|@
- * </pre>
- *
- * To render the message {@code "Hello"} in bold and red, use:
- *
- * <pre>
- *   &#64;|bold,red Warning!|@
- * </pre>
- * 
- * You can also define custom style names in the configuration with the syntax:
- * 
- * <pre>
- * %message{ansi}{StyleName=value(,value)*( StyleName=value(,value)*)*}%n
- * </pre>
- * 
- * For example:
- * 
- * <pre>
- * %message{ansi}{WarningStyle=red,bold KeyStyle=white ValueStyle=blue}%n
- * </pre>
- * 
- * The call site can look like this:
- * 
- * <pre>
- * logger.info("@|KeyStyle {}|@ = @|ValueStyle {}|@", entry.getKey(), entry.getValue());
- * </pre>
- * 
- * Note: This class originally copied and then heavily modified code from JAnsi's AnsiRenderer (which is licensed as
- * Apache 2.0.)
- * 
- * @see AnsiRenderer
- */
-public final class JAnsiMessageRenderer implements MessageRenderer {
-
-    private final String beginToken;
-    private final int beginTokenLen;
-    private final String endToken;
-    private final int endTokenLen;
-    private final Map<String, Code[]> styleMap;
-
-    public JAnsiMessageRenderer(final String[] formats) {
-        String tempBeginToken = AnsiRenderer.BEGIN_TOKEN;
-        String tempEndToken = AnsiRenderer.END_TOKEN;
-        Map<String, Code[]> map;
-        if (formats.length > 1) {
-            final String allStylesStr = formats[1];
-            // Style def split
-            final String[] allStyleAssignmentsArr = allStylesStr.split(" ");
-            map = new HashMap<>(allStyleAssignmentsArr.length);
-            for (final String styleAssignmentStr : allStyleAssignmentsArr) {
-                final String[] styleAssignmentArr = styleAssignmentStr.split("=");
-                if (styleAssignmentArr.length != 2) {
-                    StatusLogger.getLogger().warn("{} parsing style \"{}\", expected format: StyleName=Code(,Code)*",
-                            getClass().getSimpleName(), styleAssignmentStr);
-                } else {
-                    final String styleName = styleAssignmentArr[0];
-                    final String codeListStr = styleAssignmentArr[1];
-                    final String[] codeNames = codeListStr.split(",");
-                    if (codeNames.length == 0) {
-                        StatusLogger.getLogger().warn(
-                                "{} parsing style \"{}\", expected format: StyleName=Code(,Code)*",
-                                getClass().getSimpleName(), styleAssignmentStr);
-                    } else {
-                        switch (styleName) {
-                        case "BeginToken":
-                            tempBeginToken = codeNames[0];
-                            break;
-                        case "EndToken":
-                            tempEndToken = codeNames[0];
-                            break;
-                        default:
-                            final Code[] codes = new Code[codeNames.length];
-                            for (int i = 0; i < codes.length; i++) {
-                                codes[i] = toCode(codeNames[i]);
-                            }
-                            map.put(styleName, codes);
-                        }
-                    }
-                }
-            }
-        } else {
-            map = new HashMap<>(0);
-        }
-        styleMap = map;
-        beginToken = tempBeginToken;
-        endToken = tempEndToken;
-        beginTokenLen = tempBeginToken.length();
-        endTokenLen = tempEndToken.length();
-    }
-
-    private void render(final Ansi ansi, final Code code) {
-        if (code.isColor()) {
-            if (code.isBackground()) {
-                ansi.bg(code.getColor());
-            } else {
-                ansi.fg(code.getColor());
-            }
-        } else if (code.isAttribute()) {
-            ansi.a(code.getAttribute());
-        }
-    }
-
-    private void render(final Ansi ansi, final Code... codes) {
-        for (final Code code : codes) {
-            render(ansi, code);
-        }
-    }
-
-    private String render(final String text, final String... names) {
-        final Ansi ansi = Ansi.ansi();
-        for (final String name : names) {
-            final Code[] codes = styleMap.get(name);
-            if (codes != null) {
-                render(ansi, codes);
-            } else {
-                render(ansi, toCode(name));
-            }
-        }
-        return ansi.a(text).reset().toString();
-    }
-
-    @Override
-    public void render(final StringBuilder input, final StringBuilder output) throws IllegalArgumentException {
-        int i = 0;
-        int j, k;
-
-        while (true) {
-            j = input.indexOf(beginToken, i);
-            if (j == -1) {
-                if (i == 0) {
-                    output.append(input);
-                    return;
-                }
-                output.append(input.substring(i, input.length()));
-                return;
-            }
-            output.append(input.substring(i, j));
-            k = input.indexOf(endToken, j);
-
-            if (k == -1) {
-                output.append(input);
-                return;
-            }
-            j += beginTokenLen;
-            final String spec = input.substring(j, k);
-
-            final String[] items = spec.split(AnsiRenderer.CODE_TEXT_SEPARATOR, 2);
-            if (items.length == 1) {
-                output.append(input);
-                return;
-            }
-            final String replacement = render(items[1], items[0].split(","));
-
-            output.append(replacement);
-
-            i = k + endTokenLen;
-        }
-    }
-
-    private Code toCode(final String name) {
-        return Code.valueOf(name.toUpperCase(Locale.ENGLISH));
-    }
-
-    @Override
-    public String toString() {
-        return "JAnsiMessageRenderer [beginToken=" + beginToken + ", beginTokenLen=" + beginTokenLen + ", endToken="
-                + endToken + ", endTokenLen=" + endTokenLen + ", styleMap=" + styleMap + "]";
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/d7f9d951/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/JAnsiTextRenderer.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/JAnsiTextRenderer.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/JAnsiTextRenderer.java
new file mode 100644
index 0000000..21788ed
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/JAnsiTextRenderer.java
@@ -0,0 +1,268 @@
+/*
+ * 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.core.pattern;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.logging.log4j.status.StatusLogger;
+import org.fusesource.jansi.Ansi;
+import org.fusesource.jansi.AnsiRenderer;
+import org.fusesource.jansi.AnsiRenderer.Code;
+
+/**
+ * Renders an input as ANSI escaped output.
+ * 
+ * Uses the JAnsi rendering syntax as the default to render a message into an ANSI escaped string.
+ * 
+ * The default syntax for embedded ANSI codes is:
+ *
+ * <pre>
+ *   &#64;|<em>code</em>(,<em>code</em>)* <em>text</em>|@
+ * </pre>
+ *
+ * For example, to render the message {@code "Hello"} in green, use:
+ *
+ * <pre>
+ *   &#64;|green Hello|@
+ * </pre>
+ *
+ * To render the message {@code "Hello"} in bold and red, use:
+ *
+ * <pre>
+ *   &#64;|bold,red Warning!|@
+ * </pre>
+ * 
+ * You can also define custom style names in the configuration with the syntax:
+ * 
+ * <pre>
+ * %message{ansi}{StyleName=value(,value)*( StyleName=value(,value)*)*}%n
+ * </pre>
+ * 
+ * For example:
+ * 
+ * <pre>
+ * %message{ansi}{WarningStyle=red,bold KeyStyle=white ValueStyle=blue}%n
+ * </pre>
+ * 
+ * The call site can look like this:
+ * 
+ * <pre>
+ * logger.info("@|KeyStyle {}|@ = @|ValueStyle {}|@", entry.getKey(), entry.getValue());
+ * </pre>
+ * 
+ * Note: This class originally copied and then heavily modified code from JAnsi's AnsiRenderer (which is licensed as
+ * Apache 2.0.)
+ * 
+ * @see AnsiRenderer
+ */
+public final class JAnsiTextRenderer implements TextRenderer {
+
+    public static final Map<String, Code[]> DefaultExceptionStyleMap;
+    public static final Map<String, Code[]> DefaultMessageStyleMap;
+
+    static {
+        {
+            // TODO Should the keys be in an enum?
+            Map<String, Code[]> temp = new HashMap<>();
+            temp.put("Prefix", new Code[] { Code.WHITE });
+            temp.put("Name", new Code[] { Code.RED });
+            temp.put("Message", new Code[] { Code.RED });
+            temp.put("At", new Code[] { Code.WHITE });
+            temp.put("CauseLabel", new Code[] { Code.WHITE });
+            temp.put("Text", new Code[] { Code.WHITE });
+            // StackTraceElement
+            temp.put("StackTraceElement.ClassName", new Code[] { Code.YELLOW });
+            temp.put("StackTraceElement.ClassMethodSeparator", new Code[] { Code.YELLOW });
+            temp.put("StackTraceElement.MethodName", new Code[] { Code.YELLOW });
+            temp.put("StackTraceElement.NativeMethod", new Code[] { Code.YELLOW });
+            temp.put("StackTraceElement.FileName", new Code[] { Code.MAGENTA });
+            temp.put("StackTraceElement.LineNumber", new Code[] { Code.MAGENTA });
+            temp.put("StackTraceElement.Container", new Code[] { Code.MAGENTA });
+            temp.put("StackTraceElement.ContainerSeparator", new Code[] { Code.MAGENTA});
+            temp.put("StackTraceElement.UnknownSource", new Code[] { Code.MAGENTA });
+            // ExtraClassInfo
+            temp.put("ExtraClassInfo.Inexact", new Code[] { Code.GREEN });
+            temp.put("ExtraClassInfo.Container", new Code[] { Code.GREEN, Code.INTENSITY_FAINT });
+            temp.put("ExtraClassInfo.ContainerSeparator", new Code[] { Code.GREEN, Code.INTENSITY_FAINT });
+            temp.put("ExtraClassInfo.Location", new Code[] { Code.GREEN });
+            temp.put("ExtraClassInfo.Version", new Code[] { Code.GREEN });
+            // Save
+            DefaultExceptionStyleMap = Collections.unmodifiableMap(temp);
+        }
+        {
+            Map<String, Code[]> temp = new HashMap<>();
+            // TODO
+            DefaultMessageStyleMap = Collections.unmodifiableMap(temp);
+        }
+    }
+
+    private final String beginToken;
+    private final int beginTokenLen;
+    private final String endToken;
+    private final int endTokenLen;
+    private final Map<String, Code[]> styleMap;
+
+    public JAnsiTextRenderer(final String[] formats, Map<String, Code[]> defaultStyleMap) {
+        String tempBeginToken = AnsiRenderer.BEGIN_TOKEN;
+        String tempEndToken = AnsiRenderer.END_TOKEN;
+        Map<String, Code[]> map;
+        if (formats.length > 1) {
+            final String allStylesStr = formats[1];
+            // Style def split
+            final String[] allStyleAssignmentsArr = allStylesStr.split(" ");
+            map = new HashMap<>(allStyleAssignmentsArr.length + defaultStyleMap.size());
+            map.putAll(defaultStyleMap);
+            for (final String styleAssignmentStr : allStyleAssignmentsArr) {
+                final String[] styleAssignmentArr = styleAssignmentStr.split("=");
+                if (styleAssignmentArr.length != 2) {
+                    StatusLogger.getLogger().warn("{} parsing style \"{}\", expected format: StyleName=Code(,Code)*",
+                            getClass().getSimpleName(), styleAssignmentStr);
+                } else {
+                    final String styleName = styleAssignmentArr[0];
+                    final String codeListStr = styleAssignmentArr[1];
+                    final String[] codeNames = codeListStr.split(",");
+                    if (codeNames.length == 0) {
+                        StatusLogger.getLogger().warn(
+                                "{} parsing style \"{}\", expected format: StyleName=Code(,Code)*",
+                                getClass().getSimpleName(), styleAssignmentStr);
+                    } else {
+                        switch (styleName) {
+                        case "BeginToken":
+                            tempBeginToken = codeNames[0];
+                            break;
+                        case "EndToken":
+                            tempEndToken = codeNames[0];
+                            break;
+                        default:
+                            final Code[] codes = new Code[codeNames.length];
+                            for (int i = 0; i < codes.length; i++) {
+                                codes[i] = toCode(codeNames[i]);
+                            }
+                            map.put(styleName, codes);
+                        }
+                    }
+                }
+            }
+        } else {
+            map = defaultStyleMap;
+        }
+        styleMap = map;
+        beginToken = tempBeginToken;
+        endToken = tempEndToken;
+        beginTokenLen = tempBeginToken.length();
+        endTokenLen = tempEndToken.length();
+    }
+
+    private void render(final Ansi ansi, final Code code) {
+        if (code.isColor()) {
+            if (code.isBackground()) {
+                ansi.bg(code.getColor());
+            } else {
+                ansi.fg(code.getColor());
+            }
+        } else if (code.isAttribute()) {
+            ansi.a(code.getAttribute());
+        }
+    }
+
+    private void render(final Ansi ansi, final Code... codes) {
+        for (final Code code : codes) {
+            render(ansi, code);
+        }
+    }
+
+    /**
+     * Renders the given text with the given names which can be ANSI code names or Log4j style names.
+     * 
+     * @param text
+     *            The text to render
+     * @param names
+     *            ANSI code names or Log4j style names.
+     * @return A rendered string containing ANSI codes.
+     */
+    private String render(final String text, final String... names) {
+        final Ansi ansi = Ansi.ansi();
+        for (final String name : names) {
+            final Code[] codes = styleMap.get(name);
+            if (codes != null) {
+                render(ansi, codes);
+            } else {
+                render(ansi, toCode(name));
+            }
+        }
+        return ansi.a(text).reset().toString();
+    }
+
+    @Override
+    public void render(final StringBuilder input, final StringBuilder output) throws IllegalArgumentException {
+        int i = 0;
+        int j, k;
+
+        while (true) {
+            j = input.indexOf(beginToken, i);
+            if (j == -1) {
+                if (i == 0) {
+                    output.append(input);
+                    return;
+                }
+                output.append(input.substring(i, input.length()));
+                return;
+            }
+            output.append(input.substring(i, j));
+            k = input.indexOf(endToken, j);
+
+            if (k == -1) {
+                output.append(input);
+                return;
+            }
+            j += beginTokenLen;
+            final String spec = input.substring(j, k);
+
+            final String[] items = spec.split(AnsiRenderer.CODE_TEXT_SEPARATOR, 2);
+            if (items.length == 1) {
+                output.append(input);
+                return;
+            }
+            final String replacement = render(items[1], items[0].split(","));
+
+            output.append(replacement);
+
+            i = k + endTokenLen;
+        }
+    }
+
+    // EXACT COPY OF StringBuilder version of the method but typed as String for input
+    @Override
+    public void render(final String input, final StringBuilder output, final String styleName)
+            throws IllegalArgumentException {
+        output.append(render(input, styleName));
+    }
+
+    private Code toCode(final String name) {
+        return Code.valueOf(name.toUpperCase(Locale.ENGLISH));
+    }
+
+    @Override
+    public String toString() {
+        return "JAnsiMessageRenderer [beginToken=" + beginToken + ", beginTokenLen=" + beginTokenLen + ", endToken="
+                + endToken + ", endTokenLen=" + endTokenLen + ", styleMap=" + styleMap + "]";
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/d7f9d951/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MessagePatternConverter.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MessagePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MessagePatternConverter.java
index a5058b0..75af44c 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MessagePatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MessagePatternConverter.java
@@ -36,7 +36,7 @@ public final class MessagePatternConverter extends LogEventPatternConverter {
 
     private final String[] formats;
     private final Configuration config;
-    private final MessageRenderer messageRenderer;
+    private final TextRenderer textRenderer;
 
     /**
      * Private constructor.
@@ -48,22 +48,22 @@ public final class MessagePatternConverter extends LogEventPatternConverter {
         super("Message", "message");
         this.formats = options;
         this.config = config;
-        this.messageRenderer = loadMessageRenderer(options);
+        this.textRenderer = loadMessageRenderer(options);
     }
 
-    private MessageRenderer loadMessageRenderer(String[] options) {
+    private TextRenderer loadMessageRenderer(String[] options) {
         if (formats != null && formats.length > 0) {
             final String format = formats[0].toUpperCase(Locale.ROOT);
             switch (format) {
             case "ANSI":
-                if (Loader.isClassAvailable("org.fusesource.jansi.AnsiRenderer")) {
-                    return new JAnsiMessageRenderer(formats);
+                if (Loader.isJansiAvailable()) {
+                    return new JAnsiTextRenderer(formats, JAnsiTextRenderer.DefaultMessageStyleMap);
                 }
                 StatusLogger.getLogger()
                         .warn("You requested ANSI message rendering but JANSI is not on the classpath.");
                 return null;
             case "HTML":
-                return new HtmlMessageRenderer(formats);
+                return new HtmlTextRenderer(formats);
             }
         }
         return null;
@@ -90,7 +90,7 @@ public final class MessagePatternConverter extends LogEventPatternConverter {
         final Message msg = event.getMessage();
         if (msg instanceof StringBuilderFormattable) {
 
-            boolean doRender = messageRenderer != null;
+            boolean doRender = textRenderer != null;
             StringBuilder workingBuilder = doRender ? new StringBuilder(80) : toAppendTo;
 
             StringBuilderFormattable stringBuilderFormattable = (StringBuilderFormattable) msg;
@@ -108,7 +108,7 @@ public final class MessagePatternConverter extends LogEventPatternConverter {
                 }
             }
             if (doRender) {
-                messageRenderer.render(workingBuilder, toAppendTo);
+                textRenderer.render(workingBuilder, toAppendTo);
             }
             return;
         }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/d7f9d951/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MessageRenderer.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MessageRenderer.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MessageRenderer.java
deleted file mode 100644
index 3612403..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MessageRenderer.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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.core.pattern;
-
-/**
- * Renders an input to an output.
- */
-public interface MessageRenderer {
-
-    /**
-     * Renders an input to an output.
-     * 
-     * @param input 
-     * @param output
-     */
-    void render(StringBuilder input, StringBuilder output);
-
-}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/d7f9d951/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/PlainTextRenderer.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/PlainTextRenderer.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/PlainTextRenderer.java
new file mode 100644
index 0000000..9e26bba
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/PlainTextRenderer.java
@@ -0,0 +1,40 @@
+/*
+ * 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.core.pattern;
+
+/**
+ Renders input unchanged.
+ */
+public final class PlainTextRenderer implements TextRenderer {
+    
+    private static final PlainTextRenderer INSTANCE = new PlainTextRenderer();
+
+    public static PlainTextRenderer getInstance() {
+        return INSTANCE;
+    }
+
+    @Override
+    public void render(String input, StringBuilder output, String styleName) {
+        output.append(input);
+    }
+
+    @Override
+    public void render(StringBuilder input, StringBuilder output) {
+        output.append(input);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/d7f9d951/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/TextRenderer.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/TextRenderer.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/TextRenderer.java
new file mode 100644
index 0000000..2355c88
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/TextRenderer.java
@@ -0,0 +1,46 @@
+/*
+ * 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.core.pattern;
+
+/**
+ * Renders an input to an output.
+ */
+public interface TextRenderer {
+
+    /**
+     * Renders input text to an output.
+     * 
+     * @param input
+     *            The input
+     * @param output
+     *            The output
+     * @param styleName
+     *            The style name to use to render the input on the output.
+     */
+    void render(String input, StringBuilder output, String styleName);
+
+    /**
+     * Renders input text to an output.
+     * 
+     * @param input
+     *            The input
+     * @param output
+     *            The output
+     */
+    void render(StringBuilder input, StringBuilder output);
+
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/d7f9d951/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Loader.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Loader.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Loader.java
index bb35752..9f402cd 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Loader.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Loader.java
@@ -318,4 +318,9 @@ public final class Loader {
             return false;
         }
     }
+
+    public static boolean isJansiAvailable() {
+        return isClassAvailable("org.fusesource.jansi.AnsiRenderer");
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/d7f9d951/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderJAnsiXExceptionMain.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderJAnsiXExceptionMain.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderJAnsiXExceptionMain.java
new file mode 100644
index 0000000..d85c5b0
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderJAnsiXExceptionMain.java
@@ -0,0 +1,74 @@
+/*
+ * 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.core.appender;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.Configurator;
+import org.junit.Test;
+
+/**
+ * Shows how to use ANSI escape codes to color messages. Each message is printed to the console in color, but the rest
+ * of the log entry (time stamp for example) is in the default color for that console.
+ * <p>
+ * Running from a Windows command line from the root of the project:
+ * </p>
+ *
+ * <pre>
+ * mvn -Dtest=org.apache.logging.log4j.core.appender.ConsoleAppenderJAnsiXExceptionMain test
+ * </pre>
+ * 
+ * or, on Windows:
+ * 
+ * <pre>
+ * java -classpath log4j-core\target\test-classes;log4j-core\target\classes;log4j-api\target\classes;%USERPROFILE%\.m2\repository\org\fusesource\jansi\jansi\1.13\jansi-1.13.jar; org.apache.logging.log4j.core.appender.ConsoleAppenderJAnsiXExceptionMain log4j-core/src/test/resources/log4j2-console-xex-ansi.xml
+ * </pre>
+ * 
+ */
+public class ConsoleAppenderJAnsiXExceptionMain {
+
+    public static void main(final String[] args) {
+        new ConsoleAppenderJAnsiXExceptionMain().test(args);
+    }
+
+    /**
+     * This is a @Test method to make it easy to run from a command line with {@code mvn -Dtest=FQCN test}
+     */
+    @Test
+    public void test() {
+        test(null);
+    }
+
+    public void test(final String[] args) {
+        // System.out.println(System.getProperty("java.class.path"));
+        final String config = args == null || args.length == 0 ? "target/test-classes/log4j2-console-xex-ansi.xml"
+                : args[0];
+        final LoggerContext ctx = Configurator.initialize(ConsoleAppenderAnsiMessagesMain.class.getName(), config);
+        final Logger logger = LogManager.getLogger(ConsoleAppenderJAnsiXExceptionMain.class);
+        try {
+            IllegalArgumentException e = new IllegalArgumentException("Bad argument foo");
+            e.fillInStackTrace();
+            throw new IllegalStateException("You're going down!", e);
+        } catch (Exception e) {
+            logger.info("Gotcha!", e);
+        } finally {
+            Configurator.shutdown(ctx);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/d7f9d951/log4j-core/src/test/resources/log4j2-console-xex-ansi.xml
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/resources/log4j2-console-xex-ansi.xml b/log4j-core/src/test/resources/log4j2-console-xex-ansi.xml
new file mode 100644
index 0000000..d28b169
--- /dev/null
+++ b/log4j-core/src/test/resources/log4j2-console-xex-ansi.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+
+-->
+<Configuration status="OFF">
+  <Appenders>
+    <Console name="Console" target="SYSTEM_OUT">
+      <PatternLayout pattern="%d [%t] %-5level: %msg{ansi}{CaughtMsg=red}%n%xThrowable{full}{ansi(CaughtMsg=red)}" />
+    </Console>
+  </Appenders>
+  <Loggers>
+    <Logger name="org.foo" level="DEBUG" />
+    <Root level="TRACE">
+      <AppenderRef ref="Console" />
+    </Root>
+  </Loggers>
+</Configuration>