log4j已成为大型系统必不可少的一部分,log4j可以很方便的帮助我们在程序的任何位置输出所要打印的信息,便于我们对系统在调试阶段和正式运行阶段对问题分析和定位。
1.日志级从低到高:debug
,info
,warn
,error
, 依次升高,性能越高;
2.影响输出性能条件:
根据不同场景选择不同的日志级别,一般按环境划分(开发,测试,生产),开发需要足够的调试日志信息,线上只需要一些关键的warn/更高级别的日志,避免日志过多或影响性能。
总的来说,slf4j就是众多日志接口的集合(接口定义),不负责具体的日志实现,只在编译时寻找合适的日志系统进行绑定;
外观模式:外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面;
SLF4j的logger需要直接或间接指向实际依赖的日志系统的logger,通过实现LoggerFactoryBinder接口完成日志实现的绑定;
【强制】应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架 SLF4J 中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(Abc.class);
保证不会创建多个日志系统的logger,会选择绑定其中一个(根据配置文件,无配置文件则根据pom引入顺序)
lombok slf4j
1.找到log.info()
@Override
public void info(final Marker marker, final String s, final Object o, final Object o1) {
logger.logIfEnabled(FQCN, Level.INFO, getMarker(marker), s, o, o1);
}
2.进一步查看:
private void logMessageSafely(final String fqcn, final Level level, final Marker marker, final Message msg,
final Throwable throwable) {
try {
logMessage(fqcn, level, marker, msg, throwable);
} finally {
// LOG4J2-1583 prevent scrambled logs when logging calls are nested (logging in toString())
ReusableMessageFactory.release(msg);
}
}
3.从日志中读取要输出到appender的策略(从log4j2.xml
中解析),执行log操作:
@Override
public void logMessage(final String fqcn, final Level level, final Marker marker, final Message message,
final Throwable t) {
final Message msg = message == null ? new SimpleMessage(Strings.EMPTY) : message;
final ReliabilityStrategy strategy = privateConfig.loggerConfig.getReliabilityStrategy();
strategy.log(this, getName(), fqcn, marker, level, msg, t);
}
4.这时会发现打印日志却是由config去执行的(黑人问号脸❓):
@Override
public void log(final Supplier<LoggerConfig> reconfigured, final String loggerName, final String fqcn,
final Marker marker, final Level level, final Message data, final Throwable t) {
final LoggerConfig config = getActiveLoggerConfig(reconfigured);
try {
config.log(loggerName, fqcn, marker, level, data, t);
} finally {
config.getReliabilityStrategy().afterLogEvent();
}
}
5.查看具体实现:
@PerformanceSensitive("allocation")
public void log(final String loggerName, final String fqcn, final Marker marker, final Level level,
final Message data, final Throwable t) {
List<Property> props = null;
if (!propertiesRequireLookup) {
props = properties;
} else {
if (properties != null) {
props = new ArrayList<>(properties.size());
final LogEvent event = Log4jLogEvent.newBuilder()
.setMessage(data)
.setMarker(marker)
.setLevel(level)
.setLoggerName(loggerName)
.setLoggerFqcn(fqcn)
.setThrown(t)
.build();
for (int i = 0; i < properties.size(); i++) {
final Property prop = properties.get(i);
final String value = prop.isValueNeedsLookup() // since LOG4J2-1575
? config.getStrSubstitutor().replace(event, prop.getValue()) //
: prop.getValue();
props.add(Property.createProperty(prop.getName(), value));
}
}
}
final LogEvent logEvent = logEventFactory.createEvent(loggerName, marker, fqcn, level, data, props, t);
try {
log(logEvent);
} finally {
// LOG4J2-1583 prevent scrambled logs when logging calls are nested (logging in toString())
ReusableLogEventFactory.release(logEvent);
}
}
6.经过一系列标志位确认,过滤器确定是否输出appender日志,以AsyncAppender为例进行进一步实现分析,实际写日志的操作,执行步骤有:
@Override
public void append(final LogEvent logEvent) {
if (!isStarted()) {
throw new IllegalStateException("AsyncAppender " + getName() + " is not active");
}
if (!Constants.FORMAT_MESSAGES_IN_BACKGROUND) {
// LOG4J2-898: user may choose
logEvent.getMessage().getFormattedMessage(); // LOG4J2-763: ask message to freeze parameters
}
final Log4jLogEvent memento = Log4jLogEvent.createMemento(logEvent, includeLocation);
if (!transfer(memento)) {
if (blocking) {
// delegate to the event router (which may discard, enqueue and block, or log in current thread)
final EventRoute route = asyncQueueFullPolicy.getRoute(thread.getId(), memento.getLevel());
route.logMessage(this, memento);
} else {
error("Appender " + getName() + " is unable to write primary appenders. queue is full");
logToErrorAppenderIfNecessary(false, memento);
}
}
}
处处皆异步,还委托小三来干活。
7.执行线程实现日志异步输出:
@Override
public void run() {
while (!shutdown) {
LogEvent event;
try {
event = queue.take();
if (event == SHUTDOWN) {
shutdown = true;
continue;
}
} catch (final InterruptedException ex) {
break; // LOG4J2-830
}
event.setEndOfBatch(queue.isEmpty());
final boolean success = callAppenders(event);
if (!success && errorAppender != null) {
try {
errorAppender.callAppender(event);
} catch (final Exception ex) {
// Silently accept the error.
}
}
}
// Process any remaining items in the queue.
LOGGER.trace("AsyncAppender.AsyncThread shutting down. Processing remaining {} queue events.",
queue.size());
int count = 0;
int ignored = 0;
while (!queue.isEmpty()) {
try {
final LogEvent event = queue.take();
if (event instanceof Log4jLogEvent) {
final Log4jLogEvent logEvent = (Log4jLogEvent) event;
logEvent.setEndOfBatch(queue.isEmpty());
callAppenders(logEvent);
count++;
} else {
ignored++;
LOGGER.trace("Ignoring event of class {}", event.getClass().getName());
}
} catch (final InterruptedException ex) {
// May have been interrupted to shut down.
// Here we ignore interrupts and try to process all remaining events.
}
}
LOGGER.trace("AsyncAppender.AsyncThread stopped. Queue has {} events remaining. "
+ "Processed {} and ignored {} events since shutdown started.", queue.size(), count, ignored);
}
看到这些操作套路可能会有些熟悉,【添加任务到队列中(workers池子)->根据相应策略->由异步线程(thread)去执行】,与java线程池的设计较为相似;
AsyncAppender采用的是生产者消费者的模型,异步地将Logging Event送到对应的Appender中。
8.AsyncLogger的logMsessage
方法,通过日志分发确定是否使用ThreadLocal对象,这里以org.apache.logging.log4j.core.async.AsyncLogger#logWithThreadLocalTranslator
为例:
private void logWithThreadLocalTranslator(final String fqcn, final Level level, final Marker marker,
final Message message, final Throwable thrown) {
// Implementation note: this method is tuned for performance. MODIFY WITH CARE!
final RingBufferLogEventTranslator translator = getCachedTranslator();
initTranslator(translator, fqcn, level, marker, message, thrown);
initTranslatorThreadValues(translator);
publish(translator);
}
log4j2的异步模式表现了绝对的性能优势,优势主要得益于Disruptor框架的使用
LMAX Disruptor technology. Asynchronous Loggers internally use the Disruptor, a lock-free inter-thread communication library, instead of queues, resulting in higher throughput and lower latency.
即一个无锁的线程间通信库代替了原来的队列
调用操作,当发布者将事件发送到RingBuffer时即调用
最终调用:
异步模式+Disruptor