logback和slf4j的关系
刚开始接触日志,公司一般都采用springboot中去整合slf4j和logback来去开发。提及它们的关系,发现我们更多是在代码层面上操作slf4j,对于logback我们一般都是去配置。logback是日志内部的实现,而slf4j对外提供操作的接口。
什么场景下使用日志
- 系统日志:打印当前传入的参数和返回的结果
- 操作日志:当前业务操作流水
logback介绍
logback一共三个模块:logback-core、logback-classic 和 logback-access。
logback-core 模块为其他两个模块奠定了基础。
logback-classic 原生实现了SLF4J API。
logback-access 模块与 Tomcat 和 Jetty 等 Servlet 容器集成,以提供 HTTP 访问日志功能。
springboot默认引入logback日志
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
springboot启动的日志分析
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.3.3.RELEASE)
2022-04-19 13:59:18.086 INFO 4444 --- [ main] c.s.e.EurekaServerApplication : No active profile set, falling back to default profiles: default
2022-04-19 13:59:18.792 WARN 4444 --- [ main] o.s.boot.actuate.endpoint.EndpointId : Endpoint ID 'service-registry' contains invalid characters, please migrate to a valid format.
2022-04-19 13:59:18.903 INFO 4444 --- [ main] o.s.cloud.context.scope.GenericScope : BeanFactory id=7006a679-7862-3a5a-adca-267c2acdc775
2022-04-19 13:59:18.086 日期时间
INFO 日志等级
4444 进程id
main 线程名称
c.s.e.EurekaServerApplication 日志所在类名称
No active profile set, falling back to default profiles: default 日志内容
引入自定义的logback配置文件
logging:
config: classpath:logback.xml
logback自定义Layout
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<appender name="outConsole" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>
%30(%green(%date{yyyy-MM-dd HH:mm:ss.SSS}) %highlight(%-5level) [%thread]) %cyan(%logger{32}.%method) - %msg%n
</pattern>
</layout>
</appender>
<root level="INFO">
<appender-ref ref="outConsole"/>
</root>
</configuration>
logback输出到文件
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<appender name="outInfoFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>DENY</onMatch>
<onMismatch>ACCEPT</onMismatch>
</filter>
<encoder>
<pattern>
%30(%green(%date{yyyy-MM-dd HH:mm:ss.SSS}) %highlight(%-5level) [%thread]) %cyan(%logger{32}.%method) - %msg%n
</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>log/eureka-provider.%d.log</fileNamePattern>
</rollingPolicy>
</appender>
<root level="INFO">
<appender-ref ref="outInfoFile"/>
</root>
</configuration>
rollingPolicy:滚动记录,就是按照指定时间分割记录
logback配置滚动记录设置
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<appender name="outErrorFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<encoder>
<pattern>
%30(%green(%date{yyyy-MM-dd HH:mm:ss.SSS}) %highlight(%-5level) [%thread]) %cyan(%logger{32}.%method) - %msg%n
</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>log/error.%d.log</fileNamePattern>
<!--控制保留的归档文件的最大数量,超出数量就删除旧文件,假设设置每个月滚动,
且maxHistory是1,则只保存最近1个月的文件,删除之前的旧文件-->
<MaxHistory>1</MaxHistory>
</rollingPolicy>
</appender>
<root level="INFO">
<appender-ref ref="outErrorFile"/>
</root>
</configuration>
logback配置生产环境
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 日志文件名 -->
<property name="LOG_FILE" value="sys-log" />
<!-- 日志文件路径 -->
<property name="LOG_PATH" value="D://log//dev" />
<!-- 控制台日志输出格式 -->
<property name="LOG_PATTERN_CONSOLE" value="%30(%boldGreen(%d{yyyy-MM-dd HH:mm:ss.SSS}) %highlight(%-5level) %magenta([%thread])) %boldCyan(%logger{32}.%method) - %yellow(%msg%n)" />
<!-- 文件日志输出格式,不要配置颜色样式,会造成乱码 -->
<property name="LOG_PATTERN_FILE" value="%30(%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread]) %logger{32}.%method - %msg%n" />
<!--- 设置控制台日志 -->
<appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${LOG_PATTERN_CONSOLE}</pattern>
</encoder>
</appender>
<!-- 设置日志文件 -->
<appender name="fileLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>${LOG_PATTERN_FILE}</pattern>
</encoder>
<!-- 总文件日志 -->
<file>${LOG_PATH}/${LOG_FILE}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 每天的文件日志 -->
<fileNamePattern>${LOG_PATH}/${LOG_FILE}-%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志文件保存30天,超过30天的自动删除 -->
<maxHistory>30</maxHistory>
<!--启动项目后清理历史日志-->
<cleanHistoryOnStart>true</cleanHistoryOnStart>
<!-- 日志总大小 -->
<totalSizeCap>20GB</totalSizeCap>
<!-- maxFileSize:这是活动文件的大小,默认值是10MB -->
<maxFileSize>20MB</maxFileSize>
</rollingPolicy>
</appender>
<!-- 设置日志级别,及需要记录日志的类 -->
<root level="INFO">
<appender-ref ref="consoleLog" />
<appender-ref ref="fileLog" />
</root>
</configuration>
配置多源日志
思路:利用springboot提供的多种环境配置的切换来实现对不同日志的配置。
## application.yml
spring:
application:
name: authority-service
profiles:
active: dev
## application-dev.yml
logging:
config: classpath:logback-dev.xml
实现操作日志
自定义注解:
/**
* 日志操作注解
*/
@Documented
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LogOperation {
/**
* 操作所属的模块
* @return
*/
public String module() default "";
/**
* 操作
* @return
*/
public String operation() default "";
}
注解处理:
@Slf4j
@Component
@Aspect
public class LogAspect {
@Autowired
private MachineBO machine;
@Autowired
private LogMapper logMapper;
@Pointcut("@annotation(com.sunpy.permissionservice.log.LogOperation)")
public void logPoint() {
}
@AfterReturning(pointcut = "logPoint()", returning = "result")
public void doLog(JoinPoint joinPoint, Object result) {
// 从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取切入点所在的方法
Method method = signature.getMethod();
// 获取请求的类名
String className = joinPoint.getTarget().getClass().getName();
LogOperation logOperation = method.getAnnotation(LogOperation.class);
Log log = new Log();
log.setLogModule(logOperation.module());
log.setLogOperation(logOperation.operation());
log.setLogClazz(className);
log.setLogMethod(method.getName());
log.setLogMethodResult(method.getReturnType().getName());
StringBuilder sb = new StringBuilder();
for (Parameter parameter : method.getParameters()) {
sb.append(parameter.getParameterizedType().getTypeName());
sb.append(",");
}
log.setLogMethodParam(sb.toString());
insertLog(log);
}
private void insertLog(Log log) {
long logId = new SnowFlakeIdUtil(machine.getWorkerId(), machine.getDatacenterId()).genNextId();
log.setLogId(logId);
log.setCreateTime(TimeUtil.getLocalDateTime());
logMapper.insert(log);
}
}
分布式日志系统
- 上面的方式部署到单台机器:
单台机器的日志如果发生暴增,那么我们需要将大日志文件切割。 - 上面的方式部署到多台机器:
因为负载均衡,会将请求分发到不同的机器上,所以每台机器都会存有一个日志,这样如果我们排查问题时,需要先找到处理请求的机器,然后查看日志,这样排查日志就麻烦一些。 - 实现方式1:Elasticsearch + Logstash + filebeat + redis架构
- 实现方式2:Elasticsearch + Logstash + filebeat + kafka + Kibana架构