Springboot2 Log4j2 Es7.8.1 Kibana7.8.1 做用户行为分析

目录

一.搭建ES和Kibana集群

二.Springboot2代码编写和依赖配置

1.pom文件依赖

2.编写ElasticSearchUtils

3.编写ElasticSearchConfig

三.通过Log4j2输出行为日志

1.编写行为日志接口

2.实现行为日志接口(之前的项目是插入数据库的这个实现用来插入ES)

3.创建ConsoleLogAppender用于Log4j2输出配置

四.编写AOP插入行为日志

1.编写日志类型枚举(老大之前写的,之前的日志是插入到数据库的.copy一下哈哈)

2.自定义Es日志注解

3.编写AOP切面具体业务

五.log4j2.xml配置文件增加ConsoleLogAppender

六.使用

七.创建Es索引和字段

八.创建kibana索引

九.在kibana中创建仪表盘等(前提是ES有了一定量数据哈)

仿照kibana系统数据创建自己的仪表盘等


一.搭建ES和Kibana集群

https://blog.csdn.net/qq_40174211/article/details/113255162

按照文章中的步骤  只搭建ES和Kibana集群即可

二.Springboot2代码编写和依赖配置

1.pom文件依赖

            <!--ElasticSearch 依赖-->
            <dependency>
                <groupId>org.elasticsearch.client</groupId>
                <artifactId>elasticsearch-rest-high-level-client</artifactId>
                <version>7.8.1</version>
            </dependency>
            <dependency>
                <groupId>org.elasticsearch</groupId>
                <artifactId>elasticsearch</artifactId>
                <version>7.8.1</version>
            </dependency>
            <dependency>
                <groupId>org.elasticsearch.client</groupId>
                <artifactId>elasticsearch-rest-client</artifactId>
                <version>7.8.1</version>
            </dependency>

2.编写ElasticSearchUtils

package com.sinosoft.springbootplus.util;

import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.query.*;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @author fan
 */
@Configuration
public class ElasticSearchUtils {

    @Resource
    private RestHighLevelClient restHighLevelClient;


    // ================================新增========================================

    /**
     *  
     *  * @Description: 更新文档 
     *  * @param [index 索引, id 索引id, map 待更新内容] 
     *  * @return java.lang.String 
     *  * @author fan 
     *  * @date 2021/4/28 14:58  
     *  
     **/
    public String updateDoc(String index, String id, Map<String, Object> map) throws IOException {
        UpdateRequest updateRequest = new UpdateRequest(index, id);
        updateRequest.doc(map);
        UpdateResponse updateResponse = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
        return updateResponse.getResult().getLowercase();
    }

    /**
     *  
     *  * @Description: 新增文档 
     *  * @param [index 索引, id doc的 _id, map 待插入内容]  
     *  * @return java.lang.String 
     *  * @author fan 
     *  * @date 2021/4/28 14:59  
     *  
     **/
    public String addDoc(String index, Map<String, Object> map) throws IOException {
        IndexRequest indexRequest = new IndexRequest(index);
        indexRequest.source(map);
        indexRequest.id(IdUtil.simpleUUID());
        IndexResponse indexResponse = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
        return indexResponse.getResult().getLowercase();
    }


    /** 
     * @Description: 批量插入文档 
     * @param [index 索引, id 索引id, list 待插入内容] 
     * @return void 
     * @author fan 
     * @date 2021/4/28 14:59  
     **/
    public void bulkInsertDoc(String index, String id, List<Map<String, Object>> list) throws IOException {
        if (StringUtils.isBlank(index) || StringUtils.isBlank(id) || CollectionUtils.isEmpty(list)) {
            return;
        }
        BulkRequest bulkRequest = new BulkRequest();
        list.forEach(map -> {
            IndexRequest indexRequest = new IndexRequest(index);
            indexRequest.source(map);
            indexRequest.id(map.get(id).toString());
            bulkRequest.add(indexRequest);
        });

        restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
    }

    // ======================删除======================================

    /** 
     * @Description: 根据文档Id删除数据 
     * @param [index 索引, id 文档id] 
     * @return long 
     * @author fan 
     * @date 2021/4/29 14:19  
     **/
    public long deleteDocById(String index, String id) throws IOException {

        if (StringUtils.isBlank(index) || StringUtils.isBlank(id)) {
            return -1;
        }
        return deleteDocByValue(index, "_id", id);
    }


