1.SpringBoot整合log4j日志系统
大家都知道程序员敲代码是非常爽的,把自己头脑里的想法行云流水的变成一行行代码敲出来,但是!!!每次找BUG确实非常痛苦,那么在程序中记录日志就是很有必要的了,今天我就要教大家如何在SpringBoot整合log4j日志系统。
1.首先把SpringBoot自带的日志系统剔除
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
2.添加日志框架依赖
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
</dependency>
3.添加log4j.properties
#把指定级别以上的日志信息输出到指定的一个或者多个位置
log4j.rootLogger=DEBUG,stdout,file
#表示Logger会在父Logger的appender里输出,默认为true
log4j.additivity.org.apache=true
#表示日志在控制台上输出 (1)org.apache.log4j.ConsoleAppender(控制台)
#(2)org.apache.log4j.FileAppender(文件)
#(3)org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件)
#(4)org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件)
#(5)org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方)
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
#日志级别是INFO级别的,除了DEBUG以外都会输出
log4j.appender.stdout.threshold=INFO
# 日志的输出格式:(1)org.apache.log4j.HTMLLayout(以HTML表格形式布局)
#(2)org.apache.log4j.PatternLayout(可以灵活地指定布局模式)
#(3)org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串)
#(4)org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等等信息)
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
# 日志的具体输出格式:格式化符号说明:
#
#%p:输出日志信息的优先级,即DEBUG,INFO,WARN,ERROR,FATAL。
#%d:输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,如:%d{yyyy/MM/dd HH:mm:ss,SSS}。
#%r:输出自应用程序启动到输出该log信息耗费的毫秒数。
#%t:输出产生该日志事件的线程名。
#%l:输出日志事件的发生位置,相当于%c.%M(%F:%L)的组合,包括类全名、方法、文件名以及在代码中的行数。例如:test.TestLog4j.main(TestLog4j.java:10)。
#%c:输出日志信息所属的类目,通常就是所在类的全名。
#%M:输出产生日志信息的方法名。
#%F:输出日志消息产生时所在的文件名称。
#%L::输出代码中的行号。
#%m::输出代码中指定的具体日志信息。
#%n:输出一个回车换行符,Windows平台为"rn",Unix平台为"n"。
#%x:输出和当前线程相关联的NDC(嵌套诊断环境),尤其用到像java servlets这样的多客户多线程的应用中。
#%%:输出一个"%"字符。
log4j.appender.stdout.layout.ConversionPattern=%-5p %c{1}:%L - %m%n
log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.DatePattern='.'yyyy-MM-dd-HH-mm
log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
log4j.appender.file.Threshold=INFO
log4j.appender.file.append=true
log4j.appender.file.File=/workspaces/logs/foodie-api/imooc.log
2.通过aop实现通过日志实现监听service执行时间
1.aop是什么
AOP是Spring框架面向切面的编程思想,AOP采用一种称为“横切”的技术,将涉及多业务流程的通用功能抽取并单独封装,形成独立的切面,在合适的时机将这些切面横向切入到业务流程指定的位置中。
例如,在一个业务系统中,用户登录是基础功能,凡是涉及到用户的业务流程都要求用户进行系统登录。如果把用户登录功能代码写入到每个业务流程中,会造成代码冗余,维护也非常麻烦,当需要修改用户登录功能时,就需要修改每个业务流程的用户登录代码,这种处理方式显然是不可取的。比较好的做法是把用户登录功能抽取出来,形成独立的模块,当业务流程需要用户登录时,系统自动把登录功能切入到业务流程中。
2.在项目中使用Aop,添加aop的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
3.创建切面类
要想把一个类变成切面类,需要两步,
① 在类上使用 @Component 注解 把切面类加入到IOC容器中
② 在类上使用 @Aspect 注解 使之成为切面类
4.AOP支持的通知
1、前置通知@Before:在某连接点之前执行的通知,除非抛出一个异常,否则这个通知不能阻止连接点之前的执行流程。
2、后置通知@AfterReturning:在某连接点之后执行的通知,通常在一个匹配的方法返回的时候执行(可以在后置通知中绑定返回值)
3、后置异常通知@AfterThrowing:在方法抛出异常退出时执行的通知。
4、后置最终通知@After:当某连接点退出时执行的通知(不论是正常返回还是异常退出)。
5、环绕通知@Around:包围一个连接点的通知,如方法调用等。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为,它也会选择是否继续执行连接点或者直接返回它自己的返回值或抛出异常来结束执行。
5.切入点表达式
定义切入点的时候需要一个包含名字和任意参数的签名,还有一个切入点表达式,如execution(public * com.example.aop…(…))
切入点表达式的格式:execution([可见性]返回类型[声明类型].方法名(参数)[异常])
其中[]内的是可选的,其它的还支持通配符的使用:
- *:匹配所有字符
- …:一般用于匹配多个包,多个参数
- +:表示类及其子类
4)运算符有:&&,||,!
6.通过aop实现通过日志实现监听service执行时间代码
package com.test.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class ServiceLogAspect {
final static Logger log = LoggerFactory.getLogger(ServiceLogAspect.class);
/**
* AOP通知:
* 1. 前置通知:在方法调用之前执行
* 2. 后置通知:在方法正常调用之后执行
* 3. 环绕通知:在方法调用之前和之后,都分别可以执行的通知
* 4. 异常通知:如果在方法调用过程中发生异常,则通知
* 5. 最终通知:在方法调用之后执行
*/
/**
* 切面表达式:
* execution 代表所要执行的表达式主体
* 第一处 * 代表方法返回类型 *代表所有类型
* 第二处 包名代表aop监控的类所在的包
* 第三处 .. 代表该包以及其子包下的所有类方法
* 第四处 * 代表类名,*代表所有类
* 第五处 *(..) *代表类中的方法名,(..)表示方法中的任何参数
*
* @param joinPoint
* @return
* @throws Throwable
*/
@Around("execution(* com.imooc.service.impl..*.*(..))")
public Object recordTimeLog(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("====== 开始执行 {}.{} ======",
joinPoint.getTarget().getClass(),
joinPoint.getSignature().getName());
// 记录开始时间
long begin = System.currentTimeMillis();
// 执行目标 service
Object result = joinPoint.proceed();
// 记录结束时间
long end = System.currentTimeMillis();
long takeTime = end - begin;
if (takeTime > 3000) {
log.error("====== 执行结束,耗时:{} 毫秒 ======", takeTime);
} else if (takeTime > 2000) {
log.warn("====== 执行结束,耗时:{} 毫秒 ======", takeTime);
} else {
log.info("====== 执行结束,耗时:{} 毫秒 ======", takeTime);
}
return result;
}
}