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;
022
023import org.apache.logging.log4j.core.Filter;
024import org.apache.logging.log4j.core.Layout;
025import org.apache.logging.log4j.core.config.Configuration;
026import org.apache.logging.log4j.core.config.plugins.Plugin;
027import org.apache.logging.log4j.core.config.plugins.PluginAliases;
028import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
029import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
030import org.apache.logging.log4j.core.config.plugins.PluginElement;
031import org.apache.logging.log4j.core.config.plugins.PluginFactory;
032import org.apache.logging.log4j.core.layout.SerializedLayout;
033import org.apache.logging.log4j.core.net.AbstractSocketManager;
034import org.apache.logging.log4j.core.net.Advertiser;
035import org.apache.logging.log4j.core.net.DatagramSocketManager;
036import org.apache.logging.log4j.core.net.Protocol;
037import org.apache.logging.log4j.core.net.SslSocketManager;
038import org.apache.logging.log4j.core.net.TcpSocketManager;
039import org.apache.logging.log4j.core.net.ssl.SslConfiguration;
040import org.apache.logging.log4j.core.util.Booleans;
041
042/**
043 * An Appender that delivers events over socket connections. Supports both TCP and UDP.
044 */
045@Plugin(name = "Socket", category = "Core", elementType = "appender", printObject = true)
046public class SocketAppender extends AbstractOutputStreamAppender<AbstractSocketManager> {
047
048    private static final long serialVersionUID = 1L;
049
050    private Object advertisement;
051    private final Advertiser advertiser;
052
053    protected SocketAppender(final String name, final Layout<? extends Serializable> layout, final Filter filter,
054            final AbstractSocketManager manager, final boolean ignoreExceptions, final boolean immediateFlush,
055            final Advertiser advertiser) {
056        super(name, layout, filter, ignoreExceptions, immediateFlush, manager);
057        if (advertiser != null) {
058            final Map<String, String> configuration = new HashMap<>(layout.getContentFormat());
059            configuration.putAll(manager.getContentFormat());
060            configuration.put("contentType", layout.getContentType());
061            configuration.put("name", name);
062            this.advertisement = advertiser.advertise(configuration);
063        }
064        this.advertiser = advertiser;
065    }
066
067    @Override
068    public void stop() {
069        super.stop();
070        if (this.advertiser != null) {
071            this.advertiser.unadvertise(this.advertisement);
072        }
073    }
074
075    /**
076     * Creates a socket appender.
077     * 
078     * @param host
079     *            The name of the host to connect to.
080     * @param port
081     *            The port to connect to on the target host.
082     * @param protocol
083     *            The Protocol to use.
084     * @param sslConfig
085     *            The SSL configuration file for TCP/SSL, ignored for UPD.
086     * @param connectTimeoutMillis
087     *            the connect timeout in milliseconds.
088     * @param reconnectDelayMillis
089     *            The interval in which failed writes should be retried.
090     * @param immediateFail
091     *            True if the write should fail if no socket is immediately available.
092     * @param name
093     *            The name of the Appender.
094     * @param immediateFlush
095     *            "true" if data should be flushed on each write.
096     * @param ignoreExceptions
097     *            If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise they
098     *            are propagated to the caller.
099     * @param layout
100     *            The layout to use (defaults to SerializedLayout).
101     * @param filter
102     *            The Filter or null.
103     * @param advertise
104     *            "true" if the appender configuration should be advertised, "false" otherwise.
105     * @param config
106     *            The Configuration
107     * @return A SocketAppender.
108     */
109    @PluginFactory
110    public static SocketAppender createAppender(
111            // @formatter:off
112            @PluginAttribute("host") final String host,
113            @PluginAttribute(value = "port", defaultInt = 0) final int port,
114            @PluginAttribute("protocol") final Protocol protocol,
115            @PluginElement("SSL") final SslConfiguration sslConfig,
116            @PluginAttribute(value = "connectTimeoutMillis", defaultInt = 0) final int connectTimeoutMillis,
117            @PluginAliases("reconnectionDelay") // deprecated
118            @PluginAttribute(value = "reconnectionDelayMillis", defaultInt = 0) final int reconnectDelayMillis,
119            @PluginAttribute(value = "immediateFail", defaultBoolean = true) final boolean immediateFail,
120            @PluginAttribute("name") final String name,
121            @PluginAttribute(value = "immediateFlush", defaultBoolean = true) boolean immediateFlush,
122            @PluginAttribute(value = "ignoreExceptions", defaultBoolean = true) final boolean ignoreExceptions,
123            @PluginElement("Layout") Layout<? extends Serializable> layout,
124            @PluginElement("Filter") final Filter filter,
125            @PluginAttribute(value = "advertise", defaultBoolean = false) final boolean advertise, 
126            @PluginConfiguration final Configuration config) {
127            // @formatter:on
128        
129        if (layout == null) {
130            layout = SerializedLayout.createLayout();
131        }
132
133        if (name == null) {
134            LOGGER.error("No name provided for SocketAppender");
135            return null;
136        }
137
138        final Protocol actualProtocol = protocol != null ? protocol : Protocol.TCP;
139        if (actualProtocol == Protocol.UDP) {
140            immediateFlush = true;
141        }
142
143        final AbstractSocketManager manager = createSocketManager(name, actualProtocol, host, port, connectTimeoutMillis,
144                sslConfig, reconnectDelayMillis, immediateFail, layout);
145
146        return new SocketAppender(name, layout, filter, manager, ignoreExceptions, immediateFlush,
147                advertise ? config.getAdvertiser() : null);
148    }
149
150    /**
151     * Creates a socket appender.
152     * 
153     * @param host
154     *            The name of the host to connect to.
155     * @param portNum
156     *            The port to connect to on the target host.
157     * @param protocolIn
158     *            The Protocol to use.
159     * @param sslConfig
160     *            The SSL configuration file for TCP/SSL, ignored for UPD.
161     * @param connectTimeoutMillis
162     *            the connect timeout in milliseconds.
163     * @param delayMillis
164     *            The interval in which failed writes should be retried.
165     * @param immediateFail
166     *            True if the write should fail if no socket is immediately available.
167     * @param name
168     *            The name of the Appender.
169     * @param immediateFlush
170     *            "true" if data should be flushed on each write.
171     * @param ignore
172     *            If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise they
173     *            are propagated to the caller.
174     * @param layout
175     *            The layout to use (defaults to SerializedLayout).
176     * @param filter
177     *            The Filter or null.
178     * @param advertise
179     *            "true" if the appender configuration should be advertised, "false" otherwise.
180     * @param config
181     *            The Configuration
182     * @return A SocketAppender.
183     * @deprecated Use {@link #createAppender(String, String, Protocol, SslConfiguration, int, String, String, String, String, String, Layout, Filter, String, Configuration)}.
184     */
185    @Deprecated
186    public static SocketAppender createAppender(
187            // @formatter:off
188            final String host,
189            final String portNum,
190            final String protocolIn,
191            final SslConfiguration sslConfig,
192            final int connectTimeoutMillis,
193            // deprecated
194            final String delayMillis,
195            final String immediateFail,
196            final String name,
197            final String immediateFlush,
198            final String ignore,
199            Layout<? extends Serializable> layout,
200            final Filter filter,
201            final String advertise, 
202            final Configuration config) {
203            // @formatter:on
204        boolean isFlush = Booleans.parseBoolean(immediateFlush, true);
205        final boolean isAdvertise = Boolean.parseBoolean(advertise);
206        final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
207        final boolean fail = Booleans.parseBoolean(immediateFail, true);
208        final int reconnectDelayMillis = AbstractAppender.parseInt(delayMillis, 0);
209        final int port = AbstractAppender.parseInt(portNum, 0);
210        final Protocol p = protocolIn == null ? Protocol.UDP : Protocol.valueOf(protocolIn);
211        return createAppender(host, port, p, sslConfig, connectTimeoutMillis, reconnectDelayMillis, fail, name, isFlush,
212                ignoreExceptions, layout, filter, isAdvertise, config);
213    }
214
215    /**
216     * Creates an AbstractSocketManager for TCP, UDP, and SSL.
217     * 
218     * @throws IllegalArgumentException
219     *             if the protocol cannot be handled.
220     */
221    protected static AbstractSocketManager createSocketManager(final String name, Protocol protocol, final String host,
222            final int port, final int connectTimeoutMillis, final SslConfiguration sslConfig, final int delayMillis,
223            final boolean immediateFail, final Layout<? extends Serializable> layout) {
224        if (protocol == Protocol.TCP && sslConfig != null) {
225            // Upgrade TCP to SSL if an SSL config is specified.
226            protocol = Protocol.SSL;
227        }
228        if (protocol != Protocol.SSL && sslConfig != null) {
229            LOGGER.info("Appender {} ignoring SSL configuration for {} protocol", name, protocol);
230        }
231        switch (protocol) {
232        case TCP:
233            return TcpSocketManager.getSocketManager(host, port, connectTimeoutMillis, delayMillis, immediateFail,
234                    layout);
235        case UDP:
236            return DatagramSocketManager.getSocketManager(host, port, layout);
237        case SSL:
238            return SslSocketManager.getSocketManager(sslConfig, host, port, connectTimeoutMillis, delayMillis,
239                    immediateFail, layout);
240        default:
241            throw new IllegalArgumentException(protocol.toString());
242        }
243    }
244}