    /** 
     * @Description: 根据字段值 删除数据 
     * @param [index 索引, fieldName 映射名, value 文档值] 
     * @return long 
     * @author fan 
     * @date 2021/4/29 16:36  
     **/
    public long deleteDocByValue(String index, String fieldName, String value) throws IOException {

        if (StringUtils.isBlank(index) || StringUtils.isBlank(fieldName) || StringUtils.isBlank(value)) {
            return -1;
        }
        DeleteByQueryRequest deleteByQueryRequest = new DeleteByQueryRequest(index);
        //设置版本冲突时继续执行
        deleteByQueryRequest.setConflicts("proceed");
        // 设置查询条件
        deleteByQueryRequest.setQuery(new TermQueryBuilder(fieldName, value));
        deleteByQueryRequest.setMaxDocs(60);
        deleteByQueryRequest.setBatchSize(1000);
        // 并行
        deleteByQueryRequest.setSlices(2);
        // 使用滚动参数来控制“搜索上下文”存活的时间
        deleteByQueryRequest.setScroll(TimeValue.timeValueMinutes(10));
        // 超时
        deleteByQueryRequest.setTimeout(TimeValue.timeValueMinutes(2));
        // 刷新索引
        deleteByQueryRequest.setRefresh(true);

        BulkByScrollResponse response = restHighLevelClient.deleteByQuery(deleteByQueryRequest, RequestOptions.DEFAULT);
        return response.getStatus().getUpdated();
    }


    // ====================查询======================================

    /** 
     * @Description: 多字段匹配查询 
     * @param [index 索引, fieldMap 字段map] 
     * @return java.lang.String 
     * @author fan 
     * @date 2021/4/29 16:39  
     **/
    public String getByMultiFieldNames(String index, Map<String, Object> fieldMap) throws IOException {
        if (StringUtils.isBlank(index) || MapUtils.isEmpty(fieldMap)) {
            return null;
        }
        SearchRequest searchRequest = new SearchRequest(index);

        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //循环传入搜索参数
        fieldMap.forEach((key, value) -> {
            boolQueryBuilder.must(QueryBuilders.termQuery(key, value));
        });

        sourceBuilder.query(boolQueryBuilder);
        searchRequest.source(sourceBuilder);
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        return handleSearchResponse2Json(searchResponse);
    }


