Coverage Report - com.jcabi.log.MulticolorLayout
 
Classes in this File Line Coverage Branch Coverage Complexity
MulticolorLayout
0%
0/61
0%
0/26
2.1
 
 1  
 /**
 2  
  * Copyright (c) 2012-2015, jcabi.com
 3  
  * All rights reserved.
 4  
  *
 5  
  * Redistribution and use in source and binary forms, with or without
 6  
  * modification, are permitted provided that the following conditions
 7  
  * are met: 1) Redistributions of source code must retain the above
 8  
  * copyright notice, this list of conditions and the following
 9  
  * disclaimer. 2) Redistributions in binary form must reproduce the above
 10  
  * copyright notice, this list of conditions and the following
 11  
  * disclaimer in the documentation and/or other materials provided
 12  
  * with the distribution. 3) Neither the name of the jcabi.com nor
 13  
  * the names of its contributors may be used to endorse or promote
 14  
  * products derived from this software without specific prior written
 15  
  * permission.
 16  
  *
 17  
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 18  
  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
 19  
  * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 20  
  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 21  
  * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 22  
  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 23  
  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 24  
  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 25  
  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 26  
  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 27  
  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 28  
  * OF THE POSSIBILITY OF SUCH DAMAGE.
 29  
  */
 30  
 package com.jcabi.log;
 31  
 
 32  
 import java.util.Locale;
 33  
 import java.util.concurrent.ConcurrentHashMap;
 34  
 import java.util.concurrent.ConcurrentMap;
 35  
 import java.util.regex.Matcher;
 36  
 import java.util.regex.Pattern;
 37  
 import lombok.EqualsAndHashCode;
 38  
 import lombok.ToString;
 39  
 import org.apache.log4j.EnhancedPatternLayout;
 40  
 import org.apache.log4j.Level;
 41  
 import org.apache.log4j.spi.LoggingEvent;
 42  
 
 43  
 /**
 44  
  * Multi-color layout for LOG4J.
 45  
  *
 46  
  * <p>Use it in your LOG4J configuration:
 47  
  *
 48  
  * <pre> log4j.rootLogger=INFO, CONSOLE
 49  
  * log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
 50  
  * log4j.appender.CONSOLE.layout=com.jcabi.log.MulticolorLayout
 51  
  * log4j.appender.CONSOLE.layout.ConversionPattern=[%color{%-5p}] %c: %m%n</pre>
 52  
  *
 53  
  * <p>The part of the message wrapped with {@code %color{...}}
 54  
  * will change its color according to the logging level of the event. Without
 55  
  * this highlighting the behavior of the layout is identical to
 56  
  * {@link EnhancedPatternLayout}. You can use {@code %color-red{...}} if you
 57  
  * want to use specifically red color for the wrapped piece of text. Supported
 58  
  * colors are: {@code red}, {@code blue}, {@code yellow}, {@code cyan},
 59  
  * {@code black}, and {@code white}.
 60  
  *
 61  
  * <p>Besides that you can specify any ANSI color you like with
 62  
  * {@code %color-<attr>;<bg>;<fg>{...}}, where
 63  
  * {@code <attr>} is a binary mask of attributes,
 64  
  * {@code <bg>} is a background color, and
 65  
  * {@code <fg>} is a foreground color. Read more about
 66  
  * <a href="http://en.wikipedia.org/wiki/ANSI_escape_code">ANSI escape code</a>.
 67  
  *
 68  
  * <p>This class or its parents are <b>not</b> serializable.
 69  
  *
 70  
  * <p>Maven dependency for this class is
 71  
  * (see <a href="http://www.jcabi.com/jcabi-log/multicolor.html">How
 72  
  * to use with Maven</a> instructions):
 73  
  *
 74  
  * <pre>&lt;dependency&gt;
 75  
  *  &lt;groupId&gt;com.jcabi&lt;/groupId&gt;
 76  
  *  &lt;artifactId&gt;jcabi-log&lt;/artifactId&gt;
 77  
  * &lt;/dependency&gt;</pre>
 78  
  *
 79  
  * @author Yegor Bugayenko (yegor@teamed.io)
 80  
  * @version $Id: d8edddf544b5f8f08ca4197d167afb803b966ac1 $
 81  
  * @since 0.1.10
 82  
  * @see <a href="http://en.wikipedia.org/wiki/ANSI_escape_code">ANSI escape code</a>
 83  
  * @see <a href="http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/PatternLayout.html">PatternLayout from LOG4J</a>
 84  
  * @see <a href="http://www.jcabi.com/jcabi-log/multicolor.html">How to use with Maven</a>
 85  
  */
 86  0
 @ToString
 87  0
 @EqualsAndHashCode(callSuper = false)
 88  
 @SuppressWarnings("PMD.NonStaticInitializer")
 89  0
 public final class MulticolorLayout extends EnhancedPatternLayout {
 90  
     /**
 91  
      * Control sequence indicator.
 92  
      */
 93  
     private static final String CSI = "\u001b[";
 94  
 
 95  
     /**
 96  
      * To split strings with javascript like map syntax.
 97  
      */
 98  
     private static final String SPLIT_ITEMS = ",";
 99  
 
 100  
     /**
 101  
      * To split key:value pairs.
 102  
      */
 103  
     private static final String SPLIT_VALUES = ":";
 104  
 
 105  
     /**
 106  
      * Regular expression for all matches.
 107  
      */
 108  0
     private static final Pattern METAS = Pattern.compile(
 109  
         "%color(?:-([a-z]+|[0-9]{1,3};[0-9]{1,3};[0-9]{1,3}))?\\{(.*?)\\}"
 110  
     );
 111  
 
 112  
     /**
 113  
      * A format string for a color placeholder.
 114  
      */
 115  
     private static final String COLOR_PLACEHOLDER = "%s?m";
 116  
 
 117  
     /**
 118  
      * Name of the property that is used to disable log coloring.
 119  
      */
 120  
     private static final String COLORING_PROPERY = "com.jcabi.log.coloring";
 121  
 
 122  
     /**
 123  
      * Colors with names.
 124  
      */
 125  0
     private final transient ConcurrentMap<String, String> colors =
 126  
         MulticolorLayout.colorMap();
 127  
 
 128  
     /**
 129  
      * Colors of levels.
 130  
      */
 131  0
     private final transient ConcurrentMap<String, String> levels =
 132  
         MulticolorLayout.levelMap();
 133  
 
 134  
     /**
 135  
      * Store original conversation pattern to be able
 136  
      * to recalculate it, if new colors are provided.
 137  
      */
 138  
     private transient String base;
 139  
 
 140  
     @Override
 141  
     public void setConversionPattern(final String pattern) {
 142  0
         this.base = pattern;
 143  0
         final Matcher matcher = MulticolorLayout.METAS.matcher(pattern);
 144  0
         final StringBuffer buf = new StringBuffer(0);
 145  0
         while (matcher.find()) {
 146  0
             matcher.appendReplacement(buf, "");
 147  0
             buf.append(MulticolorLayout.CSI)
 148  
                 .append(this.ansi(matcher.group(1)))
 149  
                 .append('m')
 150  
                 .append(matcher.group(2))
 151  
                 .append(MulticolorLayout.CSI)
 152  
                 .append('m');
 153  
         }
 154  0
         matcher.appendTail(buf);
 155  0
         super.setConversionPattern(buf.toString());
 156  0
     }
 157  
 
 158  
     /**
 159  
      * Allow to overwrite or specify new ANSI color names
 160  
      * in a javascript map like format.
 161  
      *
 162  
      * @param cols JavaScript like map of color names
 163  
      * @since 0.9
 164  
      */
 165  
     public void setColors(final String cols) {
 166  0
         for (final String item : cols.split(MulticolorLayout.SPLIT_ITEMS)) {
 167  0
             final String[] values = item.split(MulticolorLayout.SPLIT_VALUES);
 168  0
             this.colors.put(values[0], values[1]);
 169  
         }
 170  
         /**
 171  
          * If setConversionPattern was called before me must call again
 172  
          * to be sure to replace all custom color constants with
 173  
          * new values.
 174  
          */
 175  0
         if (this.base != null) {
 176  0
             this.setConversionPattern(this.base);
 177  
         }
 178  0
     }
 179  
 
 180  
     /**
 181  
      * Allow to overwrite the ANSI color values for the log levels
 182  
      * in a javascript map like format.
 183  
      *
 184  
      * @param lev JavaScript like map of levels
 185  
      * @since 0.9
 186  
      */
 187  
     public void setLevels(final String lev) {
 188  0
         for (final String item : lev.split(MulticolorLayout.SPLIT_ITEMS)) {
 189  0
             final String[] values = item.split(MulticolorLayout.SPLIT_VALUES);
 190  0
             final String level = values[0].toUpperCase(Locale.ENGLISH);
 191  0
             if (Level.toLevel(level, null) == null) {
 192  0
                 throw new IllegalArgumentException(
 193  
                     String.format(Locale.ENGLISH, "Unknown level '%s'", level)
 194  
                 );
 195  
             }
 196  0
             this.levels.put(level, values[1]);
 197  
         }
 198  0
     }
 199  
 
 200  
     @Override
 201  
     public String format(final LoggingEvent event) {
 202  
         final String answer;
 203  0
         if (this.isColoringEnabled()) {
 204  0
             answer = this.colorfulFormatting(event);
 205  
         } else {
 206  0
             answer = this.dullFormatting(event);
 207  
         }
 208  0
         return answer;
 209  
     }
 210  
 
 211  
     /**
 212  
      * Formats a log event without using ANSI color codes.
 213  
      * @param event Log event
 214  
      * @return Text of a log event, not colored with ANSI color codes even
 215  
      *  if there is markup that tells to color it.
 216  
      */
 217  
     private String dullFormatting(final LoggingEvent event) {
 218  0
         return super.format(event)
 219  
             .replace(
 220  
                 String.format(
 221  
                     MulticolorLayout.COLOR_PLACEHOLDER,
 222  
                     MulticolorLayout.CSI
 223  
                 ),
 224  
                 ""
 225  
             )
 226  
             .replace(String.format("%sm", MulticolorLayout.CSI), "");
 227  
     }
 228  
 
 229  
     /**
 230  
      * Formats a log event using ANSI color codes.
 231  
      * @param event Log event
 232  
      * @return Text of a log event, probably colored with ANSI color codes.
 233  
      */
 234  
     private String colorfulFormatting(final LoggingEvent event) {
 235  0
         return super.format(event).replace(
 236  
             String.format(COLOR_PLACEHOLDER, MulticolorLayout.CSI),
 237  
             String.format(
 238  
                 "%s%sm",
 239  
                 MulticolorLayout.CSI,
 240  
                 this.levels.get(event.getLevel().toString())
 241  
             )
 242  
         );
 243  
     }
 244  
 
 245  
     /**
 246  
      * Checks if coloring is enabled.
 247  
      * @return True iff coloring is enabled.
 248  
      */
 249  
     private boolean isColoringEnabled() {
 250  0
         return !"false".equals(
 251  
             System.getProperty(MulticolorLayout.COLORING_PROPERY)
 252  
         );
 253  
     }
 254  
 
 255  
     /**
 256  
      * Convert our text to ANSI color.
 257  
      * @param meta Meta text
 258  
      * @return ANSI color
 259  
      */
 260  
     private String ansi(final String meta) {
 261  
         final String ansi;
 262  0
         if (meta == null) {
 263  0
             ansi = "?";
 264  0
         } else if (meta.matches("[a-z]+")) {
 265  0
             ansi = this.colors.get(meta);
 266  0
             if (ansi == null) {
 267  0
                 throw new IllegalArgumentException(
 268  
                     String.format("unknown color '%s'", meta)
 269  
                 );
 270  
             }
 271  
         } else {
 272  0
             ansi = meta;
 273  
         }
 274  0
         return ansi;
 275  
     }
 276  
 
 277  
     /**
 278  
      * Color map.
 279  
      * @return Map of colors
 280  
      */
 281  
     private static ConcurrentMap<String, String> colorMap() {
 282  0
         final ConcurrentMap<String, String> map =
 283  
             new ConcurrentHashMap<String, String>();
 284  0
         map.put("black", "30");
 285  0
         map.put("blue", "34");
 286  0
         map.put("cyan", "36");
 287  0
         map.put("green", "32");
 288  0
         map.put("magenta", "35");
 289  0
         map.put("red", "31");
 290  0
         map.put("yellow", "33");
 291  0
         map.put("white", "37");
 292  0
         return map;
 293  
     }
 294  
 
 295  
     /**
 296  
      * Level map.
 297  
      * @return Map of levels
 298  
      */
 299  
     private static ConcurrentMap<String, String> levelMap() {
 300  0
         final ConcurrentMap<String, String> map =
 301  
             new ConcurrentHashMap<String, String>();
 302  0
         map.put(Level.TRACE.toString(), "2;33");
 303  0
         map.put(Level.DEBUG.toString(), "2;37");
 304  0
         map.put(Level.INFO.toString(), "0;37");
 305  0
         map.put(Level.WARN.toString(), "0;33");
 306  0
         map.put(Level.ERROR.toString(), "0;31");
 307  0
         map.put(Level.FATAL.toString(), "0;35");
 308  0
         return map;
 309  
     }
 310  
 
 311  
 }