一、log4j2简介
log4j2是log4j 1.x和logback的改进版,据说采用了一些新技术(无锁异步、等等),使得日志的吞吐量、性能比log4j 1.x提高10倍,并解决了一些死锁的bug,而且配置更加简单灵活
maven配置
<dependency><groupId>org.apache.logging.log4jgroupId><artifactId>log4j-apiartifactId><version>2.9.1version>
dependency>
<dependency><groupId>org.apache.logging.log4jgroupId><artifactId>log4j-coreartifactId><version>2.9.1version>
dependency>
<dependency><groupId>org.apache.logging.log4jgroupId><artifactId>log4j-webartifactId><version>2.9.1version>
dependency>
<dependency><groupId>org.apache.logging.log4jgroupId><artifactId>log4j-slf4j-implartifactId><version>2.9.1version>
dependency>
<dependency><groupId>org.slf4jgroupId><artifactId>slf4j-apiartifactId><version>1.7.25version>
也可以配置starter
<dependency><groupId>org.springframework.bootgroupId><artifactId>spring-boot-starter-log4j2artifactId>
dependency>
二、log4j2.xml配置
实现类在log4j2.xml
配置文件中的标签名。
xml version&#61;"1.0" encoding&#61;"UTF-8"?>
<configuration status&#61;"WARN" monitorInterval&#61;"30"><properties><property name&#61;"server.port">property>properties><appenders><console name&#61;"Console" target&#61;"SYSTEM_OUT"><PatternLayout pattern&#61;"%d{yyyy-MM-dd HH:mm:ss,SSS} [%thread] %p %m%n"/>console><RollingFile name&#61;"RollingFile" filePattern&#61;"/data/log/tomcat${sys:server.port}/catalina.%d{yyyy-MM-dd}.log"><ThresholdFilter level&#61;"info" onMatch&#61;"ACCEPT" onMismatch&#61;"DENY"/><PatternLayout pattern&#61;"%d{yyyy-MM-dd HH:mm:ss,SSS} [%thread] %p %m%n"/><Policies><TimeBasedTriggeringPolicy interval&#61;"1" modulate&#61;"true"/>Policies><DirectWriteRolloverStrategy/>RollingFile>appenders><loggers><logger name&#61;"org.springframework" level&#61;"INFO">logger><logger name&#61;"org.mybatis" level&#61;"INFO">logger><root level&#61;"INFO"><appender-ref ref&#61;"Console"/><appender-ref ref&#61;"RollingFile"/>root>loggers>
configuration>
简单说Appender就是一个管道&#xff0c;定义了日志内容的去向(保存位置)。
配置一个或者多个Filter进行过滤
配置Layout
来控制日志信息的输出格式。
配置Policies
以控制日志何时(When)进行滚动。
配置Strategy
以控制日志如何(How)进行滚动。
简单说了下配置项&#xff0c;具体可参考博客&#xff1a;https://www.imooc.com/article/78966
https://www.cnblogs.com/hafiz/p/6170702.html
三、log4j2其实现原理
首先介绍下log4j2中的几个重要的概念
LoggerContext
LoggerContext在Logging System中扮演了锚点的角色。根据情况的不同&#xff0c;一个应用可能同时存在于多个有效的LoggerContext中。在同一LoggerContext下&#xff0c;log system是互通的。如&#xff1a;Standalone Application、Web Applications、Java EE Applications、”Shared” Web Applications 和REST Service Containers&#xff0c;就是不同广度范围的log上下文环境。
Configuration
每一个LoggerContext都有一个有效的Configuration。Configuration包含了所有的Appenders、上下文范围内的过滤器、LoggerConfigs以及StrSubstitutor.的引用。在重配置期间&#xff0c;新与旧的Configuration将同时存在。当所有的Logger对象都被重定向到新的Configuration对象后&#xff0c;旧的Configuration对象将被停用和丢弃。
Logger
Loggers 是通过调用LogManager.getLogger方法获得的。Logger对象本身并不实行任何实际的动作。它只是拥有一个name 以及与一个LoggerConfig相关联。它继承了AbstractLogger类并实现了所需的方法。当Configuration改变时&#xff0c;Logger将会与另外的LoggerConfig相关联&#xff0c;从而改变这个Logger的行为。
LoggerConfig
每个LoggerConfig和logger是对应的&#xff0c;获取到一个logger&#xff0c;写日志时其实是通过LoggerConfig来记日志的
1、获取LoggerFactory
和logback一样&#xff0c;slf4j委托具体实现框架的StaticLoggerBinder来返回一个ILoggerFactory&#xff0c;从而对接到具体实现框架上&#xff0c;我们看下这个类(省略了部分代码)
public final class StaticLoggerBinder implements LoggerFactoryBinder {private static final StaticLoggerBinder SINGLETON &#61; new StaticLoggerBinder();private final ILoggerFactory loggerFactory;/*** Private constructor to prevent instantiation*/private StaticLoggerBinder() {loggerFactory &#61; new Log4jLoggerFactory();}/*** Returns the singleton of this class.** &#64;return the StaticLoggerBinder singleton*/public static StaticLoggerBinder getSingleton() {return SINGLETON;}/*** Returns the factory.* &#64;return the factor.*/&#64;Overridepublic ILoggerFactory getLoggerFactory() {return loggerFactory;}
}
可以看到
- 1、通过getSingleton()获取该类的单例
- 2、通过构造函数新建了Log4jLoggerFactory实例&#xff0c;
- 3、通过getLoggerFactory()方法返回该实例
2、获取logger
进入Log4jLoggerFactory类中查看getLogger()方法&#xff0c;发现是在AbstractLoggerAdapter类中
&#64;Overridepublic L getLogger(final String name) {final LoggerContext context &#61; getContext();final ConcurrentMap
1、通过getContext()得到LoggerContext实例
2、在context中查找是否已经有该logger&#xff0c;有就返回
3、如果没有则调用newLogger(name, context)方法新建logger
Log4jLoggerFactory只有两个方法&#xff0c;就是上面说的getContext()和newLogger(name, context)。下面分两节分别讲下这两个方法
public class Log4jLoggerFactory extends AbstractLoggerAdapter
2.1 getContext()
getContext()方法就是返回合适的loggerContext&#xff0c;进入LogManager.getContext()方法
public static LoggerContext getContext() {try {return factory.getContext(FQCN, null, null, true);} catch (final IllegalStateException ex) {LOGGER.warn(ex.getMessage() &#43; " Using SimpleLogger");return new SimpleLoggerContextFactory().getContext(FQCN, null, null, true);}}
factory实在LoggerContext静态代码块中初始化的&#xff0c;继续进入factory.getContext(FQCN, null, null, true)方法中&#xff0c;进入实现类Log4jContextFactory中
&#64;Overridepublic LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext,final boolean currentContext) {final LoggerContext ctx &#61; selector.getContext(fqcn, loader, currentContext);if (externalContext !&#61; null && ctx.getExternalContext() &#61;&#61; null) {ctx.setExternalContext(externalContext);}if (ctx.getState() &#61;&#61; LifeCycle.State.INITIALIZED) {ctx.start();}return ctx;}
LoggerContext是从selector.getContext(fqcn, loader, currentContext)中获取的&#xff0c;此时判断ctx.getState()是否等于LifeCycle.State.INITIALIZED&#xff0c;第一次调用getlogger()时&#xff0c;会进入此方法&#xff0c;我们看下ctx.start();
public void start() {LOGGER.debug("Starting LoggerContext[name&#61;{}, {}]...", getName(), this);if (PropertiesUtil.getProperties().getBooleanProperty("log4j.LoggerContext.stacktrace.on.start", false)) {LOGGER.debug("Stack trace to locate invoker",new Exception("Not a real error, showing stack trace to locate invoker"));}if (configLock.tryLock()) {try {if (this.isInitialized() || this.isStopped()) {this.setStarting();reconfigure();if (this.configuration.isShutdownHookEnabled()) {setUpShutdownHook();}this.setStarted();}} finally {configLock.unlock();}}LOGGER.debug("LoggerContext[name&#61;{}, {}] started OK.", getName(), this);}
进入reconfigure()方法
private void reconfigure(final URI configURI) {final ClassLoader cl &#61; ClassLoader.class.isInstance(externalContext) ? (ClassLoader) externalContext : null;LOGGER.debug("Reconfiguration started for context[name&#61;{}] at URI {} ({}) with optional ClassLoader: {}",contextName, configURI, this, cl);final Configuration instance &#61; ConfigurationFactory.getInstance().getConfiguration(this, contextName, configURI, cl);if (instance &#61;&#61; null) {LOGGER.error("Reconfiguration failed: No configuration found for &#39;{}&#39; at &#39;{}&#39; in &#39;{}&#39;", contextName, configURI, cl);} else {setConfiguration(instance);/** instance.start(); Configuration old &#61; setConfiguration(instance); updateLoggers(); if (old !&#61; null) {* old.stop(); }*/final String location &#61; configuration &#61;&#61; null ? "?" : String.valueOf(configuration.getConfigurationSource());LOGGER.debug("Reconfiguration complete for context[name&#61;{}] at URI {} ({}) with optional ClassLoader: {}",contextName, location, this, cl);}}
我们的配置文件log4j2.xml就是该函数中实现的&#xff0c;ConfigurationFactory.getInstance().getConfiguration(this, contextName, configURI, cl)得到了配置文件&#xff0c;并解析成Configuration。进入setConfiguration(instance)方法&#xff0c;启动当前的configuration&#xff0c;并启动该配置下的所有appender&#xff0c;logger和root。
public Configuration setConfiguration(final Configuration config) {if (config &#61;&#61; null) {LOGGER.error("No configuration found for context &#39;{}&#39;.", contextName);// No change, return the current configuration.return this.configuration;}configLock.lock();try {final Configuration prev &#61; this.configuration;config.addListener(this);final ConcurrentMap
Log4jLogEvent.setNanoClock(configuration.getNanoClock());return prev;} finally {configLock.unlock();}}
2.2 newLogger(name, context)
protected Logger newLogger(final String name, final LoggerContext context) {final String key &#61; Logger.ROOT_LOGGER_NAME.equals(name) ? LogManager.ROOT_LOGGER_NAME : name;return new Log4jLogger(context.getLogger(key), name);}
进入context.getLogger(key)方法
&#64;Overridepublic Logger getLogger(final String name) {return getLogger(name, null);}
&#64;Override
public Logger getLogger(final String name, final MessageFactory messageFactory) {// Note: This is the only method where we add entries to the &#39;loggerRegistry&#39; ivar.Logger logger &#61; loggerRegistry.getLogger(name, messageFactory);if (logger !&#61; null) {AbstractLogger.checkMessageFactory(logger, messageFactory);return logger;}logger &#61; newInstance(this, name, messageFactory);loggerRegistry.putIfAbsent(name, messageFactory, logger);return loggerRegistry.getLogger(name, messageFactory);}
进入newInstance(this, name, messageFactory)方法
protected Logger newInstance(final LoggerContext ctx, final String name, final MessageFactory messageFactory) {return new Logger(ctx, name, messageFactory);}
protected Logger(final LoggerContext context, final String name, final MessageFactory messageFactory) {super(name, messageFactory);this.context &#61; context;privateConfig &#61; new PrivateConfig(context.getConfiguration(), this);}
public PrivateConfig(final Configuration config, final Logger logger) {this.config &#61; config;this.loggerConfig &#61; config.getLoggerConfig(getName());this.loggerConfigLevel &#61; this.loggerConfig.getLevel();this.intLevel &#61; this.loggerConfigLevel.intLevel();this.logger &#61; logger;}
public LoggerConfig getLoggerConfig(final String loggerName) {LoggerConfig loggerConfig &#61; loggerConfigs.get(loggerName);if (loggerConfig !&#61; null) {return loggerConfig;}String substr &#61; loggerName;while ((substr &#61; NameUtil.getSubName(substr)) !&#61; null) {loggerConfig &#61; loggerConfigs.get(substr);if (loggerConfig !&#61; null) {return loggerConfig;}}return root;}
可以看到首先从loggerConfigs也就是配置文件中配置的logger中获取&#xff0c;如果获取不到则循环递归name中"."之前的logger&#xff0c;如果还是获取不到&#xff0c;则默认使用root的配置。
3、logger.info()
Log4jLogger.class
public void info(final String format) {logger.logIfEnabled(FQCN, Level.INFO, null, format);}
&#64;Overridepublic void logIfEnabled(final String fqcn, final Level level, final Marker marker, final String message) {if (isEnabled(level, marker, message)) {logMessage(fqcn, level, marker, message);}}public boolean isEnabled(final Level level, final Marker marker, final String message) {return privateConfig.filter(level, marker, message);}protected void logMessage(final String fqcn, final Level level, final Marker marker, final String message) {final Message msg &#61; messageFactory.newMessage(message);logMessageSafely(fqcn, level, marker, msg, msg.getThrowable());}
可以看到isEnabled()方法中用来通过配置的filter来判断是否符合&#xff0c;如果符合则进入logMessage()方法
protected void logMessage(final String fqcn, final Level level, final Marker marker, final String message) {final Message msg &#61; messageFactory.newMessage(message);logMessageSafely(fqcn, level, marker, msg, msg.getThrowable());}private void logMessageSafely(final String fqcn, final Level level, final Marker marker, final Message msg,final Throwable throwable) {try {logMessageTrackRecursion(fqcn, level, marker, msg, throwable);} finally {// LOG4J2-1583 prevent scrambled logs when logging calls are nested (logging in toString())
ReusableMessageFactory.release(msg);}}private void logMessageTrackRecursion(final String fqcn,final Level level,final Marker marker,final Message msg,final Throwable throwable) {try {incrementRecursionDepth(); // LOG4J2-1518, LOG4J2-2031
tryLogMessage(fqcn, level, marker, msg, throwable);} finally {decrementRecursionDepth();}}
private void tryLogMessage(final String fqcn,final Level level,final Marker marker,final Message msg,final Throwable throwable) {try {logMessage(fqcn, level, marker, msg, throwable);} catch (final Exception e) {// LOG4J2-1990 Log4j2 suppresses all exceptions that occur once application called the logger
handleLogMessageException(e, fqcn, msg);}}
public void logMessage(final String fqcn, final Level level, final Marker marker, final Message message,final Throwable t) {final Message msg &#61; message &#61;&#61; null ? new SimpleMessage(Strings.EMPTY) : message;final ReliabilityStrategy strategy &#61; privateConfig.loggerConfig.getReliabilityStrategy();strategy.log(this, getName(), fqcn, marker, level, msg, t);}
public void log(final Supplier
public void log(final String loggerName, final String fqcn, final Marker marker, final Level level,final Message data, final Throwable t) {List
: prop.getValue();props.add(Property.createProperty(prop.getName(), value));}}}final LogEvent logEvent &#61; logEventFactory.createEvent(loggerName, marker, fqcn, level, data, props, t);try {log(logEvent, LoggerConfigPredicate.ALL);} finally {// LOG4J2-1583 prevent scrambled logs when logging calls are nested (logging in toString())
ReusableLogEventFactory.release(logEvent);}}
protected void log(final LogEvent event, final LoggerConfigPredicate predicate) {if (!isFiltered(event)) { processLogEvent(event, predicate);}}
private void processLogEvent(final LogEvent event, final LoggerConfigPredicate predicate) {event.setIncludeLocation(isIncludeLocation());if (predicate.allow(this)) {callAppenders(event);}logParent(event, predicate);}protected void callAppenders(final LogEvent event) {final AppenderControl[] controls &#61; appenders.get();//noinspection ForLoopReplaceableByForEachfor (int i &#61; 0; i
这时候终于到了appender的处理了&#xff0c;直接定位到RollingFileAppender类中
public void append(final LogEvent event) {getManager().checkRollover(event);super.append(event);}
private void tryAppend(final LogEvent event) {if (Constants.ENABLE_DIRECT_ENCODERS) {directEncodeEvent(event);} else {writeByteArrayToManager(event);}}
protected void directEncodeEvent(final LogEvent event) {getLayout().encode(event, manager);if (this.immediateFlush || event.isEndOfBatch()) {manager.flush();}}
这时候可以看到layout和encode的使用了
public void encode(final StringBuilder source, final ByteBufferDestination destination) {try {final Object[] threadLocalState &#61; getThreadLocalState();final CharsetEncoder charsetEncoder &#61; (CharsetEncoder) threadLocalState[0];final CharBuffer charBuffer &#61; (CharBuffer) threadLocalState[1];final ByteBuffer byteBuffer &#61; (ByteBuffer) threadLocalState[2];TextEncoderHelper.encodeText(charsetEncoder, charBuffer, byteBuffer, source, destination);} catch (final Exception ex) {logEncodeTextException(ex, source, destination);TextEncoderHelper.encodeTextFallBack(charset, source, destination);}}
最后写日志。
四、通过代码动态生成logger对象
public class LoggerHolder {//加个前缀防止配置的name正好是我们某个类名&#xff0c;导致使用的日志路径使用了类名的路径private static final String PREFIX &#61; "logger_";/*** 支持生成写大数据文件的logger** &#64;param name logger name* &#64;return Logger*/public static Logger getLogger(String name) {String loggerName &#61; PREFIX &#43; name;Log4jLoggerFactory loggerFactory &#61; (Log4jLoggerFactory) LoggerFactory.getILoggerFactory();LoggerContext context &#61; (LoggerContext) LogManager.getContext();//如果未加载过该logger,则新建一个if (loggerFactory.getLoggersInContext(context).get(loggerName) &#61;&#61; null) {buildLogger(name);}//
return loggerFactory.getLogger(loggerName);}/*** 包装了Loggerfactory&#xff0c;和LoggerFactory.getLogger(T.class)功能一致** &#64;param clazz* &#64;return*/public static Logger getLogger(Class> clazz) {Log4jLoggerFactory loggerFactory &#61; (Log4jLoggerFactory) LoggerFactory.getILoggerFactory();return loggerFactory.getLogger(clazz.getName());}/*** &#64;param name logger name*/private static void buildLogger(String name) {String loggerName &#61; PREFIX &#43; name;LoggerContext context &#61; (LoggerContext) LogManager.getContext();Configuration configuration &#61; context.getConfiguration();//配置PatternLayout输出格式PatternLayout layout &#61; PatternLayout.newBuilder().withCharset(UTF_8).withPattern("%msg%n").build();//配置基于时间的滚动策略TimeBasedTriggeringPolicy policy &#61; TimeBasedTriggeringPolicy.newBuilder().withInterval(24).build();//配置同类型日志策略DirectWriteRolloverStrategy strategy &#61; DirectWriteRolloverStrategy.newBuilder().withConfig(configuration).build();//配置appenderRollingFileAppender appender &#61; RollingFileAppender.newBuilder().setName(loggerName).withFilePattern("/data/bigdata/" &#43; name &#43; "/" &#43; name &#43; ".%d{yyyyMMdd}.log").setLayout(layout).withPolicy(policy).withStrategy(strategy).withAppend(true).build();//改变appender状态
appender.start();//新建loggerLoggerConfig loggerConfig &#61; new LoggerConfig(loggerName, Level.INFO, false);loggerConfig.addAppender(appender, Level.INFO, null);configuration.addLogger(loggerName, loggerConfig);
context.updateLoggers();}
}