    /** 
     * @Description: 根据文档id 查询 
     * @param [index 索引, id 文档id] 
     * @return java.lang.String 
     * @author fan 
     * @date 2021/4/29 16:39  
     **/
    public String getByIndexAndDocId(String index, String id) throws IOException {
        if (StringUtils.isBlank(index) || StringUtils.isBlank(id)) {
            return null;
        }
        GetRequest getRequest = new GetRequest(index, id);
        GetResponse getResponse = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT);
        return getResponse.getSourceAsString();
    }

    /** 
     * @Description: 根据索引分页查询 
     * @param [index 索引, pageNum, pageSize] 
     * @return java.lang.String 
     * @author fan 
     * @date 2021/4/29 16:41  
     **/
    public String getByDoc(String index, int pageNum, int pageSize) throws IOException {
        if (StringUtils.isBlank(index)) {
            return null;
        }

        // 搜索请求
        SearchRequest searchRequest = new SearchRequest(index);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        // 分页
        if (pageNum >= 0 && pageSize >= 0) {
            searchSourceBuilder.from(pageSize * (pageNum - 1));
            searchSourceBuilder.size(pageSize);
        } else {
            // 如果不传分页参数 默认给20条数据
            searchSourceBuilder.from(0);
            searchSourceBuilder.size(19);
        }

        // 查询条件
        MatchAllQueryBuilder queryBuilder = QueryBuilders.matchAllQuery();

        // 传入搜索条件
        searchSourceBuilder.query(queryBuilder);
        searchRequest.source(searchSourceBuilder);

        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        return handleSearchResponse2Json(searchResponse);
    }


    /** 
     * @Description: 根据映射查询 
     * @param [index 索引, fileName 映射, value 映射值] 
     * @return java.lang.String 
     * @author fan 
     * @date 2021/4/29 16:44  
     **/
    public String getByFieldName(String index, String fileName, String value) throws IOException {

        if (StringUtils.isBlank(index) || StringUtils.isBlank(fileName) || StringUtils.isBlank(value)) {
            return null;
        }
        SearchRequest searchRequest = new SearchRequest(index);
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        QueryBuilder queryBuilder = QueryBuilders.matchQuery(fileName, value);
        sourceBuilder.query(queryBuilder);

        searchRequest.source(sourceBuilder);
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        return handleSearchResponse2Json(searchResponse);
    }

    /** 
     * @Description: 在多个映射中查找一个值 
     * @param [index 索引, value 要查找的值, fieldNames 映射] 
     * @return java.lang.String 
     * @author fan 
     * @date 2021/4/29 16:45  
     **/
    public String multiFieldsMatchOneValue(String index, String value, String... fieldNames) throws IOException {

        if (StringUtils.isBlank(index) || StringUtils.isBlank(value) || null == fieldNames) {
            return null;
        }
        SearchRequest searchRequest = new SearchRequest(index);

        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        QueryBuilder queryBuilder = QueryBuilders.multiMatchQuery(value, fieldNames);
        sourceBuilder.query(queryBuilder);

        searchRequest.source(sourceBuilder);
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        return handleSearchResponse2Json(searchResponse);
    }


    /** 
     * @Description: 字段模糊匹配 
     * @param [index 索引, fieldName 映射, wildValueStr 模糊值] 
     * @return java.lang.String 
     * @author fan 
     * @date 2021/4/29 16:48  
     **/
    public String getByFieldNameWild(String index, String fieldName, String wildValueStr) throws IOException {

        if (StringUtils.isBlank(index) || StringUtils.isBlank(fieldName) || StringUtils.isBlank(wildValueStr)) {
            return null;
        }

        SearchRequest searchRequest = new SearchRequest(index);

        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        WildcardQueryBuilder queryBuilder = QueryBuilders.wildcardQuery(fieldName, wildValueStr);
        sourceBuilder.query(queryBuilder);

        searchRequest.source(sourceBuilder);
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        return handleSearchResponse2Json(searchResponse);
    }

    /** 
     * @Description:  范围查询
     * @param [index 索引, fieldName 映射, start 开始, includeUpperStart 是否包含开始, end 结束, includeUpperEnd 是否包含结束] 
     * @return java.lang.String 
     * @author fan 
     * @date 2021/4/29 16:48  
     **/
    public String getByFieldRange(String index, String fieldName, String start, boolean includeUpperStart, String end, boolean includeUpperEnd) throws IOException {
        if (StringUtils.isBlank(index) || StringUtils.isBlank(fieldName)) {
            return null;
        }

        SearchRequest searchRequest = new SearchRequest(index);

        RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery(fieldName);
        rangeQueryBuilder.from(start, includeUpperStart);
        rangeQueryBuilder.to(end, includeUpperEnd);

        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(rangeQueryBuilder);
        searchRequest.source(searchSourceBuilder);
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        return handleSearchResponse2Json(searchResponse);
    }

    /** 
     * @Description: 根据文档id判断文档是否存在 
     * @param [index 索引, id 文档id] 
     * @return boolean 
     * @author fan 
     * @date 2021/4/29 16:49  
     **/
    public boolean isExists(String index, String id) throws IOException {
        if (StringUtils.isBlank(index) || StringUtils.isBlank(id)) {
            return Boolean.FALSE;
        }
        GetRequest getRequest = new GetRequest(index, id);
        //禁用fetching _source
        getRequest.fetchSourceContext(new FetchSourceContext(false));
        // 关闭 存储字段
        getRequest.storedFields("_none_");

        return restHighLevelClient.exists(getRequest, RequestOptions.DEFAULT);
    }

    /** 
     * @Description: 将SearchResponse 取出数据 转换成json 
     * @param [searchResponse 查询结果] 
     * @return java.lang.String 
     * @author fan 
     * @date 2021/4/29 16:50  
     **/
    private String handleSearchResponse2Json(SearchResponse searchResponse) {
        SearchHit[] hits = searchResponse.getHits().getHits();
        if (hits.length == 0) {
            return null;
        }
        List<Map<String, Object>> dataList = new ArrayList<>(hits.length);
        for (int i = 0; i < hits.length; i++) {
            dataList.add(hits[i].getSourceAsMap());
        }
        return JSONObject.toJSONString(dataList);
    }


}

