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.Property;
032import org.apache.logging.log4j.core.config.plugins.Plugin;
033import org.apache.logging.log4j.core.config.plugins.PluginAliases;
034import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
035import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
036import org.apache.logging.log4j.core.config.plugins.PluginElement;
037import org.apache.logging.log4j.core.config.plugins.PluginFactory;
038import org.apache.logging.log4j.core.config.plugins.validation.constraints.ValidHost;
039import org.apache.logging.log4j.core.config.plugins.validation.constraints.ValidPort;
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     * <h1>Defaults</h1>
059     * <ul>
060     * <li>host: "localhost"</li>
061     * <li>protocol: "TCP"</li>
062     * </ul>
063     * <h1>Changes</h1>
064     * <ul>
065     * <li>Removed deprecated "delayMillis", use "reconnectionDelayMillis".</li>
066     * <li>Removed deprecated "reconnectionDelay", use "reconnectionDelayMillis".</li>
067     * </ul>
068     *
069     * @param <B>
070     *            The type to build.
071     */
072    public static abstract class AbstractBuilder<B extends AbstractBuilder<B>> extends AbstractOutputStreamAppender.Builder<B> {
073
074        @PluginBuilderAttribute
075        private boolean advertise;
076
077        @PluginBuilderAttribute
078        private int connectTimeoutMillis;
079
080        @PluginBuilderAttribute
081        @ValidHost
082        private String host = "localhost";
083
084        @PluginBuilderAttribute
085        private boolean immediateFail = true;
086
087        @PluginBuilderAttribute
088        @ValidPort
089        private int port;
090
091        @PluginBuilderAttribute
092        private Protocol protocol = Protocol.TCP;
093
094        @PluginBuilderAttribute
095        @PluginAliases({ "reconnectDelay", "reconnectionDelay", "delayMillis", "reconnectionDelayMillis" })
096        private int reconnectDelayMillis;
097
098        @PluginElement("SocketOptions")
099        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}