java web 操作日志_JAVA记录操作日志步骤

本文介绍了一种系统日志管理方案,包括使用Spring AOP技术自动记录请求参数及响应结果,通过自定义注解记录操作日志,并利用统一异常处理机制记录异常日志。

说明

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

日志记录的不规范性

日志记录的重复性

日志记录的难分类

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

请求的入参,出参

关于业务上的操作

异常日常日志的打印

解决方案

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 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() {

@Override

public 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() {

@Override

public 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() {

@Override

public 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() {

@Override

public 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() {

@Override

public 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 obj2 = HttpKit.getRequestParameters();

msg = Contrast.contrastObj(dictClass, key, obj1, obj2);

} else {

Map 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)

@ResponseBody

public 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)

@ResponseBody

public ErrorTip credentials(UndeclaredThrowableException e) {

getRequest().setAttribute("tip", "权限异常");

log.error("权限异常!", e);

return new ErrorTip(BizExceptionEnum.NO_PERMITION);

}

/**

* 拦截未知的运行时异常

*/

@ExceptionHandler(RuntimeException.class)

@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)

@ResponseBody

public 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-with

response.setHeader("sessionstatus", "timeout");//在响应头设置session状态

}

}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值