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