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