项目开发扩展-1.日志管理
项目开发扩展-日志管理
[TOC]
1.日志管理实现思路
a.项目开发日志配置
b.业务操作日志配置
2.日志管理实现说明
a.项目运行日志管理
https://blog.csdn.net/gexiaoyizhimei/article/details/93907932
https://www.pianshen.com/article/1405802166/
日志级别设定参考:https://blog.csdn.net/qq_38085240/article/details/83142355
日志配置相关:https://www.jianshu.com/p/1fa12b92d5c4
(1)配置说明
(1)创建springboot项目
(2)在yml文件中配置日志输出格式
(2)实现步骤
普通方式实现:控制台输出
# log config
logging:
level:
# 配置日志级别(默认info)
root: info
# 配置指定包下的日志级别(info\warn)
# com.bestvike: debug
# 默认日志文件名(指定当前目录下指定文件夹)
file: boxing_logs/boxing_demo.log
# 指定logback规则配置文件(指定config默认优先使用config配置)
# config: classpath:logback-boxing.xml
# 默认日志路径(与logging.file同时配置默认采用logging.file)
# path: ./boxing_logs
pattern:
file: '%d{yyyy-MMM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{15} - %msg%n'
console: '%d{yyyy-MMM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{15} - %msg%n'
通过自定义log配置文件实现
日志配置需重启服务器重新发布项目后生效,生成相应的日志配置文件
# log config
logging:
level:
# 配置日志级别(默认info; debug)
root: info
# 配置指定包下的日志级别(info\warn)
# com.bestvike: debug
# 指定logback规则配置文件(可自定义日志配置文件路径)
config: classpath:config/logback-boxing.xml
logback.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!--
contextName属性:设置上下文名称(默认为"default",用于区分不同的应用程序记录)
contextName属性一旦设置,不能修改(需重启服务器加载)
-->
<contextName>BOXING</contextName>
<!-- property属性:文件变量名定义(用于后续节点配置引入) -->
<property name="logback.logDir" value="./logs/boxing_logs"/>
<property name="logback.appName" value="BOXING"/>
<!-- 彩色日志依赖的渲染类 -->
<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" />
<!-- 自定义控制台彩色日志格式 -->
<!--<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}}"/>-->
<property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} -[%contextName]%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}}"/>
<!-- 统一FILE日志格式 -->
<property name="FILE_LOG_PATTERN" value="%d [%contextName]-[%thread] %-5level %logger{36} %line - %msg%n"/>
<!-- a.控制台日志文件输出配置 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<!-- 如果只需要Error级别的日志,则需要过滤一下,默认是 info 级别的(ThresholdFilter) -->
<!--
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
-->
<!--如果只需要Info 级别的日志,由于Error的级别高于info,只是过滤info还是会输出Error日志,可以使用下面的策略避免输出 Error 的日志-->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!--过滤 Error-->
<level>ERROR</level>
<!--匹配到就禁止-->
<onMatch>DENY</onMatch>
<!--没有匹配到就允许-->
<onMismatch>ACCEPT</onMismatch>
</filter>
<encoder>
<!--<pattern>%d{HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>-->
<!--<pattern>%d{HH:mm:ss} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>-->
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- 自定义日志文件输出规则:按天生成日志(可拆分INFO、ERROR级别分文件夹存储) -->
<!-- INFO级别日志文件定义 -->
<appender name="infoLogFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--如果只需要Info 级别的日志,由于Error的级别高于info,只是过滤info还是会输出Error日志,可以使用下面的策略避免输出 Error 的日志-->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!--过滤 Error-->
<level>ERROR</level>
<!--匹配到就禁止-->
<onMatch>DENY</onMatch>
<!--没有匹配到就允许-->
<onMismatch>ACCEPT</onMismatch>
</filter>
<!--
Prudent为true日志会被安全的写入文件,即使其他的FileAppender也在向此文件做写入操作,效率低,默认是 false
Prudent为true时,不支持FixedWindowRollingPolicy;支持TimeBasedRollingPolicy,但是有两个限制:不支持也不允许文件压缩;不能设置file属性,必须留空
-->
<Prudent>false</Prudent>
<!--
日志名称配置:
如果没有指定File 属性,那么只会使用FileNamePattern的文件路径规则
如果同时有<File>和<FileNamePattern>,那么当天日志命名规则参考<File>,隔日会自动将对应的日志文件名调整为当日对应日期格式。
即<File>定义的日志都是当天的数据
-->
<File>${logback.logDir}/${logback.appName}.info.log</File>
<!--滚动策略,按照时间滚动 TimeBasedRollingPolicy-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--文件路径,定义了日志的切分方式——把每一天的日志归档到一个文件中,以防止日志填满整个磁盘空间-->
<FileNamePattern>${logback.logDir}/${logback.appName}.info.%d{yyyy-MM-dd}.log</FileNamePattern>
<!--maxHistory:指定保留日志记录天数(此处设置为30天)-->
<maxHistory>30</maxHistory>
<!--totalSizeCap:指定日志文件的上限大小,超出文件限制则清理旧日志文件-->
<totalSizeCap>100MB</totalSizeCap>
</rollingPolicy>
<!--日志输出编码格式化-->
<encoder>
<charset>UTF-8</charset>
<!--<pattern>%d [%contextName]-[%thread] %-5level %logger{36} %line - %msg%n</pattern>-->
<pattern>${FILE_LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- ERROR级别日志文件定义 -->
<appender name="errorLogFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--如果只需要Error级别的日志,则需要过滤一下,默认是 info 级别(ThresholdFilter)-->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>Error</level>
</filter>
<!-- <File>、<FileNamePattern>参考上述说明 -->
<File>${logback.logDir}/${logback.appName}.error.log</File>
<!--滚动策略,按照时间滚动 TimeBasedRollingPolicy-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${logback.logDir}/${logback.appName}.error.%d{yyyy-MM-dd}.log</FileNamePattern>
<maxHistory>30</maxHistory>
<totalSizeCap>100MB</totalSizeCap>
</rollingPolicy>
<!--日志输出编码格式化-->
<encoder>
<charset>UTF-8</charset>
<!--<pattern>%d [%contextName]-[%thread] %-5level %logger{36} %line - %msg%n</pattern>-->
<pattern>${FILE_LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- 按天生成日志 -->
<!--
<appender name="logFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<Prudent>true</Prudent>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>
boxing_logs/boxing_daily_%d{yyyy-MM-dd}.log
</FileNamePattern>
</rollingPolicy>
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>
%d{yyyy-MM-dd HH:mm:ss} -%msg%n
</Pattern>
</layout>
</appender>
-->
<root level="INFO">
<appender-ref ref="console" />
<!--<appender-ref ref="logFile" />-->
<appender-ref ref="infoLogFile" />
<!--<appender-ref ref="errorLogFile" />-->
</root>
</configuration>
(3)常见问题
需要注意application.yml作为基类配置文件,而application-xxx.yml作为子类配置文件会填充基类配置文件没有的属性、继承并可以覆盖基类配置文件已有的属性,因此在基类配置文件中需要取消日志打印相关配置,随后由子类配置文件自定义配置日志打印的形式(否则继承属性还是会打印数据)
根据不同的日志系统,按如下规则组织配置文件名,就能被正确加载:
- Logback: logback-spring.xml, logback-spring.groovy, logback.xml, logback.groovy
- Log4j: log4j-spring.properties, log4j-spring.xml, log4j.properties, log4j.xml
- Log4j2: log4j2-spring.xml, log4j2.xml
- JDK (Java Util Logging): logging.properties
在resources下放置logback-spring.xml,springboot会自动优先加载该文件(必须是默认文件命名)生成相应规则的日志而忽略file的配置;如果是要自定义文件名则需要配置“logging.config=classpath:logback-boxing.xml”
如果同时定义了logging.file、logging.config配置,springboot优先处理config配置
如果要编写除控制台输出之外的日志文件,则需在 application.yml中设置 logging.file 或 logging.path 属性(二者不能同时使用,如若同时使用,则只有 logging.file 生效)
- logging.file ,设置文件,可以是绝对路径,也可以是相对路径。如: logging.file=my.log
- logging.path ,设置目录,会在该目录下创建 spring.log 文件,并写入日志内容,如: logging.path=/var/log
如果只配置 logging.file ,会在项目的当前路径下生成一个 xxx.log 日志文件
如果只配置 logging.path ,在指定文件夹生成一个日志文件为 spring.log
默认情况下,日志文件的大小达到 10MB 时会切分一次,产生新的日志文件,默认级别为: ERROR、WARN、INFO
b.AOP业务日志管理
参考链接
一个springboot的基础库,负责springboot项目的一些初始化工作,例如日志切面,接口通用返回格式baseController:https://gitee.com/han8/base-spring-boot-starter/tree/31cae5160760fd2524e22d541907ddc3190df518/src/main
一个springboot的基础库,负责springboot项目的一些初始化工作,例如接口日志切面,接口通用返回格式baseController、通用异常处理、通用api接口、钉钉异常推送:https://gitee.com/free2free/base-spring-boot-starter/tree/master/src/main
(1)配置说明
spring AOP自定义注解方式实现日志管理:https://www.cnblogs.com/jianjianyang/p/4910851.html
(1)定义日志管理相关model、mapper、service、controller
(2)引入aop相关jar,定义日志注解、AOP切面配置
(3)在指定的位置(controller或service方法)中通过注解实现日志管理
系统业务操作日志、系统运行日志
可定义两个切点,业务操作日志在controller、系统运行日志在service层控制
不同的日志记录在不同的表中
(2)实现步骤
定义日志管理相关model、mapper、service、controller
- model
@Data
@TableName("CFA_SYS_OPER_LOG")
public class SysOperLog extends Model<SysOperLog> {
private static final long serialVersionUID = 1L;
/**
* 日志ID
*/
@TableId("LOG_ID")
private String logId;
/**
* 日志标题
*/
@TableField("LOG_TITLE")
private String logTitle;
/**
* 业务类型(CRUD)
*/
@TableField("BUSINESS_TYPE")
private String businessType;
/**
* 操作类别
*/
@TableField("OPER_TYPE")
private String operType;
/**
* 操作人员(包含操作人员基本信息:USER_NUM、USER_NAME等)
*/
@TableField("OPER_CREW")
private String operCrew;
/**
* 操作主机IP
*/
@TableField("OPER_HOST_IP")
private String operHostIp;
/**
* 操作地点
*/
@TableField("OPER_LOCATION")
private String operLocation;
/**
* 操作时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@TableField("OPER_TIME")
private Date operTime;
/**
* 操作状态(0-正常;1-异常)
*/
@TableField("OPER_STATUS")
private String operStatus;
/**
* 请求方式
*/
@TableField("REQUEST_MODE")
private String requestMode;
/**
* 请求方法名称
*/
@TableField("REQUEST_METHOD")
private String requestMethod;
/**
* 请求URL
*/
@TableField("REQUEST_URL")
private String requestUrl;
/**
* 请求参数
*/
@TableField("REQUEST_PARAM")
private String requestParam;
/**
* 返回结果
*/
@TableField("RESPONSE_RESULT")
private String responseResult;
/**
* 错误消息提示
*/
@TableField("ERROR_MSG")
private String errorMsg;
/**
* 子系统标识
*/
@TableField("SYSTEM_CODE")
private String systemCode;
@Override
protected Serializable pkVal() {
return this.logId;
}
}
- mapper
@Repository
@Mapper
public interface SysOperLogMapper extends BaseMapper<SysOperLog> {
public List<SysOperLog> getByPage(Page page, @Param(value = "queryCond") JSONObject jsonObject);
}
- service
// 日志公有接口定义,供不同日志配置引用
public interface LogManageService<T> {
/**
* 添加日志信息
**/
public boolean addLog(T log);
/**
* 根据条件分页查找日志列表
**/
public Page<T> getLogByPage(JSONObject jsonObject);
/**
* 根据日志id获取详细日志信息
**/
public T getLogById(String logId);
/**
* 根据日志id删除指定日志数据
**/
public boolean delLogById(String logId);
}
@Service
public class SysOperLogServiceImpl extends ServiceImpl<SysOperLogMapper, SysOperLog> implements LogManageService<SysOperLog> {
@Autowired
private SysOperLogMapper sysOperLogMapper;
@Override
public boolean addLog(SysOperLog log) {
// 封装日志信息
return log.insert();
}
@Override
public Page<SysOperLog> getLogByPage(JSONObject jsonObject) {
// 根据筛选条件获取日志数据
Page<SysOperLog> page = new PageUtil<SysOperLog>().getPage(jsonObject);
List<SysOperLog> sysOperLogList = sysOperLogMapper.getByPage(page, jsonObject);
page.setRecords(sysOperLogList);
return page;
}
@Override
public SysOperLog getLogById(String logId) {
SysOperLog sysOperLog = getById(logId);
return sysOperLog;
}
@Override
public boolean delLogById(String logId) {
if (StringUtils.isEmpty(logId)) {
throw new BoxingException("指定日志id不能为空");
}
return removeById(logId);
}
}
- controller
@RestController("systemMonitor-sysOperLog")
@RequestMapping("/systemMonitor/sysOperLog")
public class SysOperLogController {
/**
* 操作模块限定
**/
private static final String TITLE = "系统监控-系统日志管理";
private static final String SUPER_ADMIN = "superAdmin";
private static final String SUB_ADMIN = "subAdmin";
@Autowired
private SysOperLogServiceImpl sysOperLogServiceImpl;
/**
* 根据日志id删除指定日志信息
**/
@SysOperLogAnno(title = TITLE, businessType = BusinessType.OTHER, operationDesc = "删除系统日志操作")
@RequiresRoles(value = {SUPER_ADMIN, SUB_ADMIN}, logical = Logical.OR)
@PostMapping("/deleteById")
public AjaxResult deleteById(@RequestBody JSONObject jsonObject) {
if (!sysOperLogServiceImpl.delLogById(jsonObject.getString("logId"))) {
return AjaxResultUtil.error("日志信息删除失败");
}
return AjaxResultUtil.success();
}
/**
* 查看日志列表
**/
@RequiresRoles(value = {SUPER_ADMIN, SUB_ADMIN}, logical = Logical.OR)
@PostMapping("/getByPage")
public AjaxResult getByPage(@RequestBody JSONObject jsonObject) {
return AjaxResultUtil.success("pageData", sysOperLogServiceImpl.getLogByPage(jsonObject));
}
/**
* 根据日志id查看日志详情
**/
@RequiresRoles(value = {SUPER_ADMIN, SUB_ADMIN}, logical = Logical.OR)
@PostMapping("/getById")
public AjaxResult getById(@RequestBody JSONObject jsonObject) {
return AjaxResultUtil.success("sysOperLog", sysOperLogServiceImpl.getLogById(jsonObject.getString("logId")));
}
}
定义日志注解、AOP切面配置
- AOP相关jar引入
<!-- springboot整合aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>${spring.boot.version}</version>
</dependency>
- 日志注解:SysOperLogAnno
// 自定义日志操作注解(系统业务操作)
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysOperLogAnno {
// 标题:模块
public String title() default "";
// 操作业务类型(CRUD)
public BusinessType businessType() default BusinessType.OTHER;
// 操作人类别
public OperatorType operatorType() default OperatorType.MANAGE;
// 要执行的具体操作比如:执行xx操作
public String operationDesc() default "";
// 子系统标识:
public String systemCode() default "CFA-ADMIN";
// 是否保存请求的参数
public boolean isSaveRequestData() default true;
}
- AOP切面配:SysOperLogAspect
@Aspect
@Component
public class SysOperLogAspect {
//注入Service用于把日志保存数据库
@Autowired
private SysOperLogServiceImpl sysOperLogServiceImpl;
// 本地日志记录
private static final Logger logger = LoggerFactory.getLogger(SysOperLogAspect.class);
// 定义Controller切点
// @Pointcut("execution (public * *Controller(..))") 通过方法签名定义切点
// @Pointcut("execution (* com.sz.mip.*.*.controller..*.*(..))") 通过包名定义切点(凡是controller包下的类都会执行)
// 配置指定注解为切入点(自定义:只有当配置了自定义注解的方法或者类才执行)
@Pointcut("@annotation(com.sz.framework.annotation.SysOperLogAnno)")
public void controllerAspect() {
}
// 前置通知
@Before("controllerAspect()")
public void doBefore(JoinPoint joinPoint) {
try {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
// 类名
String className = joinPoint.getTarget().getClass().getName();
// 请求方法
String method = joinPoint.getSignature().getName() + "()";
// 方法参数
String methodParam = JSON.toJSONString(joinPoint.getArgs());
// 方法描述
// String methodDescription = getControllerMethodDescription(joinPoint);
StringBuilder sb = new StringBuilder(1000);
sb.append("\n");
sb.append("*********************************start-Request请求-前置通知***************************************");
sb.append("\n");
sb.append("ClassName : ").append(className).append("\n");
sb.append("RequestMethod : ").append(method).append("\n");
sb.append("RequestParams : ").append(methodParam).append("\n");
sb.append("RequestType : ").append(request.getMethod()).append("\n");
// sb.append("Description : ").append("").append("\n");
sb.append("serverAddr : ").append(request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()).append("\n");
sb.append("RemoteAddr : ").append(IpUtil.getRemoteAddr(request)).append("\n");
UserAgent userAgent = UserAgentUtil.getUserAgent(request);
sb.append("DeviceName : ").append(userAgent.getOperatingSystem().getName()).append("\n");
sb.append("BrowserName : ").append(userAgent.getBrowser().getName()).append("\n");
sb.append("UserAgent : ").append(request.getHeader("User-Agent")).append("\n");
sb.append("RequestUri : ").append(request.getRequestURI()).append("\n");
sb.append("*********************************end-Request请求-前置通知***************************************");
logger.info(sb.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 是否存在注解,如果存在就获取
*/
private SysOperLogAnno getAnnotationLog(JoinPoint joinPoint) throws Exception {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method != null) {
return method.getAnnotation(SysOperLogAnno.class);
}
return null;
}
/**
* 获取请求的参数,设置到系统操作日志中
**/
private void setRequestValue(SysOperLog sysOperLog) throws Exception {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
Map<String, String[]> map = request.getParameterMap();
String params = JSONObject.toJSONString(map);
sysOperLog.setRequestParam(StringUtils.substring(params, 0, 2000));
}
/**
* 获取注解中对方法的描述信息,用于Controller层注解
**/
public void getControllerMethodDescription(SysOperLogAnno sysOperLogAnno, SysOperLog sysOperLog) throws Exception {
// 设置action动作
sysOperLog.setBusinessType(sysOperLogAnno.businessType().name());
// 设置标题
sysOperLog.setLogTitle(sysOperLogAnno.title());
// 设置操作人类别
sysOperLog.setOperType(sysOperLogAnno.operatorType().name());
// 设置子系统标识
sysOperLog.setSystemCode(sysOperLogAnno.systemCode());
// 是否需要保存request,参数和值
if (sysOperLogAnno.isSaveRequestData()) {
// 获取参数的信息,传入到数据库中。
setRequestValue(sysOperLog);
}
}
private void handleLog(JoinPoint joinPoint, final Exception e, AjaxResult ajaxResult) {
try {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
// 获得注解
SysOperLogAnno sysOperLogAnno = getAnnotationLog(joinPoint);
if (sysOperLogAnno == null) {
return;
}
// 获取当前登录的管理员信息
Object currentUser = SingleShiroUtil.getPrincipal();
String operatorName = "";
// if(currentUser instanceof Admin){
// Admin admin = (Admin)currentUser;
// operatorName = admin.getAdminNum() + "-" + admin.getAdminName();
// }else if(currentUser instanceof User){
// User user = (User)currentUser;
// operatorName = user.getUserNum() + "-" + user.getUserName();
// }
LoginUser user = (LoginUser)currentUser;
// *========数据库日志=========*//
SysOperLog sysOperLog = new SysOperLog();
sysOperLog.setOperTime(CommonUtil.getCurrentTimestamp());
// 设置响应结果
// sysOperLog.setOperStatus(ConstantEnum.BUSINESS_STATUS_SUCCESS.getValue());
// 请求的ip、地址 ShiroUtil.getIp()
sysOperLog.setOperHostIp(IpUtil.getRemoteAddr(request));
sysOperLog.setOperLocation(AddressUtil.getRealAddressByIP(IpUtil.getRemoteAddr(request)));
// 返回参数(出现异常的时候无法获取到ajaxResult,需要相应处理空指针问题)
sysOperLog.setResponseResult(JSONObject.toJSONString(ajaxResult));
// operLog.setJsonResult(JSON.marshal(jsonResult)); 自定义JSON工具类
sysOperLog.setRequestUrl(request.getRequestURI());
if (currentUser != null) {
// 设定操作人名称
sysOperLog.setOperCrew(operatorName);
}
// 如果存在异常情况
if (e != null) {
// sysOperLog.setOperStatus(ConstantEnum.BUSINESS_STATUS_FAIL.getValue());
sysOperLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
}
// 设置方法名称
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
sysOperLog.setRequestMethod(className + "." + methodName + "()");
// 设置请求方式
sysOperLog.setRequestMode(request.getMethod());
// 处理设置注解上的参数
getControllerMethodDescription(sysOperLogAnno, sysOperLog);
// 保存数据库(异步保存数据)
// AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
// 直接本地插入数据
sysOperLogServiceImpl.addLog(sysOperLog);
} catch (Exception exp) {
// 记录本地异常日志
logger.error("== 后置通知异常 ==");
logger.error("异常信息:{}", exp.getMessage());
exp.printStackTrace();
}
}
/**
* 配置后置返回通知,使用在方法aspect()上注册的切入点
**/
@AfterReturning(value = "controllerAspect()", returning = "ajaxResult")
public void afterReturn(JoinPoint joinPoint, AjaxResult ajaxResult) {
handleLog(joinPoint, null, ajaxResult);
}
/**
* 异常通知:用于拦截记录异常日志
**/
@AfterThrowing(pointcut = "controllerAspect()", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
handleLog(joinPoint, e, null);
}
}
注解引用:配置日志管理
/**
* 添加/修改管理员信息(根据指定的adminId进行判断)
**/
@RequiresRoles(SUPER_ADMIN)
@SysOperLogAnno(title = TITLE, businessType = BusinessType.INSERT, operationDesc = "编辑管理员账号")
@PostMapping("/edit")
public AjaxResult edit(@Validated @RequestBody Admin admin) {
if (!adminService.edit(admin)) {
return AjaxResultUtil.error(ResultEnum.OPER_FAIL_ERROR);
}
return AjaxResultUtil.success();
}
@Target:注解的作用目标
@Target(ElementType.TYPE)——接口、类、枚举、注解
@Target(ElementType.FIELD)——字段、枚举的常量
@Target(ElementType.METHOD)——方法
@Target(ElementType.PARAMETER)——方法参数
@Target(ElementType.CONSTRUCTOR) ——构造函数
@Target(ElementType.LOCAL_VARIABLE)——局部变量
@Target(ElementType.ANNOTATION_TYPE)——注解
@Target(ElementType.PACKAGE)——包
@Retention:注解的保留位置
RetentionPolicy.SOURCE:这种类型的Annotations只在源代码级别保留,编译时就会被忽略,在class字节码文件中不包含。
RetentionPolicy.CLASS:这种类型的Annotations编译时被保留,默认的保留策略,在class文件中存在,但JVM将会忽略,运行时无法获得。
RetentionPolicy.RUNTIME:这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用。
@Document:说明该注解将被包含在javadoc中
@Inherited:说明子类可以继承父类中的该注解
Spring Aop实例@Around、@Before、@After、@AfterReturning 、@AfterThrowing注解方式配置:https://blog.csdn.net/zhangsweet1991/article/details/83859026?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase
(3)常见问题
不同aspectj的处理:切点相同,注意处理顺序
注意点:
如果在同一个 aspect 类中,针对同一个 pointcut,定义了两个相同的advice(定义了两个@Before),那么这两个advice的执行顺序是无法确定的,就算给 advice 添加了 @Order这个注解,也无法确定执行顺序。
对于@Around这个advice,不管它有没有返回值,但是必须要方法内部,调用一下pjp.proceed();否则,Controller 中的接口将没有机会被执行,从而也导致了@Before这个advice不会被触发。
参考链接:https://blog.csdn.net/u011781521/article/details/80528049
深入剖析动态代理--性能比较
https://blog.csdn.net/kingson_wu/article/details/50864637
日志处理的是接口正常结束后的数据:当接口中抛出异常后方法终止,则日志无法正常打印。因此需要设定断点,判断异常通知是否正常执行,如果非正常执行则设置断点查看语句块中的语法是否存在隐藏的问题