JAVA Recording Operational Logging Steps

Keywords: Java Shiro Session Apache

Explain

System log plays an important role in daily management and maintenance, but there are many problems in log recording.

  1. Irregularity of log records
  2. Repeatability of log records
  3. Difficult classification of log records

At present, there are three main aspects of log records.

  1. Entry and exit of requests
  2. Operations in Business
  3. Printing of Exceptional Daily Logs

Solution

1. Record the entry and exit of requests

Logging out parameters is the best part of logging operations, and there will be some repeatability here, because every request needs to be logged, which is a repetitive operation. Spring AOP can be used to log in and out parameters.

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 Layer log section
 */
@Aspect    //Here you set up AOP using @Aspect annotation
@Order(5)  //The smaller the value, the earlier it loads.
@Component
public class WebLogAspect {

    private Logger logger = Logger.getLogger(getClass());

    ThreadLocal<Long> startTime = new ThreadLocal<>();
	
	//Here @Pointcut settings can be set to the address of the Controller layer
    @Pointcut("execution(public * com.training..*.*Controller(..))")
    public void webLog(){}

	//@ Before refers to the execution before the tangent method, that is, before the execution of the Controller layer method. Here you can get some information about the method through JoinPoint, and here you can also modify the value of the parameter.
    
	//@ Before() parentheses set the name of the tangent method
	@Before("webLog()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        startTime.set(System.currentTimeMillis());

        // Receive the request and record the content of the request
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        // Record the content of the request
        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 {
        // Processing the request and returning the content
        logger.info("RESPONSE : " + ret);
        logger.info("SPEND TIME : " + (System.currentTimeMillis() - startTime.get()));
    }

}

2. Recording operation log

There may be a lot of additions, deletions, modifications or business operations involved in the system. At this time, we need to record the entry of the operation, the type of operation, or the name of the business. However, we need to build the basic components of the log before we can operate the log.

Log object

import java.util.Date;

/**
 * Operational Log
 */
public class OperationLog extends Base {

    private static final long serialVersionUID = 1L;

    /**
     * Log type
     */
    private String  logtype;
    /**
     * Log name
     */
    private String  logname;
    /**
     * User id
     */
    private Integer userid;
    /**
     * Class name
     */
    private String  classname;
    /**
     * Method name
     */
    private String  method;
    /**
     * Creation time
     */
    private Date    createtime;
    /**
     * Success or not
     */
    private String  succeed;
    /**
     * Remarks
     */
    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;
    }
}

Log object creation factory

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;

/**
 * Log Object Creation Factory
 */
public class LogFactory {

    /**
     * Create operation log
     */
    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;
    }

    /**
     * Create login log
     */
    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;
    }
}

Log Task Creation Factory

The role of the log task creation factory is to store log records in a database

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;

/**
 * Log Operation Task Creation Factory
 */
public class LogTaskFactory {

    private static Logger logger  = LoggerFactory.getLogger(LogManager.class);
	//LoginLogMapper logs in and out of logs
    private static LoginLogMapper loginLogMapper = SpringContextHolder.getBean(LoginLogMapper.class);
	//OperationLogMapper records operation logs	    
	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("Create Log Exceptions!", 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, "Account number:" + username + "," + msg, ip);
                try {
                    loginLogMapper.insert(loginLog);
                } catch (Exception e) {
                    logger.error("Create Logon Failure Exception!", 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("Create exit log exceptions!", 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("Create business log exceptions!", 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("Create exception log exceptions!", e);
                }
            }
        };
    }
}

Record operation log

This is the most critical step.

Principle: Through the custom annotation @BussinessLog (can be named arbitrarily), it defines the name of the business, the unique identification of the modified entity, the dictionary (used to find the Chinese name of the key and the Chinese name of the field), and then through AOP, intercepts all methods that add the @BussinessLog annotation, parses the attributes in the annotation, and records them in the corresponding operation log table, completes the operation log. Records of Records

@BussinessLog

import java.lang.annotation.*;

/**
 * Method of marking up business logs
 */
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface BussinessLog {

    /**
     * The name of the business, such as "Modify Menu"
     */
    String value() default "";

    /**
     * The unique identifier of the modified entity, for example, the unique identifier of the menu entity is "id"
     */
    String key() default "id";

    /**
     * Dictionary (Chinese name for key and field)
     */
    String dict() default "SystemDict";
}

@ BussinessLog annotation intercepts 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;

