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