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.util.LoaderUtil;
42 import org.apache.logging.log4j.util.PropertiesUtil;
43
44
45
46
47
48
49
50
51
52
53
54 @Plugin(name = ConsoleAppender.PLUGIN_NAME, category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true)
55 public final class ConsoleAppender extends AbstractOutputStreamAppender<OutputStreamManager> {
56
57 public static final String PLUGIN_NAME = "Console";
58 private static final String JANSI_CLASS = "org.fusesource.jansi.WindowsAnsiOutputStream";
59 private static ConsoleManagerFactory factory = new ConsoleManagerFactory();
60 private static final Target DEFAULT_TARGET = Target.SYSTEM_OUT;
61 private static final AtomicInteger COUNT = new AtomicInteger();
62
63 private final Target target;
64
65
66
67
68 public enum Target {
69
70
71 SYSTEM_OUT {
72 @Override
73 public Charset getDefaultCharset() {
74 return getCharset("sun.stdout.encoding", Charset.defaultCharset());
75 }
76 },
77
78
79 SYSTEM_ERR {
80 @Override
81 public Charset getDefaultCharset() {
82 return getCharset("sun.stderr.encoding", Charset.defaultCharset());
83 }
84 };
85
86 public abstract Charset getDefaultCharset();
87
88 protected Charset getCharset(final String property, Charset defaultCharset) {
89 return new PropertiesUtil(PropertiesUtil.getSystemProperties()).getCharsetProperty(property, defaultCharset);
90 }
91
92 }
93
94 private ConsoleAppender(final String name, final Layout<? extends Serializable> layout, final Filter filter,
95 final OutputStreamManager manager, final boolean ignoreExceptions, final Target target) {
96 super(name, layout, filter, ignoreExceptions, true, 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);
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);
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);
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);
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") || direct) {
261 return outputStream;
262 }
263 try {
264
265 final Class<?> clazz = LoaderUtil.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 {}", JANSI_CLASS);
274 }
275 return outputStream;
276 }
277
278
279
280
281 private static class SystemErrStream extends OutputStream {
282 public SystemErrStream() {
283 }
284
285 @Override
286 public void close() {
287
288 }
289
290 @Override
291 public void flush() {
292 System.err.flush();
293 }
294
295 @Override
296 public void write(final byte[] b) throws IOException {
297 System.err.write(b);
298 }
299
300 @Override
301 public void write(final byte[] b, final int off, final int len) throws IOException {
302 System.err.write(b, off, len);
303 }
304
305 @Override
306 public void write(final int b) {
307 System.err.write(b);
308 }
309 }
310
311
312
313
314 private static class SystemOutStream extends OutputStream {
315 public SystemOutStream() {
316 }
317
318 @Override
319 public void close() {
320
321 }
322
323 @Override
324 public void flush() {
325 System.out.flush();
326 }
327
328 @Override
329 public void write(final byte[] b) throws IOException {
330 System.out.write(b);
331 }
332
333 @Override
334 public void write(final byte[] b, final int off, final int len) throws IOException {
335 System.out.write(b, off, len);
336 }
337
338 @Override
339 public void write(final int b) throws IOException {
340 System.out.write(b);
341 }
342 }
343
344
345
346
347 private static class FactoryData {
348 private final OutputStream os;
349 private final String name;
350 private final Layout<? extends Serializable> layout;
351
352
353
354
355
356
357
358
359 public FactoryData(final OutputStream os, final String type, final Layout<? extends Serializable> layout) {
360 this.os = os;
361 this.name = type;
362 this.layout = layout;
363 }
364 }
365
366
367
368
369 private static class ConsoleManagerFactory implements ManagerFactory<OutputStreamManager, FactoryData> {
370
371
372
373
374
375
376
377
378 @Override
379 public OutputStreamManager createManager(final String name, final FactoryData data) {
380 return new OutputStreamManager(data.os, data.name, data.layout, true);
381 }
382 }
383
384 public Target getTarget() {
385 return target;
386 }
387
388 }