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.flume.appender;
018
019import java.io.Serializable;
020import java.util.Locale;
021import java.util.concurrent.TimeUnit;
022
023import org.apache.logging.log4j.core.Appender;
024import org.apache.logging.log4j.core.Filter;
025import org.apache.logging.log4j.core.Layout;
026import org.apache.logging.log4j.core.LogEvent;
027import org.apache.logging.log4j.core.appender.AbstractAppender;
028import org.apache.logging.log4j.core.config.Property;
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.PluginAttribute;
032import org.apache.logging.log4j.core.config.plugins.PluginElement;
033import org.apache.logging.log4j.core.config.plugins.PluginFactory;
034import org.apache.logging.log4j.core.layout.Rfc5424Layout;
035import org.apache.logging.log4j.core.net.Facility;
036import org.apache.logging.log4j.core.util.Booleans;
037import org.apache.logging.log4j.core.util.Integers;
038
039/**
040 * An Appender that uses the Avro protocol to route events to Flume.
041 */
042@Plugin(name = "Flume", category = "Core", elementType = Appender.ELEMENT_TYPE, printObject = true)
043public final class FlumeAppender extends AbstractAppender implements FlumeEventFactory {
044
045    private static final String[] EXCLUDED_PACKAGES = {"org.apache.flume", "org.apache.avro"};
046    private static final int DEFAULT_MAX_DELAY = 60000;
047
048    private static final int DEFAULT_LOCK_TIMEOUT_RETRY_COUNT = 5;
049
050    private final AbstractFlumeManager manager;
051
052    private final String mdcIncludes;
053    private final String mdcExcludes;
054    private final String mdcRequired;
055
056    private final String eventPrefix;
057
058    private final String mdcPrefix;
059
060    private final boolean compressBody;
061
062    private final FlumeEventFactory factory;
063
064    /**
065     * Which Manager will be used by the appender instance.
066     */
067    private enum ManagerType {
068        AVRO, EMBEDDED, PERSISTENT;
069
070        public static ManagerType getType(final String type) {
071            return valueOf(type.toUpperCase(Locale.US));
072        }
073    }
074
075    private FlumeAppender(final String name, final Filter filter, final Layout<? extends Serializable> layout,
076            final boolean ignoreExceptions, final String includes, final String excludes, final String required,
077            final String mdcPrefix, final String eventPrefix, final boolean compress, final FlumeEventFactory factory,
078            final Property[] properties, final AbstractFlumeManager manager) {
079        super(name, filter, layout, ignoreExceptions, properties);
080        this.manager = manager;
081        this.mdcIncludes = includes;
082        this.mdcExcludes = excludes;
083        this.mdcRequired = required;
084        this.eventPrefix = eventPrefix;
085        this.mdcPrefix = mdcPrefix;
086        this.compressBody = compress;
087        this.factory = factory == null ? this : factory;
088    }
089
090    /**
091     * Publish the event.
092     * @param event The LogEvent.
093     */
094    @Override
095    public void append(final LogEvent event) {
096        final String name = event.getLoggerName();
097        if (name != null) {
098            for (final String pkg : EXCLUDED_PACKAGES) {
099                if (name.startsWith(pkg)) {
100                    return;
101                }
102            }
103        }
104        final FlumeEvent flumeEvent = factory.createEvent(event, mdcIncludes, mdcExcludes, mdcRequired, mdcPrefix,
105            eventPrefix, compressBody);
106        flumeEvent.setBody(getLayout().toByteArray(flumeEvent));
107        manager.send(flumeEvent);
108    }
109
110    @Override
111    public boolean stop(final long timeout, final TimeUnit timeUnit) {
112        setStopping();
113        boolean stopped = super.stop(timeout, timeUnit, false);
114        stopped &= manager.stop(timeout, timeUnit);
115        setStopped();
116        return stopped;
117    }
118
119    /**
120     * Create a Flume event.
121     * @param event The Log4j LogEvent.
122     * @param includes comma separated list of mdc elements to include.
123     * @param excludes comma separated list of mdc elements to exclude.
124     * @param required comma separated list of mdc elements that must be present with a value.
125     * @param mdcPrefix The prefix to add to MDC key names.
126     * @param eventPrefix The prefix to add to event fields.
127     * @param compress If true the body will be compressed.
128     * @return A Flume Event.
129     */
130    @Override
131    public FlumeEvent createEvent(final LogEvent event, final String includes, final String excludes,
132                                  final String required, final String mdcPrefix, final String eventPrefix,
133                                  final boolean compress) {
134        return new FlumeEvent(event, mdcIncludes, mdcExcludes, mdcRequired, mdcPrefix,
135            eventPrefix, compressBody);
136    }
137
138    /**
139     * Create a Flume Avro Appender.
140     * @param agents An array of Agents.
141     * @param properties Properties to pass to the embedded agent.
142     * @param embedded true if the embedded agent manager should be used. otherwise the Avro manager will be used.
143     * <b>Note: </b><i>The embedded attribute is deprecated in favor of specifying the type attribute.</i>
144     * @param type Avro (default), Embedded, or Persistent.
145     * @param dataDir The directory where the Flume FileChannel should write its data.
146     * @param connectionTimeoutMillis The amount of time in milliseconds to wait before a connection times out. Minimum is
147     *                          1000.
148     * @param requestTimeoutMillis The amount of time in milliseconds to wait before a request times out. Minimum is 1000.
149     * @param agentRetries The number of times to retry an agent before failing to the next agent.
150     * @param maxDelayMillis The maximum number of milliseconds to wait for a complete batch.
151     * @param name The name of the Appender.
152     * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise
153     *               they are propagated to the caller.
154     * @param excludes A comma separated list of MDC elements to exclude.
155     * @param includes A comma separated list of MDC elements to include.
156     * @param required A comma separated list of MDC elements that are required.
157     * @param mdcPrefix The prefix to add to MDC key names.
158     * @param eventPrefix The prefix to add to event key names.
159     * @param compressBody If true the event body will be compressed.
160     * @param batchSize Number of events to include in a batch. Defaults to 1.
161     * @param lockTimeoutRetries Times to retry a lock timeout when writing to Berkeley DB.
162     * @param factory The factory to use to create Flume events.
163     * @param layout The layout to format the event.
164     * @param filter A Filter to filter events.
165     *
166     * @return A Flume Avro Appender.
167     */
168    @PluginFactory
169    public static FlumeAppender createAppender(@PluginElement("Agents") final Agent[] agents,
170                                               @PluginElement("Properties") final Property[] properties,
171                                               @PluginAttribute("hosts") final String hosts,
172                                               @PluginAttribute("embedded") final String embedded,
173                                               @PluginAttribute("type") final String type,
174                                               @PluginAttribute("dataDir") final String dataDir,
175                                               @PluginAliases("connectTimeout")
176                                               @PluginAttribute("connectTimeoutMillis") final String connectionTimeoutMillis,
177                                               @PluginAliases("requestTimeout")
178                                               @PluginAttribute("requestTimeoutMillis") final String requestTimeoutMillis,
179                                               @PluginAttribute("agentRetries") final String agentRetries,
180                                               @PluginAliases("maxDelay") // deprecated
181                                               @PluginAttribute("maxDelayMillis") final String maxDelayMillis,
182                                               @PluginAttribute("name") final String name,
183                                               @PluginAttribute("ignoreExceptions") final String ignore,
184                                               @PluginAttribute("mdcExcludes") final String excludes,
185                                               @PluginAttribute("mdcIncludes") final String includes,
186                                               @PluginAttribute("mdcRequired") final String required,
187                                               @PluginAttribute("mdcPrefix") final String mdcPrefix,
188                                               @PluginAttribute("eventPrefix") final String eventPrefix,
189                                               @PluginAttribute("compress") final String compressBody,
190                                               @PluginAttribute("batchSize") final String batchSize,
191                                               @PluginAttribute("lockTimeoutRetries") final String lockTimeoutRetries,
192                                               @PluginElement("FlumeEventFactory") final FlumeEventFactory factory,
193                                               @PluginElement("Layout") Layout<? extends Serializable> layout,
194                                               @PluginElement("Filter") final Filter filter) {
195
196        final boolean embed = embedded != null ? Boolean.parseBoolean(embedded) :
197            (agents == null || agents.length == 0 || hosts == null || hosts.isEmpty()) && properties != null && properties.length > 0;
198        final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
199        final boolean compress = Booleans.parseBoolean(compressBody, true);
200        ManagerType managerType;
201        if (type != null) {
202            if (embed && embedded != null) {
203                try {
204                    managerType = ManagerType.getType(type);
205                    LOGGER.warn("Embedded and type attributes are mutually exclusive. Using type " + type);
206                } catch (final Exception ex) {
207                    LOGGER.warn("Embedded and type attributes are mutually exclusive and type " + type +
208                        " is invalid.");
209                    managerType = ManagerType.EMBEDDED;
210                }
211            } else {
212                try {
213                    managerType = ManagerType.getType(type);
214                } catch (final Exception ex) {
215                    LOGGER.warn("Type " + type + " is invalid.");
216                    managerType = ManagerType.EMBEDDED;
217                }
218            }
219        }  else if (embed) {
220           managerType = ManagerType.EMBEDDED;
221        }  else {
222           managerType = ManagerType.AVRO;
223        }
224
225        final int batchCount = Integers.parseInt(batchSize, 1);
226        final int connectTimeoutMillis = Integers.parseInt(connectionTimeoutMillis, 0);
227        final int reqTimeoutMillis = Integers.parseInt(requestTimeoutMillis, 0);
228        final int retries = Integers.parseInt(agentRetries, 0);
229        final int lockTimeoutRetryCount = Integers.parseInt(lockTimeoutRetries, DEFAULT_LOCK_TIMEOUT_RETRY_COUNT);
230        final int delayMillis = Integers.parseInt(maxDelayMillis, DEFAULT_MAX_DELAY);
231
232        if (layout == null) {
233            final int enterpriseNumber = Rfc5424Layout.DEFAULT_ENTERPRISE_NUMBER;
234            layout = Rfc5424Layout.createLayout(Facility.LOCAL0, null, enterpriseNumber, true, Rfc5424Layout.DEFAULT_MDCID,
235                    mdcPrefix, eventPrefix, false, null, null, null, excludes, includes, required, null, false, null,
236                    null);
237        }
238
239        if (name == null) {
240            LOGGER.error("No name provided for Appender");
241            return null;
242        }
243
244        AbstractFlumeManager manager;
245
246        switch (managerType) {
247            case EMBEDDED:
248                manager = FlumeEmbeddedManager.getManager(name, agents, properties, batchCount, dataDir);
249                break;
250            case AVRO:
251                manager = FlumeAvroManager.getManager(name, getAgents(agents, hosts), batchCount, delayMillis, retries, connectTimeoutMillis, reqTimeoutMillis);
252                break;
253            case PERSISTENT:
254                manager = FlumePersistentManager.getManager(name, getAgents(agents, hosts), properties, batchCount, retries,
255                    connectTimeoutMillis, reqTimeoutMillis, delayMillis, lockTimeoutRetryCount, dataDir);
256                break;
257            default:
258                LOGGER.debug("No manager type specified. Defaulting to AVRO");
259                manager = FlumeAvroManager.getManager(name, getAgents(agents, hosts), batchCount, delayMillis, retries, connectTimeoutMillis, reqTimeoutMillis);
260        }
261
262        if (manager == null) {
263            return null;
264        }
265
266        return new FlumeAppender(name, filter, layout,  ignoreExceptions, includes,
267            excludes, required, mdcPrefix, eventPrefix, compress, factory, Property.EMPTY_ARRAY, manager);
268    }
269
270    private static Agent[] getAgents(Agent[] agents, final String hosts) {
271        if (agents == null || agents.length == 0) {
272            if (hosts != null && !hosts.isEmpty()) {
273                LOGGER.debug("Parsing agents from hosts parameter");
274                final String[] hostports = hosts.split(",");
275                agents = new Agent[hostports.length];
276                for(int i = 0; i < hostports.length; ++i) {
277                    final String[] h = hostports[i].split(":");
278                    agents[i] = Agent.createAgent(h[0], h.length > 1 ? h[1] : null);
279                }
280            } else {
281                LOGGER.debug("No agents provided, using defaults");
282                agents = new Agent[] {Agent.createAgent(null, null)};
283            }
284        }
285
286        LOGGER.debug("Using agents {}", agents);
287        return agents;
288    }
289}