- 浏览: 980984 次
文章分类
- 全部博客 (428)
- Hadoop (2)
- HBase (1)
- ELK (1)
- ActiveMQ (13)
- Kafka (5)
- Redis (14)
- Dubbo (1)
- Memcached (5)
- Netty (56)
- Mina (34)
- NIO (51)
- JUC (53)
- Spring (13)
- Mybatis (17)
- MySQL (21)
- JDBC (12)
- C3P0 (5)
- Tomcat (13)
- SLF4J-log4j (9)
- P6Spy (4)
- Quartz (12)
- Zabbix (7)
- JAVA (9)
- Linux (15)
- HTML (9)
- Lucene (0)
- JS (2)
- WebService (1)
- Maven (4)
- Oracle&MSSQL (14)
- iText (11)
- Development Tools (8)
- UTILS (4)
- LIFE (8)
最新评论
-
Donald_Draper:
Donald_Draper 写道刘落落cici 写道能给我发一 ...
DatagramChannelImpl 解析三(多播) -
Donald_Draper:
刘落落cici 写道能给我发一份这个类的源码吗Datagram ...
DatagramChannelImpl 解析三(多播) -
lyfyouyun:
请问楼主,执行消息发送的时候,报错:Transport sch ...
ActiveMQ连接工厂、连接详解 -
ezlhq:
关于 PollArrayWrapper 状态含义猜测:参考 S ...
WindowsSelectorImpl解析一(FdMap,PollArrayWrapper) -
flyfeifei66:
打算使用xmemcache作为memcache的客户端,由于x ...
Memcached分布式客户端(Xmemcached)
java中volatile关键字的含义:http://www.cnblogs.com/aigongsi/archive/2012/04/01/2429166.html
Java transient关键字使用小记:http://www.cnblogs.com/lanxuezaipiao/p/3369962.html
Log4j初始化详解:http://donald-draper.iteye.com/blog/2332385
Log4j日志输出详解 :http://donald-draper.iteye.com/blog/2332395
slf4j + Log4j的使用:http://donald-draper.iteye.com/blog/2332407
上一篇简单学习了Log4j的使用,今天来看一下,日志初始化,
我们就从下面这一句来看:
查看Logger
来看LogManager的getLogger方法
//LogManager
//NOPLoggerRepository
//NOPLogger
而Logger继承Category
//Category
//Level
//Priority
我们在回到LogManager加载log4j属性文件,关键在这一句
这个LoggerRepository实际为NOPLoggerRepository
来看log4j.properties属性文件解析
//PropertyConfigurator
//配置RootCategory,rootLogger,appenders
来看 OptionConverter.findAndSubst
//返回key对应的属性
//NOPLoggerRepository,获取rootLogger
//PropertyConfigurator
//根据appenderName解析Appender
//OptionConverter.instantiateByKey
//PropertyConfigurator
// protected Hashtable registry;HashTable<String,Appender>
//将Appender放入registry
下面我们来看看logger.addAppender,都做了些什么
//AppenderAttachableImpl
总结:
从以上我们可以看出:log4j,通过LogManager的static语句块,加载配置log4j.properties,由OptionConverter去加载log4j.properties,并委托给PropertyConfigurator,去配置RootLogger,根据RootLogger获取Appender,然后根据属性文件初始化Appender,并添加到RootLogger的appender集合中。
Java transient关键字使用小记:http://www.cnblogs.com/lanxuezaipiao/p/3369962.html
Log4j初始化详解:http://donald-draper.iteye.com/blog/2332385
Log4j日志输出详解 :http://donald-draper.iteye.com/blog/2332395
slf4j + Log4j的使用:http://donald-draper.iteye.com/blog/2332407
上一篇简单学习了Log4j的使用,今天来看一下,日志初始化,
我们就从下面这一句来看:
private static Logger log = Logger.getLogger(testLog4j.class);
查看Logger
public class Logger extends Category { protected Logger(String name) { super(name); } //获取Logger public static Logger getLogger(String name) { return LogManager.getLogger(name); } public static Logger getLogger(Class clazz) { return LogManager.getLogger(clazz.getName()); } static { FQCN = (org.apache.log4j.Logger.class).getName(); } }
来看LogManager的getLogger方法
//LogManager
public class LogManager { public static Logger getLogger(String name) { return getLoggerRepository().getLogger(name); } //获取本机LoggerRepository public static LoggerRepository getLoggerRepository() { if(repositorySelector == null) { repositorySelector = new DefaultRepositorySelector(new NOPLoggerRepository()); guard = null; Exception ex = new IllegalStateException("Class invariant violation"); String msg = "log4j called after unloading, see http://logging.apache.org/log4j/1.2/faq.html#unload."; if(isLikelySafeScenario(ex)) LogLog.debug(msg, ex); else LogLog.error(msg, ex); } return repositorySelector.getLoggerRepository(); } public static final String DEFAULT_CONFIGURATION_FILE = "log4j.properties"; static final String DEFAULT_XML_CONFIGURATION_FILE = "log4j.xml"; public static final String DEFAULT_CONFIGURATION_KEY = "log4j.configuration"; public static final String CONFIGURATOR_CLASS_KEY = "log4j.configuratorClass"; public static final String DEFAULT_INIT_OVERRIDE_KEY = "log4j.defaultInitOverride"; private static Object guard = null; private static RepositorySelector repositorySelector; //加载log4j配置文件,先加载log4j.xml,如果log4j.xml不存在, //则加载log4j.properties文件 static { Hierarchy h = new Hierarchy(new RootLogger(Level.DEBUG)); repositorySelector = new DefaultRepositorySelector(h); String override = OptionConverter.getSystemProperty("log4j.defaultInitOverride", null); if(override == null || "false".equalsIgnoreCase(override)) { String configurationOptionStr = OptionConverter.getSystemProperty("log4j.configuration", null); String configuratorClassName = OptionConverter.getSystemProperty("log4j.configuratorClass", null); URL url = null; if(configurationOptionStr == null) { url = Loader.getResource("log4j.xml"); if(url == null) url = Loader.getResource("log4j.properties"); } else { try { url = new URL(configurationOptionStr); } catch(MalformedURLException ex) { url = Loader.getResource(configurationOptionStr); } } if(url != null) { LogLog.debug("Using URL [" + url + "] for automatic log4j configuration."); try { OptionConverter.selectAndConfigure(url, configuratorClassName, getLoggerRepository()); } } } } }
//DefaultRepositorySelector public class DefaultRepositorySelector implements RepositorySelector { public DefaultRepositorySelector(LoggerRepository repository) { this.repository = repository; } public LoggerRepository getLoggerRepository() { return repository; } final LoggerRepository repository; }
//NOPLoggerRepository
public final class NOPLoggerRepository implements LoggerRepository { //从这里看,Logger实际上为NOPLogger public Logger getLogger(String name) { return new NOPLogger(this, name); } }
//NOPLogger
public final class NOPLogger extends Logger { public NOPLogger(NOPLoggerRepository repo, String name) { super(name); repository = repo; level = Level.OFF; parent = this; } }
而Logger继承Category
public class Logger extends Category { protected Logger(String name) { super(name); } }
//Category
public class Category implements AppenderAttachable { protected Category(String name) { additive = true; this.name = name; } protected String name; //volatile protected volatile Level level; protected volatile Category parent; private static final String FQCN; protected ResourceBundle resourceBundle; protected LoggerRepository repository; AppenderAttachableImpl aai; protected boolean additive; static { FQCN = (org.apache.log4j.Category.class).getName(); } }
//Level
public class Level extends Priority implements Serializable { protected Level(int level, String levelStr, int syslogEquivalent) { super(level, levelStr, syslogEquivalent); } public static final int TRACE_INT = 5000; public static final Level OFF = new Level(2147483647, "OFF", 0); public static final Level FATAL = new Level(50000, "FATAL", 0); public static final Level ERROR = new Level(40000, "ERROR", 3); public static final Level WARN = new Level(30000, "WARN", 4); public static final Level INFO = new Level(20000, "INFO", 6); public static final Level DEBUG = new Level(10000, "DEBUG", 7); public static final Level TRACE = new Level(5000, "TRACE", 7); public static final Level ALL = new Level(-2147483648, "ALL", 7); static final long serialVersionUID = 3491141966387921974L; }
//Priority
public class Priority { protected Priority() { level = 10000; levelStr = "DEBUG"; syslogEquivalent = 7; } protected Priority(int level, String levelStr, int syslogEquivalent) { this.level = level; this.levelStr = levelStr; this.syslogEquivalent = syslogEquivalent; } //比较日志级别 public boolean isGreaterOrEqual(Priority r) { return level >= r.level; } transient int level; transient String levelStr; transient int syslogEquivalent; public static final int OFF_INT = 2147483647; public static final int FATAL_INT = 50000; public static final int ERROR_INT = 40000; public static final int WARN_INT = 30000; public static final int INFO_INT = 20000; public static final int DEBUG_INT = 10000; public static final int ALL_INT = -2147483648; public static final Priority FATAL = new Level(50000, "FATAL", 0); public static final Priority ERROR = new Level(40000, "ERROR", 3); public static final Priority WARN = new Level(30000, "WARN", 4); public static final Priority INFO = new Level(20000, "INFO", 6); public static final Priority DEBUG = new Level(10000, "DEBUG", 7); }
我们在回到LogManager加载log4j属性文件,关键在这一句
OptionConverter.selectAndConfigure(url, configuratorClassName, getLoggerRepository());
这个LoggerRepository实际为NOPLoggerRepository
public class OptionConverter { public static void selectAndConfigure(URL url, String clazz, LoggerRepository hierarchy) { Configurator configurator = null; String filename = url.getFile(); if(clazz == null && filename != null && filename.endsWith(".xml")) clazz = "org.apache.log4j.xml.DOMConfigurator"; //XML配置解析 if(clazz != null) { LogLog.debug("Preferred configurator class: " + clazz); configurator = (Configurator)instantiateByClassName(clazz, org.apache.log4j.spi.Configurator.class, null); } else { //java属性文件解析log4j.properties configurator = new PropertyConfigurator(); } //配置NOPLoggerRepository configurator.doConfigure(url, hierarchy); } static String DELIM_START = "${"; static char DELIM_STOP = '}'; static int DELIM_START_LEN = 2; static int DELIM_STOP_LEN = 1; } }
来看log4j.properties属性文件解析
//PropertyConfigurator
public class PropertyConfigurator implements Configurator { protected Hashtable registry; private LoggerRepository repository; protected LoggerFactory loggerFactory; 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"; private static final String INTERNAL_ROOT_NAME = "root"; public PropertyConfigurator() { registry = new Hashtable(11); loggerFactory = new DefaultCategoryFactory(); } //加载URL文件到Properties public void doConfigure(URL configURL, LoggerRepository hierarchy) { Properties props; InputStream istream; props = new Properties(); istream = null; URLConnection uConn = null; URLConnection uConn = configURL.openConnection(); uConn.setUseCaches(false); istream = uConn.getInputStream(); props.load(istream); //加载URL文件到Properties doConfigure(props, hierarchy); } //根据properties文件配置NOPLoggerRepository public void doConfigure(Properties properties, LoggerRepository hierarchy) { repository = hierarchy; String value = properties.getProperty("log4j.debug"); if(value == null) { value = properties.getProperty("log4j.configDebug"); if(value != null) LogLog.warn("[log4j.configDebug] is deprecated. Use [log4j.debug] instead."); } if(value != null) LogLog.setInternalDebugging(OptionConverter.toBoolean(value, true)); String reset = properties.getProperty("log4j.reset"); if(reset != null && OptionConverter.toBoolean(reset, false)) hierarchy.resetConfiguration(); String thresholdStr = OptionConverter.findAndSubst("log4j.threshold", properties); if(thresholdStr != null) { hierarchy.setThreshold(OptionConverter.toLevel(thresholdStr, Level.ALL)); LogLog.debug("Hierarchy threshold set to [" + hierarchy.getThreshold() + "]."); } //配置RootCategory,rootLogger,appenders configureRootCategory(properties, hierarchy); configureLoggerFactory(properties); parseCatsAndRenderers(properties, hierarchy); LogLog.debug("Finished configuring."); registry.clear(); } }
//配置RootCategory,rootLogger,appenders
void configureRootCategory(Properties props, LoggerRepository hierarchy) { String effectiveFrefix = "log4j.rootLogger"; String value = OptionConverter.findAndSubst("log4j.rootLogger", props); if(value == null) { LogLog.debug("Could not find root logger information. Is this OK?"); } else { Logger root = hierarchy.getRootLogger(); synchronized(root) { parseCategory(props, root, effectiveFrefix, "root", value); } } }
来看 OptionConverter.findAndSubst
//返回key对应的属性
public static String findAndSubst(String key, Properties props) { String value; value = props.getProperty(key); }
//NOPLoggerRepository,获取rootLogger
public Logger getRootLogger() { return new NOPLogger(this, "root"); }
//PropertyConfigurator
void parseCategory(Properties props, Logger logger, String optionKey, String loggerName, String value) { StringTokenizer st = new StringTokenizer(value, ","); if(!value.startsWith(",") && !value.equals("")) { if(!st.hasMoreTokens()) return; //获取rootLogger,日志级别 String levelStr = st.nextToken(); if("inherited".equalsIgnoreCase(levelStr) || "null".equalsIgnoreCase(levelStr)) { if(loggerName.equals("root")) LogLog.warn("The root logger cannot be set to null."); else logger.setLevel(null); } else { //设置rootLogger,日志级别 logger.setLevel(OptionConverter.toLevel(levelStr, Level.DEBUG)); } LogLog.debug("Category " + loggerName + " set to " + logger.getLevel()); } //移除Appenders logger.removeAllAppenders(); do { if(!st.hasMoreTokens()) break; String appenderName = st.nextToken().trim(); if(appenderName != null && !appenderName.equals(",")) { LogLog.debug("Parsing appender named \"" + appenderName + "\"."); //根据appenderName解析Appender Appender appender = parseAppender(props, appenderName); if(appender != null) //添加appender logger.addAppender(appender); } } while(true); }
//根据appenderName解析Appender
Appender parseAppender(Properties props, String appenderName) { Appender appender = registryGet(appenderName); if(appender != null) { LogLog.debug("Appender \"" + appenderName + "\" was already parsed."); return appender; } //log4j.appender的前缀 String prefix = "log4j.appender." + appenderName; //log4j.appender.appenderName.layout String layoutPrefix = prefix + ".layout"; //加载Appender,Class, //log4j.appender.D = org.apache.log4j.DailyRollingFileAppender appender = (Appender)OptionConverter.instantiateByKey(props, prefix, org.apache.log4j.Appender.class, null); appender.setName(appenderName); if(appender instanceof OptionHandler) { if(appender.requiresLayout()) { //加载Layout,class, //log4j.appender.D.layout = org.apache.log4j.PatternLayout Layout layout = (Layout)OptionConverter.instantiateByKey(props, layoutPrefix, org.apache.log4j.Layout.class, null); if(layout != null) { appender.setLayout(layout); LogLog.debug("Parsing layout options for \"" + appenderName + "\"."); //设置layout的属性 PropertySetter.setProperties(layout, props, layoutPrefix + "."); LogLog.debug("End of parsing for \"" + appenderName + "\"."); } } String errorHandlerPrefix = prefix + ".errorhandler"; String errorHandlerClass = OptionConverter.findAndSubst(errorHandlerPrefix, props); if(errorHandlerClass != null) { ErrorHandler eh = (ErrorHandler)OptionConverter.instantiateByKey(props, errorHandlerPrefix, org.apache.log4j.spi.ErrorHandler.class, null); if(eh != null) { appender.setErrorHandler(eh); LogLog.debug("Parsing errorhandler options for \"" + appenderName + "\"."); parseErrorHandler(eh, errorHandlerPrefix, props, repository); Properties edited = new Properties(); String keys[] = { errorHandlerPrefix + "." + "root-ref", errorHandlerPrefix + "." + "logger-ref", errorHandlerPrefix + "." + "appender-ref" }; Iterator iter = props.entrySet().iterator(); do { if(!iter.hasNext()) break; java.util.Map.Entry entry = (java.util.Map.Entry)iter.next(); int i; for(i = 0; i < keys.length && !keys[i].equals(entry.getKey()); i++); if(i == keys.length) edited.put(entry.getKey(), entry.getValue()); } while(true); PropertySetter.setProperties(eh, edited, errorHandlerPrefix + "."); LogLog.debug("End of errorhandler parsing for \"" + appenderName + "\"."); } } //设置Appender属性 PropertySetter.setProperties(appender, props, prefix + "."); LogLog.debug("Parsed \"" + appenderName + "\" options."); } parseAppenderFilters(props, appenderName, appender); //将Appender放入registry registryPut(appender); return appender; }
//OptionConverter.instantiateByKey
public static Object instantiateByKey(Properties props, String key, Class superClass, Object defaultValue) { String className = findAndSubst(key, props); if(className == null) { LogLog.error("Could not find value for key " + key); return defaultValue; } else { return instantiateByClassName(className.trim(), superClass, defaultValue); } }
//PropertyConfigurator
// protected Hashtable registry;HashTable<String,Appender>
//将Appender放入registry
void registryPut(Appender appender) { registry.put(appender.getName(), appender); }
下面我们来看看logger.addAppender,都做了些什么
public class Category implements AppenderAttachable { //添加Appender public synchronized void addAppender(Appender newAppender) { if(aai == null) aai = new AppenderAttachableImpl(); aai.addAppender(newAppender); //通知repository,添加Appender事件 repository.fireAddAppenderEvent(this, newAppender); } //移除AllAppenders public synchronized void removeAllAppenders() { if(aai != null) { Vector appenders = new Vector(); for(Enumeration iter = aai.getAllAppenders(); iter != null && iter.hasMoreElements(); appenders.add(iter.nextElement())); aai.removeAllAppenders(); for(Enumeration iter = appenders.elements(); iter.hasMoreElements(); fireRemoveAppenderEvent((Appender)iter.nextElement())); aai = null; } } }
//AppenderAttachableImpl
public class AppenderAttachableImpl implements AppenderAttachable { //添加Appender public void addAppender(Appender newAppender) { if(newAppender == null) return; if(appenderList == null) appenderList = new Vector(1); if(!appenderList.contains(newAppender)) appenderList.addElement(newAppender); } //Vector<Appender> protected Vector appenderList; }
总结:
从以上我们可以看出:log4j,通过LogManager的static语句块,加载配置log4j.properties,由OptionConverter去加载log4j.properties,并委托给PropertyConfigurator,去配置RootLogger,根据RootLogger获取Appender,然后根据属性文件初始化Appender,并添加到RootLogger的appender集合中。
发表评论
-
一次服务器事故处理过程及教训
2017-02-24 18:50 1164java虚拟机内存查看相关 ... -
slf4j + Logback使用
2016-10-23 19:44 1374使用logback轻松管理日志: http://blog.cs ... -
slf4j + Logback详解
2016-10-23 19:41 1307Log4j初始化详解:http://donald-draper ... -
slf4j + Log4j 详解
2016-10-23 19:40 1757Log4j初始化详解:http://donald-draper ... -
sfl4j与log4j使用
2016-10-23 11:24 2429slf4j-api、slf4j-log4j12以及log4j之 ... -
Log4j日志输出详解
2016-10-23 00:46 2848Log4j初始化详解:http://donald-draper ... -
log4j的使用
2016-10-22 11:56 626Log4j使用教程:http://www.codeceo.co ... -
Spring集成log4j,日志初始化过程详解
2016-06-22 17:41 7775以前研究过slf4j-log4j的使用,但具体初始化过程不是很 ...
相关推荐
### log4j初始化详解 #### 引言 `log4j`是一款开源的日志记录工具,广泛应用于Java应用程序中,用于记录程序运行时的各种信息,包括错误、警告、信息等不同级别的日志。其强大的配置功能和灵活性使得开发人员能够...
### Log4j 使用详解 #### 一、Log4j简介 Log4j 是 Apache 的一个开源项目,通过使用 Log4j,开发者能够控制日志信息的输出等级及去向,从而更加灵活地处理日志信息。它具有强大的功能,简单的配置,并且能够支持...
3. **通过环境变量传递配置文件名**:利用Log4j默认的初始化过程进行解析和配置。 4. **通过应用服务器配置传递配置文件名**:利用一个特殊的servlet来完成配置。 #### 六、为不同的Appender设置日志输出级别 在...
2. **编写初始化 Servlet**:创建一个自定义的 Servlet 来初始化 Log4j。该 Servlet 的主要任务是在启动时加载特定的 `log4j.properties` 文件。 ```java public class Log4jInitServlet extends HttpServlet { ...
在Web应用程序中,log4j的配置主要分为两步:配置log4j配置文件和在Servlet中初始化配置。 1. 配置log4j配置文件 log4j的配置文件通常为log4j.properties或log4j.xml,这里我们以.properties文件为例。配置文件的...
当我们添加这个依赖到项目中,Spring Boot会自动配置Log4j2,无需手动进行繁琐的初始化设置。 三、集成步骤 1. 添加依赖:在Maven或Gradle的配置文件中引入"spring-boot-starter-log4j2"依赖,如下所示(以Maven为...
### Log4j中配置日志文件相对路径方法详解 #### 概述 在软件开发过程中,日志记录是一项重要的功能,它有助于开发者调试程序、监控应用程序的运行状态以及追踪问题。`Log4j`作为一款优秀的日志管理工具,被广泛应用...
这个 Servlet 的作用是在应用启动时读取配置文件并初始化 Log4j。具体步骤如下: ```java package ttzl.log.web; import javax.servlet.http.HttpServlet; import org.apache.log4j.PropertyConfigurator; public...
3. **配置文件通过环境变量传递**:利用Log4j默认的初始化过程解析并配置配置文件。 4. **配置文件直接读取**:通过`DOMConfigurator.configure("path/to/config.xml")`等方法直接读取配置文件。 通过以上介绍,...
#### 四、log4j 配置详解 1. **配置文件**: - `log4j.properties` 或 `log4j.xml` 文件用于定义 log4j 的配置规则。 - 配置文件通常放置在项目的根目录或者类路径下。 - 如果未指定配置文件的位置,则 log4j 会...
3. **通过环境变量传递配置文件**: Log4j会在初始化过程中查找特定的环境变量来加载配置文件。 4. **通过应用服务器配置**: 在部署到应用服务器时,可以通过特定的Servlet来配置Log4j。 #### 六、总结 Log4j的配置...
6. **org.apache.log4j.PropertyConfigurator**:用于从属性文件中读取配置并初始化Log4j。 五、实际应用与最佳实践 在实际项目中,合理使用Log4j可以帮助调试、监控和分析程序运行状态。以下是一些最佳实践: 1....
下面是一个简单的Servlet示例,展示了如何在Servlet的`init()`方法中初始化Log4j: ```java public class InitServlet extends HttpServlet { public void init() { ServletContext sct = getServletContext(); ...
3. 建立一个用于初始化的 InitServlet,在 init 方法指定 log4j 读取应用程序下的 log4j.properties 文件。 4. 把 jboss 的 jboss-common.jar($JBOSS-HOME/lib 目录下)复制到应用程序/WEB-INF/lib 目录下。 Log4j ...
// 根据.properties文件初始化Log4j PropertyConfigurator.configure("log4j.properties"); // 根据.xml文件初始化Log4j DOMConfigurator.configure("log4j.xml"); Logger logger = Logger.getLogger(Log4...
3. 配置放在文件里,通过环境变量传递文件名等信息,利用 log4j 默认的初始化过程解析并配置。 4. 配置放在文件里,通过应用服务器配置。 Log4j.properties 文件是 Log4j 框架的核心配置文件,用于设置记录器的级别...
首先,需要初始化log4net: ```csharp log4net.Config.XmlConfigurator.Configure(); ``` 然后,根据需求创建并配置appender和logger。 ### 7. 使用log4net 在代码中使用log4net非常简单,只需要获取一个logger...
在实际应用中,Log4j 需要通过配置文件来初始化。配置文件可以是 XML 或者 properties 文件格式。以下是以 properties 文件为例的配置示例: ##### 1. 配置根 Logger 根 Logger 通常用于设置全局的日志级别以及...