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