项目地址:https://gitee.com/Selegant/logs-demo.git

说明

系统日志不论是在日常的管理还是维护中都会起到很大的作用,但是在日志的记录中通常会存在很多的问题

  1. 日志记录的不规范性
  2. 日志记录的重复性
  3. 日志记录的难分类

目前日志主要记录的有三方面

  1. 请求的入参,出参
  2. 关于业务上的操作
  3. 异常日常日志的打印

解决方案

1.记录请求的出参入参

记录出参入参这是日志记录最好操作的一部分,而这里会存在一定的重复性,因为每个请求都需要记录,这是重复操作,完全可以使用Spring AOP进行入参和出参的记录

import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;/*** Web层日志切面*/
@Aspect    //这里使用@Aspect注解方式设置AOP
@Order(5)  //值越小,越先加载
@Component
public class WebLogAspect {private Logger logger = Logger.getLogger(getClass());ThreadLocal<Long> startTime = new ThreadLocal<>();//这里@Pointcut设置切点可以设置为Controller层的地址@Pointcut("execution(public * com.training..*.*Controller(..))")public void webLog(){}//@Before指在切点方法之前执行,也就是在Controller层方法执行之前执行,这里可以通过JoinPoint获取一些有关方法的信息,在这里也可以修改参数的值//@Before()括号里设置的是切点方法的名称@Before("webLog()")public void doBefore(JoinPoint joinPoint) throws Throwable {startTime.set(System.currentTimeMillis());// 接收到请求,记录请求内容ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();// 记录下请求内容logger.info("URL : " + request.getRequestURL().toString());logger.info("HTTP_METHOD : " + request.getMethod());logger.info("IP : " + request.getRemoteAddr());logger.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());logger.info("ARGS : " + Arrays.toString(joinPoint.getArgs()));}@AfterReturning(returning = "ret", pointcut = "webLog()")public void doAfterReturning(Object ret) throws Throwable {// 处理完请求,返回内容logger.info("RESPONSE : " + ret);logger.info("SPEND TIME : " + (System.currentTimeMillis() - startTime.get()));}}

2.记录操作日志

在系统中可能会有很多的增删改查或是会涉及到一些业务操作,这时候我们需要记录一下操作的入口,在此还可以记录下操作的类型,或是业务的名称.不过在就操作日志前需要构建日志的基础部件

日志对象

import java.util.Date;/*** 操作日志*/
public class OperationLog extends Base {private static final long serialVersionUID = 1L;/*** 日志类型*/private String  logtype;/*** 日志名称*/private String  logname;/*** 用户id*/private Integer userid;/*** 类名称*/private String  classname;/*** 方法名称*/private String  method;/*** 创建时间*/private Date    createtime;/*** 是否成功*/private String  succeed;/*** 备注*/private String  message;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getLogtype() {return logtype;}public void setLogtype(String logtype) {this.logtype = logtype;}public String getLogname() {return logname;}public void setLogname(String logname) {this.logname = logname;}public Integer getUserid() {return userid;}public void setUserid(Integer userid) {this.userid = userid;}public String getClassname() {return classname;}public void setClassname(String classname) {this.classname = classname;}public String getMethod() {return method;}public void setMethod(String method) {this.method = method;}public Date getCreatetime() {return createtime;}public void setCreatetime(Date createtime) {this.createtime = createtime;}public String getSucceed() {return succeed;}public void setSucceed(String succeed) {this.succeed = succeed;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}
}

日志对象创建工厂

import com.stylefeng.guns.common.constant.state.LogSucceed;
import com.stylefeng.guns.common.constant.state.LogType;
import com.stylefeng.guns.common.persistence.model.LoginLog;
import com.stylefeng.guns.common.persistence.model.OperationLog;import java.util.Date;/*** 日志对象创建工厂*/
public class LogFactory {/*** 创建操作日志*/public static OperationLog createOperationLog(LogType logType, Integer userId, String bussinessName, String clazzName, String methodName, String msg, LogSucceed succeed) {OperationLog operationLog = new OperationLog();operationLog.setLogtype(logType.getMessage());operationLog.setLogname(bussinessName);operationLog.setUserid(userId);operationLog.setClassname(clazzName);operationLog.setMethod(methodName);operationLog.setCreatetime(new Date());operationLog.setSucceed(succeed.getMessage());operationLog.setMessage(msg);return operationLog;}/*** 创建登录日志*/public static LoginLog createLoginLog(LogType logType, Integer userId, String msg,String ip) {LoginLog loginLog = new LoginLog();loginLog.setLogname(logType.getMessage());loginLog.setUserid(userId);loginLog.setCreatetime(new Date());loginLog.setSucceed(LogSucceed.SUCCESS.getMessage());loginLog.setIp(ip);loginLog.setMessage(msg);return loginLog;}
}

日志任务创建工厂

日志任务创建工厂的作用是将日志记录存储到数据库中

import com.stylefeng.guns.common.constant.state.LogSucceed;
import com.stylefeng.guns.common.constant.state.LogType;
import com.stylefeng.guns.common.persistence.dao.LoginLogMapper;
import com.stylefeng.guns.common.persistence.dao.OperationLogMapper;
import com.stylefeng.guns.common.persistence.model.LoginLog;
import com.stylefeng.guns.common.persistence.model.OperationLog;
import com.stylefeng.guns.core.log.LogManager;
import com.stylefeng.guns.core.util.SpringContextHolder;
import com.stylefeng.guns.core.util.ToolUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.util.TimerTask;/*** 日志操作任务创建工厂*/
public class LogTaskFactory {private static Logger logger  = LoggerFactory.getLogger(LogManager.class);//LoginLogMapper记录登录登出日志private static LoginLogMapper loginLogMapper = SpringContextHolder.getBean(LoginLogMapper.class);//OperationLogMapper记录操作日志        private static OperationLogMapper operationLogMapper = SpringContextHolder.getBean(OperationLogMapper.class);public static TimerTask loginLog(final Integer userId, final String ip) {return new TimerTask() {@Overridepublic void run() {try {LoginLog loginLog = LogFactory.createLoginLog(LogType.LOGIN, userId, null, ip);loginLogMapper.insert(loginLog);} catch (Exception e) {logger.error("创建登录日志异常!", e);}}};}public static TimerTask loginLog(final String username, final String msg, final String ip) {return new TimerTask() {@Overridepublic void run() {LoginLog loginLog = LogFactory.createLoginLog(LogType.LOGIN_FAIL, null, "账号:" + username + "," + msg, ip);try {loginLogMapper.insert(loginLog);} catch (Exception e) {logger.error("创建登录失败异常!", e);}}};}public static TimerTask exitLog(final Integer userId, final String ip) {return new TimerTask() {@Overridepublic void run() {LoginLog loginLog = LogFactory.createLoginLog(LogType.EXIT, userId, null,ip);try {loginLogMapper.insert(loginLog);} catch (Exception e) {logger.error("创建退出日志异常!", e);}}};}public static TimerTask bussinessLog(final Integer userId, final String bussinessName, final String clazzName, final String methodName, final String msg) {return new TimerTask() {@Overridepublic void run() {OperationLog operationLog = LogFactory.createOperationLog(LogType.BUSSINESS, userId, bussinessName, clazzName, methodName, msg, LogSucceed.SUCCESS);try {operationLogMapper.insert(operationLog);} catch (Exception e) {logger.error("创建业务日志异常!", e);}}};}public static TimerTask exceptionLog(final Integer userId, final Exception exception) {return new TimerTask() {@Overridepublic void run() {String msg = ToolUtil.getExceptionMsg(exception);OperationLog operationLog = LogFactory.createOperationLog(LogType.EXCEPTION, userId, "", null, null, msg, LogSucceed.FAIL);try {operationLogMapper.insert(operationLog);} catch (Exception e) {logger.error("创建异常日志异常!", e);}}};}
}

记录操作日志

这一步是最关键的一环

原理:通过自定义的注解@BussinessLog(可以任意命名),里面定义了业务的名称,被修改的实体的唯一标识,字典(用于查找key的中文名称和字段的中文名称),然后通过AOP,拦截所有添加了@BussinessLog注解的方法,解析其注解里面的属性,然后记录到对应的操作日志表中,完成操作日志的记录

@BussinessLog

import java.lang.annotation.*;/*** 标记需要做业务日志的方法*/
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface BussinessLog {/*** 业务的名称,例如:"修改菜单"*/String value() default "";/*** 被修改的实体的唯一标识,例如:菜单实体的唯一标识为"id"*/String key() default "id";/*** 字典(用于查找key的中文名称和字段的中文名称)*/String dict() default "SystemDict";
}

@BussinessLog注解拦截AOP

import com.stylefeng.guns.common.annotion.log.BussinessLog;
import com.stylefeng.guns.common.constant.dictmap.base.AbstractDictMap;
import com.stylefeng.guns.common.constant.dictmap.factory.DictMapFactory;
import com.stylefeng.guns.core.log.LogManager;
import com.stylefeng.guns.core.log.LogObjectHolder;
import com.stylefeng.guns.core.log.factory.LogTaskFactory;
import com.stylefeng.guns.core.shiro.ShiroKit;
import com.stylefeng.guns.core.shiro.ShiroUser;
import com.stylefeng.guns.core.support.HttpKit;
import com.stylefeng.guns.core.util.Contrast;
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
@Component
public class LogAop {private Logger log = LoggerFactory.getLogger(this.getClass());@Pointcut(value = "@annotation(com.stylefeng.guns.common.annotion.log.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();String dictClass = annotation.dict();StringBuilder sb = new StringBuilder();for (Object param : params) {sb.append(param);sb.append(" & ");}//如果涉及到修改,比对变化String msg;if (bussinessName.indexOf("修改") != -1 || bussinessName.indexOf("编辑") != -1) {Object obj1 = LogObjectHolder.me().get();Map<String, String> obj2 = HttpKit.getRequestParameters();msg = Contrast.contrastObj(dictClass, key, obj1, obj2);} else {Map<String, String> parameters = HttpKit.getRequestParameters();AbstractDictMap dictMap = DictMapFactory.createDictMap(dictClass);msg = Contrast.parseMutiKey(dictMap,key,parameters);}LogManager.me().executeLog(LogTaskFactory.bussinessLog(user.getId(), bussinessName, className, methodName, msg));}
}

@BussinessLog使用实例

/*** 新增字典@param dictValues 格式例如   "1:启用;2:禁用;3:冻结"*/
@BussinessLog(value = "添加字典记录", key = "dictName,dictValues", dict = com.stylefeng.guns.common.constant.Dict.DictMap)
@RequestMapping(value = "/add")
@Permission(Const.ADMIN_NAME)
@ResponseBody
public Object add(String dictName, String dictValues) {if (ToolUtil.isOneEmpty(dictName, dictValues)) {throw new BussinessException(BizExceptionEnum.REQUEST_NULL);}dictService.addDict(dictName, dictValues);return SUCCESS_TIP;
}

3.记录异常日志

记录异常日志其实也是一个重复式的过程,这也可以通过统一的处理来记录异常抛出的日志

import com.stylefeng.guns.common.constant.tips.ErrorTip;
import com.stylefeng.guns.common.exception.BizExceptionEnum;
import com.stylefeng.guns.common.exception.BussinessException;
import com.stylefeng.guns.common.exception.InvalidKaptchaException;
import com.stylefeng.guns.core.log.LogManager;
import com.stylefeng.guns.core.log.factory.LogTaskFactory;
import com.stylefeng.guns.core.shiro.ShiroKit;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.CredentialsException;
import org.apache.shiro.authc.DisabledAccountException;
import org.apache.shiro.session.InvalidSessionException;
import org.apache.shiro.session.UnknownSessionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.UndeclaredThrowableException;import static com.stylefeng.guns.core.support.HttpKit.getIp;
import static com.stylefeng.guns.core.support.HttpKit.getRequest;/*** 全局的的异常拦截器(拦截所有的控制器)(带有@RequestMapping注解的方法上都会拦截)*/
@ControllerAdvice
public class GlobalExceptionHandler {private Logger log = LoggerFactory.getLogger(this.getClass());/*** 拦截业务异常*/@ExceptionHandler(BussinessException.class)@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)@ResponseBodypublic ErrorTip notFount(BussinessException e) {LogManager.me().executeLog(LogTaskFactory.exceptionLog(ShiroKit.getUser().getId(), e));getRequest().setAttribute("tip", e.getMessage());log.error("业务异常:", e);return new ErrorTip(e.getCode(), e.getMessage());}/*** 用户未登录*/@ExceptionHandler(AuthenticationException.class)@ResponseStatus(HttpStatus.UNAUTHORIZED)public String unAuth(AuthenticationException e) {log.error("用户未登陆:", e);return "/login.html";}/*** 账号被冻结*/@ExceptionHandler(DisabledAccountException.class)@ResponseStatus(HttpStatus.UNAUTHORIZED)public String accountLocked(DisabledAccountException e, Model model) {String username = getRequest().getParameter("username");LogManager.me().executeLog(LogTaskFactory.loginLog(username, "账号被冻结", getIp()));model.addAttribute("tips", "账号被冻结");return "/login.html";}/*** 账号密码错误*/@ExceptionHandler(CredentialsException.class)@ResponseStatus(HttpStatus.UNAUTHORIZED)public String credentials(CredentialsException e, Model model) {String username = getRequest().getParameter("username");LogManager.me().executeLog(LogTaskFactory.loginLog(username, "账号密码错误", getIp()));model.addAttribute("tips", "账号密码错误");return "/login.html";}/*** 验证码错误*/@ExceptionHandler(InvalidKaptchaException.class)@ResponseStatus(HttpStatus.BAD_REQUEST)public String credentials(InvalidKaptchaException e, Model model) {String username = getRequest().getParameter("username");LogManager.me().executeLog(LogTaskFactory.loginLog(username, "验证码错误", getIp()));model.addAttribute("tips", "验证码错误");return "/login.html";}/*** 无权访问该资源*/@ExceptionHandler(UndeclaredThrowableException.class)@ResponseStatus(HttpStatus.UNAUTHORIZED)@ResponseBodypublic ErrorTip credentials(UndeclaredThrowableException e) {getRequest().setAttribute("tip", "权限异常");log.error("权限异常!", e);return new ErrorTip(BizExceptionEnum.NO_PERMITION);}/*** 拦截未知的运行时异常*/@ExceptionHandler(RuntimeException.class)@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)@ResponseBodypublic ErrorTip notFount(RuntimeException e) {LogManager.me().executeLog(LogTaskFactory.exceptionLog(ShiroKit.getUser().getId(), e));getRequest().setAttribute("tip", "服务器未知运行时异常");log.error("运行时异常:", e);return new ErrorTip(BizExceptionEnum.SERVER_ERROR);}/*** session失效的异常拦截*/@ExceptionHandler(InvalidSessionException.class)@ResponseStatus(HttpStatus.BAD_REQUEST)public String sessionTimeout(InvalidSessionException e, Model model, HttpServletRequest request, HttpServletResponse response) {model.addAttribute("tips", "session超时");assertAjax(request, response);return "/login.html";}/*** session异常*/@ExceptionHandler(UnknownSessionException.class)@ResponseStatus(HttpStatus.BAD_REQUEST)public String sessionTimeout(UnknownSessionException e, Model model, HttpServletRequest request, HttpServletResponse response) {model.addAttribute("tips", "session超时");assertAjax(request, response);return "/login.html";}private void assertAjax(HttpServletRequest request, HttpServletResponse response) {if (request.getHeader("x-requested-with") != null&& request.getHeader("x-requested-with").equalsIgnoreCase("XMLHttpRequest")) {//如果是ajax请求响应头会有,x-requested-withresponse.setHeader("sessionstatus", "timeout");//在响应头设置session状态}}}

项目地址:https://gitee.com/Selegant/logs-demo.git

JAVA记录操作日志步骤相关推荐

