Package org.apdplat.platform.log.handler

Source Code of org.apdplat.platform.log.handler.ElasticSearchLogHandler

/**
*
* APDPlat - Application Product Development Platform
* Copyright (c) 2013, 杨尚川, yang-shangchuan@qq.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*
*/

package org.apdplat.platform.log.handler;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.lang.reflect.Field;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import org.apdplat.module.monitor.model.MemoryState;
import org.apdplat.module.system.service.PropertyHolder;
import org.apdplat.platform.action.converter.DateTypeConverter;
import org.apdplat.platform.log.APDPlatLogger;
import org.apdplat.platform.log.APDPlatLoggerFactory;
import org.apdplat.platform.model.Model;
import org.apdplat.platform.util.ConvertUtils;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.stereotype.Service;

/**
*
* 日志处理实现:
* 将日志保存到ElasticSearch中
* 进行高性能实时搜索和分析
* 支持大规模分布式搜索
*
* The bulk API makes it possible to perform
* many index/delete operations in a single API call.
* This can greatly increase the indexing speed.
* The REST API endpoint is /_bulk, and it
* expects the following JSON structure:
* action_and_meta_data\n
* optional_source\n
* action_and_meta_data\n
* optional_source\n
* ....
* action_and_meta_data\n
* optional_source\n
*
* NOTE: the final line of data must end with a newline character \n.
*
* The possible actions are index, create, delete and since version 0.90.1 also update.
* index and create expect a source on the next line,
* and have the same semantics as the op_type parameter to the standard index API
* (i.e. create will fail if a document with the same index and type exists already,
* whereas index will add or replace a document as necessary).
* delete does not expect a source on the following line,
* and has the same semantics as the standard delete API.
* update expects that the partial doc, upsert and script
* and its options are specified on the next line.
*
* @author 杨尚川
*/
@Service
public class ElasticSearchLogHandler implements LogHandler{
    private static final APDPlatLogger LOG = APDPlatLoggerFactory.getAPDPlatLogger(ElasticSearchLogHandler.class);
   
    private static final String INDEX_NAME = PropertyHolder.getProperty("elasticsearch.log.index.name");
    private static final String HOST = PropertyHolder.getProperty("elasticsearch.host");
    private static final String PORT = PropertyHolder.getProperty("elasticsearch.port");
    private static final ObjectMapper MAPPER = new ObjectMapper();
    private static URL URL;
   
    private int success;
  
    public ElasticSearchLogHandler(){
        LOG.info("elasticsearch.log.index.name: "+INDEX_NAME);
        LOG.info("elasticsearch.host: "+HOST);
        LOG.info("elasticsearch.port: "+PORT);
        try {
            URL = new URL("http://"+HOST+":"+PORT+"/_bulk");
        } catch (MalformedURLException ex) {
            LOG.error("构造URL失败",ex);
        }
    }
   