3.编写ElasticSearchConfig

package com.sinosoft.springbootplus.config;

import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;

/**
 * @Description: ElasticSearch 配置类
 * @Author: karma
 * @Date: 2019/11/25 3:11 下午
 */
@Configuration
public class ElasticSearchConfig {

    /**
     * 集群地址,多个用,隔开
     **/
    @Value("${elasticSearch.hosts}")
    private String hosts;

    /**
     * 端口号
     **/
    @Value("${elasticSearch.port}")
    private int port;

    /**
     * 使用的协议
     **/
    @Value("${elasticSearch.schema}")
    private String schema;

    /**
     * 连接超时时间
     **/
    @Value("${elasticSearch.client.connectTimeOut}")
    private int connectTimeOut;

    /**
     * 连接超时时间
     **/
    @Value("${elasticSearch.client.socketTimeOut}")
    private int socketTimeOut;

    /**
     * 获取连接的超时时间
     **/
    @Value("${elasticSearch.client.connectionRequestTimeOut}")
    private static int connectionRequestTimeOut;

    /**
     * 最大连接数
     **/
    @Value("${elasticSearch.client.maxConnectNum}")
    private static int maxConnectNum;

    /**
     *  最大路由连接数
     **/
    @Value("${elasticSearch.client.maxConnectPerRoute}")
    private static int maxConnectPerRoute;


    private List<HttpHost> hostList = new ArrayList<>();

    @PostConstruct
    private  void init(){
        hostList = new ArrayList<>();
        String[] hostArray = hosts.split(",");
        for (String host : hostArray) {
            hostList.add(new HttpHost(host, port, schema));
        }
    }

    @Bean
    public RestHighLevelClient getRestHighLevelClient() {

        RestClientBuilder builder = RestClient.builder(hostList.toArray(new HttpHost[0]));
        // 异步httpclient连接延时配置
        builder.setRequestConfigCallback(requestConfigBuilder -> {
            requestConfigBuilder.setConnectTimeout(connectTimeOut);
            requestConfigBuilder.setSocketTimeout(socketTimeOut);
            requestConfigBuilder.setConnectionRequestTimeout(connectionRequestTimeOut);
            return requestConfigBuilder;
        });
        // 异步httpclient连接数配置
        builder.setHttpClientConfigCallback(httpClientBuilder -> {
            httpClientBuilder.setMaxConnTotal(maxConnectNum);
            httpClientBuilder.setMaxConnPerRoute(maxConnectPerRoute);
            return httpClientBuilder;
        });
        RestHighLevelClient client = new RestHighLevelClient(builder);
        return client;
    }

}


三.通过Log4j2输出行为日志

1.编写行为日志接口

package com.sinosoft.springbootplus.log4j2.service;


import java.util.List;

/**
 * <pre>
 * 系统行为日志
 * </pre>
 *
 * @author 波哥
 * @since 2020-02-23
 */
public interface BaseLogService {
    /**
     * 单条保存行为日志
     */
    void save(String loginfoJson);

    /**
     * 批量保存行为日志
     */
    void saveBatch(List<String> loginfoJsons);
}

2.实现行为日志接口(之前的项目是插入数据库的这个实现用来插入ES)

package com.sinosoft.springbootplus.impl;

import com.sinosoft.springbootplus.log4j2.service.BaseLogService;
import com.sinosoft.springbootplus.entity.EsSaveInfo;
import com.sinosoft.springbootplus.util.ElasticSearchUtils;
import com.sinosoft.springbootplus.util.Jackson;
import com.sinosoft.springbootplus.util.MapperUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * @author fan
 * @Date 2021/5/14 10:36 <pre>
 *
 * </per>
 */
@Slf4j
@Component
public class EsLogServiceImpl implements BaseLogService {

    ElasticSearchUtils elasticSearchUtils;

    public EsLogServiceImpl(ElasticSearchUtils elasticSearchUtils) {
        this.elasticSearchUtils = elasticSearchUtils;
    }

    @Override
    public void save(String logInfoJson) {
        log.info(Jackson.toJsonString(logInfoJson));
        try {
            saveSingle(logInfoJson);
        } catch (Exception e) {
            log.error("ES保存操作异常,【{}】",e.getMessage(),e);
        }
    }

