Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
MulticolorLayout |
|
| 2.1;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><dependency> | |
75 | * <groupId>com.jcabi</groupId> | |
76 | * <artifactId>jcabi-log</artifactId> | |
77 | * </dependency></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 | } |