系统中 用户操作日志管理

效果展示

在这里插入图片描述

自定义日志格式和日志管理,使用 Spring 切面注解来对用户操作轨迹的记录。
整合 Logback 日志框架
日志是追溯系统使用记录、跟踪问题的的依据,是系统不可缺少的重要组成部分。在 Spring Boot 中,默认使用的是 Logback 日志。如果创建一个项目没有引用其他的日志框架的话,默认使用 Logback 打印日志

默认的日志格式
目前我们还没有对日志信息做任何设置,直接运行 Spring Boot 项目后,在控制台中就会生成 Logback 默认的日志格式
在这里插入图片描述

默认格式内容如下。
时间日期:日志打印时间,精确到毫秒。
日志级别:ERROR,WARN,INFO,DEBUG,TRACE。
进程 ID:进程 ID 指的是当前应用对应的 PID。
分隔符:–标识实际日志的开始。
线程名:方括号括起来(可能会阶段控制台输出)。
Logger 名:通常使用源代码的类名。
日志内容:日志正文。

配置日志文件

日志都是打印到控制台的,如果关闭项目或者系统,日志就会消失,这样不便于后期对问题的分析。所以除了将日志打印在控制台便于实时查看外,也需要将日志做定期归档,便于以后查找问题。针对这些问题, Spring Boot 默认支持使用 xml 自定义日志格式,只需要在 upms/src/main/resources 目录下新建 logback-spring.xml。
在这里插入图片描述

<?xml version="1.0" encoding="UTF-8"?>
<configuration  scan="true" scanPeriod="60 seconds" debug="false">
    <contextName>logback</contextName>
    <!-- 输出到控制台 -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36}-[%line] - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- 按天生成日志 -->
    <appender name="logFile"  class="ch.qos.logback.core.rolling.RollingFileAppender">
        <Prudent>true</Prudent>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 日志文件命名 -->
            <FileNamePattern>
                /IdeaProjects/applog/%d{yyyy-MM-dd}/common-all-%d{yyyy-MM-dd}-%i.log
            </FileNamePattern>
            <!-- 保存最近 10 天的日志,防止磁盘被占满 -->
            <maxHistory>10</maxHistory>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <!-- 设置最大日志文件大小 -->
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>

        </rollingPolicy>
        <layout class="ch.qos.logback.classic.PatternLayout">
            <Pattern>
                %d{yyyy-MM-dd HH:mm:ss} [%thread] %logger{20}-[%line] - %msg%n
            </Pattern>
        </layout>
    </appender>

    <!-- 输出指定包文件的日志 -->
    <logger name="com.ming.bms" additivity="false">
        <appender-ref ref="console"/>
        <appender-ref ref="logFile" />
    </logger>
    <!-- Spring 日志级别控制  -->
    <logger name="org.springframework" level="warn" />

    <!-- 日志处理级别 -->
    <root level="info">
        <appender-ref ref="console"/>
        <appender-ref ref="logFile" />
    </root>

</configuration>

logback-spring.xml 配置完成后,重新启动项目,此时可以看到自定义的控制台日志格式如下。
在这里插入图片描述

通过 logback-spring.xml 配置文件的设置,我们主要对控制台输出和打印到日志文件做了如下调整。
1、控制台输出中,输出格式为 时:分:秒.毫秒 [线程名] 日志级别 类名-日志信息。
2、打印到日志文件,日志文件的根目录为 D:\IdeaProjects\applog 日志按天进行归档,且单个日志文件最大为 100M,如果超过将生产两个文件。
在这里插入图片描述

用户操作行为记录

日志可以让作为开发人员排除问题的工具,但是对于系统的管理员,他们根本不会如何查看日志文件。如何才能让他们在页面上就可以看到用户的操作历史呢。此时就需要将用户的操作行为线性地记录在日志表中。记录下用户所访问的方法内容。

新增用户操作日志表
package com.example.bms.system.dao;

import com.example.bms.system.domain.BmsLog;

import java.util.List;
import java.util.Map;

public interface BmsLogMapper {
    int deleteByPrimaryKey(Integer logId);

