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