    /**
     * 批量提交索引JSON文档
     *
     * @param <T> 泛型参数
     * @param list 批量模型
     */
    public <T extends Model> void index(List<T> list){
        success = 0;
        String json = getJsonDocuments(list);
        try{
            LOG.debug("开始批量提交索引JSON文档");
            HttpURLConnection conn = (HttpURLConnection) URL.openConnection();
            conn.setRequestMethod("PUT");
            conn.setDoOutput(true);
            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(conn.getOutputStream(),"utf-8"));   
            writer.write(json.toString());
            writer.flush();
            StringBuilder result = new StringBuilder();
            try (BufferedReader reader = new BufferedReader (new InputStreamReader (conn.getInputStream()))) {
                String line = reader.readLine();
                while(line != null){
                    result.append(line);
                    line = reader.readLine();
                }
            }
            String resultStr = result.toString();
            LOG.debug(resultStr);         
            //使用Jackson解析返回的JSON
            JsonNode node = MAPPER.readTree(resultStr);
            for(JsonNode item : node.get("items")){
                JsonNode createJsonNode = item.get("create");
                JsonNode okJsonNode = createJsonNode.get("ok");
                if(okJsonNode != null){
                    boolean r = okJsonNode.getBooleanValue();
                    if(r){
                        success++;
                    }
                }else{
                    JsonNode errorJsonNode = createJsonNode.get("error");
                    if(errorJsonNode != null){
                        String errorMessage = errorJsonNode.getTextValue();
                        LOG.error("索引失败:"+errorMessage);
                    }
                }
            }
            LOG.debug("批量提交索引JSON文档完毕");
        }catch(IOException e){
            LOG.error("批量提交索引失败", e);
        }
    }
    /**
     * 将待索引的日志对象列表按照ElasticSearch的要求
     * 构造成合适的JSON文档
     * @param <T> 泛型参数
     * @param list 待索引的日志对象列表
     * @return 符合ElasticSearch要求的JSON文档
     */
    public <T extends Model> String getJsonDocuments(List<T> list){       
        StringBuilder json = new StringBuilder();
        int j = 1;
        LOG.debug("开始构造JSON文档");
        for(T model : list){
            try{
                String simpleName = model.getClass().getSimpleName();
                LOG.debug((j++)+"、simpleName: 【"+simpleName+"】");
                json.append("{\"index\":{\"_index\":\"")
                        .append(INDEX_NAME)
                        .append("\",\"_type\":\"")
                        .append(simpleName)
                        .append("\"}}")
                        .append("\n");
                json.append("{");
                Field[] fields = model.getClass().getDeclaredFields();
                int len = fields.length;
                for(int i = 0; i < len; i++){
                    Field field = fields[i];
                    String name = field.getName();
                    field.setAccessible(true);
                    Object value = field.get(model);
                    //小心空指针异常,LogHandler线程会悄无声息地推出!
                    if(value == null){
                        LOG.debug("忽略空字段:"+name);
                        continue;
                    }
                    if(i>0){
                        json.append(",");
                    }
                    String valueClass=value.getClass().getSimpleName();
                    LOG.debug("name: "+name+"   type: "+valueClass);
                    if("Timestamp".equals(valueClass) || "Date".equals(valueClass)){
                        //提交给ES的日期时间值要为"2014-01-31T13:53:54"这样的形式
                        value=DateTypeConverter.toDefaultDateTime((Date)value).replace(" ", "T");
                    }
                    String prefix = "\"";
                    String suffix = "\"";
                    //提交给ES的数字和布尔值不要加双引号
                    if("Float".equals(valueClass)
                            || "Double".equals(valueClass)
                            || "Long".equals(valueClass)
                            || "Integer".equals(valueClass)
                            || "Short".equals(valueClass)
                            || "Boolean".equals(valueClass)){
                        prefix="";
                        suffix="";
                    }
                    json.append("\"")
                            .append(name)
                            .append("\":")
                            .append(prefix)
                            .append(value)
                            .append(suffix);
                }
                json.append("}\n");
            }catch(SecurityException | IllegalArgumentException | IllegalAccessException e){
                LOG.error("构造索引请求失败【"+model.getMetaData()+"】\n"+model, e);
            }
        }
        LOG.debug("JSON文档构造完毕:\n"+json.toString());
        return json.toString();
    }
   
    @Override
    public <T extends Model> void handle(List<T> list) {
        LOG.info("开始将 "+list.size()+" 个日志对象索引到ElasticSearch服务器");
        long start = System.currentTimeMillis();
        index(list);
        long cost = System.currentTimeMillis() - start;
       
        if(success != list.size()){
            LOG.info("索引失败: "+(list.size()-success)+" 个");           
        }
        if(success > 0){
            LOG.info("索引成功: "+success+" 个");
        }
        LOG.info("耗时:"+ConvertUtils.getTimeDes(cost));
    }   
   
    public static void main(String[] args){
        List<Model> list = new LinkedList<>();
       
        MemoryState m = new MemoryState();
        m.setAppName("杨尚川");
        m.setFreeMemory(1000f);
        m.setMaxMemory(5000f);
        m.setRecordTime(new Date());
        m.setServerIP("127.0.0.1");
        m.setTotalMemory(10000f);
        m.setUsableMemory(8000f);
       
        list.add(m);
       
        m = new MemoryState();
        m.setAppName("APDPlat");
        m.setFreeMemory(2000f);
        m.setMaxMemory(6000f);
        m.setRecordTime(new Date());
        m.setServerIP("127.0.0.1");
        m.setTotalMemory(11000f);
        m.setUsableMemory(9000f);
       
        list.add(m);
       
        LogHandler h = new ElasticSearchLogHandler();
        h.handle(list);
       
        //可使用以下命令查看自动生成的结构:
        //curl  -XGET http://localhost:9200/_mapping?pretty=true
        //curl  -XGET http://localhost:9200/apdplat_for_log/_search?pretty=true&q=ysc
    }
}
TOP

Related Classes of org.apdplat.platform.log.handler.ElasticSearchLogHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.