    private void saveSingle(String logParamJson) throws Exception {
        EsSaveInfo esSaveInfo = MapperUtils.json2pojo(logParamJson, EsSaveInfo.class);
        elasticSearchUtils.addDoc(esSaveInfo.getIndex(), esSaveInfo.getEsSaveDetail());
    }

    @Override
    public void saveBatch(List<String> logInfoJsons) {
        log.info(Jackson.toJsonString(logInfoJsons));
        try {
            for (String logParamJson : logInfoJsons) {
                saveSingle(logParamJson);
            }
        } catch (Exception e) {
            log.error("ES批量保存操作异常,【{}】",e.getMessage(),e);
        }
    }
}

3.创建ConsoleLogAppender用于Log4j2输出配置

package com.sinosoft.springbootplus.log4j2;

import cn.hutool.json.JSONUtil;
import com.sinosoft.springbootplus.log4j2.service.BaseLogService;
import com.sinosoft.springbootplus.util.SpringContextUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.layout.PatternLayout;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

/**
 * @author 波哥
 * @Title:
 * @Description: 添加类的描述
 * @date 2020/2/22 18:05
 */
@Slf4j
@Plugin(name = "ConsoleLogAppender", category = "Core", elementType = "appender", printObject = true)
public class ConsoleLogAppender extends AbstractAppender {
    /**
     * 日志出现延迟时间大于1秒时开启批量入库
     */
    private static final int DELAY_TIME = 1000;
    /**
     * 出现延迟后每次批量的插入的数量
     */
    private static final int BATCH_SIZE = 20;
    /**
     * 日志服务用于保存日志
     */
    private BaseLogService baseLogService;
    private List<String> list = new ArrayList<>();

    private ConsoleLogAppender(String name, Filter filter, Layout<? extends Serializable> layout, boolean ignoreExceptions) {

        super(name, filter, layout, ignoreExceptions, Property.EMPTY_ARRAY);
    }

    @Override
    public void append(LogEvent logEvent) {
        if (baseLogService == null) {
            baseLogService = SpringContextUtil.getBean(BaseLogService.class);
        }
        int size = list.size();
        long  curr = System.currentTimeMillis();
        if ((curr - logEvent.getTimeMillis()) > DELAY_TIME && size < BATCH_SIZE) {
            list.add(logEvent.getMessage().getFormattedMessage());
        } else if (size == 0) {
            try {
                //baseLogService.save(JSONUtil.toBean(logEvent.getMessage().getFormattedMessage(), SysLog.class));
                baseLogService.save(logEvent.getMessage().getFormattedMessage());
            } catch (Exception e) {
                log.error("保存操作异常,【{}】,未保存日志【{}】",e.getMessage(),logEvent.getMessage().getFormattedMessage(),e);
            }
        } else {
            list.add(logEvent.getMessage().getFormattedMessage());
            try {
                baseLogService.saveBatch(new ArrayList<>(list));
            } catch (Exception e) {
                log.error("保存操作异常,【{}】,未保存日志【{}】",e.getMessage(),JSONUtil.toJsonStr(list),e);
            }
            list.clear();
        }

    }

    @PluginFactory
    public static ConsoleLogAppender createAppender(@PluginAttribute(value = "name", defaultString = "consoleLog") String name,
                                                    @PluginElement("Filter") final Filter filter,
                                                    @PluginElement("Layout") Layout<? extends Serializable> layout,
                                                    @PluginAttribute(value = "ignoreExceptions") boolean ignoreExceptions
    ) {
        if (layout == null) {
            layout = PatternLayout.createDefaultLayout();
        }
        return new ConsoleLogAppender(name, filter, layout, ignoreExceptions);

    }
}

四.编写AOP插入行为日志

1.编写日志类型枚举(老大之前写的,之前的日志是插入到数据库的.copy一下哈哈)

package com.sinosoft.springbootplus.common.enums;

/**
 * 枚举类型父接口
 *
 * @author 波哥
 */
public interface BaseEnum {

    /**
     * 获取枚举索引
     *
     * @return
     */
    Integer getCode();

    /**
     * 获取描述
     *
     * @return
     */
    String getDesc();

}
package com.sinosoft.springbootplus.log4j2.enums;

import com.sinosoft.springbootplus.common.enums.BaseEnum;

/**
 * 日志类型枚举
* @author 波哥
* @date 2020/2/23 11:40
*/

