/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache license, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the license for the specific language governing permissions and * limitations under the license. */ package org.apache.logging.log4j.flume.appender; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.concurrent.TimeUnit; import org.apache.flume.Event; import org.apache.flume.EventDeliveryException; import org.apache.flume.agent.embedded.EmbeddedAgent; import org.apache.logging.log4j.LoggingException; import org.apache.logging.log4j.core.appender.ManagerFactory; import org.apache.logging.log4j.core.config.ConfigurationException; import org.apache.logging.log4j.core.config.Property; import org.apache.logging.log4j.core.util.NameUtil; import org.apache.logging.log4j.util.PropertiesUtil; import org.apache.logging.log4j.util.Strings; /** * */ public class FlumeEmbeddedManager extends AbstractFlumeManager { private static final String FILE_SEP = PropertiesUtil.getProperties().getStringProperty("file.separator"); private static final String IN_MEMORY = "InMemory"; private static FlumeManagerFactory factory = new FlumeManagerFactory(); private final EmbeddedAgent agent; private final String shortName; /** * Constructor * @param name The unique name of this manager. * @param shortName The short version of the agent name. * @param agent The embedded agent. */ protected FlumeEmbeddedManager(final String name, final String shortName, final EmbeddedAgent agent) { super(name); this.agent = agent; this.shortName = shortName; } /** * Returns a FlumeEmbeddedManager. * @param name The name of the manager. * @param agents The agents to use. * @param properties Properties for the embedded manager. * @param batchSize The number of events to include in a batch. * @param dataDir The directory where the Flume FileChannel should write to. * @return A FlumeAvroManager. */ public static FlumeEmbeddedManager getManager(final String name, final Agent[] agents, final Property[] properties, int batchSize, final String dataDir) { if (batchSize <= 0) { batchSize = 1; } if ((agents == null || agents.length == 0) && (properties == null || properties.length == 0)) { throw new IllegalArgumentException("Either an Agent or properties are required"); } else if (agents != null && agents.length > 0 && properties != null && properties.length > 0) { throw new IllegalArgumentException("Cannot configure both Agents and Properties."); } final StringBuilder sb = new StringBuilder(); boolean first = true; if (agents != null && agents.length > 0) { sb.append(name).append('['); for (final Agent agent : agents) { if (!first) { sb.append('_'); } sb.append(agent.getHost()).append('-').append(agent.getPort()); first = false; } sb.append(']'); } else { String sep = Strings.EMPTY; sb.append(name).append('-'); final StringBuilder props = new StringBuilder(); for (final Property prop : properties) { props.append(sep); props.append(prop.getName()).append('=').append(prop.getValue()); sep = "_"; } sb.append(NameUtil.md5(props.toString())); } return getManager(sb.toString(), factory, new FactoryData(name, agents, properties, batchSize, dataDir)); } @Override public void send(final Event event) { try { agent.put(event); } catch (final EventDeliveryException ex) { throw new LoggingException("Unable to deliver event to Flume Appender " + shortName, ex); } } @Override protected boolean releaseSub(final long timeout, final TimeUnit timeUnit) { agent.stop(); return true; } /** * Factory data. */ private static class FactoryData { private final Agent[] agents; private final Property[] properties; private final int batchSize; private final String dataDir; private final String name; /** * Constructor. * @param name The name of the Appender. * @param agents The agents. * @param properties The Flume configuration properties. * @param batchSize The number of events to include in a batch. * @param dataDir The directory where Flume should write to. */ public FactoryData(final String name, final Agent[] agents, final Property[] properties, final int batchSize, final String dataDir) { this.name = name; this.agents = agents; this.batchSize = batchSize; this.properties = properties; this.dataDir = dataDir; } } /** * Avro Manager Factory. */ private static class FlumeManagerFactory implements ManagerFactory<FlumeEmbeddedManager, FactoryData> { /** * Create the FlumeAvroManager. * @param name The name of the entity to manage. * @param data The data required to create the entity. * @return The FlumeAvroManager. */ @Override public FlumeEmbeddedManager createManager(final String name, final FactoryData data) { try { final Map<String, String> props = createProperties(data.name, data.agents, data.properties, data.batchSize, data.dataDir); final EmbeddedAgent agent = new EmbeddedAgent(name); agent.configure(props); agent.start(); LOGGER.debug("Created Agent " + name); return new FlumeEmbeddedManager(name, data.name, agent); } catch (final Exception ex) { LOGGER.error("Could not create FlumeEmbeddedManager", ex); } return null; } private Map<String, String> createProperties(final String name, final Agent[] agents, final Property[] properties, final int batchSize, String dataDir) { final Map<String, String> props = new HashMap<>(); if ((agents == null || agents.length == 0) && (properties == null || properties.length == 0)) { LOGGER.error("No Flume configuration provided"); throw new ConfigurationException("No Flume configuration provided"); } if (agents != null && agents.length > 0 && properties != null && properties.length > 0) { LOGGER.error("Agents and Flume configuration cannot both be specified"); throw new ConfigurationException("Agents and Flume configuration cannot both be specified"); } if (agents != null && agents.length > 0) { if (Strings.isNotEmpty(dataDir)) { if (dataDir.equals(IN_MEMORY)) { props.put("channel.type", "memory"); } else { props.put("channel.type", "file"); if (!dataDir.endsWith(FILE_SEP)) { dataDir = dataDir + FILE_SEP; } props.put("channel.checkpointDir", dataDir + "checkpoint"); props.put("channel.dataDirs", dataDir + "data"); } } else { props.put("channel.type", "file"); } final StringBuilder sb = new StringBuilder(); String leading = Strings.EMPTY; final int priority = agents.length; for (int i = 0; i < priority; ++i) { sb.append(leading).append("agent").append(i); leading = " "; final String prefix = "agent" + i; props.put(prefix + ".type", "avro"); props.put(prefix + ".hostname", agents[i].getHost()); props.put(prefix + ".port", Integer.toString(agents[i].getPort())); props.put(prefix + ".batch-size", Integer.toString(batchSize)); props.put("processor.priority." + prefix, Integer.toString(agents.length - i)); } props.put("sinks", sb.toString()); props.put("processor.type", "failover"); } else { String[] sinks = null; for (final Property property : properties) { final String key = property.getName(); if (Strings.isEmpty(key)) { final String msg = "A property name must be provided"; LOGGER.error(msg); throw new ConfigurationException(msg); } final String upperKey = key.toUpperCase(Locale.ENGLISH); if (upperKey.startsWith(name.toUpperCase(Locale.ENGLISH))) { final String msg = "Specification of the agent name is not allowed in Flume Appender configuration: " + key; LOGGER.error(msg); throw new ConfigurationException(msg); } final String value = property.getValue(); if (Strings.isEmpty(value)) { final String msg = "A value for property " + key + " must be provided"; LOGGER.error(msg); throw new ConfigurationException(msg); } if (upperKey.equals("SINKS")) { sinks = value.trim().split(" "); } props.put(key, value); } if (sinks == null || sinks.length == 0) { final String msg = "At least one Sink must be specified"; LOGGER.error(msg); throw new ConfigurationException(msg); } } return props; } } }