  1. 记录操作日志(JAVA版某大厂基础实践)

    1. 操作日志的使用场景 2. 实现方式 2.1 使用 Canal 监听数据库记录操作日志 2.2 通过日志文件的方式记录 2.3 通过 LogUtil 的方式记录日志 2.4 方法注解实现操作日志 ...

  2. 【实践】万字干货:如何优雅地记录操作日志?(附代码)

    猜你喜欢 1.如何搭建一套个性化推荐系统? 2.从零开始搭建创业公司后台技术栈 3.某视频APP推荐详解(万字长文) 4.微博推荐算法实践与机器学习平台演进 5.腾讯PCG推荐系统应用实践 6.强化学 ...

  3. 美团的系统是如何记录操作日志?

    来源:美团技术团队 操作日志几乎存在于每个系统中,而这些系统都有记录操作日志的一套 API.操作日志和系统日志不一样,操作日志必须要做到简单易懂.所以如何让操作日志不跟业务逻辑耦合,如何让操作日志的内 ...

  4. 如何使用SpringBoot AOP 记录操作日志、异常日志?

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 作者:咫尺的梦想_w cnblogs.com/wm-dv/ ...

  5. Appfuse:记录操作日志

    appfuse的数据维护操作都发生在***form页面,与之对应的是***FormController,在Controller中处理数据的操作是onSubmit方法,既然所有的操作都通过onSubmi ...

