一般来说在我们logback.xml配置文件中,root的logger一般会引用
两个类型的Appender,一个是ConsoleAppender,一个是RollingFileAppender,
ConsoleAppender负责将logger的日志信息写到Console控制台。其定义如下
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.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>
而RollingFileAppender则是按照策略滚动的生成归档日志文件,定义如下
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名-->
<fileNamePattern>${LOG_HOME}/%d{yyyy-MM-dd}/info_%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!--日志文件保留天数-->
<maxHistory>30</maxHistory>
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!-- or whenever the file size reaches 100MB -->
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder class="ch.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之后,就可以在不同的logger去ref的引用就可以了,如下的root logger引用定义如下
<root level="INFO">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE"/>
</root>
由于我们在root的logger上ref了这两个appender,那么所有的logger都会继承这两个Appender。
但是在RollingFileAppender有个小瑕疵,我们知道Appender的doAppend方法会将事件写入到outputStream,一路封装之后,在RollingFileAppender会调用
subAppend方法,如下,根据事件驱动,先检查是否要归档,如果不要直接的调用super.subAppend(event)
@Override
protected void subAppend(E event) {
// The roll-over check must precede actual writing. This is the
// only correct behavior for time driven triggers.
// We need to synchronize on triggeringPolicy so that only one rollover
// occurs at a time
synchronized (triggeringPolicy) {
if (triggeringPolicy.isTriggeringEvent(currentlyActiveFile, event)) {
rollover();
}
}
super.subAppend(event);
}
在OutputStreamAppender类中
protected void subAppend(E event) {
if (!isStarted()) {
return;
}
try {
// this step avoids LBCLASSIC-139
if (event instanceof DeferredProcessingAware) {
((DeferredProcessingAware) event).prepareForDeferredProcessing();
}
// the synchronization prevents the OutputStream from being closed while we
// are writing. It also prevents multiple threads from entering the same
// converter. Converters assume that they are in a synchronized block.
// lock.lock();
byte[] byteArray = this.encoder.encode(event);
writeBytes(byteArray);
} catch (IOException ioe) {
// as soon as an exception occurs, move to non-started state
// and add a single ErrorStatus to the SM.
this.started = false;
addStatus(new ErrorStatus("IO failure in appender", this, ioe));
}
}
核心的语句就两条,
//格式化日志事件
byte[] byteArray = this.encoder.encode(event);
//写到outputStream里面去
writeBytes(byteArray);
而writeBytes代码如下
private void writeBytes(byte[] byteArray) throws IOException {
if(byteArray == null || byteArray.length == 0)
return;
lock.lock();
try {
this.outputStream.write(byteArray);
if (immediateFlush) {
this.outputStream.flush();
}
} finally {
lock.unlock();
}
}
如下的核心语句,我们可以把immediateFlush设置为false,这样就不会立即刷盘了,默认是true,性能不好。
this.outputStream.write(byteArray);
if (immediateFlush) {
this.outputStream.flush();
}
但是设置成false之后,我们是否也能灵活的控制刷盘策略呢,是可以的,如下
我们定义一个AsyncFlushRollingFileAppender继承于RollingFileAppender,重写start方法,如下
@Override
public void start() {
super.start();
setImmediateFlush(true);
if (schedule == null) {
synchronized (this) {
if (schedule == null) {
this.schedule = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder()
.setNameFormat("asyncFlush-pool-%d").setDaemon(true).build());
schedule.scheduleWithFixedDelay(() -> {
lock.lock();
try {
if (getOutputStream() != null ) {
getOutputStream().flush();
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
lock.unlock();
}
}, 1000, 1000L, TimeUnit.MILLISECONDS);
}
}
}
}
这样我们使用线程池,做到了一秒刷新一次。
只需要在logback-spring.xml文件里面添加如下的配置就可以了
<encoder>
<ImmediateFlush>false</ImmediateFlush>
<pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] ${LOG_LEVEL_PATTERN:-%p} ${PID:- } [%traceId] [%t] [%logger] : %m%n</pattern>
<charset>UTF-8</charset>
</encoder>
如上我们通过自定义的Appender灵活的实现刷盘的策略。当然上述自定义Appender还是比较的弱,比如在日志量很大的时候,我们如何实现自定义的丢弃策略且有灵活的缓冲策略的Appender,很显然上面的这个自定义Appender是做不到的。后面我们会自定义一个更加灵活的Appender来实现前面的场景。









网友评论