    int insert(BmsLog record);

    int insertSelective(BmsLog record);

    BmsLog selectByPrimaryKey(Integer logId);

    int updateByPrimaryKeySelective(BmsLog record);

    int updateByPrimaryKeyWithBLOBs(BmsLog record);

    int updateByPrimaryKey(BmsLog record);

    /**
     * 分页查询
     */
    List<BmsLog> list(Map<String,Object> map);

    /**
     * 总数
     */
    int count(Map<String,Object> map);
}

Mapper 文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.bms.system.dao.BmsLogMapper">
  <resultMap id="BaseResultMap" type="com.example.bms.system.domain.BmsLog">
    <id column="log_id" jdbcType="INTEGER" property="logId" />
    <result column="description" jdbcType="VARCHAR" property="description" />
    <result column="username" jdbcType="VARCHAR" property="username" />
    <result column="permissions" jdbcType="VARCHAR" property="permissions" />
    <result column="start_time" jdbcType="TIMESTAMP" property="startTime" />
    <result column="spend_time" jdbcType="INTEGER" property="spendTime" />
    <result column="base_path" jdbcType="VARCHAR" property="basePath" />
    <result column="uri" jdbcType="VARCHAR" property="uri" />
    <result column="method" jdbcType="VARCHAR" property="method" />
    <result column="user_agent" jdbcType="VARCHAR" property="userAgent" />
    <result column="ip" jdbcType="VARCHAR" property="ip" />
    <result column="result" jdbcType="TINYINT" property="result" />
  </resultMap>
  <resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.example.bms.system.domain.BmsLog">
    <result column="parameter" jdbcType="LONGVARCHAR" property="parameter" />
  </resultMap>
  <sql id="Base_Column_List">
    log_id, description, username, permissions, start_time, spend_time, base_path, uri, 
    method, user_agent, ip, result
  </sql>
  <sql id="Blob_Column_List">
    parameter
  </sql>
  <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="ResultMapWithBLOBs">
    select 
    <include refid="Base_Column_List" />
    ,
    <include refid="Blob_Column_List" />
    from bms_log
    where log_id = #{logId,jdbcType=INTEGER}
  </select>
  <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
    delete from bms_log
    where log_id = #{logId,jdbcType=INTEGER}
  </delete>
  <insert id="insert" parameterType="com.example.bms.system.domain.BmsLog">
    insert into bms_log (log_id, description, username, 
      permissions, start_time, spend_time, 
      base_path, uri, method, 
      user_agent, ip, result, 
      parameter)
    values (#{logId,jdbcType=INTEGER}, #{description,jdbcType=VARCHAR}, #{username,jdbcType=VARCHAR}, 
      #{permissions,jdbcType=VARCHAR}, #{startTime,jdbcType=TIMESTAMP}, #{spendTime,jdbcType=INTEGER}, 
      #{basePath,jdbcType=VARCHAR}, #{uri,jdbcType=VARCHAR}, #{method,jdbcType=VARCHAR}, 
      #{userAgent,jdbcType=VARCHAR}, #{ip,jdbcType=VARCHAR}, #{result,jdbcType=TINYINT}, 
      #{parameter,jdbcType=LONGVARCHAR})
  </insert>
  <insert id="insertSelective" parameterType="com.example.bms.system.domain.BmsLog">
    insert into bms_log
    <trim prefix="(" suffix=")" suffixOverrides=",">
      <if test="logId != null">
        log_id,
      </if>
      <if test="description != null">
        description,
      </if>
      <if test="username != null">
        username,
      </if>
      <if test="permissions != null">
        permissions,
      </if>
      <if test="startTime != null">
        start_time,
      </if>
      <if test="spendTime != null">
        spend_time,
      </if>
      <if test="basePath != null">
        base_path,
      </if>
      <if test="uri != null">
        uri,
      </if>
      <if test="method != null">
        method,
      </if>
      <if test="userAgent != null">
        user_agent,
      </if>
      <if test="ip != null">
        ip,
      </if>
      <if test="result != null">
        result,
      </if>
      <if test="parameter != null">
        parameter,
      </if>
    </trim>
    <trim prefix="values (" suffix=")" suffixOverrides=",">
      <if test="logId != null">
        #{logId,jdbcType=INTEGER},
      </if>
      <if test="description != null">
        #{description,jdbcType=VARCHAR},
      </if>
      <if test="username != null">
        #{username,jdbcType=VARCHAR},
      </if>
      <if test="permissions != null">
        #{permissions,jdbcType=VARCHAR},
      </if>
      <if test="startTime != null">
        #{startTime,jdbcType=TIMESTAMP},
      </if>
      <if test="spendTime != null">
        #{spendTime,jdbcType=INTEGER},
      </if>
      <if test="basePath != null">
        #{basePath,jdbcType=VARCHAR},
      </if>
      <if test="uri != null">
        #{uri,jdbcType=VARCHAR},
      </if>
      <if test="method != null">
        #{method,jdbcType=VARCHAR},
      </if>
      <if test="userAgent != null">
        #{userAgent,jdbcType=VARCHAR},
      </if>
      <if test="ip != null">
        #{ip,jdbcType=VARCHAR},
      </if>
      <if test="result != null">
        #{result,jdbcType=TINYINT},
      </if>
      <if test="parameter != null">
        #{parameter,jdbcType=LONGVARCHAR},
      </if>
    </trim>
  </insert>
  <update id="updateByPrimaryKeySelective" parameterType="com.example.bms.system.domain.BmsLog">
    update bms_log
    <set>
      <if test="description != null">
        description = #{description,jdbcType=VARCHAR},
      </if>
      <if test="username != null">
        username = #{username,jdbcType=VARCHAR},
      </if>
      <if test="permissions != null">
        permissions = #{permissions,jdbcType=VARCHAR},
      </if>
      <if test="startTime != null">
        start_time = #{startTime,jdbcType=TIMESTAMP},
      </if>
      <if test="spendTime != null">
        spend_time = #{spendTime,jdbcType=INTEGER},
      </if>
      <if test="basePath != null">
        base_path = #{basePath,jdbcType=VARCHAR},
      </if>
      <if test="uri != null">
        uri = #{uri,jdbcType=VARCHAR},
      </if>
      <if test="method != null">
        method = #{method,jdbcType=VARCHAR},
      </if>
      <if test="userAgent != null">
        user_agent = #{userAgent,jdbcType=VARCHAR},
      </if>
      <if test="ip != null">
        ip = #{ip,jdbcType=VARCHAR},
      </if>
      <if test="result != null">
        result = #{result,jdbcType=TINYINT},
      </if>
      <if test="parameter != null">
        parameter = #{parameter,jdbcType=LONGVARCHAR},
      </if>
    </set>
    where log_id = #{logId,jdbcType=INTEGER}
  </update>
  <update id="updateByPrimaryKeyWithBLOBs" parameterType="com.example.bms.system.domain.BmsLog">
    update bms_log
    set description = #{description,jdbcType=VARCHAR},
      username = #{username,jdbcType=VARCHAR},
      permissions = #{permissions,jdbcType=VARCHAR},
      start_time = #{startTime,jdbcType=TIMESTAMP},
      spend_time = #{spendTime,jdbcType=INTEGER},
      base_path = #{basePath,jdbcType=VARCHAR},
      uri = #{uri,jdbcType=VARCHAR},
      method = #{method,jdbcType=VARCHAR},
      user_agent = #{userAgent,jdbcType=VARCHAR},
      ip = #{ip,jdbcType=VARCHAR},
      result = #{result,jdbcType=TINYINT},
      parameter = #{parameter,jdbcType=LONGVARCHAR}
    where log_id = #{logId,jdbcType=INTEGER}
  </update>
  <update id="updateByPrimaryKey" parameterType="com.example.bms.system.domain.BmsLog">
    update bms_log
    set description = #{description,jdbcType=VARCHAR},
      username = #{username,jdbcType=VARCHAR},
      permissions = #{permissions,jdbcType=VARCHAR},
      start_time = #{startTime,jdbcType=TIMESTAMP},
      spend_time = #{spendTime,jdbcType=INTEGER},
      base_path = #{basePath,jdbcType=VARCHAR},
      uri = #{uri,jdbcType=VARCHAR},
      method = #{method,jdbcType=VARCHAR},
      user_agent = #{userAgent,jdbcType=VARCHAR},
      ip = #{ip,jdbcType=VARCHAR},
      result = #{result,jdbcType=TINYINT}
    where log_id = #{logId,jdbcType=INTEGER}
  </update>
  <select id="list" resultType="com.example.bms.system.domain.BmsLog">
    select `log_id`,`description`,`username`,`start_time`,`spend_time`,`base_path`,`uri`,`method`,`parameter`,
    `user_agent`,`ip`,`result`,`permissions` from bms_log
    <where>
      <if test="logId != null and logId != ''"> and log_id = #{logId} </if>
      <if test="description != null and description != ''"> and description = #{description} </if>
      <if test="username != null and username != ''"> and username = #{username} </if>
      <if test="startTime != null and startTime != ''"> and start_time = #{startTime} </if>
      <if test="spendTime != null and spendTime != ''"> and spend_time = #{spendTime} </if>
      <if test="basePath != null and basePath != ''"> and base_path = #{basePath} </if>
      <if test="uri != null and uri != ''"> and uri = #{uri} </if>
      <if test="method != null and method != ''"> and method = #{method} </if>
      <if test="parameter != null and parameter != ''"> and parameter = #{parameter} </if>
      <if test="userAgent != null and userAgent != ''"> and user_agent = #{userAgent} </if>
      <if test="ip != null and ip != ''"> and ip = #{ip} </if>
      <if test="result != null and result != ''"> and result = #{result} </if>
      <if test="permissions != null and permissions != ''"> and permissions = #{permissions} </if>
    </where>
    <choose>
      <when test="sort != null and sort.trim() != ''">
        order by ${sort} ${order}
      </when>
      <otherwise>
        order by log_id desc
      </otherwise>
    </choose>
    <if test="offset != null and limit != null">
      limit #{offset}, #{limit}
    </if>
  </select>

  <select id="count" resultType="int">
    select count(*) from bms_log
    <where>
      <if test="logId != null and logId != ''"> and log_id = #{logId} </if>
      <if test="description != null and description != ''"> and description = #{description} </if>
      <if test="username != null and username != ''"> and username = #{username} </if>
      <if test="startTime != null and startTime != ''"> and start_time = #{startTime} </if>
      <if test="spendTime != null and spendTime != ''"> and spend_time = #{spendTime} </if>
      <if test="basePath != null and basePath != ''"> and base_path = #{basePath} </if>
      <if test="uri != null and uri != ''"> and uri = #{uri} </if>
      <if test="method != null and method != ''"> and method = #{method} </if>
      <if test="parameter != null and parameter != ''"> and parameter = #{parameter} </if>
      <if test="userAgent != null and userAgent != ''"> and user_agent = #{userAgent} </if>
      <if test="ip != null and ip != ''"> and ip = #{ip} </if>
      <if test="result != null and result != ''"> and result = #{result} </if>
      <if test="permissions != null and permissions != ''"> and permissions = #{permissions} </if>
    </where>
  </select>
</mapper>

在添加相关的管理类,service controller

新增日志查询页面

添加log页面

使用 Spring 切面注解记录日志
1、添加依赖

需要在 pom.xml 中添加 aop 相关的依赖

<!-- Spring AOP 依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2、定义 @Log 注解
package com.example.bms.common.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author pangxie
 * @data 2020/11/15 15:52
 *
 * log 注解类
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
    String value() default "";
}