  6. slf4j注解log报错_SpringBoot自定义日志注解,用于数据库记录操作日志,你用过吗?...

    大家好,我是程序员7歌! 今天我将为大家讲解如何通过自定义注解记录接口访问日志.一般的开发中,有两种方式可以记录日志信息,第一种:把接口日志信息保存到日志文件中,第二种:把接口操作日志保存到数据库中, ...

  7. 如何优雅地记录操作日志?

    1 前言 在日常的工作开发中,记录业务操作产生的日志是很普遍的操作.通过它可以看到每条数据产生的变化,也能在出现问题的时候快速找到原因. 对于我自己而言,因为我这里记录的日志需要进行一些逻辑判断,并不 ...

  8. SpringBoot AOP 记录操作日志、异常日志

    使用SpringBoot AOP 记录操作日志.异常日志 我们在做项目时经常需要对一些重要功能操作记录日志,方便以后跟踪是谁在操作此功能.在操作某些功能时也有可能会发生异常,但是每次发生异常要定位原因 ...

  9. 用aspect在springboot中记录操作日志至数据库的详细过程

    代码来自若依管理系统的后台,我截取的其中用于记录操作日志的部分 1.切面 2.操作日志表 3.spring工具类 4.客户端工具类 异步工厂(产生任务用) 异步任务管理器 5.服务层 6.控制层 1. ...

最新文章

