目录
2.实现行为日志接口(之前的项目是插入数据库的这个实现用来插入ES)
3.创建ConsoleLogAppender用于Log4j2输出配置
1.编写日志类型枚举(老大之前写的,之前的日志是插入到数据库的.copy一下哈哈)
五.log4j2.xml配置文件增加ConsoleLogAppender
九.在kibana中创建仪表盘等(前提是ES有了一定量数据哈)
一.搭建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"
}
}
}