springboot|springboot 实现记录业务日志和异常业务日志的操作
日志记录到redis展现形式
文章图片
文章图片
文章图片
1.基于注解的方式实现日志记录,扫描对应的方法实现日志记录
@Inherited@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD})public @interface BussinessLog { /*** 业务的名称,例如:"修改菜单"*/String value() default ""; /*** 被修改的实体的唯一标识,例如:菜单实体的唯一标识为"id"*/String key() default "id"; /*** 业务类型*/String type() default "0"; /*** 字典(用于查找key的中文名称和字段的中文名称)*/Class extends AbstractDictMap> dict() default SystemDict.class; }
2.扫描的方法,基于注解实现方法扫描并且记录日志
文章图片
3.基于@Aspect注解,实现日志扫描,并且记录日志
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 java.lang.reflect.Method; import java.util.Map; /** * 日志记录 * */@Aspect@Componentpublic class LogAop { private Logger log = LoggerFactory.getLogger(this.getClass()); @Pointcut(value = "https://www.it610.com/article/@annotation(com.stylefeng.guns.core.common.annotion.BussinessLog)")public void cutService() {} @Around("cutService()")public Object recordSysLog(ProceedingJoinPoint point) throws Throwable { //先执行业务Object result = point.proceed(); try {handle(point); } catch (Exception e) {log.error("日志记录出错!", e); } return result; } private void handle(ProceedingJoinPoint point) throws Exception { //获取拦截的方法名Signature sig = point.getSignature(); MethodSignature msig = null; if (!(sig instanceof MethodSignature)) {throw new IllegalArgumentException("该注解只能用于方法"); }msig = (MethodSignature) sig; Object target = point.getTarget(); Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes()); String methodName = currentMethod.getName(); //如果当前用户未登录,不做日志ShiroUser user = ShiroKit.getUser(); if (null == user) {return; } //获取拦截方法的参数String className = point.getTarget().getClass().getName(); Object[] params = point.getArgs(); //获取操作名称BussinessLog annotation = currentMethod.getAnnotation(BussinessLog.class); String bussinessName = annotation.value(); String key = annotation.key(); Class dictClass = annotation.dict(); StringBuilder sb = new StringBuilder(); for (Object param : params) {sb.append(param); sb.append(" & "); } //如果涉及到修改,比对变化String msg; if (bussinessName.contains("修改") || bussinessName.contains("编辑")) {Object obj1 = LogObjectHolder.me().get(); Map obj2 = HttpContext.getRequestParameters(); msg = Contrast.contrastObj(dictClass, key, obj1, obj2); } else {Map parameters = HttpContext.getRequestParameters(); AbstractDictMap dictMap = (AbstractDictMap) dictClass.newInstance(); msg = Contrast.parseMutiKey(dictMap, key, parameters); }log.info("[记录日志][RESULT:{}]",user.getId()+bussinessName+className+methodName+msg.toString()); LogManager.me().executeLog(LogTaskFactory.bussinessLog(user.getId(), bussinessName, className, methodName, msg)); }}
4.比较两个对象的工具类
import java.beans.PropertyDescriptor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Date; import java.util.Map; /** * 对比两个对象的变化的工具类 * * @author ... * @Date 2017/3/31 10:36 */public class Contrast { //记录每个修改字段的分隔符public static final String separator = "; ; ; "; /*** 比较两个对象,并返回不一致的信息** @author ...* @Date 2017/5/9 19:34*/public static String contrastObj(Object pojo1, Object pojo2) {String str = ""; try {Class clazz = pojo1.getClass(); Field[] fields = pojo1.getClass().getDeclaredFields(); int i = 1; for (Field field : fields) {if ("serialVersionUID".equals(field.getName())) {continue; }PropertyDescriptor pd = new PropertyDescriptor(field.getName(), clazz); Method getMethod = pd.getReadMethod(); Object o1 = getMethod.invoke(pojo1); Object o2 = getMethod.invoke(pojo2); if (o1 == null || o2 == null) {continue; }if (o1 instanceof Date) {o1 = DateUtil.getDay((Date) o1); }if (!o1.toString().equals(o2.toString())) {if (i != 1) {str += separator; }str += "字段名称" + field.getName() + ",旧值:" + o1 + ",新值:" + o2; i++; }}} catch (Exception e) {e.printStackTrace(); }return str; } /*** 比较两个对象pojo1和pojo2,并输出不一致信息** @author ...* @Date 2017/5/9 19:34*/public static String contrastObj(Class dictClass, String key, Object pojo1, Map pojo2) throws IllegalAccessException, InstantiationException {AbstractDictMap dictMap = (AbstractDictMap) dictClass.newInstance(); String str = parseMutiKey(dictMap, key, pojo2) + separator; try {Class clazz = pojo1.getClass(); Field[] fields = pojo1.getClass().getDeclaredFields(); int i = 1; for (Field field : fields) {if ("serialVersionUID".equals(field.getName())) {continue; }PropertyDescriptor pd = new PropertyDescriptor(field.getName(), clazz); Method getMethod = pd.getReadMethod(); Object o1 = getMethod.invoke(pojo1); Object o2 = pojo2.get(StrKit.firstCharToLowerCase(getMethod.getName().substring(3))); if (o1 == null || o2 == null) {continue; }if (o1 instanceof Date) {o1 = DateUtil.getDay((Date) o1); } else if (o1 instanceof Integer) {o2 = Integer.parseInt(o2.toString()); }if (!o1.toString().equals(o2.toString())) {if (i != 1) {str += separator; }String fieldName = dictMap.get(field.getName()); String fieldWarpperMethodName = dictMap.getFieldWarpperMethodName(field.getName()); if (fieldWarpperMethodName != null) {Object o1Warpper = DictFieldWarpperFactory.createFieldWarpper(o1, fieldWarpperMethodName); Object o2Warpper = DictFieldWarpperFactory.createFieldWarpper(o2, fieldWarpperMethodName); str += "字段名称:" + fieldName + ",旧值:" + o1Warpper + ",新值:" + o2Warpper; } else {str += "字段名称:" + fieldName + ",旧值:" + o1 + ",新值:" + o2; }i++; }}} catch (Exception e) {e.printStackTrace(); }return str; } /*** 比较两个对象pojo1和pojo2,并输出不一致信息** @author ...* @Date 2017/5/9 19:34*/public static String contrastObjByName(Class dictClass, String key, Object pojo1, Map pojo2) throws IllegalAccessException, InstantiationException {AbstractDictMap dictMap = (AbstractDictMap) dictClass.newInstance(); String str = parseMutiKey(dictMap, key, pojo2) + separator; try {Class clazz = pojo1.getClass(); Field[] fields = pojo1.getClass().getDeclaredFields(); int i = 1; for (Field field : fields) {if ("serialVersionUID".equals(field.getName())) {continue; }String prefix = "get"; int prefixLength = 3; if (field.getType().getName().equals("java.lang.Boolean")) {prefix = "is"; prefixLength = 2; }Method getMethod = null; try {getMethod = clazz.getDeclaredMethod(prefix + StrKit.firstCharToUpperCase(field.getName())); } catch (NoSuchMethodException e) {System.err.println("this className:" + clazz.getName() + " is not methodName: " + e.getMessage()); continue; }Object o1 = getMethod.invoke(pojo1); Object o2 = pojo2.get(StrKit.firstCharToLowerCase(getMethod.getName().substring(prefixLength))); if (o1 == null || o2 == null) {continue; }if (o1 instanceof Date) {o1 = DateUtil.getDay((Date) o1); } else if (o1 instanceof Integer) {o2 = Integer.parseInt(o2.toString()); }if (!o1.toString().equals(o2.toString())) {if (i != 1) {str += separator; }String fieldName = dictMap.get(field.getName()); String fieldWarpperMethodName = dictMap.getFieldWarpperMethodName(field.getName()); if (fieldWarpperMethodName != null) {Object o1Warpper = DictFieldWarpperFactory.createFieldWarpper(o1, fieldWarpperMethodName); Object o2Warpper = DictFieldWarpperFactory.createFieldWarpper(o2, fieldWarpperMethodName); str += "字段名称:" + fieldName + ",旧值:" + o1Warpper + ",新值:" + o2Warpper; } else {str += "字段名称:" + fieldName + ",旧值:" + o1 + ",新值:" + o2; }i++; }}} catch (Exception e) {e.printStackTrace(); }return str; } /*** 解析多个key(逗号隔开的)** @author ...* @Date 2017/5/16 22:19*/public static String parseMutiKey(AbstractDictMap dictMap, String key, Map requests) {StringBuilder sb = new StringBuilder(); if (key.indexOf(",") != -1) {String[] keys = key.split(","); for (String item : keys) {String fieldWarpperMethodName = dictMap.getFieldWarpperMethodName(item); String value = https://www.it610.com/article/requests.get(item); if (fieldWarpperMethodName != null) {Object valueWarpper = DictFieldWarpperFactory.createFieldWarpper(value, fieldWarpperMethodName); sb.append(dictMap.get(item) +"=" + valueWarpper + ","); } else {sb.append(dictMap.get(item) + "=" + value + ","); }}return StrKit.removeSuffix(sb.toString(), ","); } else {String fieldWarpperMethodName = dictMap.getFieldWarpperMethodName(key); String value = https://www.it610.com/article/requests.get(key); if (fieldWarpperMethodName != null) {Object valueWarpper = DictFieldWarpperFactory.createFieldWarpper(value, fieldWarpperMethodName); sb.append(dictMap.get(key) +"=" + valueWarpper); } else {sb.append(dictMap.get(key) + "=" + value); }return sb.toString(); }} }
5.根据输入方法获取数据字典的数据
import java.lang.reflect.Method; public class DictFieldWarpperFactory {public static Object createFieldWarpper(Object parameter, String methodName) {IConstantFactory constantFactory = ConstantFactory.me(); try {Method method = IConstantFactory.class.getMethod(methodName, parameter.getClass()); return method.invoke(constantFactory, parameter); } catch (Exception e) {try {Method method = IConstantFactory.class.getMethod(methodName, Integer.class); return method.invoke(constantFactory, Integer.parseInt(parameter.toString())); } catch (Exception e1) {throw new RuntimeException("BizExceptionEnum.ERROR_WRAPPER_FIELD"); }}} }
6.对应获取数据字典的方法
public interface IConstantFactory { /*** 获取状态*/String getWordStatus(Integer DATA_STATUS); }
import com.qihoinfo.dev.log.util.SpringContextHolder; import org.anyline.service.AnylineService; import org.springframework.context.annotation.DependsOn; import org.springframework.stereotype.Component; @Component@DependsOn("springContextHolder")public class ConstantFactory implements IConstantFactory { private AnylineService anylineService = SpringContextHolder.getBean(AnylineService.class); public static IConstantFactory me() {return SpringContextHolder.getBean("constantFactory"); } @Overridepublic String getWordStatus(Integer DATA_STATUS) {if ("1".equals(DATA_STATUS.toString())) {return "启用"; } else {return "停用"; }} }
7.spring根据方法名获取对应容器中的对象
import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; /** * Spring的ApplicationContext的持有者,可以用静态方法的方式获取spring容器中的bean */@Componentpublic class SpringContextHolder implements ApplicationContextAware {private static ApplicationContext applicationContext; @Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {SpringContextHolder.applicationContext = applicationContext; } public static ApplicationContext getApplicationContext() {assertApplicationContext(); return applicationContext; } @SuppressWarnings("unchecked")public staticT getBean(String beanName) {assertApplicationContext(); return (T) applicationContext.getBean(beanName); } public static T getBean(Class requiredType) {assertApplicationContext(); return applicationContext.getBean(requiredType); } private static void assertApplicationContext() {if (SpringContextHolder.applicationContext == null) {throw new RuntimeException("applicaitonContext属性为null,请检查是否注入了SpringContextHolder!"); }} }
8.字符串工具类
/** * 字符串工具类 */public class StrKit {/*** 首字母变小写*/public static String firstCharToLowerCase(String str) {char firstChar = str.charAt(0); if (firstChar >= 'A' && firstChar <= 'Z') {char[] arr = str.toCharArray(); arr[0] += ('a' - 'A'); return new String(arr); }return str; }/*** 首字母变大写*/public static String firstCharToUpperCase(String str) {char firstChar = str.charAt(0); if (firstChar >= 'a' && firstChar <= 'z') {char[] arr = str.toCharArray(); arr[0] -= ('a' - 'A'); return new String(arr); }return str; }/*** 去掉指定后缀*/public static String removeSuffix(String str, String suffix) {if (isEmpty(str) || isEmpty(suffix)) {return str; }if (str.endsWith(suffix)) {return str.substring(0, str.length() - suffix.length()); }return str; }/*** 字符串是否为空,空的定义如下 1、为null
* 2、为""
*/public static boolean isEmpty(String str) {return str == null || str.length() == 0; }}
import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; public class ToolUtil {public static final int SALT_LENGTH = 6; public ToolUtil() {} public static String getExceptionMsg(Throwable e) {StringWriter sw = new StringWriter(); try {e.printStackTrace(new PrintWriter(sw)); } finally {try {sw.close(); } catch (IOException var8) {var8.printStackTrace(); } } return sw.getBuffer().toString().replaceAll("\\$", "T"); }}
9.获取数据字典的类
import java.util.HashMap; public abstract class AbstractDictMap { protected HashMap dictory = new HashMap<>(); protected HashMap fieldWarpperDictory = new HashMap<>(); public AbstractDictMap() {put("ID", "主键ID"); init(); initBeWrapped(); } public abstract void init(); protected abstract void initBeWrapped(); public String get(String key) {return this.dictory.get(key); } public void put(String key, String value) {this.dictory.put(key, value); } public String getFieldWarpperMethodName(String key) {return this.fieldWarpperDictory.get(key); } public void putFieldWrapperMethodName(String key, String methodName) {this.fieldWarpperDictory.put(key, methodName); }}
public class SystemDict extends AbstractDictMap { @Overridepublic void init() {} @Overrideprotected void initBeWrapped() { }}
public class WordMap extends AbstractDictMap { @Overridepublic void init() {put("EN", "英文"); put("CN", "中文"); put("SHORT", "简称"); put("REMARK", "备注"); put("DATA_STATUS", "状态"); } @Overrideprotected void initBeWrapped() {putFieldWrapperMethodName("DATA_STATUS","getWordStatus"); } }
10.获取缓存对象的bean
import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import org.springframework.web.context.WebApplicationContext; import java.io.Serializable; @Component@Scope(scopeName = WebApplicationContext.SCOPE_SESSION)public class LogObjectHolder implements Serializable{ private Object object = null; public void set(Object obj) {this.object = obj; } public Object get() {return object; } public static LogObjectHolder me(){LogObjectHolder bean = SpringContextHolder.getBean(LogObjectHolder.class); return bean; } }
11.运行时异常的获取
@ControllerAdvicepublic class GlobalExceptionHandler extends BasicMemberJSONController { private Logger log = LoggerFactory.getLogger(this.getClass()); @ExceptionHandler(RuntimeException.class)@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)@ResponseBodypublic String notFount(RuntimeException e) {String userName = curManage().get("USERNAME").toString(); LogManager.me().executeLog(LogTaskFactory.exceptionLog(userName, e)); log.error("运行时异常:", e); return fail(); }}
12.使用线程池创建操作日志
import java.util.Date; public class LogFactory {/*** 创建操作日志*/public static DataRow createOperationLog(LogType logType, String userName, String bussinessName, String clazzName, String methodName, String msg, LogSucceed succeed) {DataRow operationLog = new DataRow(); operationLog.put("log_type", logType.getMessage()); operationLog.put("USER_NAME", userName); operationLog.put("log_name", bussinessName); operationLog.put("CLASS_NAME", clazzName); operationLog.put("METHOD", methodName); operationLog.put("CREATE_TIME", new Date()); operationLog.put("SUCCEED", succeed.getMessage()); if (msg.length() > 800) {msg = msg.substring(0, 800); operationLog.put("MESSAGE", msg); } else {operationLog.put("MESSAGE", msg); }return operationLog; }}
import java.util.TimerTask; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class LogManager { //日志记录操作延时private final int OPERATE_DELAY_TIME = 10; //异步操作记录日志的线程池private ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(10); private LogManager() {} public static LogManager logManager = new LogManager(); public static LogManager me() {return logManager; } public void executeLog(TimerTask task) {executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS); } }
public enum LogSucceed { SUCCESS("成功"),FAIL("失败"); String message; LogSucceed(String message) {this.message = message; } public String getMessage() {return message; } public void setMessage(String message) {this.message = message; } }
import com.qihoinfo.dev.log.annotation.RedisDb; import com.qihoinfo.dev.log.util.ToolUtil; import org.anyline.entity.DataRow; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.DependsOn; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import java.util.TimerTask; @Component@DependsOn("springContextHolder")public class LogTaskFactory { private static Logger logger = LoggerFactory.getLogger(LogManager.class); private static StringRedisTemplate redisTemplate = RedisDb.getMapper(StringRedisTemplate.class); public static TimerTask bussinessLog(final String userName, final String bussinessName, final String clazzName, final String methodName, final String msg) {return new TimerTask() {@Overridepublic void run() {DataRow operationLog = LogFactory.createOperationLog(LogType.BUSSINESS, userName, bussinessName, clazzName, methodName, msg, LogSucceed.SUCCESS); try { redisTemplate.opsForList().rightPush("sys_operation_log", operationLog.getJson()); } catch (Exception e) {logger.error("创建业务日志异常!", e); }}}; } public static TimerTask exceptionLog(final String userName, final Exception exception) {return new TimerTask() {@Overridepublic void run() {String msg = ToolUtil.getExceptionMsg(exception); DataRow operationLog = LogFactory.createOperationLog(LogType.EXCEPTION, userName, "", null, null, msg, LogSucceed.FAIL); try {redisTemplate.opsForList().rightPush("sys_operation_log", operationLog.getJson()); } catch (Exception e) {logger.error("创建异常日志异常!", e); }}}; } }
public enum LogType { EXCEPTION("异常日志"),BUSSINESS("业务日志"); String message; LogType(String message) {this.message = message; } public String getMessage() {return message; } public void setMessage(String message) {this.message = message; } }
13.将日志记录到redis数据库
package com.qihoinfo.dev.log.annotation; import com.qihoinfo.dev.log.util.SpringContextHolder; import org.springframework.data.redis.core.StringRedisTemplate; public class RedisDb{private Class clazz; private StringRedisTemplate baseMapper; private RedisDb(Class clazz) {this.clazz = clazz; this.baseMapper = (StringRedisTemplate) SpringContextHolder.getBean(clazz); } public static RedisDb create(Class clazz) {return new RedisDb (clazz); } public StringRedisTemplate getMapper() {return this.baseMapper; } public static T getMapper(Class clazz) {return SpringContextHolder.getBean(clazz); }}
import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; /** * @Description: * @Auther: wj * @Date: 2019/5/28 13:56 */public class HttpContext {public HttpContext() {} public static String getIp() {HttpServletRequest request = getRequest(); return request == null ? "127.0.0.1" : request.getRemoteHost(); } public static HttpServletRequest getRequest() {ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); return requestAttributes == null ? null : requestAttributes.getRequest(); } public static HttpServletResponse getResponse() {ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); return requestAttributes == null ? null : requestAttributes.getResponse(); } public static Map getRequestParameters() {HashMap values = new HashMap(); HttpServletRequest request = getRequest(); if (request == null) {return values; } else {Enumeration enums = request.getParameterNames(); while (enums.hasMoreElements()) {String paramName = (String) enums.nextElement(); String paramValue = https://www.it610.com/article/request.getParameter(paramName); values.put(paramName, paramValue); } return values; }}}
【springboot|springboot 实现记录业务日志和异常业务日志的操作】以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
推荐阅读
- 20170612时间和注意力开销记录
- Activiti(一)SpringBoot2集成Activiti6
- 关于QueryWrapper|关于QueryWrapper,实现MybatisPlus多表关联查询方式
- MybatisPlus使用queryWrapper如何实现复杂查询
- python学习之|python学习之 实现QQ自动发送消息
- 孩子不是实现父母欲望的工具——林哈夫
- SpringBoot调用公共模块的自定义注解失效的解决
- opencv|opencv C++模板匹配的简单实现
- Node.js中readline模块实现终端输入
- 解决SpringBoot引用别的模块无法注入的问题