001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017package org.apache.logging.log4j.core.appender;
018
019import java.io.Serializable;
020import java.util.HashMap;
021import java.util.Map;
022import java.util.concurrent.TimeUnit;
023
024import org.apache.logging.log4j.core.AbstractLifeCycle;
025import org.apache.logging.log4j.core.Appender;
026import org.apache.logging.log4j.core.Core;
027import org.apache.logging.log4j.core.Filter;
028import org.apache.logging.log4j.core.Layout;
029import org.apache.logging.log4j.core.LogEvent;
030import org.apache.logging.log4j.core.config.Configuration;
031import org.apache.logging.log4j.core.config.plugins.Plugin;
032import org.apache.logging.log4j.core.config.plugins.PluginAliases;
033import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
034import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
035import org.apache.logging.log4j.core.config.plugins.PluginElement;
036import org.apache.logging.log4j.core.config.plugins.PluginFactory;
037import org.apache.logging.log4j.core.config.plugins.validation.constraints.ValidHost;
038import org.apache.logging.log4j.core.config.plugins.validation.constraints.ValidPort;
039import org.apache.logging.log4j.core.net.AbstractSocketManager;
040import org.apache.logging.log4j.core.net.Advertiser;
041import org.apache.logging.log4j.core.net.DatagramSocketManager;
042import org.apache.logging.log4j.core.net.Protocol;
043import org.apache.logging.log4j.core.net.SocketOptions;
044import org.apache.logging.log4j.core.net.SslSocketManager;
045import org.apache.logging.log4j.core.net.TcpSocketManager;
046import org.apache.logging.log4j.core.net.ssl.SslConfiguration;
047import org.apache.logging.log4j.core.util.Booleans;
048
049/**
050 * An Appender that delivers events over socket connections. Supports both TCP and UDP.
051 */
052@Plugin(name = "Socket", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true)
053public class SocketAppender extends AbstractOutputStreamAppender<AbstractSocketManager> {
054
055    /**
056     * Subclasses can extend this abstract Builder.
057     * <h1>Defaults</h1>
058     * <ul>
059     * <li>host: "localhost"</li>
060     * <li>protocol: "TCP"</li>
061     * </ul>
062     * <h1>Changes</h1>
063     * <ul>
064     * <li>Removed deprecated "delayMillis", use "reconnectionDelayMillis".</li>
065     * <li>Removed deprecated "reconnectionDelay", use "reconnectionDelayMillis".</li>
066     * </ul>
067     * 
068     * @param <B>
069     *            The type to build.
070     */
071    public static abstract class AbstractBuilder<B extends AbstractBuilder<B>> extends AbstractOutputStreamAppender.Builder<B> {
072
073        @PluginBuilderAttribute
074        private boolean advertise;
075
076        @PluginBuilderAttribute
077        private int connectTimeoutMillis;
078
079        @PluginBuilderAttribute
080        @ValidHost
081        private String host = "localhost";
082
083        @PluginBuilderAttribute
084        private boolean immediateFail = true;
085
086        @PluginBuilderAttribute
087        @ValidPort
088        private int port;
089
090        @PluginBuilderAttribute
091        private Protocol protocol = Protocol.TCP;
092
093        @PluginBuilderAttribute
094        @PluginAliases({ "reconnectDelay", "reconnectionDelay", "delayMillis", "reconnectionDelayMillis" })
095        private int reconnectDelayMillis;
096        
097        @PluginElement("SocketOptions")
098        private SocketOptions socketOptions;
099        
100        @PluginElement("SslConfiguration")
101        @PluginAliases({ "SslConfig" })
102        private SslConfiguration sslConfiguration;
103
104        public boolean getAdvertise() {
105            return advertise;
106        }
107
108        public int getConnectTimeoutMillis() {
109            return connectTimeoutMillis;
110        }
111
112        public String getHost() {
113            return host;
114        }
115
116        public int getPort() {
117            return port;
118        }
119
120        public Protocol getProtocol() {
121            return protocol;
122        }
123
124        public SslConfiguration getSslConfiguration() {
125            return sslConfiguration;
126        }
127
128        public boolean getImmediateFail() {
129            return immediateFail;
130        }
131
132        public B withAdvertise(final boolean advertise) {
133            this.advertise = advertise;
134            return asBuilder();
135        }
136
137        public B withConnectTimeoutMillis(final int connectTimeoutMillis) {
138            this.connectTimeoutMillis = connectTimeoutMillis;
139            return asBuilder();
140        }
141
142        public B withHost(final String host) {
143            this.host = host;
144            return asBuilder();
145        }
146
147        public B withImmediateFail(final boolean immediateFail) {
148            this.immediateFail = immediateFail;
149            return asBuilder();
150        }
151
152        public B withPort(final int port) {
153            this.port = port;
154            return asBuilder();
155        }
156
157        public B withProtocol(final Protocol protocol) {
158            this.protocol = protocol;
159            return asBuilder();
160        }
161
162        public B withReconnectDelayMillis(final int reconnectDelayMillis) {
163            this.reconnectDelayMillis = reconnectDelayMillis;
164            return asBuilder();
165        }
166
167        public B withSocketOptions(final SocketOptions socketOptions) {
168            this.socketOptions = socketOptions;
169            return asBuilder();
170        }
171
172        public B withSslConfiguration(final SslConfiguration sslConfiguration) {
173            this.sslConfiguration = sslConfiguration;
174            return asBuilder();
175        }
176
177        public int getReconnectDelayMillis() {
178            return reconnectDelayMillis;
179        }
180
181        public SocketOptions getSocketOptions() {
182            return socketOptions;
183        }
184
185    }
186    
187    /**
188     * Builds a SocketAppender.
189     * <ul> 
190     * <li>Removed deprecated "delayMillis", use "reconnectionDelayMillis".</li>
191     * <li>Removed deprecated "reconnectionDelay", use "reconnectionDelayMillis".</li>
192     * </ul> 
193     */
194    public static class Builder extends AbstractBuilder<Builder>
195            implements org.apache.logging.log4j.core.util.Builder<SocketAppender> {
196
197        @SuppressWarnings("resource")
198        @Override
199        public SocketAppender build() {
200            boolean immediateFlush = isImmediateFlush();
201            final boolean bufferedIo = isBufferedIo();
202            final Layout<? extends Serializable> layout = getLayout();
203            if (layout == null) {
204                AbstractLifeCycle.LOGGER.error("No layout provided for SocketAppender");
205                return null;
206            }
207
208            final String name = getName();
209            if (name == null) {
210                AbstractLifeCycle.LOGGER.error("No name provided for SocketAppender");
211                return null;
212            }
213
214            final Protocol protocol = getProtocol();
215            final Protocol actualProtocol = protocol != null ? protocol : Protocol.TCP;
216            if (actualProtocol == Protocol.UDP) {
217                immediateFlush = true;
218            }
219
220            final AbstractSocketManager manager = SocketAppender.createSocketManager(name, actualProtocol, getHost(), getPort(),
221                    getConnectTimeoutMillis(), getSslConfiguration(), getReconnectDelayMillis(), getImmediateFail(), layout, getBufferSize(), getSocketOptions());
222
223            return new SocketAppender(name, layout, getFilter(), manager, isIgnoreExceptions(),
224                    !bufferedIo || immediateFlush, getAdvertise() ? getConfiguration().getAdvertiser() : null);
225        }
226    }
227    
228    @PluginBuilderFactory
229    public static Builder newBuilder() {
230        return new Builder();
231    }
232
233    private final Object advertisement;
234    private final Advertiser advertiser;
235
236    protected SocketAppender(final String name, final Layout<? extends Serializable> layout, final Filter filter,
237            final AbstractSocketManager manager, final boolean ignoreExceptions, final boolean immediateFlush,
238            final Advertiser advertiser) {
239        super(name, layout, filter, ignoreExceptions, immediateFlush, manager);
240        if (advertiser != null) {
241            final Map<String, String> configuration = new HashMap<>(layout.getContentFormat());
242            configuration.putAll(manager.getContentFormat());
243            configuration.put("contentType", layout.getContentType());
244            configuration.put("name", name);
245            this.advertisement = advertiser.advertise(configuration);
246        } else {
247            this.advertisement = null;
248        }
249        this.advertiser = advertiser;
250    }
251
252    @Override
253    public boolean stop(final long timeout, final TimeUnit timeUnit) {
254        setStopping();
255        super.stop(timeout, timeUnit, false);
256        if (this.advertiser != null) {
257            this.advertiser.unadvertise(this.advertisement);
258        }
259        setStopped();
260        return true;
261    }
262
263    /**
264     * Creates a socket appender.
265     *
266     * @param host
267     *            The name of the host to connect to.
268     * @param port
269     *            The port to connect to on the target host.
270     * @param protocol
271     *            The Protocol to use.
272     * @param sslConfig
273     *            The SSL configuration file for TCP/SSL, ignored for UPD.
274     * @param connectTimeoutMillis
275     *            the connect timeout in milliseconds.
276     * @param reconnectDelayMillis
277     *            The interval in which failed writes should be retried.
278     * @param immediateFail
279     *            True if the write should fail if no socket is immediately available.
280     * @param name
281     *            The name of the Appender.
282     * @param immediateFlush
283     *            "true" if data should be flushed on each write.
284     * @param ignoreExceptions
285     *            If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise they
286     *            are propagated to the caller.
287     * @param layout
288     *            The layout to use. Required, there is no default.
289     * @param filter
290     *            The Filter or null.
291     * @param advertise
292     *            "true" if the appender configuration should be advertised, "false" otherwise.
293     * @param configuration
294     *            The Configuration
295     * @return A SocketAppender.
296     * @deprecated Deprecated in 2.7; use {@link #newBuilder()}
297     */
298    @Deprecated
299    @PluginFactory
300    public static SocketAppender createAppender(
301            // @formatter:off
302            final String host,
303            final int port,
304            final Protocol protocol,
305            final SslConfiguration sslConfig,
306            final int connectTimeoutMillis,
307            final int reconnectDelayMillis,
308            final boolean immediateFail,
309            final String name,
310            final boolean immediateFlush,
311            final boolean ignoreExceptions,
312            final Layout<? extends Serializable> layout,
313            final Filter filter,
314            final boolean advertise,
315            final Configuration configuration) {
316            // @formatter:on
317
318        // @formatter:off
319        return newBuilder()
320            .withAdvertise(advertise)
321            .setConfiguration(configuration)
322            .withConnectTimeoutMillis(connectTimeoutMillis)
323            .withFilter(filter)
324            .withHost(host)
325            .withIgnoreExceptions(ignoreExceptions)
326            .withImmediateFail(immediateFail)
327            .withLayout(layout)
328            .withName(name)
329            .withPort(port)
330            .withProtocol(protocol)
331            .withReconnectDelayMillis(reconnectDelayMillis)
332            .withSslConfiguration(sslConfig)
333            .build();
334        // @formatter:on
335    }
336    
337    /**
338     * Creates a socket appender.
339     *
340     * @param host
341     *            The name of the host to connect to.
342     * @param portNum
343     *            The port to connect to on the target host.
344     * @param protocolIn
345     *            The Protocol to use.
346     * @param sslConfig
347     *            The SSL configuration file for TCP/SSL, ignored for UPD.
348     * @param connectTimeoutMillis
349     *            the connect timeout in milliseconds.
350     * @param delayMillis
351     *            The interval in which failed writes should be retried.
352     * @param immediateFail
353     *            True if the write should fail if no socket is immediately available.
354     * @param name
355     *            The name of the Appender.
356     * @param immediateFlush
357     *            "true" if data should be flushed on each write.
358     * @param ignore
359     *            If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise they
360     *            are propagated to the caller.
361     * @param layout
362     *            The layout to use. Required, there is no default.
363     * @param filter
364     *            The Filter or null.
365     * @param advertise
366     *            "true" if the appender configuration should be advertised, "false" otherwise.
367     * @param config
368     *            The Configuration
369     * @return A SocketAppender.
370     * @deprecated Deprecated in 2.5; use {@link #newBuilder()}
371     */
372    @Deprecated
373    public static SocketAppender createAppender(
374            // @formatter:off
375            final String host,
376            final String portNum,
377            final String protocolIn,
378            final SslConfiguration sslConfig,
379            final int connectTimeoutMillis,
380            // deprecated
381            final String delayMillis,
382            final String immediateFail,
383            final String name,
384            final String immediateFlush,
385            final String ignore,
386            final Layout<? extends Serializable> layout,
387            final Filter filter,
388            final String advertise,
389            final Configuration config) {
390            // @formatter:on
391        final boolean isFlush = Booleans.parseBoolean(immediateFlush, true);
392        final boolean isAdvertise = Boolean.parseBoolean(advertise);
393        final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
394        final boolean fail = Booleans.parseBoolean(immediateFail, true);
395        final int reconnectDelayMillis = AbstractAppender.parseInt(delayMillis, 0);
396        final int port = AbstractAppender.parseInt(portNum, 0);
397        final Protocol p = protocolIn == null ? Protocol.UDP : Protocol.valueOf(protocolIn);
398        return createAppender(host, port, p, sslConfig, connectTimeoutMillis, reconnectDelayMillis, fail, name, isFlush,
399                ignoreExceptions, layout, filter, isAdvertise, config);
400    }
401
402    /**
403     * Creates an AbstractSocketManager for TCP, UDP, and SSL.
404     *
405     * @throws IllegalArgumentException
406     *             if the protocol cannot be handled.
407     * @deprecated Use {@link #createSocketManager(String, Protocol, String, int, int, SslConfiguration, int, boolean, Layout, int, SocketOptions)}.
408     */
409    @Deprecated
410    protected static AbstractSocketManager createSocketManager(final String name, final Protocol protocol, final String host,
411            final int port, final int connectTimeoutMillis, final SslConfiguration sslConfig, final int reconnectDelayMillis,
412            final boolean immediateFail, final Layout<? extends Serializable> layout, final int bufferSize) {
413        return createSocketManager(name, protocol, host, port, connectTimeoutMillis, sslConfig, reconnectDelayMillis, immediateFail, layout, bufferSize, null);
414    }
415
416    /**
417     * Creates an AbstractSocketManager for TCP, UDP, and SSL.
418     *
419     * @throws IllegalArgumentException
420     *             if the protocol cannot be handled.
421     */
422    protected static AbstractSocketManager createSocketManager(final String name, Protocol protocol, final String host,
423            final int port, final int connectTimeoutMillis, final SslConfiguration sslConfig,
424            final int reconnectDelayMillis, final boolean immediateFail, final Layout<? extends Serializable> layout,
425            final int bufferSize, final SocketOptions socketOptions) {
426        if (protocol == Protocol.TCP && sslConfig != null) {
427            // Upgrade TCP to SSL if an SSL config is specified.
428            protocol = Protocol.SSL;
429        }
430        if (protocol != Protocol.SSL && sslConfig != null) {
431            LOGGER.info("Appender {} ignoring SSL configuration for {} protocol", name, protocol);
432        }
433        switch (protocol) {
434        case TCP:
435            return TcpSocketManager.getSocketManager(host, port, connectTimeoutMillis, reconnectDelayMillis,
436                    immediateFail, layout, bufferSize, socketOptions);
437        case UDP:
438            return DatagramSocketManager.getSocketManager(host, port, layout, bufferSize);
439        case SSL:
440            return SslSocketManager.getSocketManager(sslConfig, host, port, connectTimeoutMillis, reconnectDelayMillis,
441                    immediateFail, layout, bufferSize, socketOptions);
442        default:
443            throw new IllegalArgumentException(protocol.toString());
444        }
445    }
446
447    @Override
448    protected void directEncodeEvent(final LogEvent event) {
449        // Disable garbage-free logging for now:
450        // problem with UDP: 8K buffer size means that largish messages get broken up into chunks
451        writeByteArrayToManager(event); // revert to classic (non-garbage free) logging
452    }
453}