/**
 * Logging
 */
@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 {

        //Carry out business first
        Object result = point.proceed();

        try {
            handle(point);
        } catch (Exception e) {
            log.error("Logging error!", e);
        }

        return result;
    }

    private void handle(ProceedingJoinPoint point) throws Exception {

        //Get the interception method name
        Signature sig = point.getSignature();
        MethodSignature msig = null;
        if (!(sig instanceof MethodSignature)) {
            throw new IllegalArgumentException("This annotation can only be used for methods");
        }
        msig = (MethodSignature) sig;
        Object target = point.getTarget();
        Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
        String methodName = currentMethod.getName();

        //If the current user is not logged in, do not log
        ShiroUser user = ShiroKit.getUser();
        if (null == user) {
            return;
        }

        //Obtaining the parameters of interception method
        String className = point.getTarget().getClass().getName();
        Object[] params = point.getArgs();

        //Get the operation name
        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(" & ");
        }

        //If changes are involved, compare changes
        String msg;
        if (bussinessName.indexOf("modify") != -1 || bussinessName.indexOf("edit") != -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 usage example

/**
 * New dictionary
 @param dictValues Formats such as "1: Enable; 2: Disable; 3: Freeze"
 */
@BussinessLog(value = "Add dictionary records", 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. Recording abnormal logs

Recording exception logs is actually a repetitive process, which can also be used to record exceptions thrown by a unified process.

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;

/**
 * Global exception interceptors (intercepting all controllers) (methods with @RequestMapping annotations intercept)
 */
@ControllerAdvice
public class GlobalExceptionHandler {

    private Logger log = LoggerFactory.getLogger(this.getClass());

    /**
     * Intercepting business anomalies
     */
    @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("Business exceptions:", e);
        return new ErrorTip(e.getCode(), e.getMessage());
    }

    /**
     * User not logged in
     */
    @ExceptionHandler(AuthenticationException.class)
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    public String unAuth(AuthenticationException e) {
        log.error("User not logged in:", e);
        return "/login.html";
    }

    /**
     * Accounts frozen
     */
    @ExceptionHandler(DisabledAccountException.class)
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    public String accountLocked(DisabledAccountException e, Model model) {
        String username = getRequest().getParameter("username");
        LogManager.me().executeLog(LogTaskFactory.loginLog(username, "Accounts frozen", getIp()));
        model.addAttribute("tips", "Accounts frozen");
        return "/login.html";
    }

    /**
     * Account Password Error
     */
    @ExceptionHandler(CredentialsException.class)
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    public String credentials(CredentialsException e, Model model) {
        String username = getRequest().getParameter("username");
        LogManager.me().executeLog(LogTaskFactory.loginLog(username, "Account Password Error", getIp()));
        model.addAttribute("tips", "Account Password Error");
        return "/login.html";
    }

    /**
     * Verification code error
     */
    @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, "Verification code error", getIp()));
        model.addAttribute("tips", "Verification code error");
        return "/login.html";
    }

    /**
     * No access to this resource
     */
    @ExceptionHandler(UndeclaredThrowableException.class)
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    @ResponseBody
    public ErrorTip credentials(UndeclaredThrowableException e) {
        getRequest().setAttribute("tip", "Permission exception");
        log.error("Permission exception!", e);
        return new ErrorTip(BizExceptionEnum.NO_PERMITION);
    }

    /**
     * Intercepting Unknown Runtime Anomalies
     */
    @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", "Server Unknown Runtime Exception");
        log.error("Runtime exception:", e);
        return new ErrorTip(BizExceptionEnum.SERVER_ERROR);
    }

    /**
     * session Failure Abnormal Interception
     */
    @ExceptionHandler(InvalidSessionException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public String sessionTimeout(InvalidSessionException e, Model model, HttpServletRequest request, HttpServletResponse response) {
        model.addAttribute("tips", "session overtime");
        assertAjax(request, response);
        return "/login.html";
    }

    /**
     * session abnormal
     */
    @ExceptionHandler(UnknownSessionException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public String sessionTimeout(UnknownSessionException e, Model model, HttpServletRequest request, HttpServletResponse response) {
        model.addAttribute("tips", "session overtime");
        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")) {
            //If it's an ajax request response header, x-requested-with
            response.setHeader("sessionstatus", "timeout");//Setting session state in response header
        }
    }

}

Posted by BigX on Tue, 08 Oct 2019 18:21:02 -0700