# 日志

# 概述

系统日志是记录系统中硬件、软件和系统问题的信息,同时还可以监视系统中发生的事件。用户可以通过它来检查错误发生的原因,或者寻找受到攻击时攻击者留下的痕迹。
本章节将日志分为4种使用场景分别介绍在HOS平台如何使用

  • slf4j+logback:最灵活的一种方式,根据具体业务在代码中调用logAPI输出日志信息
  • 全局异常日志:在HOS平台中有一个全局异常拦截机制,可以将全局异常进行统一拦截,并且在拦截后将异常信息输出error日志中(底层)
  • OperLog操作日志接口:HOS平台定义了业务行为操作日志接口,可以将操作日志存到数据库中,方便查询。
  • @OperLog注解:添加在controller层的方法上即可将操作操作日志存储到数据库中或输出到log文件中。

# 一、slf4j+logback

在HOS平台中,底层日志框架使用slf4j+logback实现。
使用步骤如下:

# 使用说明

# 1.引入依赖

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.30</version>
</dependency>

由于springboot内置了logback,所以无需手动添加logback依赖

# 2.获取Logger对象

在项目中使用Slf4j有两种方式:

备注:在代码中应该使用Slf4j输出日志,不可以直接使用logback输出日志

方式一:通过工厂方式使用

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

private static final Logger log = LoggerFactory.getLogger(XXX.class);

方式二:即使用lombok的注解@Slf4j,不过需要提前配置好lombok依赖及idea中下载好插件,这块前边文档已经有所说明。使用注解方式如下:

@Slf4j
@RestController
@RequestMapping("/user/staff")
public class StaffController extends BaseController<Staff> {
    //...
}

备注:在代码中应该使用Slf4j输出日志,不可以直接使用logback输出日志,

# 3.日志打印

@GetMapping("/selectPageStaff")
public BaseResponse<IPage<Staff>> selectPageStaff(@RequestBody Staff staff) {
    log.info("StaffController.selectPageStaff(),args:{}", staff.toString());
    return BaseResponse.success(staffService.selectPageStaff(staff));
}

slf4j支持使用占位符方式打印参数

# 4.添加配置文件

在 resources 文件夹下新建 logback-spring.xml 文件:

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">

    <!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
    <property name="LOG_HOME" value="/logs/hos/" />

    <!-- 彩色日志格式 -->
    <property name="CONSOLE_LOG_PATTERN"
              value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
    <!-- 彩色日志依赖的渲染类 -->
    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
    <conversionRule conversionWord="wex"
                    converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
    <conversionRule conversionWord="wEx"
                    converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
    <!-- Console log output -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <!--文件日志, 按照每天生成日志文件 -->
    <appender name="info_log" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件输出的文件名-->
            <FileNamePattern>${LOG_HOME}/hos.info.%d{yyyy-MM-dd}.log</FileNamePattern>
            <!--日志文件保留天数-->
            <MaxHistory>30</MaxHistory>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
        <!--日志文件最大的大小-->
        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <MaxFileSize>100MB</MaxFileSize>
        </triggeringPolicy>

        <!-- ThresholdFilter:临界值过滤器,过滤掉 TRACE 和 DEBUG 级别的日志 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level><!--等级从低到高分别是ALL < TRACE < DEBUG < INFO < WARN < ERROR < OFF-->
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!--文件日志, 按照每天生成日志文件 -->
    <appender name="error_log" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件输出的文件名-->
            <FileNamePattern>${LOG_HOME}/hos.error.%d{yyyy-MM-dd}.log</FileNamePattern>
            <!--日志文件保留天数-->
            <MaxHistory>30</MaxHistory>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
        <!--日志文件最大的大小-->
        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <MaxFileSize>100MB</MaxFileSize>
        </triggeringPolicy>
        <filter class="ch.qos.logback.classic.filter.LevelFilter"><!-- 只打印ERROR日志 -->
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>


    <!-- Level: FATAL 0  ERROR 3  WARN 4  INFO 6  DEBUG 7 -->
    <root level="INFO">
        <appender-ref ref="console"/>
        <appender-ref ref="info_log"/>
        <appender-ref ref="error_log"/>
    </root>

</configuration>

# 5.打印效果

slf4j_1

# 二、全局异常日志

在HOS平台中有一个全局异常拦截机制,可以将全局异常进行统一拦截,并且在拦截后将异常信息输出error日志中,该机制已集成到了HOS平台,不需要开发人员实现。