public enum LogTypeEnum implements BaseEnum {
    SEARCH(1, "查询"),
    UPDATE(2, "更新"),
    DELETE(3,"删除"),
    INSERT(4,"新增"),
    UPLOAD(5,"上传"),
    DOWNLOAD(6,"下载");

    private Integer code;
    private String desc;

    LogTypeEnum(Integer code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    @Override
    public Integer getCode() {
        return this.code;
    }

    @Override
    public String getDesc() {
        return this.desc;
    }
}

2.自定义Es日志注解

import com.sinosoft.springbootplus.log4j2.enums.LogTypeEnum;

import java.lang.annotation.*;

/**
 *
 * ES日志注解
* @author fan
*/

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLogEs {
    String value() default "";
    LogTypeEnum type() default LogTypeEnum.SEARCH;
    String index() default  "";
}

3.编写AOP切面具体业务

/*
 * Copyright 2019-2029 geekidea(https://github.com/geekidea)
 *
 * Licensed 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 com.sinosoft.springbootplus.log4j2.aop;

import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONObject;
import com.sinosoft.springbootplus.core.context.RequestContext;
import com.sinosoft.springbootplus.log4j2.entity.SysLog;
import com.sinosoft.springbootplus.log4j2.entity.SysLogEs;
import com.sinosoft.springbootplus.util.ClientInfoUtil;
import com.sinosoft.springbootplus.util.IpUtil;
import com.sinosoft.springbootplus.util.vo.ClientInfo;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 用于保存请求数据到Es
 *
 * @author fan
 */

@Aspect
@Component
@Slf4j
public class SysLogAopEs {
    private static final Logger logLogger = LoggerFactory.getLogger("sinosoft.consoleLog");

    /**
     * POST请求
     **/
    private static final String POST = "POST";

    @Pointcut("@annotation(com.sinosoft.springbootplus.log4j2.annotation.SysLogEs)")
    public void consoleLog() {
    }

    @Around("consoleLog()")
    public Object handle(ProceedingJoinPoint joinPoint) throws Throwable {
        SysLogEs sysLogEs = new SysLogEs();
        SysLog sysLog = new SysLog();
        sysLogEs.setEsSaveMap(sysLog);
        // 获取请求相关信息
        try {
            // 获取当前的HttpServletRequest对象
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (attributes == null) {
                throw new RuntimeException("获取ServletRequestAttributes为空");
            }
            HttpServletRequest request = attributes.getRequest();
            // 获取请求类名和方法名称
            Signature signature = joinPoint.getSignature();

            // 获取真实的方法对象
            MethodSignature methodSignature = (MethodSignature) signature;
            Method method = methodSignature.getMethod();
            //获取拦截额日志注解
            com.sinosoft.springbootplus.log4j2.annotation.SysLogEs sysLogAnnotation = method.getAnnotation(com.sinosoft.springbootplus.log4j2.annotation.SysLogEs.class);
            //获取注解中的信息,包括描述
            sysLog.setTitle(sysLogAnnotation.value());
            //获取操作类型
            sysLog.setType(sysLogAnnotation.type().getDesc());
            //获取索引
            sysLogEs.setIndex(sysLogAnnotation.index());
            // 请求全路径
            String url = request.getRequestURI();
            sysLog.setRequestUri(url);
            // IP地址
            String ip = IpUtil.getRequestIp();
            sysLog.setRemoteAddr(ip);
            ClientInfo clientInfo = ClientInfoUtil.get(request);
            sysLog.setBrowser(clientInfo.toString());
            //设置当前用户
            sysLog.setUsername(RequestContext.getLoginUserNickName());
            sysLog.setUserId(RequestContext.getLoginUserId());
        } catch (Exception e) {
            log.error("获取操作日志信息过程异常,【{}】", e.getMessage(), e);
        }
        long b = System.currentTimeMillis();
        // 执行目标方法,获得返回值
        try {
            Object result = joinPoint.proceed();
            long e = System.currentTimeMillis();
            sysLog.setUseTime(e - b);
            logLogger.info(JSONUtil.toJsonStr(sysLogEs));
            return result;
        } catch (Exception ep) {
            long e = System.currentTimeMillis();
            sysLog.setUseTime(e - b);
            sysLog.setException(ep.getMessage());
            logLogger.info(JSONUtil.toJsonStr(sysLogEs));
            throw ep;
        }
    }

