使用redis作为消息中间件异步保存日志
自定义日志拦截器,在拦截器中发送日志信息
@Component
public class LogInterceptor implements HandlerInterceptor {
/**
* 加锁标志
*/
public static final String LOCKED = "TRUE";
@Resource
private StringRedisTemplate stringRedisTemplate;
private static ThreadLocal<Long> START = new ThreadLocal<Long>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
/**
* 得到进接口时间
*/
START.set(System.currentTimeMillis());
return true;
}
/**
* 使用redis进行日志操作
*
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
if (!(handler instanceof HandlerMethod)) {
return;
}
HandlerMethod method = (HandlerMethod) handler;
LogInfo logInfo = method.getMethod().getDeclaredAnnotation(LogInfo.class);
if (null == logInfo) {
return;
}
LogType lt = logInfo.targetType();
String action = logInfo.value();
//得到运行接口花费的时间
Long spendTime = System.currentTimeMillis() - START.get();
START.remove();
Log log = new Log();
log.setException(null == ex ? null : ex.getClass().toString());
log.setHandler(method.getMethod().getDeclaringClass().getName() + " " + method.getMethod().getName());
log.setMethod(request.getMethod().toUpperCase());
//得到接口入参
log.setParams(getRequestParams(request));
log.setRemoteAddr(HttpUtil.getIPAddress(request));
log.setRequestUri(request.getRequestURI());
log.setTitle(action);
log.setType(lt.name());
log.setUserAgent(request.getHeader("User-Agent"));
log.setSpendTime(spendTime.intValue());
log.setUserId(Session.getCurrentUserIdIfNullReturnN1());
String jsonLog = JSON.toJSONString(log);
//redis发布日志信息
stringRedisTemplate.convertAndSend("operationLog", jsonLog);
//设置日志的过期时间 时间单位是秒
//stringRedisTemplate.expire("operationLog", 1000, TimeUnit.MILLISECONDS);
stringRedisTemplate.opsForValue().setIfAbsent("operationLog", LOCKED);
}
/**
* 得到入参
*
* @param request
* @return
*/
private static String getRequestParams(HttpServletRequest request) {
Map<String, String[]> ParameterMap = request.getParameterMap();
Map<String, String> reqMap = new HashMap<>();
Set<Map.Entry<String, String[]>> entry = ParameterMap.entrySet();
Iterator<Map.Entry<String, String[]>> it = entry.iterator();
while (it.hasNext()) {
Map.Entry<String, String[]> me = it.next();
String key = me.getKey();
String value = me.getValue()[0];
reqMap.put(key, value);
}
return JSONObject.toJSONString(reqMap);
}
自定义日志注解
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface LogInfo {
@AliasFor("action")
String value() default "";
@AliasFor("value")
String action() default "";
LogType targetType() default LogType.A;
}
自定义日志注解使用
@PostMapping
@ApiOperation(value = "更新附件", notes = "更新附件")
@LogInfo("更新附件")
public Result updateAttach(@ModelAttribute Attach attach) {
attachService.updateById(attach);
return Result.success();
}
订阅保存日志信息
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory, LogService logService) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.addMessageListener(new MessageListenerAdapter() {
@Override
public void onMessage(Message message, byte[] pattern) {
logger.error("收到:" + new String(new String(message.getBody())));
//得到redis订阅的信息并保存到数据库
JSONObject logJson = JSONObject.parseObject(new String(message.getBody()));
Log logObj = logJson.toJavaObject(Log.class);
logService.save(logObj);
}
}, new PatternTopic("operationLog"));
return container;
}
log实体类
@lombok
public class Log extends BaseEntity {
private static final long serialVersionUID = 211107161244665895L;
// 日志类型(1:接入日志;2:错误日志)
private String type;
// 日志标题
private String title;
// 操作用户的IP地址
private String remoteAddr;
// 操作的URI
private String requestUri;
// 操作的方式
private String method;
// 操作提交的数据
private String params;
// 操作用户代理信息
private String userAgent;
// 异常信息
private String exception;
// 备注
private String remarks;
// 后台处理时间
private int spendTime;
// 处理器
private String handler;
// 用户ID
private String userId;
}
总结:
1、在订阅得到消息后会开始一个新的线程,不用自己写线程
2、该operationLog并不是指在redis中的key,而是一个管道,消息通过管道发送,订阅者得到管道的消息,管道里可以有很多这种消息。