@Slf4j
@RestControllerAdvice(basePackages = {"com"})
public class ExceptionHandlerAdvice {
    /**
     * 自定义异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler({BaseBusinessException.class})
    public BaseResponse handleBusinessMsgException(BaseBusinessException e) {
        log.error("BusinessException code:{}, msg:{}", e.getCode(), e.getMessage());
        return BaseResponse.error(e.getCode(), e.getMessage());
    }
    
    /**
     * 异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler({Exception.class})
    public BaseResponse handleException(Exception e) {
        log.error("系统处理异常", e);
        return BaseResponse.error(SysExceptionEnum.BUSINESS_EXE_ERROR.getCode(), SysExceptionEnum.BUSINESS_EXE_ERROR.getMsg());
    }
}

# 三、OperLog操作日志接口

# 介绍

HOS平台定义了一个业务行为操作日志接口,可以将操作日志存到数据库中。

# 使用说明

1.导入依赖

<dependency>
    <groupId>com.mediway.hos</groupId>
    <artifactId>hos-framework-log-starter</artifactId>
</dependency>

2.创建sys_oper_log表

CREATE TABLE `sys_oper_log` (
  `id` varchar(32) NOT NULL,
  `type` varchar(10) DEFAULT NULL COMMENT '类型 1:操作日志',
  `title` varchar(50) DEFAULT NULL COMMENT '模块标题',
  `content` varchar(50) DEFAULT NULL COMMENT '日志内容',
  `method` varchar(200) DEFAULT NULL COMMENT '方法名称',
  `request_method` varchar(10) DEFAULT NULL COMMENT '请求方式',
  `request_url` varchar(100) DEFAULT NULL COMMENT '请求URL',
  `ip` varchar(32) DEFAULT NULL COMMENT '请求IP地址',
  `client_ip` varchar(32) DEFAULT NULL COMMENT '客户端ip',
  `client_mac` varchar(255) DEFAULT NULL COMMENT '客户端mac地址',
  `ip_location` varchar(255) DEFAULT NULL COMMENT 'ip_location 暂时不赋值',
  `request_param` text COMMENT '请求参数',
  `response_result` text COMMENT '方法响应参数',
  `status` tinyint(1) DEFAULT NULL COMMENT 'status',
  `error_msg` text COMMENT '错误消息',
  `take_time` int(11) DEFAULT NULL COMMENT '方法执行耗时',
  `oper_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '操作时间',
  `user_id` varchar(32) DEFAULT NULL COMMENT '用户id',
  `user_name` varchar(255) DEFAULT NULL COMMENT '用户名称',
  `browser` varchar(255) DEFAULT NULL COMMENT '浏览器',
  `tenant_id` varchar(32) DEFAULT NULL COMMENT '租户id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

3.调用operLogService的insert方法

@Autowired
private OperLogService operLogService;


 operLogService.insert(operLogEntity);

4.operLogEntity参数介绍

参数 类型 是否必填 说明
type String 类型(1为操作日志)
title String 模块标题
content String 日志内容
userId String 用户id
userName String 用户名称
method String 方法名称(注解方式时值为所在的方法名)
requestMethod String 请求方式(GET、POST)
requestUrl String 请求URL
ip String 请求IP地址(可以通过IpUtil工具类获取)
clientIp String 内网ip地址(需要前端放在header上可以通过IpUtil工具类获取)
clientMac String 内网MAC地址(需要前端放在header上可以通过IpUtil工具类获取)
server_ip String 服务器ip
requestParam String 请求参数
responseResult String 方法响应参数(该参数暂时不赋值)
status Integer 操作状态(1成功 0失败)
errorMsg String 错误消息(状态为1是必填)
operTime Date 操作时间
errorMsg String 错误消息(状态为1是必填)
takeTime Long 方法执行耗时(单位:毫秒)

# 四、@OperLog注解

# 介绍

@OperLog注解方式是在OperLog操作日志接口的基础上进行了进一步的封装,通过AOP机制自动填充OperLog实体信息,在controller中的方法添加注解@OperLog

# @OperLog注解参数介绍

    /**
     * 自定义注解记录系统操作日志
     */
    @Target({ElementType.METHOD })
    @Retention(RetentionPolicy.RUNTIME)
    public @interface OperLog
    {
        /**
         * 模块标题
         */
        String title() default "";
        /**
         * 日志内容
         */
        String content() default "";
    
        /**
         *  操作日志输出位置:logFile,database 二者可以同时存在,默认为logFile
         *  不填的话根据配置文件framework.oper-log.out项决定
         * @return
         */
        OperLogOut[] operLogOut() default {};
    }

@OperLog含有三个属性,分别为

  • title 用来记录方法所在模块名称
  • content 用来记录方法内容
  • operLogOut 用来设置日志输出位置,需要注意的是配置文件中的配置framework.oper-log.out也可设置该属性,不同之处在于配置文件中的为该属性的全局配置,比注解上直接设置优先级低;只有在注解上没有配置该属性时,配置文件中的设置才会生效;配置文件中默认值为logFile

# 使用说明

1.导入依赖

<dependency>
    <groupId>com.mediway.hos</groupId>
    <artifactId>hos-framework-log-starter</artifactId>
</dependency>

2.配置文件

framework:
  # 操作日志输出位置:logFile,database 二者可以同时存在,默认为logFile
  # logFile表示输出到log文件,database表示输出数据库中的sys_oper_log表中
  # 该配置只对OperLog注解生效,与其他日志输出无关
  oper-log:
    out: database,logFile

3.在方法上使用注解@OperLog

/**
 * 测试自定义异常
 *
 * @return
 */
@OperLog(title = "合同管理模块", content = "测试自定义异常")
@GetMapping("/testException")
public BaseResponse testException() {
    contractService.testException();
    return BaseResponse.success();
}

4.请求该接口查看效果

operlog_3