本示例采用SpringBoot项目使用SpringAOP记录日志,Slf4j作为日志门面,Log4j2作为日志实现实,实现开发中的日志记录.
代码具体实现如下 :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<!-- 排除springboot自带的logback框架 -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 引入log4j2依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<!-- SpringAOP启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.4.5</version>
</dependency>
代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<!--status = "warn" 日志框架本身的输入日志级别
monitorInterval = "5" 自动加载配置文件的时间间隔不低于5s
-->
<Configuration status="warn" monitorInterval="5">
<!-- 日志级别以及优先级排序 :
在log4j2中, 共有8个级别,按照从低到高为:ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF。
All:最低等级的,用于打开所有日志记录.
Trace:是追踪,就是程序推进一下.
Debug:指出细粒度信息事件对调试应用程序是非常有帮助的.
Info:消息在粗粒度级别上突出强调应用程序的运行过程.
Warn:输出警告及warn以下级别的日志.
Error:输出错误信息日志.
Fatal:输出每个严重的错误事件将会导致应用程序的退出的日志.
OFF:最高等级的,用于关闭所有日志记录.
程序会打印高于或等于所设置级别的日志,设置的日志等级越高,打印出来的日志就越少。
-->
<!--
集中配置属性进行管理
-->
<Properties>
<!--
定义格式化输出:
%d表示日期,
%thread表示线程名,
%-5level:级别从左显示5个字符宽度
%msg:日志消息,%n是换行符
%logger{36} 表示 Logger 名字最长36个字符
-->
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight{%-5level}[%thread] %style{%logger{36}}{cyan} : %msg%n" />
<!-- 定义日志存储的路径,绝对路径 -->
<property name="FILE_PATH" value="存储的绝对路径" />
<property name="FILE_NAME" value="项目名称" />
</Properties>
<!--日志处理-->
<Appenders>
<!--*********************控制台日志***********************-->
<!--
target: SYSTEM_OUT 或 SYSTEM_ERR,一般只设置默认:SYSTEM_OUT.
-->
<console name="Console" target="SYSTEM_OUT">
<!--输出日志的格式和颜色-->
<PatternLayout pattern="${LOG_PATTERN}" disableAnsi="false" noConsoleNoAnsi="false"/>
<!--控制台只输出level及其以上级别的信息(onMatch)放行,其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
</console>
<!--*********************文件日志***********************-->
<!--按照一定规则查分日志文件的appender /logs/$${date:yyyy-MM-dd}/myrollog-%d{yyyy-MM-dd-HH-mm}-%i.log
/logs:放在logs这个目录下,/$${date:yyyy-MM-dd}:以天为单位生成文件夹
myrollog-%d{yyyy-MM-dd-HH-mm}-%d: 以分钟为单位到达了指定大小在进行拆分
.gz 进行压缩归档
-->
<!--
error 运行时异常日志信息
-->
<RollingFile name = "errorRollingFile" fileName = "${FILE_NAME}/error日志.log"
filePattern = "${FILE_PATH}/$${date:yyyy-MM-dd}/error-%d{yyyy-MM-dd-HH-mm}-%i.log.gz">
<!--日志级别过滤器,文件只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
<!--日志的消息格式-->
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %msg%n"/>
<!--在系统每次启动时,触发拆分规则,生产一个新的日志文件-->
<OnStartupTriggeringPolicy/>
<!--按照文件大小进行拆分-->
<SizeBasedTriggeringPolicy size = "10 MB"/>
<!--按照时间节点进行拆分-->
<TimeBasedTriggeringPolicy/>
<!--在同一个目录下,文件的个数限定为30个,超过按照实际进行覆盖-->
<DefaultRolloverStrategy max="30"/>
</RollingFile>
<!--
fatal 正常运行时日志
-->
<RollingFile name = "fatalRollingFile" fileName = "${FILE_NAME}/fatal日志.log"
filePattern = "${FILE_PATH}/$${date:yyyy-MM-dd}/fatal-%d{yyyy-MM-dd-HH-mm}-%i.log.gz">
<!--日志级别过滤器,文件只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="fatal" onMatch="ACCEPT" onMismatch="DENY"/>
<!--日志的消息格式-->
<PatternLayout pattern="${LOG_PATTERN}"/>
<!--在系统每次启动时,触发拆分规则,生产一个新的日志文件-->
<OnStartupTriggeringPolicy/>
<!--按照文件大小进行拆分-->
<SizeBasedTriggeringPolicy size = "10 MB"/>
<!--按照时间节点进行拆分-->
<TimeBasedTriggeringPolicy/>
<!--在同一个目录下,文件的个数限定为30个,超过按照实际进行覆盖-->
<DefaultRolloverStrategy max="30"/>
</RollingFile>
<!--
info 操作日志
-->
<RollingFile name = "infoRollingFile" fileName = "${FILE_NAME}/info日志.log"
filePattern = "${FILE_PATH}/$${date:yyyy-MM-dd}/info-%d{yyyy-MM-dd-HH-mm}-%i.log.gz">
<!--日志级别过滤器,文件只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
<!--日志的消息格式-->
<PatternLayout pattern="${LOG_PATTERN}"/>
<!--在系统每次启动时,触发拆分规则,生产一个新的日志文件-->
<OnStartupTriggeringPolicy/>
<!--按照文件大小进行拆分-->
<SizeBasedTriggeringPolicy size = "10 MB"/>
<!--按照时间节点进行拆分-->
<TimeBasedTriggeringPolicy/>
<!--在同一个目录下,文件的个数限定为30个,超过按照实际进行覆盖-->
<DefaultRolloverStrategy max="30"/>
</RollingFile>
</Appenders>
<!--Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。-->
<!--然后定义loggers,只有定义了logger并引入的appender,appender才会生效-->
<loggers>
<!--过滤掉spring和mybatis的一些无用的信息-->
<logger name="org.mybatis" level="info" additivity="false">
<AppenderRef ref="Console"/>
</logger>
<!--监控系统信息-->
<!--若是additivity设为false,则 子Logger 只会在自己的appender里输出,而不会在 父Logger 的appender里输出。-->
<Logger name="org.springframework" level="info" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<root level="info">
<!--控制台-->
<appender-ref ref="Console"/>
<!--用户操作文件-->
<appender-ref ref="infoRollingFile"/>
<!--调试错误文件-->
<appender-ref ref="errorRollingFile"/>
<!--正常运行文件-->
<appender-ref ref="fatalRollingFile"/>
</root>
</loggers>
</Configuration>
Log4j2的最大优点就是它的异步Logger,主要就是性能更好,这个这里不做过多解释。
#全局异步日志开启,提高日志性能
Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
在application.properties的SpringBoot主配置文件中添加如下配置 :
我这里是给配置文件添加了个config的包目录,你要改成你自己的路径。
自定义注解 : 主要是作用就是为了自定义方法操作,用于向AOP中添加方法操作的日志信息,既然已经到项目日志阶段那么我相信你也已经不是一个小白了,注解就是一个标注这里也不做过多解释。
//作用在方法上
@Target(ElementType.METHOD)
//运行时
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogAnnotationMethod {
//模块名称
String module() default "";
//操作名称
String operator() default "";
//扩展属性
String value() default "";
}
public class HttpContextUtil {
public static HttpServletRequest getHttpServletRequest(){
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
}
}
用于获取访问的用户IP,并记录到日志中。
@Slf4j
public class IpUtils {
/**
* 获取IP地址
* <p>
* 使用Nginx等反向代理软件, 则不能通过request.getRemoteAddr()获取IP地址
* 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,X-Forwarded-For中第一个非unknown的有效IP字符串,则为真实IP地址
*/
public static String getIpAddr(HttpServletRequest request) {
String ip = null, unknown = "unknown", seperator = ",";
int maxLength = 15;
try {
ip = request.getHeader("x-forwarded-for");
if (StringUtils.isEmpty(ip) || unknown.equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (StringUtils.isEmpty(ip) || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (StringUtils.isEmpty(ip) || unknown.equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (StringUtils.isEmpty(ip) || unknown.equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (StringUtils.isEmpty(ip) || unknown.equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
} catch (Exception e) {
log.error("IpUtils ERROR ", e);
}
// 使用代理,则获取第一个IP地址
if (StringUtils.isEmpty(ip) && ip.length() > maxLength) {
int idx = ip.indexOf(seperator);
if (idx > 0) {
ip = ip.substring(0, idx);
}
}
return ip;
}
/**
* 获取ip地址
*
* @return
*/
public static String getIpAddr() {
HttpServletRequest request = HttpContextUtil.getHttpServletRequest();
return getIpAddr(request);
}
}
此类用于保存和执行具体的日志信息。
/**
* @author 码不多
* @version 1.0
* @description: 此类采用SpringAOP,用于记录日志
*/
//声明AOP类
@Aspect
//声明组件
@Component
//使用Slf4j日志门面
@Slf4j
public class AopLogUtil {
//定义切入点方法 标注这个自定义注解
@Pointcut("@annotation(com.shouzhong.epidemicprevention.annotation.LogAnnotationMethod)")
public void pt(){}
/**
* 功能描述: 执行通知日志的方法
* @author 码不多
* @date 2021/8/16
* @param point
* @return java.lang.Object
*/
//拦截所有的Controller或者RestController或者自定义注解标识的方法
@Around("@within(org.springframework.stereotype.Controller) ||"+
"@within(org.springframework.web.bind.annotation.RestController) ||"+
"pt()"
)
private Object runAndSaveLog(ProceedingJoinPoint point){
//获取获取当前时间
long beginTime = System.currentTimeMillis();
//执行原始方法
Object result = null;
try {
result = point.proceed();
} catch (Throwable throwable) {
//出现异常打印error日志
log.error("Aop中方法出现异常",throwable);
}
//获取方法执行时间
long runtime = System.currentTimeMillis() - beginTime;
//调用保存日志的方法
recordLog(point,runtime);
//将结果返回
return result;
}
/**
* 功能描述: 保存日志的方法
* @author 码不多
* @date 2021/8/16
* @param joinPoint time
* @return void
*/
private void recordLog(ProceedingJoinPoint joinPoint,long time){
//获取类名
Object target = joinPoint.getTarget();
String canonicalName = target.getClass().getCanonicalName();
//获取模块名
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
//获取方法名
Signature signature = joinPoint.getSignature();
String methodName = signature.getName();
//获取请求参数
Object[] args = joinPoint.getArgs();
String params = JSON.toJSONString(args[0]);
//获取request,设置ip地址
HttpServletRequest request = HttpContextUtil.getHttpServletRequest();
//获取IP地址
String ipAddr = IpUtils.getIpAddr(request);
//获取注解对象
LogAnnotationMethod logAnnotation = method.getAnnotation(LogAnnotationMethod.class);
//添加类名到日志,采用占位符赋值
log.info("类名: {}",canonicalName);
//添加模块日志信息,采用占位符赋值,通过注解对象获取注解中的值
log.info("模块名: {}",logAnnotation.module());
//添加方法名到日志,采用占位符赋值
log.info("方法名: {}",methodName);
//添加操作到日志,采用占位符赋值,通过注解对象获取注解中的值
log.info("操作: {}",logAnnotation.operator());
//添加请求参数信息到日志,采用占位符赋值
log.info("请求参数: {}:",params);
//添加ip地址到日志,采用占位符赋值
log.info("ip地址: {}",ipAddr);
//添加执行时间到日志,采用占位符赋值
log.info("执行时间: {} ms",time);
//日志结束
log.info("#####################log End####################");
补充001 :
//不使用IPUtils获取ip和一些其他请求头日志信息的方法: logger.info不在这里定义。还是在上面的代码中
/* // 接收请求,记录请求中的内容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 记录下请求内容
//请求的url
logger.info("URL : " + request.getRequestURL().toString());
//请求的方法类型 : GET、POST...
logger.info("HTTP_METHOD : " + request.getMethod());
//IP地址
logger.info("IP : " + request.getRemoteAddr());
//类方法
logger.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
//参数数组
logger.info("ARGS : " + Arrays.toString(joinPoint.getArgs()));
//获取所有参数方法 :
//获取请求参数们
Enumeration<String> enu=request.getParameterNames();
//遍历
while(enu.hasMoreElements()){
//取出请求参数名
String paraName=(String)enu.nextElement();
//请求的具体参数值
System.out.println(paraName+": "+request.getParameter(paraName));
}
}*/
补充002:
/*
//如果有文件上传的参数MultipartFile类型,为了避免冲突可以给它们添加进集合展示
// 接收请求,记录请求中的内容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
//创建集合存储请求参数
ArrayList paramsList = new ArrayList();
//获取所有参数方法 :
Enumeration<String> enu=request.getParameterNames();
while(enu.hasMoreElements()){
String paraName=(String)enu.nextElement();
//将参数添加到集合
paramsList.add(paraName+": "+request.getParameter(paraName));
}
log.info("请求参数 :{}",paramsList)
*/
}
}
在你需要记录操作日志的Controller层的请求映射的方法上添加你的自定义注解,并将你的方法日志信息添加到你自定义的属性值中。
//自定义注解,声明方法日志
@LogAnnotationMethod(module = "获得信息列表",operator = "分页查询或多条件查询信息需求列表")
@RequestMapping("/InforDS")
public Pagination<InforDemandSide> inforDemandSide(Pagination pagination){
xxxxxxxxxxxxxxxxx;
}
在application.properties的主配置文件中添加如下配置
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/qq_43842093/article/details/122793236
内容来源于网络,如有侵权,请联系作者删除!