专栏文章参考:
Java Util Logger源码分析
Slf4j源码分析
Logback源码分析
Springboot Logger源码分析
Log4J 是 Apache 的一个开源项目(官网),通过在项目中使用 Log4J,我们可以控制日志信息输出到控制台、文件、GUI 组件、甚至是数据库中。我们可以控制每一条日志的输出格式,通过定义日志的输出级别,可以更灵活的控制日志的输出过程。方便项目的调试。
组成
针对用户侧,Log4J 主要由 Loggers (日志记录器)、Appenders(输出端)和 Layout(日志格式化器)组成。其中 Loggers 控制日志的输出级别与日志是否输出;Appenders 指定日志的输出方式(输出到控制台、文件等);Layout 控制日志信息的输出格式。
架构
本文源码基于1.2.17
Logger UML结构
AppenderAttachable定义了管理Appender的方法,一个Logger中可以添加多个Appender
public interface AppenderAttachable {
// 增
public void addAppender(Appender newAppender);
// 查
public Enumeration getAllAppenders();
public Appender getAppender(String name);
public boolean isAttached(Appender appender);
// 删
void removeAllAppenders();
void removeAppender(Appender appender);
void removeAppender(String name);
}
其唯一实现是AppenderAttachableImpl
public class AppenderAttachableImpl implements AppenderAttachable {
// Appender 容器
protected Vector appenderList;
public void addAppender(Appender newAppender) {
// Null values for newAppender parameter are strictly forbidden.
if(newAppender == null)
return;
if(appenderList == null) {
appenderList = new Vector(1);
}
if(!appenderList.contains(newAppender))
appenderList.addElement(newAppender);
}
}
Category是早期JUL尚未发布之前的日志记录器名称,后期通过继承的方式统一名称为Logger
public class Category implements AppenderAttachable {
// 记录器名称
protected String name;
// 日志级别
volatile protected Level level;
// 父记录器
volatile protected Category parent;
// 国际化
protected ResourceBundle resourceBundle;
// 存放记录器的容器 Repository
protected LoggerRepository repository;
// 所有的Appender管理代理给AppenderAttachableImpl类
AppenderAttachableImpl aai;
// 是否继承父Logger的Appender,默认true
protected boolean additive = true;
protected Category(String name) {
this.name = name;
}
}
// logger
public class Logger extends Category {
private static final String FQCN = Logger.class.getName();
protected Logger(String name) {
super(name);
}
}
// root logger
public final class RootLogger extends Logger {
public RootLogger(Level level) {
super("root");
setLevel(level);
}
}
Appender UML结构
针对不同的日志输出目的地,log4j定义了多个Appender
public interface Appender {
// 过滤器管理
void addFilter(Filter newFilter);
Filter getFilter();
void clearFilters();
// 格式化工具管理
void setLayout(Layout layout);
public Layout getLayout();
public boolean requiresLayout();
// 日志输出
void doAppend(LoggingEvent event);
void close();
public void setName(String name);
String getName();
void setErrorHandler(ErrorHandler errorHandler);
ErrorHandler getErrorHandler();
}
其基础实现AppenderSkeleton
public abstract class AppenderSkeleton implements Appender, OptionHandler {
// 格式化工具
protected Layout layout;
// name
protected String name;
// 日志级别
protected Priority threshold;
protected ErrorHandler errorHandler = new OnlyOnceErrorHandler();
// 过滤器链
protected Filter headFilter;
protected Filter tailFilter;
// 该Appender是否关闭
protected boolean closed = false;
// 日志输出模板方法
public synchronized void doAppend(LoggingEvent event) {
if(closed) {
LogLog.error("Attempted to append to closed appender named ["+name+"].");
return;
}
// 日志级别检查
if(!isAsSevereAsThreshold(event.getLevel())) {
return;
}
// 过滤器检查
Filter f = this.headFilter;
FILTER_LOOP:
while(f != null) {
switch(f.decide(event)) {
// 拒绝
case Filter.DENY: return;
// 进行输出
case Filter.ACCEPT: break FILTER_LOOP;
// 下一个过滤器继续检查
case Filter.NEUTRAL: f = f.getNext();
}
}
// 输出(子类实现)
this.append(event);
}
}
字符流输出WriterAppender
public class WriterAppender extends AppenderSkeleton {
// 立即刷新
protected boolean immediateFlush = true;
// 编码方式
protected String encoding;
// Writer
protected QuietWriter qw;
// 输出
public void append(LoggingEvent event) {
// 参数检查
if(!checkEntryConditions()) {
return;
}
subAppend(event);
}
// outstream 输出
protected void subAppend(LoggingEvent event) {
this.qw.write(this.layout.format(event));
if(layout.ignoresThrowable()) {
String[] s = event.getThrowableStrRep();
if (s != null) {
int len = s.length;
for(int i = 0; i < len; i++) {
this.qw.write(s[i]);
this.qw.write(Layout.LINE_SEP);
}
}
}
// 及时刷新
if(shouldFlush(event)) {
this.qw.flush();
}
}
}
Layout UML结构
Layout
public abstract class Layout implements OptionHandler {
// Note that the line.separator property can be looked up even by
// applets.
public final static String LINE_SEP = System.getProperty("line.separator");
public final static int LINE_SEP_LEN = LINE_SEP.length();
// 格式化
abstract public String format(LoggingEvent event);
public String getContentType() {
return "text/plain";
}
public String getHeader() {
return null;
}
public String getFooter() {
return null;
}
abstract public boolean ignoresThrowable();
}
Layout主要定义了format()
方法,日志输出时会将LogEvent对象交给Layout,其format()
具体格式化为字符串用于输出记录或展示。
LogEvent日志信息
public class LoggingEvent implements java.io.Serializable {
private static long startTime = System.currentTimeMillis();
/** Fully qualified name of the calling category class. */
transient public final String fqnOfCategoryClass;
// 日志记录器Logger
transient private Category logger;
// LoggerName
final public String categoryName;
// 当前日志信息Level
transient public Priority level;
/** The nested diagnostic context (NDC) of logging event. */
private String ndc;
/** The mapped diagnostic context (MDC) of logging event. */
private Hashtable mdcCopy;
private boolean ndcLookupRequired = true;
private boolean mdcCopyLookupRequired = true;
// 日志信息 通常为String
transient private Object message;
private String renderedMessage;
// 线程名
private String threadName;
private ThrowableInformation throwableInfo;
public final long timeStamp;
// 日志发生位置(一般不使用,及耗性能)
private LocationInfo locationInfo;
static final long serialVersionUID = -868428216207166145L;
static final Integer[] PARAM_ARRAY = new Integer[1];
static final String TO_LEVEL = "toLevel";
static final Class[] TO_LEVEL_PARAMS = new Class[] {int.class};
static final Hashtable methodCache = new Hashtable(3); // use a tiny table
public LoggingEvent(String fqnOfCategoryClass, Category logger,
long timeStamp, Priority level, Object message,
Throwable throwable) {
this.fqnOfCategoryClass = fqnOfCategoryClass;
this.logger = logger;
this.categoryName = logger.getName();
this.level = level;
this.message = message;
// 错误信息
if(throwable != null) {
this.throwableInfo = new ThrowableInformation(throwable, logger);
}
this.timeStamp = timeStamp;
}
}
LoggerRepository Logger对象管理器
LoggerRepository
public interface LoggerRepository {
// 记录器管理方法
Logger getLogger(String name);
Logger getLogger(String name, LoggerFactory factory);
Logger getRootLogger();
Logger exists(String name);
Enumeration getCurrentLoggers();
Enumeration getCurrentCategories();
// 日志级别 低于该级别 日志不输出
void setThreshold(Level level);
void setThreshold(String val);
Level getThreshold();
void emitNoAppenderWarning(Category cat);
void shutdown();
void addHierarchyEventListener(HierarchyEventListener listener);
boolean isDisabled(int level);
void fireAddAppenderEvent(Category logger, Appender appender);
void resetConfiguration();
}
LoggerRepository(记录器仓库)主要提供对记录器的管理,使用时直接从仓库中获取,不存在时记录器会自动创建一个日志记录器返回。以及定义仓库中日志记录器的最低日志级别。
Hierarchy - 具体实现
public class Hierarchy implements LoggerRepository, RendererSupport, ThrowableRendererSupport {
// 创建Logger的工厂类
private LoggerFactory defaultFactory;
// 日志增删改查的监听器
private Vector listeners;
// 存放Logger的Map结构
Hashtable ht;
// 根日志对象,所有的Logger会在内存中构建为一个以root为根的树形结构
Logger root;
// 对象渲染格式化容器
RendererMap rendererMap;
// 日志输出级别
int thresholdInt;
Level threshold;
// 没有Appender是否告警
boolean emittedNoAppenderWarning = false;
// 没有国际化资源是否告警
boolean emittedNoResourceBundleWarning = false;
private ThrowableRenderer throwableRenderer = null;
public Hierarchy(Logger root) {
// Logger容器
ht = new Hashtable();
// 监听器
listeners = new Vector(1);
// 根日志
this.root = root;
// Enable all level levels by default.
// 默认输出所有日志
setThreshold(Level.ALL);
//
this.root.setHierarchy(this);
rendererMap = new RendererMap();
// 日志Logger创建工仓
defaultFactory = new DefaultCategoryFactory();
}
// 从仓库中获取Logger
public Logger getLogger(String name, LoggerFactory factory) {
CategoryKey key = new CategoryKey(name);
Logger logger;
synchronized(ht) {
Object o = ht.get(key);
if(o == null) {
// 工厂日志记录器
logger = factory.makeNewLoggerInstance(name);
logger.setHierarchy(this);
// 放入容器中
ht.put(key, logger);
// 设置逻辑父记录器
updateParents(logger);
return logger;
} else if(o instanceof Logger) {
return (Logger) o;
} else if (o instanceof ProvisionNode) {
// 临时存放了子节点
logger = factory.makeNewLoggerInstance(name);
logger.setHierarchy(this);
ht.put(key, logger);
// 设置子节点
updateChildren((ProvisionNode) o, logger);
// 设置父节点
updateParents(logger);
return logger;
}
else {
// It should be impossible to arrive here
return null; // but let's keep the compiler happy -_-.
}
}
}
// 更新父节点
final private void updateParents(Logger cat) {
String name = cat.name;
int length = name.length();
boolean parentFound = false;
// x.y.z 依次往上找
for(int i = name.lastIndexOf('.', length - 1); i >= 0;
i = name.lastIndexOf('.', i - 1)) {
String substr = name.substring(0, i);
// 查找父节点
CategoryKey key = new CategoryKey(substr); // simple constructor
Object o = ht.get(key);
if(o == null) {
// Create a provision node for a future parent.
ProvisionNode pn = new ProvisionNode(cat);
ht.put(key, pn);
} else if(o instanceof Category) {
parentFound = true;
cat.parent = (Category) o;
break; // no need to update the ancestors of the closest ancestor
} else if(o instanceof ProvisionNode) {
((ProvisionNode) o).addElement(cat);
} else {
Exception e = new IllegalStateException("unexpected object type " + o.getClass() + " in ht.");
e.printStackTrace();
}
}
// If we could not find any existing parents, then link with root.
if(!parentFound) {
// 父节点直接设置为根节点
cat.parent = root;
}
}
// 更新子节点的父节点
final private void updateChildren(ProvisionNode pn, Logger logger) {
final int last = pn.size();
for(int i = 0; i < last; i++) {
Logger l = (Logger) pn.elementAt(i);
// Unless this child already points to a correct (lower) parent,
// make cat.parent point to l.parent and l.parent to cat.
if(!l.parent.name.startsWith(logger.name)) {
logger.parent = l.parent;
l.parent = logger;
}
}
}
}
Hierarchy的主要作用就是创建和存储Logger对象,同时管理Logger的层级关系,在内存中构建维护一个基于root
为根的Logger树。
日志系统初始化 - 配置加载LoggerManager
LoggerManager是管理日志仓库,以及加载初始化日志配置文件的入口类,在其静态代码块中创建了一个仓库对象,同时加载配置文件到内存。
public class LogManager {
static public final String DEFAULT_CONFIGURATION_FILE = "log4j.properties";
static final public String DEFAULT_CONFIGURATION_KEY="log4j.configuration";
static final public String CONFIGURATOR_CLASS_KEY="log4j.configuratorClass";
public static final String DEFAULT_INIT_OVERRIDE_KEY = "log4j.defaultInitOverride";
// 上面的在1.2.17.0版本都标注了过期,也就是说后期就支持xml
static final String DEFAULT_XML_CONFIGURATION_FILE = "log4j.xml";
static private Object guard = null;
static private RepositorySelector repositorySelector;
static {
// 静态代码块执行,默认使用Hierarchy 可继承性的仓库
Hierarchy h = new Hierarchy(new RootLogger((Level) Level.DEBUG));
repositorySelector = new DefaultRepositorySelector(h);
// 类路径下加载
String override =OptionConverter.getSystemProperty(DEFAULT_INIT_OVERRIDE_KEY,null);
// if there is no default init override, then get the resource
// specified by the user or the default config file.
if(override == null || "false".equalsIgnoreCase(override)) {
String configurationOptionStr = OptionConverter.getSystemProperty(DEFAULT_CONFIGURATION_KEY, null);
String configuratorClassName = OptionConverter.getSystemProperty(CONFIGURATOR_CLASS_KEY, null);
URL url = null;
// 首先使用xml,然后查找properties
if(configurationOptionStr == null) {
url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE);
if(url == null) {
url = Loader.getResource(DEFAULT_CONFIGURATION_FILE);
}
} else {
try {
url = new URL(configurationOptionStr);
} catch (MalformedURLException ex) {
// so, resource is not a URL:
// attempt to get the resource from the class path
url = Loader.getResource(configurationOptionStr);
}
}
if(url != null) {
LogLog.debug("Using URL ["+url+"] for automatic log4j configuration.");
try {
// 读取配置文件
OptionConverter.selectAndConfigure(url, configuratorClassName, LogManager.getLoggerRepository());
} catch (NoClassDefFoundError e) {
LogLog.warn("Error during default initialization", e);
}
}
}
}
}
这里主要创建了一个仓库对象Hierarchy
,然后根据属性加载配置文件,如log4j.properties.
继续看如何加载配置文件OptionConverter.selectAndConfigure(url, configuratorClassName, LogManager.getLoggerRepository());
// OptionConverter中
static public void selectAndConfigure(URL url, String clazz, LoggerRepository hierarchy) {
// 根据配置文件类型确认
Configurator configurator = null;
String filename = url.getFile();
// xml
if(clazz == null && filename != null && filename.endsWith(".xml")) {
clazz = "org.apache.log4j.xml.DOMConfigurator";
}
if(clazz != null) {
LogLog.debug("Preferred configurator class: " + clazz);
configurator = (Configurator) instantiateByClassName(clazz, Configurator.class, null);
if(configurator == null) {
LogLog.error("Could not instantiate configurator ["+clazz+"].");
return;
}
} else {
// properties类型文件解析
configurator = new PropertyConfigurator();
}
// 解析配置文件,并设置内容到仓库
configurator.doConfigure(url, hierarchy);
}
这里确认配置文件类型,然后使用对应的解析工具解析配置文件
以Porperty文件为例
public class PropertyConfigurator implements Configurator {
// 解析配置
public void doConfigure(java.net.URL configURL, LoggerRepository hierarchy) {
Properties props = new Properties();
InputStream istream = null;
URLConnection uConn = null;
try {
uConn = configURL.openConnection();
uConn.setUseCaches(false);
istream = uConn.getInputStream();
props.load(istream);
}
catch (Exception e) {
return;
}
// 省略资源关闭代码....
doConfigure(props, hierarchy);
}
public void doConfigure(Properties properties, LoggerRepository hierarchy) {
//
// if log4j.reset=true then reset hierarchy
String reset = properties.getProperty(RESET_KEY);
if (reset != null && OptionConverter.toBoolean(reset, false)) {
hierarchy.resetConfiguration();
}
// 配置日志级别
String thresholdStr = OptionConverter.findAndSubst(THRESHOLD_PREFIX, properties);
if(thresholdStr != null) {
hierarchy.setThreshold(OptionConverter.toLevel(thresholdStr, (Level) Level.ALL));
}
// 解析配置 如:root的配置
configureRootCategory(properties, hierarchy);
// 解析工厂类
configureLoggerFactory(properties);
// 解析渲染工具
parseCatsAndRenderers(properties, hierarchy);
registry.clear();
}
// 定义的常量属性
static final String CATEGORY_PREFIX = "log4j.category.";
static final String LOGGER_PREFIX = "log4j.logger.";
static final String FACTORY_PREFIX = "log4j.factory";
static final String ADDITIVITY_PREFIX = "log4j.additivity.";
static final String ROOT_CATEGORY_PREFIX = "log4j.rootCategory";
static final String ROOT_LOGGER_PREFIX = "log4j.rootLogger";
static final String APPENDER_PREFIX = "log4j.appender.";
static final String RENDERER_PREFIX = "log4j.renderer.";
static final String THRESHOLD_PREFIX = "log4j.threshold";
private static final String THROWABLE_RENDERER_PREFIX = "log4j.throwableRenderer";
private static final String LOGGER_REF = "logger-ref";
private static final String ROOT_REF = "root-ref";
private static final String APPENDER_REF_TAG = "appender-ref";
public static final String LOGGER_FACTORY_KEY = "log4j.loggerFactory";
private static final String RESET_KEY = "log4j.reset";
static final private String INTERNAL_ROOT_NAME = "root";
// 配置root日志记录器
void configureRootCategory(Properties props, LoggerRepository hierarchy) {
String effectiveFrefix = ROOT_LOGGER_PREFIX;// log4j.rootLogger
String value = OptionConverter.findAndSubst(ROOT_LOGGER_PREFIX, props);
if(value == null) {
// ROOT_CATEGORY_PREFIX = log4j.rootCategory
value = OptionConverter.findAndSubst(ROOT_CATEGORY_PREFIX, props);
effectiveFrefix = ROOT_CATEGORY_PREFIX;
}
if(value == null)
LogLog.debug("Could not find root logger information. Is this OK?");
else {
Logger root = hierarchy.getRootLogger();
synchronized(root) {
// INTERNAL_ROOT_NAME = root
parseCategory(props, root, effectiveFrefix, INTERNAL_ROOT_NAME, value);
}
}
}
// 根据具体LoggerName解析Appender,Laylout等
void parseCategory(Properties props, Logger logger, String optionKey, String loggerName, String value) {
StringTokenizer st = new StringTokenizer(value, ",");
// If value is not in the form ", appender.." or "", then we should set
// the level of the loggeregory.
if(!(value.startsWith(",") || value.equals(""))) {
if(!st.hasMoreTokens())
return;
// “,”隔开的第一个字符串是日志级别
String levelStr = st.nextToken();
LogLog.debug("Level token is [" + levelStr + "].");
// 省略.....
}
// Begin by removing all existing appenders.
logger.removeAllAppenders();
Appender appender;
String appenderName;
// 后面所有的都当做appender
while(st.hasMoreTokens()) {
appenderName = st.nextToken().trim();
if(appenderName == null || appenderName.equals(","))
continue;
// parse appender
appender = parseAppender(props, appenderName);
if(appender != null) {
logger.addAppender(appender);
}
}
}
// 配置文件Appender
Appender parseAppender(Properties props, String appenderName) {
Appender appender = registryGet(appenderName);
if((appender != null)) {
return appender;
}
// APPENDER_PREFIX=log4j.appender.
String prefix = APPENDER_PREFIX + appenderName;
String layoutPrefix = prefix + ".layout";
appender = (Appender) OptionConverter.instantiateByKey(props, prefix, org.apache.log4j.Appender.class, null);
if(appender == null) {
LogLog.error("Could not instantiate appender named \"" + appenderName+"\".");
return null;
}
appender.setName(appenderName);
// 解析Laylout
if(appender instanceof OptionHandler) {
if(appender.requiresLayout()) {
Layout layout = (Layout) OptionConverter.instantiateByKey(props, layoutPrefix, Layout.class, null);
if(layout != null) {
appender.setLayout(layout);
PropertySetter.setProperties(layout, props, layoutPrefix + ".");
}
}
// 该Appender的其他属性
PropertySetter.setProperties(appender, props, prefix + ".");
}
// 解析过滤器
parseAppenderFilters(props, appenderName, appender);
registryPut(appender);
return appender;
}
// 解析其他Logger配置
protected void parseCatsAndRenderers(Properties props, LoggerRepository hierarchy) {
Enumeration enumeration = props.propertyNames();
while(enumeration.hasMoreElements()) {
String key = (String) enumeration.nextElement();
// 其他logger
if(key.startsWith("log4j.category.") || key.startsWith("log4j.logger.")) {
String loggerName = null;
if(key.startsWith("log4j.category.")) {
loggerName = key.substring("log4j.category.".length());
} else if(key.startsWith("log4j.logger.")) {
loggerName = key.substring("log4j.logger.".length());
}
// 获取该key对应的value值,并解析占位符${}
String value = OptionConverter.findAndSubst(key, props);
Logger logger = hierarchy.getLogger(loggerName, loggerFactory);
synchronized(logger) {
// 设置该Logger的Appender等
parseCategory(props, logger, key, loggerName, value);
// 设置
parseAdditivityForLogger(props, logger, loggerName);
}
} else if(key.startsWith(RENDERER_PREFIX)) {
String renderedClass = key.substring(RENDERER_PREFIX.length());
String renderingClass = OptionConverter.findAndSubst(key, props);
if(hierarchy instanceof RendererSupport) {
RendererMap.addRenderer((RendererSupport) hierarchy, renderedClass,
renderingClass);
}
} else if (key.equals(THROWABLE_RENDERER_PREFIX)) {
if (hierarchy instanceof ThrowableRendererSupport) {
ThrowableRenderer tr = (ThrowableRenderer)
OptionConverter.instantiateByKey(props,
THROWABLE_RENDERER_PREFIX,
org.apache.log4j.spi.ThrowableRenderer.class,
null);
if(tr == null) {
LogLog.error(
"Could not instantiate throwableRenderer.");
} else {
PropertySetter setter = new PropertySetter(tr);
setter.setProperties(props, THROWABLE_RENDERER_PREFIX + ".");
((ThrowableRendererSupport) hierarchy).setThrowableRenderer(tr);
}
}
}
}
}
// 解析是否继承父Logger的属性
void parseAdditivityForLogger(Properties props, Logger cat, String loggerName) {
String value = OptionConverter.findAndSubst(ADDITIVITY_PREFIX + loggerName, props);
LogLog.debug("Handling "+ADDITIVITY_PREFIX + loggerName+"=["+value+"]");
// touch additivity only if necessary
if((value != null) && (!value.equals(""))) {
boolean additivity = OptionConverter.toBoolean(value, true);
cat.setAdditivity(additivity);
}
}
}
该类主要解析配置文件,对于自定义的Logger也会提前初始化到仓库中(提前解析其配置属性)。
至此,Logger的配置加载就完成了。
整个日志输出流程
总结
- Log4j作为java领域最早的日志工具,其出现填补了行业空白,也为JUL的出现提供了参考,JUL的设计大多参考了Log4j的思路。
- Logger作为日志记录器,其定义了日志级别,绑定了多个Appender,所有的Logger都交给LoggerRepository管理,在内存中所有的Logger构建成以RootLogger为根节点的单根树形结构,通过RootLogger能方便的找到所有的Logger。
- 每个Logger可关联多个Appender,Appender作为日志输出的目的地处理器,对应于JUL的Handler,可以输出到如控制台,文件,网络等地方,其关联了对应的Layout,用于格式化日志输出。
- LogEvent封装了日志信息,如日志内容,日志发生位置,发生时间,当前日志所属级别等等。