  1. ssh免密连接远程服务器
  2. ACM模板——并查集
  3. 莫队算法(Mo's_Algorithm)
  4. 算法竞赛入门经典 例题6-2 铁轨(C、python)
  5. 结构设计模式 - 代理设计模式
  6. 视觉SLAM笔记(36) 3D-2D: PnP
  7. 怎么用ubuntu进入python_ubuntu 下python环境的切换使用
  8. 记一次被动的网卡升级:VMWare导致的无线网卡不能启用
  9. 研旭至尊板——F28335知识点总结①
  10. 特斯拉 CEO 马斯克:年轻人成功的秘诀,只有这 5 点
  11. 原来我们都让历史书骗了- -#!~
  12. Latex数学公式方程格式总结
  13. 区块链打造“红娘链”,婚姻上链让爱更“牢固”
  14. 图像合成:Multi-scale Image Harmonization
  15. 基于circom、snarkjs实现零知识证明不透漏具体地理位置的区域监控
  16. 详解中断系统 与 中断周期
  17. Ceres Solver安装及bug解决
  18. App 打包提交APP Store途中遇到蛋疼的ERROR ITMS-90035
  19. 多项式(Polynomial)的运算——利用单链表
  20. sed命令实现匹配行下一行的替换

热门文章

  1. layui内置模块(layer弹出层)
  2. Excel将日期和数字一键转为文本格式的操作
  3. Dweb3.0的核心基础设施?NA(Nirvana)Chain加速开凿链上域名流量通道
  4. 联邦学习开源框架方案选型
  5. for、while、do while三种循环的流程图
  6. 十二个“一”的特征与剑三中十二门派风格相对应
  7. CrackMe003:NAG窗口(4C法)和浮点计算
  8. 总结:云计算的4种部署模型
  9. 中国人必须知道的76个常识。。。看了才发现,自己知道的实在太少了。。。
  10. Excel通过ODBC连接MySQL