java基础教程栏目介绍如何解决Java日志级别,重复记录、丢日志问题,日志配置文件通常很繁杂,很多同学习惯从其他项目或网上博客直接复制份配置文件,但却不仔细研究如何修改。常见错误发生于重复记录日志、同步日志 ...
java基础教程栏目介绍如何解决Java日志级别等问题
1 日志常见错因1.1 日志框架繁多不同类库可能使用不同日志框架,兼容是个难题 1.2 配置复杂且容易出错日志配置文件通常很繁杂,很多同学习惯从其他项目或网上博客直接复制份配置文件,但却不仔细研究如何修改。常见错误发生于重复记录日志、同步日志的性能、异步记录的错误配置。 1.3 日志记录本身就有些误区比如没考虑到日志内容获取的代价、胡乱使用日志级别等。 2 SLF4JLogback、Log4j、Log4j2、commons-logging、JDK自带的java.util.logging等,都是Java体系的日志框架,确实非常多。而不同的类库,还可能选择使用不同的日志框架。这样一来,日志的统一管理就变得非常困难。
虽然可用 虽然图中有4个灰色的日志实现框架,但日常业务使用最多的还是Logback和Log4j,都是同一人开发的。Logback可认为是Log4j改进版,更推荐使用,基本已是主流。 Spring Boot的日志框架也是Logback。那为什么我们没有手动引入Logback包,就可直接使用Logback? spring-boot-starter模块依赖spring-boot-starter-logging模块 3 日志重复记录日志重复记录不但给查看日志和统计工作带来不必要的麻烦,还会增加磁盘和日志收集系统的负担。 logger配置继承关系导致日志重复记录
如此配置的初衷是啥呢? <logger name="org.javaedge.time.commonmistakes.logging" level="DEBUG"/> 若自定义
可设置 错误配置LevelFilter造成日志重复
日志到底为何重复呢? ThresholdFilter源码解析
该案例我们将 ThresholdFilter 置 WARN,因此可记录WARN和ERROR级日志。 LevelFilter用于比较日志级别,然后进行相应处理。
和ThresholdFilter不同,LevelFilter 由于未配置onMatch和onMismatch属性,所以该过滤器失效,导致INFO以上级别日志都记录了。 修正配置LevelFilter的onMatch属性为ACCEPT,表示接收INFO级别的日志;配置onMismatch属性为DENY,表示除了INFO级别都不记录: 如此, 4 异步日志提高性能?知道了到底如何正确将日志输出到文件后,就该考虑如何避免日志记录成为系统性能瓶颈。这可解决,磁盘(比如机械磁盘)IO性能较差、日志量又很大的情况下,如何记录日志问题。 定义如下的日志配置,一共有两个Appender: FILE是一个FileAppender,用于记录所有的日志;
执行程序后可以看到,记录1000次日志和10000次日志的调用耗时,分别是5.1秒和39秒 对只记录文件日志的代码,这耗时过长。 源码解析FileAppender继承自OutputStreamAppender 所以日志大量写入才会旷日持久。如何才能实现大量日志写入时,不会过多影响业务逻辑执行耗时而影响吞吐量呢? AsyncAppender使用Logback的AsyncAppender 即可实现异步日志记录。AsyncAppender类似装饰模式,在不改变类原有基本功能情况下为其增添新功能。这便可把AsyncAppender附加在其他Appender,将其变为异步。 定义一个异步Appender ASYNCFILE,包装之前的同步文件日志记录的FileAppender, 即可实现异步记录日志到文件
异步日志真的如此高性能?并不,因为这并没有记录下所有日志。 AsyncAppender异步日志坑
案例模拟慢日志记录场景:
源码解析
public class AsyncAppender extends AsyncAppenderBase<ILoggingEvent> {
// 是否收集调用方数据
boolean includeCallerData = false;
protected boolean isDiscardable(ILoggingEvent event) {
Level level = event.getLevel();
// 丢弃 ≤ INFO级日志
return level.toInt() <= Level.INFO_INT;
}
protected void preprocess(ILoggingEvent eventObject) {
eventObject.prepareForDeferredProcessing();
if (includeCallerData)
eventObject.getCallerData();
}}public class AsyncAppenderBase<E> extends UnsynchronizedAppenderBase<E> implements AppenderAttachable<E> {
// 阻塞队列:实现异步日志的核心
BlockingQueue<E> blockingQueue;
// 默认队列大小
public static final int DEFAULT_QUEUE_SIZE = 256;
int queueSize = DEFAULT_QUEUE_SIZE;
static final int UNDEFINED = -1;
int discardingThreshold = UNDEFINED;
// 当队列满时:加入数据时是否直接丢弃,不会阻塞等待
boolean neverBlock = false;
@Override
public void start() {
...
blockingQueue = new ArrayBlockingQueue<E>(queueSize);
if (discardingThreshold == UNDEFINED)
//默认丢弃阈值是队列剩余量低于队列长度的20%,参见isQueueBelowDiscardingThreshold方法
discardingThreshold = queueSize / 5;
...
}
@Override
protected void append(E eventObject) {
if (isQueueBelowDiscardingThreshold() && isDiscardable(eventObject)) { //判断是否可以丢数据
return;
}
preprocess(eventObject);
put(eventObject);
}
private boolean isQueueBelowDiscardingThreshold() {
return (blockingQueue.remainingCapacity() < discardingThreshold);
}
private void put(E eventObject) {
if (neverBlock) { //根据neverBlock决定使用不阻塞的offer还是阻塞的put方法
blockingQueue.offer(eventObject);
} else {
putUninterruptibly(eventObject);
}
}
//以阻塞方式添加数据到队列
private void putUninterruptibly(E eventObject) {
boolean interrupted = false;
try {
while (true) {
try {
blockingQueue.put(eventObject);
break;
} catch (InterruptedException e) {
interrupted = true;
}
}
} finally {
if (interrupted) {
Thread.currentThread().interrupt();
}
}
}}默认队列大小256,达到80%后开始丢弃<=INFO级日志后,即可理解日志中为什么只有两百多条INFO日志了。 queueSize 过大可能导致OOM queueSize 较小默认值256就已经算很小了,且discardingThreshold设置为大于0(或为默认值),队列剩余容量少于discardingThreshold的配置就会丢弃<=INFO日志。这里的坑点有两个:
neverBlock 默认false意味总可能会出现阻塞。
queueSize、discardingThreshold和neverBlock三参密不可分,务必按业务需求设置:
以上日志配置最常见两个误区 再看日志记录本身的误区。 使用日志占位符就无需判断日志级别?
若记录DEBUG日志,并设置只记录>=INFO级日志,程序是否也会耗时1秒?
前俩方式都调用slowString,所以都耗时1s。且方式二就是使用占位符记录slowString,这种方式虽允许传Object,不显式拼接String,但也只是延迟(若日志不记录那就是省去)日志参数对象.toString()和字符串拼接的耗时。 本案例除非事先判断日志级别,否则必调用slowString。 除事先判断日志级别,还可通过lambda表达式延迟参数内容获取。但SLF4J的API还不支持lambda,因此需使用Log4j2日志API,把Lombok的@Slf4j注解替换为**@Log4j2**注解,即可提供lambda表达式参数的方法: 这样调用debug,签名Supplier<?>,参数就会延迟到真正需要记录日志时再获取: 所以debug4并不会调用slowString方法 只是换成Log4j2 API,真正的日志记录还是走的Logback,这就是SLF4J适配的好处。 总结
以上就是搞懂Java日志级别,重复记录、丢日志问题的详细内容,更多请关注模板之家(www.mb5.com.cn)其它相关文章! |