    /**
     * 获取请求参数JSON字符串
     */
    private Object getRequestParamJsonString(ProceedingJoinPoint joinPoint, HttpServletRequest request, String requestMethod, boolean isRequestBody) {
        Object paramObject;
        if (POST.equals(requestMethod) && isRequestBody) {
            Object[] args = joinPoint.getArgs();
            paramObject = argsArrayToJsonString(args);
        } else {
            Map<String, String[]> paramsMap = request.getParameterMap();
            paramObject = getJsonForParamMap(paramsMap);
        }
        return paramObject;
    }

    /**
     * 判断控制器方法参数中是否有RequestBody注解
     */
    private boolean isRequestBody(Annotation[][] annotations) {
        boolean isRequestBody = false;
        for (Annotation[] annotationArray : annotations) {
            for (Annotation annotation : annotationArray) {
                if (annotation instanceof RequestBody) {
                    isRequestBody = true;
                    break;
                }
            }
        }
        return isRequestBody;
    }

    /**
     * 请求参数拼装
     */
    private Object argsArrayToJsonString(Object[] args) {
        if (args == null) {
            return null;
        }
        // 去掉HttpServletRequest和HttpServletResponse
        List<Object> realArgs = new ArrayList<>();
        for (Object arg : args) {
            if (arg instanceof HttpServletRequest) {
                continue;
            }
            if (arg instanceof HttpServletResponse) {
                continue;
            }
            if (arg instanceof MultipartFile) {
                continue;
            }
            if (arg instanceof ModelAndView) {
                continue;
            }
            realArgs.add(arg);
        }
        if (realArgs.size() == 1) {
            return realArgs.get(0);
        } else {
            return realArgs;
        }
    }


    /**
     * 获取参数Map的JSON字符串
     */
    private JSONObject getJsonForParamMap(Map<String, String[]> paramsMap) {
        if (paramsMap == null || paramsMap.size() == 0) {
            return null;
        }
        JSONObject jsonObject = new JSONObject();
        for (Map.Entry<String, String[]> kv : paramsMap.entrySet()) {
            String key = kv.getKey();
            String[] values = kv.getValue();
            // 没有值
            if (values == null) {
                jsonObject.put(key, null);
                // 一个值
            } else if (values.length == 1) {
                jsonObject.put(key, values[0]);
                // 多个值
            } else {
                jsonObject.put(key, values);
            }
        }
        return jsonObject;
    }

}

五.log4j2.xml配置文件增加ConsoleLogAppender

<Appenders>
    <ConsoleLogAppender >
            <PatternLayout pattern="%msg"  charset="${CHARSET}"/>
    </ConsoleLogAppender>
</Appenders>
<Loggers>
    <AsyncLogger name="sinosoft.consoleLog" additivity="false">
            <AppenderRef ref="consoleLog" />
    </AsyncLogger>
</Loggers>

六.使用

package com.sinosoft.springbootplus;

import com.sinosoft.springbootplus.log4j2.annotation.SysLogEs;
import com.sinosoft.springbootplus.log4j2.enums.LogTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Auther: fan
 * @Date 2021/5/20 16:11 <pre>
 *
 * </per>
 */
@Slf4j
@RestController
@RequestMapping("/www")
public class Controller {
    @GetMapping("/bbb")
    @SysLogEs(value = "testDDD",type= LogTypeEnum.DELETE,index = "user_action")
    public void addJwBjAllBaseinfo() {
        System.out.println("====");
    }
}

七.创建Es索引和字段

PUT user_action
PUT user_action/_mapping
{
    "properties": {
        "id": {
            "type": "keyword"
        },
        "type": {
            "type": "keyword"
        },
        "title": {
            "type": "keyword"
        },
        "remoteAddr": {
            "type": "ip"
        },
        "userId": {
            "type": "keyword"
        },
        "username": {
            "type": "text",
            "fielddata": true
        },
        "requestUri": {
            "type": "text"
        },
        "useTime": {
            "type": "integer"
        },
        "browser": {
            "type": "text"
        },
        "exception": {
            "type": "text"
        },
        "requestTime": {
            "type": "date"
        }
    }
}

八.创建kibana索引

九.在kibana中创建仪表盘等(前提是ES有了一定量数据哈)

仿照kibana系统数据创建自己的仪表盘等

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值