1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.logging.log4j.core.appender;
18
19 import java.io.FileDescriptor;
20 import java.io.FileOutputStream;
21 import java.io.IOException;
22 import java.io.OutputStream;
23 import java.io.PrintStream;
24 import java.io.Serializable;
25 import java.io.UnsupportedEncodingException;
26 import java.lang.reflect.Constructor;
27 import java.nio.charset.Charset;
28 import java.util.concurrent.atomic.AtomicInteger;
29
30 import org.apache.logging.log4j.core.Appender;
31 import org.apache.logging.log4j.core.Core;
32 import org.apache.logging.log4j.core.Filter;
33 import org.apache.logging.log4j.core.Layout;
34 import org.apache.logging.log4j.core.config.plugins.Plugin;
35 import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
36 import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
37 import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
38 import org.apache.logging.log4j.core.layout.PatternLayout;
39 import org.apache.logging.log4j.core.util.Booleans;
40 import org.apache.logging.log4j.core.util.CloseShieldOutputStream;
41 import org.apache.logging.log4j.core.util.Throwables;
42 import org.apache.logging.log4j.util.Chars;
43 import org.apache.logging.log4j.util.LoaderUtil;
44 import org.apache.logging.log4j.util.PropertiesUtil;
45
46
47
48
49
50
51
52
53
54
55
56 @Plugin(name = ConsoleAppender.PLUGIN_NAME, category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true)
57 public final class ConsoleAppender extends AbstractOutputStreamAppender<OutputStreamManager> {
58
59 public static final String PLUGIN_NAME = "Console";
60 private static final String JANSI_CLASS = "org.fusesource.jansi.WindowsAnsiOutputStream";
61 private static ConsoleManagerFactory factory = new ConsoleManagerFactory();
62 private static final Target DEFAULT_TARGET = Target.SYSTEM_OUT;
63 private static final AtomicInteger COUNT = new AtomicInteger();
64
65 private final Target target;
66
67
68
69
70 public enum Target {
71
72
73 SYSTEM_OUT {
74 @Override
75 public Charset getDefaultCharset() {
76
77 return getCharset("sun.stdout.encoding", Charset.defaultCharset());
78 }
79 },
80
81
82 SYSTEM_ERR {
83 @Override
84 public Charset getDefaultCharset() {
85
86 return getCharset("sun.stderr.encoding", Charset.defaultCharset());
87 }
88 };
89
90 public abstract Charset getDefaultCharset();
91
92 protected Charset getCharset(final String property, Charset defaultCharset) {
93 return new PropertiesUtil(PropertiesUtil.getSystemProperties()).getCharsetProperty(property, defaultCharset);
94 }
95
96 }
97
98 private ConsoleAppender(final String name, final Layout<? extends Serializable> layout, final Filter filter,
99 final OutputStreamManager manager, final boolean ignoreExceptions, final Target target) {
100 super(name, layout, filter, ignoreExceptions, true, manager);
101 this.target = target;
102 }
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117 @Deprecated
118 public static ConsoleAppender createAppender(Layout<? extends Serializable> layout,
119 final Filter filter,
120 final String targetStr,
121 final String name,
122 final String follow,
123 final String ignore) {
124 if (name == null) {
125 LOGGER.error("No name provided for ConsoleAppender");
126 return null;
127 }
128 if (layout == null) {
129 layout = PatternLayout.createDefaultLayout();
130 }
131 final boolean isFollow = Boolean.parseBoolean(follow);
132 final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
133 final Target target = targetStr == null ? DEFAULT_TARGET : Target.valueOf(targetStr);
134 return new ConsoleAppender(name, layout, filter, getManager(target, isFollow, false, layout), ignoreExceptions, target);
135 }
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152 @Deprecated
153 public static ConsoleAppender createAppender(
154
155 Layout<? extends Serializable> layout,
156 final Filter filter,
157 Target target,
158 final String name,
159 final boolean follow,
160 final boolean direct,
161 final boolean ignoreExceptions) {
162
163 if (name == null) {
164 LOGGER.error("No name provided for ConsoleAppender");
165 return null;
166 }
167 if (layout == null) {
168 layout = PatternLayout.createDefaultLayout();
169 }
170 target = target == null ? Target.SYSTEM_OUT : target;
171 if (follow && direct) {
172 LOGGER.error("Cannot use both follow and direct on ConsoleAppender");
173 return null;
174 }
175 return new ConsoleAppender(name, layout, filter, getManager(target, follow, direct, layout), ignoreExceptions, target);
176 }
177
178 public static ConsoleAppender createDefaultAppenderForLayout(final Layout<? extends Serializable> layout) {
179
180 return new ConsoleAppender("DefaultConsole-" + COUNT.incrementAndGet(), layout, null,
181 getDefaultManager(DEFAULT_TARGET, false, false, layout), true, DEFAULT_TARGET);
182 }
183
184 @PluginBuilderFactory
185 public static <B extends Builder<B>> B newBuilder() {
186 return new Builder<B>().asBuilder();
187 }
188
189
190
191
192
193 public static class Builder<B extends Builder<B>> extends AbstractOutputStreamAppender.Builder<B>
194 implements org.apache.logging.log4j.core.util.Builder<ConsoleAppender> {
195
196 @PluginBuilderAttribute
197 @Required
198 private Target target = DEFAULT_TARGET;
199
200 @PluginBuilderAttribute
201 private boolean follow;
202
203 @PluginBuilderAttribute
204 private boolean direct;
205
206 public B setTarget(final Target aTarget) {
207 this.target = aTarget;
208 return asBuilder();
209 }
210
211 public B setFollow(final boolean shouldFollow) {
212 this.follow = shouldFollow;
213 return asBuilder();
214 }
215
216 public B setDirect(final boolean shouldDirect) {
217 this.direct = shouldDirect;
218 return asBuilder();
219 }
220
221 @Override
222 public ConsoleAppender build() {
223 if (follow && direct) {
224 throw new IllegalArgumentException("Cannot use both follow and direct on ConsoleAppender '" + getName() + "'");
225 }
226 final Layout<? extends Serializable> layout = getOrCreateLayout(target.getDefaultCharset());
227 return new ConsoleAppender(getName(), layout, getFilter(), getManager(target, follow, direct, layout),
228 isIgnoreExceptions(), target);
229 }
230 }
231
232 private static OutputStreamManager getDefaultManager(final Target target, final boolean follow, final boolean direct,
233 final Layout<? extends Serializable> layout) {
234 final OutputStream os = getOutputStream(follow, direct, target);
235
236
237 final String managerName = target.name() + '.' + follow + '.' + direct + "-" + COUNT.get();
238 return OutputStreamManager.getManager(managerName, new FactoryData(os, managerName, layout), factory);
239 }
240
241 private static OutputStreamManager getManager(final Target target, final boolean follow, final boolean direct,
242 final Layout<? extends Serializable> layout) {
243 final OutputStream os = getOutputStream(follow, direct, target);
244 final String managerName = target.name() + '.' + follow + '.' + direct;
245 return OutputStreamManager.getManager(managerName, new FactoryData(os, managerName, layout), factory);
246 }
247
248 private static OutputStream getOutputStream(final boolean follow, final boolean direct, final Target target) {
249 final String enc = Charset.defaultCharset().name();
250 OutputStream outputStream;
251 try {
252
253 outputStream = target == Target.SYSTEM_OUT ?
254 direct ? new FileOutputStream(FileDescriptor.out) :
255 (follow ? new PrintStream(new SystemOutStream(), true, enc) : System.out) :
256 direct ? new FileOutputStream(FileDescriptor.err) :
257 (follow ? new PrintStream(new SystemErrStream(), true, enc) : System.err);
258
259 outputStream = new CloseShieldOutputStream(outputStream);
260 } catch (final UnsupportedEncodingException ex) {
261 throw new IllegalStateException("Unsupported default encoding " + enc, ex);
262 }
263 final PropertiesUtil propsUtil = PropertiesUtil.getProperties();
264 if (!propsUtil.isOsWindows() || propsUtil.getBooleanProperty("log4j.skipJansi", true) || direct) {
265 return outputStream;
266 }
267 try {
268
269 final Class<?> clazz = LoaderUtil.loadClass(JANSI_CLASS);
270 final Constructor<?> constructor = clazz.getConstructor(OutputStream.class);
271 return new CloseShieldOutputStream((OutputStream) constructor.newInstance(outputStream));
272 } catch (final ClassNotFoundException cnfe) {
273 LOGGER.debug("Jansi is not installed, cannot find {}", JANSI_CLASS);
274 } catch (final NoSuchMethodException nsme) {
275 LOGGER.warn("{} is missing the proper constructor", JANSI_CLASS);
276 } catch (final Exception ex) {
277 LOGGER.warn("Unable to instantiate {} due to {}", JANSI_CLASS, clean(Throwables.getRootCause(ex).toString()).trim());
278 }
279 return outputStream;
280 }
281
282 private static String clean(String string) {
283 return string.replace(Chars.NUL, Chars.SPACE);
284 }
285
286
287
288
289 private static class SystemErrStream extends OutputStream {
290 public SystemErrStream() {
291 }
292
293 @Override
294 public void close() {
295
296 }
297
298 @Override
299 public void flush() {
300 System.err.flush();
301 }
302
303 @Override
304 public void write(final byte[] b) throws IOException {
305 System.err.write(b);
306 }
307
308 @Override
309 public void write(final byte[] b, final int off, final int len) throws IOException {
310 System.err.write(b, off, len);
311 }
312
313 @Override
314 public void write(final int b) {
315 System.err.write(b);
316 }
317 }
318
319
320
321
322 private static class SystemOutStream extends OutputStream {
323 public SystemOutStream() {
324 }
325
326 @Override
327 public void close() {
328
329 }
330
331 @Override
332 public void flush() {
333 System.out.flush();
334 }
335
336 @Override
337 public void write(final byte[] b) throws IOException {
338 System.out.write(b);
339 }
340
341 @Override
342 public void write(final byte[] b, final int off, final int len) throws IOException {
343 System.out.write(b, off, len);
344 }
345
346 @Override
347 public void write(final int b) throws IOException {
348 System.out.write(b);
349 }
350 }
351
352
353
354
355 private static class FactoryData {
356 private final OutputStream os;
357 private final String name;
358 private final Layout<? extends Serializable> layout;
359
360
361
362
363
364
365
366
367 public FactoryData(final OutputStream os, final String type, final Layout<? extends Serializable> layout) {
368 this.os = os;
369 this.name = type;
370 this.layout = layout;
371 }
372 }
373
374
375
376
377 private static class ConsoleManagerFactory implements ManagerFactory<OutputStreamManager, FactoryData> {
378
379
380
381
382
383
384
385
386 @Override
387 public OutputStreamManager createManager(final String name, final FactoryData data) {
388 return new OutputStreamManager(data.os, data.name, data.layout, true);
389 }
390 }
391
392 public Target getTarget() {
393 return target;
394 }
395
396 }