All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.elasticsearch.hadoop.handler.impl.elasticsearch.ElasticsearchHandler Maven / Gradle / Ivy

There is a newer version: 8.16.0
Show newest version
/*
 * Licensed to Elasticsearch under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch 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.elasticsearch.hadoop.handler.impl.elasticsearch;

import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
import java.util.Properties;
import java.util.UUID;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.elasticsearch.hadoop.cfg.CompositeSettings;
import org.elasticsearch.hadoop.cfg.ConfigurationOptions;
import org.elasticsearch.hadoop.cfg.InternalConfigurationOptions;
import org.elasticsearch.hadoop.cfg.PropertiesSettings;
import org.elasticsearch.hadoop.cfg.Settings;
import org.elasticsearch.hadoop.handler.ErrorCollector;
import org.elasticsearch.hadoop.handler.ErrorHandler;
import org.elasticsearch.hadoop.handler.Exceptional;
import org.elasticsearch.hadoop.handler.HandlerResult;
import org.elasticsearch.hadoop.rest.InitializationUtils;
import org.elasticsearch.hadoop.rest.Resource;
import org.elasticsearch.hadoop.rest.RestClient;
import org.elasticsearch.hadoop.rest.RestRepository;
import org.elasticsearch.hadoop.rest.RestService;
import org.elasticsearch.hadoop.serialization.field.IndexExtractor;
import org.elasticsearch.hadoop.util.Assert;
import org.elasticsearch.hadoop.util.BytesArray;
import org.elasticsearch.hadoop.util.ObjectUtils;
import org.elasticsearch.hadoop.util.SettingsUtils;
import org.elasticsearch.hadoop.util.StringUtils;
import org.elasticsearch.hadoop.util.ecs.ElasticCommonSchema;
import org.elasticsearch.hadoop.util.ecs.ElasticCommonSchema.TemplateBuilder;
import org.elasticsearch.hadoop.util.ecs.MessageTemplate;
import org.elasticsearch.hadoop.util.unit.Booleans;

/**
 * Generic Error Handler that converts error events into JSON documents, and stores them in an Elasticsearch index.
 * 

* Handler results returned from this handler are configurable; In the case that the event is successfully written to * Elasticsearch it returns HANDLED by default and if the event cannot be written for any reason it returns ABORT by * default. *

* * @param type of error event * @param in case of retries, this is the type of the retry value * @param the type of error collector used */ public class ElasticsearchHandler> implements ErrorHandler { private static final Log LOG = LogFactory.getLog(ElasticsearchHandler.class); private static final String CONST_EVENT_CATEGORY = "error"; // Config Names /// Return logic: Use "return".[.reason] public static final String CONF_RETURN_VALUE = "return.default"; public static final String CONF_RETURN_VALUE_DEFAULT = HandlerResult.HANDLED.toString(); public static final String CONF_RETURN_ERROR = "return.error"; public static final String CONF_RETURN_ERROR_DEFAULT = HandlerResult.ABORT.toString(); public static final String CONF_PASS_REASON_SUFFIX = "reason"; /// Document metadata public static final String CONF_LABEL = "label"; public static final String CONF_TAGS = "tags"; /// Client configuration public static final String CONF_CLIENT_NODES = "client.nodes"; public static final String CONF_CLIENT_PORT = "client.port"; public static final String CONF_CLIENT_RESOURCE = "client.resource"; public static final String CONF_CLIENT_INHERIT= "client.inherit"; public static final String CONF_CLIENT_CONF = "client.conf"; // Settings private HandlerResult returnDefault; private String successReason; private HandlerResult returnError; private String errorReason; // State private Settings rootSettings; private Settings clientSettings; private EventConverter eventConverter; private MessageTemplate messageTemplate; private boolean initialized; private Resource endpoint; private RestRepository writeClient; public static > ElasticsearchHandler create(Settings rootSettings, EventConverter converter) { return new ElasticsearchHandler(rootSettings, converter); } public ElasticsearchHandler(Settings rootSettings, EventConverter eventConverter) { this.rootSettings = rootSettings; this.eventConverter = eventConverter; } @Override public void init(Properties properties) { // Collect Handler settings Settings handlerSettings = new PropertiesSettings(properties); boolean inheritRoot = true; if (handlerSettings.getProperty(CONF_CLIENT_INHERIT) != null) { inheritRoot = Booleans.parseBoolean(handlerSettings.getProperty(CONF_CLIENT_INHERIT)); } // Exception: Should persist transport pooling key if it is preset in the root config, regardless of the inherit settings. if (SettingsUtils.hasJobTransportPoolingKey(rootSettings)) { String jobKey = SettingsUtils.getJobTransportPoolingKey(rootSettings); // We want to use a different job key based on the current one since error handlers might write // to other clusters or have seriously different settings from the current rest client. String newJobKey = jobKey + "_" + UUID.randomUUID().toString(); // Place under the client configuration for the handler handlerSettings.setProperty(CONF_CLIENT_CONF + "." + InternalConfigurationOptions.INTERNAL_TRANSPORT_POOLING_KEY, newJobKey); } // Gather high level configs and push to client conf level resolveProperty(CONF_CLIENT_NODES, CONF_CLIENT_CONF + "." + ConfigurationOptions.ES_NODES, handlerSettings); resolveProperty(CONF_CLIENT_PORT, CONF_CLIENT_CONF + "." + ConfigurationOptions.ES_PORT, handlerSettings); resolveProperty(CONF_CLIENT_RESOURCE, CONF_CLIENT_CONF + "." + ConfigurationOptions.ES_RESOURCE_WRITE, handlerSettings); resolveProperty(CONF_CLIENT_RESOURCE, CONF_CLIENT_CONF + "." + ConfigurationOptions.ES_RESOURCE, handlerSettings); // Inherit the original configuration or not this.clientSettings = handlerSettings.getSettingsView(CONF_CLIENT_CONF); // Ensure we have a write resource to use Assert.hasText(clientSettings.getResourceWrite(), "Could not locate write resource for ES error handler."); if (inheritRoot) { LOG.info("Elasticsearch Error Handler inheriting root configuration"); this.clientSettings = new CompositeSettings(Arrays.asList(clientSettings, rootSettings.excludeFilter("es.internal"))); } else { LOG.info("Elasticsearch Error Handler proceeding without inheriting root configuration options as configured"); } // Ensure no pattern in Index format, and extract the index to send errors to InitializationUtils.discoverAndValidateClusterInfo(clientSettings, LOG); Resource resource = new Resource(clientSettings, false); IndexExtractor iformat = ObjectUtils.instantiate(clientSettings.getMappingIndexExtractorClassName(), handlerSettings); iformat.compile(resource.toString()); if (iformat.hasPattern()) { throw new IllegalArgumentException(String.format("Cannot use index format within Elasticsearch Error Handler. Format was [%s]", resource.toString())); } this.endpoint = resource; // Configure ECS ElasticCommonSchema schema = new ElasticCommonSchema(); TemplateBuilder templateBuilder = schema.buildTemplate() .setEventCategory(CONST_EVENT_CATEGORY); // Add any Labels and Tags to schema for (Map.Entry entry: handlerSettings.getSettingsView(CONF_LABEL).asProperties().entrySet()) { templateBuilder.addLabel(entry.getKey().toString(), entry.getValue().toString()); } templateBuilder.addTags(StringUtils.tokenize(handlerSettings.getProperty(CONF_TAGS))); // Configure template using event handler templateBuilder = eventConverter.configureTemplate(templateBuilder); this.messageTemplate = templateBuilder.build(); // Determine the behavior for successful write and error on write: this.returnDefault = HandlerResult.valueOf(handlerSettings.getProperty(CONF_RETURN_VALUE, CONF_RETURN_VALUE_DEFAULT)); if (HandlerResult.PASS == returnDefault) { this.successReason = handlerSettings.getProperty(CONF_RETURN_VALUE + "." + CONF_PASS_REASON_SUFFIX); } else { this.successReason = null; } this.returnError = HandlerResult.valueOf(handlerSettings.getProperty(CONF_RETURN_ERROR, CONF_RETURN_ERROR_DEFAULT)); if (HandlerResult.PASS == returnError) { this.errorReason = handlerSettings.getProperty(CONF_RETURN_ERROR + "." + CONF_PASS_REASON_SUFFIX); } else { this.errorReason = null; } } private void resolveProperty(String highLevelProperty, String explicitProperty, Settings subject) { String confValue = subject.getProperty(highLevelProperty); String explicitValue = subject.getProperty(explicitProperty); if (StringUtils.hasText(confValue) && StringUtils.hasText(explicitValue)) { LOG.warn( String.format("Found both [%s] and [%s] settings during elasticsearch handler init. Continuing " + "with value from [%s] (%s)", highLevelProperty, explicitProperty, highLevelProperty, confValue ) ); } if (StringUtils.hasText(confValue)) { subject.setProperty(explicitProperty, confValue); } } private void lazyInitWrite() { if (!initialized) { this.initialized = true; this.writeClient = RestService.createWriter(clientSettings, -1, 0, LOG).repository; } } @Override public HandlerResult onError(I entry, C collector) throws Exception { HandlerResult result; try { lazyInitWrite(); if (isOpen()) { putDocument(writeClient.getRestClient(), createErrorDocument(entry)); result = generateResult(returnDefault, successReason, collector); } else { result = generateResult(returnError, errorReason, collector); } } catch (Exception e) { LOG.error("Could not send error handling data to ES", e); result = generateResult(returnError, errorReason, collector); } return result; } private boolean isOpen() { return writeClient != null; } private BytesArray createErrorDocument(I entry) throws IOException { return eventConverter.generateEvent(entry, messageTemplate); } private void putDocument(RestClient client, BytesArray document) throws IOException { client.postDocument(endpoint, document); } private HandlerResult generateResult(HandlerResult expectedResult, String possiblePassReason, C collector) { if (HandlerResult.PASS == expectedResult) { return collector.pass(possiblePassReason); } else { return expectedResult; } } @Override public void close() { if (isOpen()) { // TODO: look at collecting these stats some other way later. if (clientSettings.getBatchRefreshAfterWrite()) { writeClient.getRestClient().refresh(endpoint); } writeClient.close(); } } }