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;
041import org.apache.logging.log4j.util.EnglishEnums;
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 static final long serialVersionUID = 1L;
050
051    private Object advertisement;
052    private final Advertiser advertiser;
053
054    protected SocketAppender(final String name, final Layout<? extends Serializable> layout, final Filter filter,
055            final AbstractSocketManager manager, final boolean ignoreExceptions, final boolean immediateFlush,
056            final Advertiser advertiser) {
057        super(name, layout, filter, ignoreExceptions, immediateFlush, manager);
058        if (advertiser != null) {
059            final Map<String, String> configuration = new HashMap<>(layout.getContentFormat());
060            configuration.putAll(manager.getContentFormat());
061            configuration.put("contentType", layout.getContentType());
062            configuration.put("name", name);
063            this.advertisement = advertiser.advertise(configuration);
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     * 
078     * @param host
079     *        The name of the host to connect to.
080     * @param portNum
081     *        The port to connect to on the target host.
082     * @param protocolStr
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 delayMillis
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 ignore
097     *        If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise they are
098     *        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("port") final String portNum,
114            @PluginAttribute("protocol") final String protocolStr,
115            @PluginElement("SSL") final SslConfiguration sslConfig,
116            @PluginAttribute(value = "connectTimeoutMillis", defaultInt = 0) final int connectTimeoutMillis,
117            @PluginAliases("reconnectionDelay") // deprecated
118            @PluginAttribute("reconnectionDelayMillis") final String delayMillis,
119            @PluginAttribute("immediateFail") final String immediateFail,
120            @PluginAttribute("name") final String name,
121            @PluginAttribute("immediateFlush") final String immediateFlush,
122            @PluginAttribute("ignoreExceptions") final String ignore,
123            @PluginElement("Layout") Layout<? extends Serializable> layout,
124            @PluginElement("Filter") final Filter filter, 
125            @PluginAttribute("advertise") final String advertise, @PluginConfiguration final Configuration config) {
126            // @formatter:on
127        boolean isFlush = Booleans.parseBoolean(immediateFlush, true);
128        final boolean isAdvertise = Boolean.parseBoolean(advertise);
129        final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
130        final boolean fail = Booleans.parseBoolean(immediateFail, true);
131        final int reconnectDelayMillis = AbstractAppender.parseInt(delayMillis, 0);
132        final int port = AbstractAppender.parseInt(portNum, 0);
133        if (layout == null) {
134            layout = SerializedLayout.createLayout();
135        }
136
137        if (name == null) {
138            LOGGER.error("No name provided for SocketAppender");
139            return null;
140        }
141
142        final Protocol protocol = EnglishEnums.valueOf(Protocol.class,
143                protocolStr != null ? protocolStr : Protocol.TCP.name());
144        if (protocol == Protocol.UDP) {
145            isFlush = true;
146        }
147
148        final AbstractSocketManager manager = createSocketManager(name, protocol, host, port, connectTimeoutMillis,
149                sslConfig, reconnectDelayMillis, fail, layout);
150
151        return new SocketAppender(name, layout, filter, manager, ignoreExceptions, isFlush,
152                isAdvertise ? config.getAdvertiser() : null);
153    }
154
155    /**
156     * Creates an AbstractSocketManager for TCP, UDP, and SSL.
157     * 
158     * @throws IllegalArgumentException
159     *         if the protocol cannot be handled.
160     */
161    protected static AbstractSocketManager createSocketManager(final String name, Protocol protocol, final String host,
162            final int port, final int connectTimeoutMillis, final SslConfiguration sslConfig, final int delayMillis,
163            final boolean immediateFail, final Layout<? extends Serializable> layout) {
164        if (protocol == Protocol.TCP && sslConfig != null) {
165            // Upgrade TCP to SSL if an SSL config is specified.
166            protocol = Protocol.SSL;
167        }
168        if (protocol != Protocol.SSL && sslConfig != null) {
169            LOGGER.info("Appender {} ignoring SSL configuration for {} protocol", name, protocol);
170        }
171        switch (protocol) {
172        case TCP:
173            return TcpSocketManager.getSocketManager(host, port, connectTimeoutMillis, delayMillis, immediateFail,
174                    layout);
175        case UDP:
176            return DatagramSocketManager.getSocketManager(host, port, layout);
177        case SSL:
178            return SslSocketManager.getSocketManager(sslConfig, host, port, connectTimeoutMillis, delayMillis,
179                    immediateFail, layout);
180        default:
181            throw new IllegalArgumentException(protocol.toString());
182        }
183    }
184}