3、添加 Spring 切面注解逻辑
package com.example.bms.common.aspect;

import com.example.bms.common.annotation.Log;
import com.example.bms.common.utils.HttpContextUtils;
import com.example.bms.common.utils.IPUtils;
import com.example.bms.common.utils.JSONUtils;
import com.example.bms.common.utils.ShiroUtils;
import com.example.bms.system.domain.BmsLog;
import com.example.bms.system.domain.BmsUser;
import com.example.bms.system.service.BmsLogService;
import org.aspectj.lang.ProceedingJoinPoint;
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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Map;
/**
 * @author pangxie
 * @data 2020/11/15 15:54
 *
 * 切面注解
 */
@Aspect
@Component
public class LogAspect {
    private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);

    @Autowired
    BmsLogService logService;


    @Pointcut("@annotation(com.example.bms.common.annotation.Log)")
    public void logPointCut() {
    }

    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();
        // 执行方法
        Object result = point.proceed();
        // 执行时长(毫秒)
        long time = System.currentTimeMillis() - beginTime;
        // 异步保存日志
        saveLog(point, time);
        return result;
    }

    void saveLog(ProceedingJoinPoint joinPoint, long time) throws InterruptedException {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        BmsLog sysLog = new BmsLog();
        Log syslog = method.getAnnotation(Log.class);
        if (syslog != null) {
            // 注解上的描述
            sysLog.setDescription(syslog.value());
        }

        // 获取request
        HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
        // 设置IP地址
        sysLog.setIp(IPUtils.getIpAddr(request));
        // 设置根路径
        String path = request.getContextPath();
        String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path;
        sysLog.setBasePath(basePath);
        // 获取uri
        sysLog.setUri(request.getRequestURI());
        // 请求方式
        sysLog.setMethod(request.getMethod());
        // 获取用户设备信息
        sysLog.setUserAgent(request.getHeader("User-Agent"));
        // 用户名
        BmsUser currUser = ShiroUtils.getUser();

        // 请求的参数
        Map<String, String[]> paramMap = request.getParameterMap();
        try {
            if (paramMap != null) {
                String params = JSONUtils.beanToJson(paramMap);
                params = params.substring(0, params.length() < 1000 ? params.length(): 999);
                sysLog.setParameter(params);
            }
        } catch (Exception e) {
            logger.error("error: "+ e.getMessage(),e);
        }
        if (null == currUser) {
            if (null != sysLog.getParameter()) {
                sysLog.setUsername(sysLog.getParameter());
            } else {
                sysLog.setUsername("获取用户信息为空");
            }
        } else {
            sysLog.setUsername(ShiroUtils.getUser().getUsername());
        }
        sysLog.setSpendTime((int) time);
        // 系统当前时间
        Date date = new Date();
        sysLog.setStartTime(date);
        // 相应状态 0:成功, 1:失败
        sysLog.setResult(0);
        // 保存系统日志
        logService.insert(sysLog);
    }
}

