View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.appender;
18  
19  import java.io.Serializable;
20  import java.util.HashMap;
21  import java.util.Map;
22  import java.util.concurrent.TimeUnit;
23  
24  import org.apache.logging.log4j.core.AbstractLifeCycle;
25  import org.apache.logging.log4j.core.Appender;
26  import org.apache.logging.log4j.core.Core;
27  import org.apache.logging.log4j.core.Filter;
28  import org.apache.logging.log4j.core.Layout;
29  import org.apache.logging.log4j.core.LogEvent;
30  import org.apache.logging.log4j.core.config.Configuration;
31  import org.apache.logging.log4j.core.config.Property;
32  import org.apache.logging.log4j.core.config.plugins.Plugin;
33  import org.apache.logging.log4j.core.config.plugins.PluginAliases;
34  import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
35  import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
36  import org.apache.logging.log4j.core.config.plugins.PluginElement;
37  import org.apache.logging.log4j.core.config.plugins.PluginFactory;
38  import org.apache.logging.log4j.core.config.plugins.validation.constraints.ValidHost;
39  import org.apache.logging.log4j.core.config.plugins.validation.constraints.ValidPort;
40  import org.apache.logging.log4j.core.net.AbstractSocketManager;
41  import org.apache.logging.log4j.core.net.Advertiser;
42  import org.apache.logging.log4j.core.net.DatagramSocketManager;
43  import org.apache.logging.log4j.core.net.Protocol;
44  import org.apache.logging.log4j.core.net.SocketOptions;
45  import org.apache.logging.log4j.core.net.SslSocketManager;
46  import org.apache.logging.log4j.core.net.TcpSocketManager;
47  import org.apache.logging.log4j.core.net.ssl.SslConfiguration;
48  import org.apache.logging.log4j.core.util.Booleans;
49  
50  /**
51   * An Appender that delivers events over socket connections. Supports both TCP and UDP.
52   */
53  @Plugin(name = "Socket", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true)
54  public class SocketAppender extends AbstractOutputStreamAppender<AbstractSocketManager> {
55  
56      /**
57       * Subclasses can extend this abstract Builder.
58       * <h1>Defaults</h1>
59       * <ul>
60       * <li>host: "localhost"</li>
61       * <li>protocol: "TCP"</li>
62       * </ul>
63       * <h1>Changes</h1>
64       * <ul>
65       * <li>Removed deprecated "delayMillis", use "reconnectionDelayMillis".</li>
66       * <li>Removed deprecated "reconnectionDelay", use "reconnectionDelayMillis".</li>
67       * </ul>
68       *
69       * @param <B>
70       *            The type to build.
71       */
72      public static abstract class AbstractBuilder<B extends AbstractBuilder<B>> extends AbstractOutputStreamAppender.Builder<B> {
73  
74          @PluginBuilderAttribute
75          private boolean advertise;
76  
77          @PluginBuilderAttribute
78          private int connectTimeoutMillis;
79  
80          @PluginBuilderAttribute
81          @ValidHost
82          private String host = "localhost";
83  
84          @PluginBuilderAttribute
85          private boolean immediateFail = true;
86  
87          @PluginBuilderAttribute
88          @ValidPort
89          private int port;
90  
91          @PluginBuilderAttribute
92          private Protocol protocol = Protocol.TCP;
93  
94          @PluginBuilderAttribute
95          @PluginAliases({ "reconnectDelay", "reconnectionDelay", "delayMillis", "reconnectionDelayMillis" })
96          private int reconnectDelayMillis;
97  
98          @PluginElement("SocketOptions")
99          private SocketOptions socketOptions;
100 
101         @PluginElement("SslConfiguration")
102         @PluginAliases({ "SslConfig" })
103         private SslConfiguration sslConfiguration;
104 
105         public boolean getAdvertise() {
106             return advertise;
107         }
108 
109         public int getConnectTimeoutMillis() {
110             return connectTimeoutMillis;
111         }
112 
113         public String getHost() {
114             return host;
115         }
116 
117         public int getPort() {
118             return port;
119         }
120 
121         public Protocol getProtocol() {
122             return protocol;
123         }
124 
125         public SslConfiguration getSslConfiguration() {
126             return sslConfiguration;
127         }
128 
129         public boolean getImmediateFail() {
130             return immediateFail;
131         }
132 
133         public B withAdvertise(final boolean advertise) {
134             this.advertise = advertise;
135             return asBuilder();
136         }
137 
138         public B withConnectTimeoutMillis(final int connectTimeoutMillis) {
139             this.connectTimeoutMillis = connectTimeoutMillis;
140             return asBuilder();
141         }
142 
143         public B withHost(final String host) {
144             this.host = host;
145             return asBuilder();
146         }
147 
148         public B withImmediateFail(final boolean immediateFail) {
149             this.immediateFail = immediateFail;
150             return asBuilder();
151         }
152 
153         public B withPort(final int port) {
154             this.port = port;
155             return asBuilder();
156         }
157 
158         public B withProtocol(final Protocol protocol) {
159             this.protocol = protocol;
160             return asBuilder();
161         }
162 
163         public B withReconnectDelayMillis(final int reconnectDelayMillis) {
164             this.reconnectDelayMillis = reconnectDelayMillis;
165             return asBuilder();
166         }
167 
168         public B withSocketOptions(final SocketOptions socketOptions) {
169             this.socketOptions = socketOptions;
170             return asBuilder();
171         }
172 
173         public B withSslConfiguration(final SslConfiguration sslConfiguration) {
174             this.sslConfiguration = sslConfiguration;
175             return asBuilder();
176         }
177 
178         public int getReconnectDelayMillis() {
179             return reconnectDelayMillis;
180         }
181 
182         public SocketOptions getSocketOptions() {
183             return socketOptions;
184         }
185 
186     }
187 
188     /**
189      * Builds a SocketAppender.
190      * <ul>
191      * <li>Removed deprecated "delayMillis", use "reconnectionDelayMillis".</li>
192      * <li>Removed deprecated "reconnectionDelay", use "reconnectionDelayMillis".</li>
193      * </ul>
194      */
195     public static class Builder extends AbstractBuilder<Builder>
196             implements org.apache.logging.log4j.core.util.Builder<SocketAppender> {
197 
198         @SuppressWarnings("resource")
199         @Override
200         public SocketAppender build() {
201             boolean immediateFlush = isImmediateFlush();
202             final boolean bufferedIo = isBufferedIo();
203             final Layout<? extends Serializable> layout = getLayout();
204             if (layout == null) {
205                 AbstractLifeCycle.LOGGER.error("No layout provided for SocketAppender");
206                 return null;
207             }
208 
209             final String name = getName();
210             if (name == null) {
211                 AbstractLifeCycle.LOGGER.error("No name provided for SocketAppender");
212                 return null;
213             }
214 
215             final Protocol protocol = getProtocol();
216             final Protocol actualProtocol = protocol != null ? protocol : Protocol.TCP;
217             if (actualProtocol == Protocol.UDP) {
218                 immediateFlush = true;
219             }
220 
221             final AbstractSocketManager manager = SocketAppender.createSocketManager(name, actualProtocol, getHost(), getPort(),
222                     getConnectTimeoutMillis(), getSslConfiguration(), getReconnectDelayMillis(), getImmediateFail(), layout, getBufferSize(), getSocketOptions());
223 
224             return new SocketAppender(name, layout, getFilter(), manager, isIgnoreExceptions(),
225                     !bufferedIo || immediateFlush, getAdvertise() ? getConfiguration().getAdvertiser() : null,
226                     getPropertyArray());
227         }
228     }
229 
230     @PluginBuilderFactory
231     public static Builder newBuilder() {
232         return new Builder();
233     }
234 
235     private final Object advertisement;
236     private final Advertiser advertiser;
237 
238     protected SocketAppender(final String name, final Layout<? extends Serializable> layout, final Filter filter,
239             final AbstractSocketManager manager, final boolean ignoreExceptions, final boolean immediateFlush,
240             final Advertiser advertiser, final Property[] properties) {
241         super(name, layout, filter, ignoreExceptions, immediateFlush, properties, manager);
242         if (advertiser != null) {
243             final Map<String, String> configuration = new HashMap<>(layout.getContentFormat());
244             configuration.putAll(manager.getContentFormat());
245             configuration.put("contentType", layout.getContentType());
246             configuration.put("name", name);
247             this.advertisement = advertiser.advertise(configuration);
248         } else {
249             this.advertisement = null;
250         }
251         this.advertiser = advertiser;
252     }
253 
254     /**
255      * @deprecated {@link #SocketAppender(String, Layout, Filter, AbstractSocketManager, boolean, boolean, Advertiser, Property[])}.
256      */
257     @Deprecated
258     protected SocketAppender(final String name, final Layout<? extends Serializable> layout, final Filter filter,
259             final AbstractSocketManager manager, final boolean ignoreExceptions, final boolean immediateFlush,
260             final Advertiser advertiser) {
261         this(name, layout, filter, manager, ignoreExceptions, immediateFlush, advertiser, Property.EMPTY_ARRAY);
262     }
263 
264     @Override
265     public boolean stop(final long timeout, final TimeUnit timeUnit) {
266         setStopping();
267         super.stop(timeout, timeUnit, false);
268         if (this.advertiser != null) {
269             this.advertiser.unadvertise(this.advertisement);
270         }
271         setStopped();
272         return true;
273     }
274 
275     /**
276      * Creates a socket appender.
277      *
278      * @param host
279      *            The name of the host to connect to.
280      * @param port
281      *            The port to connect to on the target host.
282      * @param protocol
283      *            The Protocol to use.
284      * @param sslConfig
285      *            The SSL configuration file for TCP/SSL, ignored for UPD.
286      * @param connectTimeoutMillis
287      *            the connect timeout in milliseconds.
288      * @param reconnectDelayMillis
289      *            The interval in which failed writes should be retried.
290      * @param immediateFail
291      *            True if the write should fail if no socket is immediately available.
292      * @param name
293      *            The name of the Appender.
294      * @param immediateFlush
295      *            "true" if data should be flushed on each write.
296      * @param ignoreExceptions
297      *            If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise they
298      *            are propagated to the caller.
299      * @param layout
300      *            The layout to use. Required, there is no default.
301      * @param filter
302      *            The Filter or null.
303      * @param advertise
304      *            "true" if the appender configuration should be advertised, "false" otherwise.
305      * @param configuration
306      *            The Configuration
307      * @return A SocketAppender.
308      * @deprecated Deprecated in 2.7; use {@link #newBuilder()}
309      */
310     @Deprecated
311     @PluginFactory
312     public static SocketAppender createAppender(
313             // @formatter:off
314             final String host,
315             final int port,
316             final Protocol protocol,
317             final SslConfiguration sslConfig,
318             final int connectTimeoutMillis,
319             final int reconnectDelayMillis,
320             final boolean immediateFail,
321             final String name,
322             final boolean immediateFlush,
323             final boolean ignoreExceptions,
324             final Layout<? extends Serializable> layout,
325             final Filter filter,
326             final boolean advertise,
327             final Configuration configuration) {
328             // @formatter:on
329 
330         // @formatter:off
331         return newBuilder()
332         .withAdvertise(advertise)
333         .setConfiguration(configuration)
334         .withConnectTimeoutMillis(connectTimeoutMillis).setFilter(filter)
335             .withHost(host).setIgnoreExceptions(ignoreExceptions)
336             .withImmediateFail(immediateFail).setLayout(layout).setName(name)
337             .withPort(port)
338             .withProtocol(protocol)
339             .withReconnectDelayMillis(reconnectDelayMillis)
340             .withSslConfiguration(sslConfig)
341             .build();
342         // @formatter:on
343     }
344 
345     /**
346      * Creates a socket appender.
347      *
348      * @param host
349      *            The name of the host to connect to.
350      * @param portNum
351      *            The port to connect to on the target host.
352      * @param protocolIn
353      *            The Protocol to use.
354      * @param sslConfig
355      *            The SSL configuration file for TCP/SSL, ignored for UPD.
356      * @param connectTimeoutMillis
357      *            the connect timeout in milliseconds.
358      * @param delayMillis
359      *            The interval in which failed writes should be retried.
360      * @param immediateFail
361      *            True if the write should fail if no socket is immediately available.
362      * @param name
363      *            The name of the Appender.
364      * @param immediateFlush
365      *            "true" if data should be flushed on each write.
366      * @param ignore
367      *            If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise they
368      *            are propagated to the caller.
369      * @param layout
370      *            The layout to use. Required, there is no default.
371      * @param filter
372      *            The Filter or null.
373      * @param advertise
374      *            "true" if the appender configuration should be advertised, "false" otherwise.
375      * @param config
376      *            The Configuration
377      * @return A SocketAppender.
378      * @deprecated Deprecated in 2.5; use {@link #newBuilder()}
379      */
380     @Deprecated
381     public static SocketAppender createAppender(
382             // @formatter:off
383             final String host,
384             final String portNum,
385             final String protocolIn,
386             final SslConfiguration sslConfig,
387             final int connectTimeoutMillis,
388             // deprecated
389             final String delayMillis,
390             final String immediateFail,
391             final String name,
392             final String immediateFlush,
393             final String ignore,
394             final Layout<? extends Serializable> layout,
395             final Filter filter,
396             final String advertise,
397             final Configuration config) {
398             // @formatter:on
399         final boolean isFlush = Booleans.parseBoolean(immediateFlush, true);
400         final boolean isAdvertise = Boolean.parseBoolean(advertise);
401         final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
402         final boolean fail = Booleans.parseBoolean(immediateFail, true);
403         final int reconnectDelayMillis = AbstractAppender.parseInt(delayMillis, 0);
404         final int port = AbstractAppender.parseInt(portNum, 0);
405         final Protocol p = protocolIn == null ? Protocol.UDP : Protocol.valueOf(protocolIn);
406         return createAppender(host, port, p, sslConfig, connectTimeoutMillis, reconnectDelayMillis, fail, name, isFlush,
407                 ignoreExceptions, layout, filter, isAdvertise, config);
408     }
409 
410     /**
411      * Creates an AbstractSocketManager for TCP, UDP, and SSL.
412      *
413      * @throws IllegalArgumentException
414      *             if the protocol cannot be handled.
415      * @deprecated Use {@link #createSocketManager(String, Protocol, String, int, int, SslConfiguration, int, boolean, Layout, int, SocketOptions)}.
416      */
417     @Deprecated
418     protected static AbstractSocketManager createSocketManager(final String name, final Protocol protocol, final String host,
419             final int port, final int connectTimeoutMillis, final SslConfiguration sslConfig, final int reconnectDelayMillis,
420             final boolean immediateFail, final Layout<? extends Serializable> layout, final int bufferSize) {
421         return createSocketManager(name, protocol, host, port, connectTimeoutMillis, sslConfig, reconnectDelayMillis, immediateFail, layout, bufferSize, null);
422     }
423 
424     /**
425      * Creates an AbstractSocketManager for TCP, UDP, and SSL.
426      *
427      * @throws IllegalArgumentException
428      *             if the protocol cannot be handled.
429      */
430     protected static AbstractSocketManager createSocketManager(final String name, Protocol protocol, final String host,
431             final int port, final int connectTimeoutMillis, final SslConfiguration sslConfig,
432             final int reconnectDelayMillis, final boolean immediateFail, final Layout<? extends Serializable> layout,
433             final int bufferSize, final SocketOptions socketOptions) {
434         if (protocol == Protocol.TCP && sslConfig != null) {
435             // Upgrade TCP to SSL if an SSL config is specified.
436             protocol = Protocol.SSL;
437         }
438         if (protocol != Protocol.SSL && sslConfig != null) {
439             LOGGER.info("Appender {} ignoring SSL configuration for {} protocol", name, protocol);
440         }
441         switch (protocol) {
442         case TCP:
443             return TcpSocketManager.getSocketManager(host, port, connectTimeoutMillis, reconnectDelayMillis,
444                     immediateFail, layout, bufferSize, socketOptions);
445         case UDP:
446             return DatagramSocketManager.getSocketManager(host, port, layout, bufferSize);
447         case SSL:
448             return SslSocketManager.getSocketManager(sslConfig, host, port, connectTimeoutMillis, reconnectDelayMillis,
449                     immediateFail, layout, bufferSize, socketOptions);
450         default:
451             throw new IllegalArgumentException(protocol.toString());
452         }
453     }
454 
455     @Override
456     protected void directEncodeEvent(final LogEvent event) {
457         // Disable garbage-free logging for now:
458         // problem with UDP: 8K buffer size means that largish messages get broken up into chunks
459         writeByteArrayToManager(event); // revert to classic (non-garbage free) logging
460     }
461 }