怎样手机做网站教程,wordpress密码解密,安阳logo设计公司,html怎么自己做网站前言
最新做项目#xff0c;发现一些历史遗留问题#xff0c;典型的是日志打印的配置问题#xff0c;其实都是些简单问题#xff0c;但是往往简单问题引起严重的事故#xff0c;比如日志打印阻塞工作线程#xff0c;以logback和log4j2为例。logback实际上是springboot的…前言
最新做项目发现一些历史遗留问题典型的是日志打印的配置问题其实都是些简单问题但是往往简单问题引起严重的事故比如日志打印阻塞工作线程以logback和log4j2为例。logback实际上是springboot的官方默认日志实现框架承载SLF4J-API所以基于java开发的云原生项目基本上就是logback打印日志logback异步appender日志的打印架构 可以看到consoleAppender实际上也是异步(非同步)的范畴
准备demo
springboot的demo这个可以用Spring官方的脚手架生成 其中启动的console日志就是logback打印的虽然我们没有配置logback的xml。而现实情况是需要配置文件的毕竟需要异步打印日志日志切割保存时间等都需要配置关键还要日志格式。
?xml version1.0 encodingUTF-8?
configuration debugfalse!--定义日志文件的存储地址 可以使用环境变量或者系统变量占位--property nameLOGBACK_HOME value/opt/xxx /!--控制台输出 --appender nameSTDOUT classch.qos.logback.core.ConsoleAppenderencoder classch.qos.logback.classic.encoder.PatternLayoutEncoder!--格式化输出%d表示日期%thread表示线程名%-5level级别从左显示5个字符宽度,%msg日志消息%n是换行符--pattern%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n/pattern/encoder/appender!-- 文件 滚动日志 --appender namefileLog classch.qos.logback.core.rolling.RollingFileAppender!-- 当前日志输出路径、文件名 --file${log.path}/app.log/file!--日志输出格式--encoderpattern%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n/patterncharsetUTF-8/charset/encoder!--历史日志归档策略--rollingPolicy classch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy!-- 历史日志 归档文件名 --fileNamePattern${log.path}/app.%d{yyyy-MM-dd}.%i.log.gz/fileNamePattern!--单个文件的最大大小--maxFileSize200MB/maxFileSize!--日志文件保留天数--maxHistory10/maxHistory/rollingPolicy/appender!-- 文件 异步日志(async) --appender nameASYNC_LOG classch.qos.logback.classic.AsyncAppender !-- 不丢失日志.默认的256/5,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 --discardingThreshold0/discardingThreshold!-- 更改默认的队列的长度,默认值为256 --queueSize1024/queueSizeneverBlocktrue/neverBlock!-- 添加附加的appender,最多只能添加一个 --appender-ref reffileLog //appender!--自定义日志级别 myibatis可以输出SQL--logger namecom.apache.ibatis levelTRACE/logger namejava.sql.Connection levelDEBUG/logger namejava.sql.Statement levelDEBUG/logger namejava.sql.PreparedStatement levelDEBUG/!-- 日志输出级别 --root levelINFOappender-ref refSTDOUT /appender-ref refASYNC_LOG//root
/configuration
笔者参考各个网站简单写了一个xml
原理
那么为什么日志打印会阻塞工作线程了不是异步的嘛吗异步是没错但是异步解耦却是依赖队列不同于分布式MQ本地队列在某些配置时是阻塞的所以异步日志实际上是半同步有点像MySQL的复制原理架构设计实际上很多地方非常相似。如果不想丢日志可以提高消费队列日志线程的数量增加CPU资源消耗。
参考logback官方文档Chapter 4: Appenders 这个就是前言的代码分析console也是属于异步范畴。
AsyncAppender配置如下 这里关键有3个配置
queueSize队列的大小默认256discardingThreshold队列数量剩余数达到或者小于就丢弃TRACE DEBUG INFO日志neverBlock从不阻塞队列满就丢日志 最简单的架构图写文件是异步线程但是写queue是同步的
源码分析 ch.qos.logback.classic.AsyncAppender关键还是父类ch.qos.logback.core.AsyncAppenderBase
/*** In order to optimize performance this appender deems events of level TRACE,* DEBUG and INFO as discardable. See the* a hrefhttp://logback.qos.ch/manual/appenders.html#AsyncAppenderchapter* on appenders/a in the manual for further information.*** author Ceki Guuml;lcuuml;* since 1.0.4*/
public class AsyncAppender extends AsyncAppenderBaseILoggingEvent {boolean includeCallerData false;/*** Events of level TRACE, DEBUG and INFO are deemed to be discardable.* 定义丢弃日志的级别文档写的就是这里实现的* * param event* return true if the event is of level TRACE, DEBUG or INFO false otherwise.*/protected boolean isDiscardable(ILoggingEvent event) {Level level event.getLevel();return level.toInt() Level.INFO_INT;}protected void preprocess(ILoggingEvent eventObject) {eventObject.prepareForDeferredProcessing();if (includeCallerData)eventObject.getCallerData();}
这个类核心还是按照级别丢日志的定义比如queue能存储的大小少于1/5时那些级别日志丢弃 再看父类启动的时候分析初始值发现queue是
ArrayBlockingQueue 定义了队列discardingThreshold的值注意这个是队列数信号量不是百分比发现一些业务配置20
public static final int DEFAULT_QUEUE_SIZE 256;
int queueSize DEFAULT_QUEUE_SIZE;
默认队列数256建议配置大一点根据内存分配情况过大会OOM 在分析日志入队列的过程 分析
ArrayBlockingQueue /*** Inserts the specified element at the tail of this queue if it is* possible to do so immediately without exceeding the queues capacity,* returning {code true} upon success and {code false} if this queue* is full. This method is generally preferable to method {link #add},* which can fail to insert an element only by throwing an exception.** throws NullPointerException if the specified element is null*/public boolean offer(E e) {Objects.requireNonNull(e);final ReentrantLock lock this.lock;lock.lock();try {if (count items.length)return false;else {enqueue(e);return true;}} finally {lock.unlock();}}
队列数满直接丢弃所以不阻塞丢失日志。
log4j2
log4j2实际上根据各方测试说比logback性能强一些但是也会出现同样的问题
官方文档Log4j – Log4j 2 Appenders apache开源的文档管理要好一些写的很详细而且有详细的说明和示例不过设计原理都差不多
总结
实际上这个问题是使用问题非常简单不过越是简单的使用却可能出现致命问题一般公司都会统一脚手架或者统一规范的方式来实现标准的日志配置文件防止错误配置导致业务问题不知道未来Java虚拟线程大规模使用会不会缓解日志打印阻塞工作线程的问题毕竟调度更优不过如果线程池满载虚拟线程也是无能为力。还是需要在丢日志和存储消费日志的能力作取舍。