改进版

上面的老方式如果业务发生异常是记录不了日志的。把保存日志的方法放到 finally 中,当业务发生异常时也会记录日志

    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();

        // 执行方法
        Object result = null;
        try {
            result = point.proceed();
        } catch (Throwable throwable) {
        	// 为了不影响业务本身的异常框架捕捉此处需要把异常抛出去
            throw throwable;
        } finally {
            // 执行时长(毫秒)
            long time = System.currentTimeMillis() - beginTime;
            //日志实体, 距离上次测试太久了,日志实体是新建的,但是字段一致
            SysLog sysLog = new SysLog();
            saveLog(point, time, sysLog);

            // 异步保存日志
            System.out.println("系统日志数据: " + sysLog);
        }

        return result;
    }

在这里插入图片描述

4、使用注解

打开 BmsUserController.java 类,在 /main 控制器的方法上添加注解。这样,所有用户访问 {。。。}/bms/user/main 时,系统都会记录下用户的操作信息。如下
在这里插入图片描述
可以在其他的请求页面中添加 @Log 注解,这样其他的方法也会纳入到我们的记录范围。这样我们就可以实现对用户的管理,可以起到对用户操作的监管追溯。也可以对系统执行效率进行分析,针对长请求可以做针对分析。

日志是追溯系统使用、问题跟踪的依据。日志的收集和归档对于开发者维护系统、BUG 定位起到至关重要的作用。

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值