本文适合1年以上编程基础的开发人员阅读,非技术创新,可作为故障排除实录参考/收藏。

背景

  笔者最近在给公司一个老的web项目改造升级,项目使用springmvc+mybatis,由于项目比较久远,没有使用maven管理jar版本,有可能是当时开发任务比较紧迫,不同的同事在不同的时期放入了jar版本各不相同,

看到那么多混乱的jar,真是操心。笔者曾花了大概半个下午的时间,把jar版本整理好,编入pom.xml中,从那个时候,笔者本地项目的jar版本算是交给maven托管了,顿时间心里舒畅了一会儿,心里也计划着和项目组大

家一起统一使用maven管控jar版本混乱的问题。可是事实有时候并不会常常如人所愿。就在部署web项目的时候,问题来了:控制台那么多可爱的日志怎么变少了呢?

原来正常的时候,日志输出大概如下:

而当笔者部署web项目到servlet容器(tomcat)中,问题来了,日志少的捉襟见肘,怎么也难应付平时的项目开发啊,开发测试过程中,怎么能少的了日志。

如此一来,这个maven上的有点让人措手不及,这不有点像邯郸学步,顾着把项目列入现代化管理方式,却失去了初衷:日志功能淡化,开发调试难度增加。

排错过程

本地日志输入代码如下:

  1. import org.slf4j.Logger;
  2. import org.slf4j.LoggerFactory;
  3. import com.xxx.system.auth.User;
  4. /***
  5. * 权限校验类
  6. */
  7.  
  8. public class AuthManager {
  9. private static Logger logger = LoggerFactory.getLogger(AuthManager.class);
  10.  
  11. public static void check(HttpSession session)
  12. {
  13. User user = (User)session.getAttribute("userData");
  14. if (user == null) {
  15. if(logger.isDebugEnabled())
  16. logger.debug("当前用户session无效!");
  17. return false;
  18. }
  19.  
  20. }
  21. }

笔者按: 代码片段中使用了slf4j框架,slf4j是一个优秀的slf4j框架,他使用了适配器设计模式,由他来屏蔽不同日志框架的差异性,实现与不同日志框架的协作。

 

  以上一段简单的代码,在项目中不同地方高频次的调用,却一直不见控制台有日志输出,笔者起初以为是ide在作祟,各种更改ide配置也无效,

把编译好的项目单独放到servlet容器tomcat中,仍不凑效,即便捶胸顿足也无计可施,以上说明问题的根源不是ide,也不是servlet容器,这可憋坏了笔者。

解决方法

log4j一般性的用法如下:

  1. import org.apache.log4j.Logger;
  2. import org.apache.log4j.PropertyConfigurator;
  3.  
  4. public class LogTest {
  5. static Logger logger = Logger.getLogger(LogTest.class.getName());
  6.  
  7. public static void main(String[] args) {
  8. PropertyConfigurator.configure("src/log4j.properties");
  9. logger.debug("Debug ...");
  10. logger.info("Info ...");
  11. logger.warn("Warn ...");
  12. logger.error("Error ...");
  13. }
  14.  
  15. }

按图索骥,通过追踪Logger.getLogger源码,在我们面前呈现出一个类LogManager

  1. static
  2. public
  3. Logger getLogger(String name) {
  4. return LogManager.getLogger(name);
  5. }

Log4jManager代码如下:

  1. package org.apache.log4j;
  2.  
  3. import org.apache.log4j.spi.LoggerRepository;
  4. import org.apache.log4j.spi.LoggerFactory;
  5. import org.apache.log4j.spi.RepositorySelector;
  6. import org.apache.log4j.spi.DefaultRepositorySelector;
  7. import org.apache.log4j.spi.RootLogger;
  8. import org.apache.log4j.spi.NOPLoggerRepository;
  9. import org.apache.log4j.helpers.Loader;
  10. import org.apache.log4j.helpers.OptionConverter;
  11. import org.apache.log4j.helpers.LogLog;
  12.  
  13. import java.net.URL;
  14. import java.net.MalformedURLException;
  15.  
  16. import java.util.Enumeration;
  17. import java.io.StringWriter;
  18. import java.io.PrintWriter;
  19.  
  20. /**
  21. * Use the <code>LogManager</code> class to retreive {@link Logger}
  22. * instances or to operate on the current {@link
  23. * LoggerRepository}. When the <code>LogManager</code> class is loaded
  24. * into memory the default initalzation procedure is inititated. The
  25. * default intialization procedure</a> is described in the <a
  26. * href="../../../../manual.html#defaultInit">short log4j manual</a>.
  27. *
  28. * @author Ceki G&uuml;lc&uuml;
  29. */
  30. public class LogManager {
  31.  
  32. /**
  33. * @deprecated This variable is for internal use only. It will
  34. * become package protected in future versions.
  35. */
  36. static public final String DEFAULT_CONFIGURATION_FILE = "log4j.properties";
  37.  
  38. static final String DEFAULT_XML_CONFIGURATION_FILE = "log4j.xml";
  39.  
  40. /**
  41. * @deprecated This variable is for internal use only. It will
  42. * become private in future versions.
  43. */
  44. static final public String DEFAULT_CONFIGURATION_KEY = "log4j.configuration";
  45.  
  46. /**
  47. * @deprecated This variable is for internal use only. It will
  48. * become private in future versions.
  49. */
  50. static final public String CONFIGURATOR_CLASS_KEY = "log4j.configuratorClass";
  51.  
  52. /**
  53. * @deprecated This variable is for internal use only. It will
  54. * become private in future versions.
  55. */
  56. public static final String DEFAULT_INIT_OVERRIDE_KEY =
  57. "log4j.defaultInitOverride";
  58.  
  59. static private Object guard = null;
  60. static private RepositorySelector repositorySelector;
  61.  
  62. static {
  63. // By default we use a DefaultRepositorySelector which always returns 'h'.
  64. Hierarchy h = new Hierarchy(new RootLogger((Level) Level.DEBUG));
  65. repositorySelector = new DefaultRepositorySelector(h);
  66.  
  67. /** Search for the properties file log4j.properties in the CLASSPATH. */
  68. String override = OptionConverter.getSystemProperty(DEFAULT_INIT_OVERRIDE_KEY,
  69. null);
  70.  
  71. // if there is no default init override, then get the resource
  72. // specified by the user or the default config file.
  73. if (override == null || "false".equalsIgnoreCase(override)) {
  74.  
  75. String configurationOptionStr = OptionConverter.getSystemProperty(
  76. DEFAULT_CONFIGURATION_KEY,
  77. null);
  78.  
  79. String configuratorClassName = OptionConverter.getSystemProperty(
  80. CONFIGURATOR_CLASS_KEY,
  81. null);
  82.  
  83. URL url = null;
  84.  
  85. // if the user has not specified the log4j.configuration
  86. // property, we search first for the file "log4j.xml" and then
  87. // "log4j.properties"
  88. if (configurationOptionStr == null) {
  89. url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE);
  90. if (url == null) {
  91. url = Loader.getResource(DEFAULT_CONFIGURATION_FILE);
  92. }
  93. } else {
  94. try {
  95. url = new URL(configurationOptionStr);
  96. } catch (MalformedURLException ex) {
  97. // so, resource is not a URL:
  98. // attempt to get the resource from the class path
  99. url = Loader.getResource(configurationOptionStr);
  100. }
  101. }
  102.  
  103. // If we have a non-null url, then delegate the rest of the
  104. // configuration to the OptionConverter.selectAndConfigure
  105. // method.
  106. if (url != null) {
  107. LogLog.debug("Using URL [" + url + "] for automatic log4j configuration.");
  108. try {
  109. OptionConverter.selectAndConfigure(url, configuratorClassName,
  110. LogManager.getLoggerRepository());
  111. } catch (NoClassDefFoundError e) {
  112. LogLog.warn("Error during default initialization", e);
  113. }
  114. } else {
  115. LogLog.debug("Could not find resource: [" + configurationOptionStr + "].");
  116. }
  117. } else {
  118. LogLog.debug("Default initialization of overridden by " +
  119. DEFAULT_INIT_OVERRIDE_KEY + "property.");
  120. }
  121. }
  122.  
  123. /**
  124. * Sets <code>LoggerFactory</code> but only if the correct
  125. * <em>guard</em> is passed as parameter.
  126. *
  127. * <p>Initally the guard is null. If the guard is
  128. * <code>null</code>, then invoking this method sets the logger
  129. * factory and the guard. Following invocations will throw a {@link
  130. * IllegalArgumentException}, unless the previously set
  131. * <code>guard</code> is passed as the second parameter.
  132. *
  133. * <p>This allows a high-level component to set the {@link
  134. * RepositorySelector} used by the <code>LogManager</code>.
  135. *
  136. * <p>For example, when tomcat starts it will be able to install its
  137. * own repository selector. However, if and when Tomcat is embedded
  138. * within JBoss, then JBoss will install its own repository selector
  139. * and Tomcat will use the repository selector set by its container,
  140. * JBoss.
  141. */
  142. static
  143. public void setRepositorySelector(RepositorySelector selector, Object guard)
  144. throws IllegalArgumentException {
  145. if ((LogManager.guard != null) && (LogManager.guard != guard)) {
  146. throw new IllegalArgumentException(
  147. "Attempted to reset the LoggerFactory without possessing the guard.");
  148. }
  149.  
  150. if (selector == null) {
  151. throw new IllegalArgumentException("RepositorySelector must be non-null.");
  152. }
  153.  
  154. LogManager.guard = guard;
  155. LogManager.repositorySelector = selector;
  156. }
  157.  
  158. /**
  159. * This method tests if called from a method that
  160. * is known to result in class members being abnormally
  161. * set to null but is assumed to be harmless since the
  162. * all classes are in the process of being unloaded.
  163. *
  164. * @param ex exception used to determine calling stack.
  165. * @return true if calling stack is recognized as likely safe.
  166. */
  167. private static boolean isLikelySafeScenario(final Exception ex) {
  168. StringWriter stringWriter = new StringWriter();
  169. ex.printStackTrace(new PrintWriter(stringWriter));
  170. String msg = stringWriter.toString();
  171. return msg.indexOf("org.apache.catalina.loader.WebappClassLoader.stop") != -1;
  172. }
  173.  
  174. static
  175. public LoggerRepository getLoggerRepository() {
  176. if (repositorySelector == null) {
  177. repositorySelector = new DefaultRepositorySelector(new NOPLoggerRepository());
  178. guard = null;
  179. Exception ex = new IllegalStateException("Class invariant violation");
  180. String msg =
  181. "log4j called after unloading, see http://logging.apache.org/log4j/1.2/faq.html#unload.";
  182. if (isLikelySafeScenario(ex)) {
  183. LogLog.debug(msg, ex);
  184. } else {
  185. LogLog.error(msg, ex);
  186. }
  187. }
  188. return repositorySelector.getLoggerRepository();
  189. }
  190.  
  191. /**
  192. * Retrieve the appropriate root logger.
  193. */
  194. public
  195. static Logger getRootLogger() {
  196. // Delegate the actual manufacturing of the logger to the logger repository.
  197. return getLoggerRepository().getRootLogger();
  198. }
  199.  
  200. /**
  201. * Retrieve the appropriate {@link Logger} instance.
  202. */
  203. public
  204. static Logger getLogger(final String name) {
  205. // Delegate the actual manufacturing of the logger to the logger repository.
  206. return getLoggerRepository().getLogger(name);
  207. }
  208.  
  209. /**
  210. * Retrieve the appropriate {@link Logger} instance.
  211. */
  212. public
  213. static Logger getLogger(final Class clazz) {
  214. // Delegate the actual manufacturing of the logger to the logger repository.
  215. return getLoggerRepository().getLogger(clazz.getName());
  216. }
  217.  
  218. /**
  219. * Retrieve the appropriate {@link Logger} instance.
  220. */
  221. public
  222. static Logger getLogger(final String name, final LoggerFactory factory) {
  223. // Delegate the actual manufacturing of the logger to the logger repository.
  224. return getLoggerRepository().getLogger(name, factory);
  225. }
  226.  
  227. public
  228. static Logger exists(final String name) {
  229. return getLoggerRepository().exists(name);
  230. }
  231.  
  232. public
  233. static Enumeration getCurrentLoggers() {
  234. return getLoggerRepository().getCurrentLoggers();
  235. }
  236.  
  237. public
  238. static void shutdown() {
  239. getLoggerRepository().shutdown();
  240. }
  241.  
  242. public
  243. static void resetConfiguration() {
  244. getLoggerRepository().resetConfiguration();
  245. }
  246. }

  笔者本地使用log4j-1.2.16,通过阅读静态域代码了解到,log4j寻找配置文件顺序如下

1、检测当前JVM是否配置log4j.configuration属性,如果有,加载对应的配置文件,也就是说,你可以在JVM启动时加载参数

  1. -Dlog4j.configuration=d:\log4j.properties

或者在程序里注册系统属性

  1. System.setProperty("log4j.configuration","d:\\log4j.properties"); 

  2、当前jvm环境中log4j.configuration属性查找不到,再从当前类加载路径依次查找log4j.xml、log4j.properties。显然,是要从jar包加载了。笔者用ide导航了一些,果不其然有的jar内置了log4j.xml,

这不是我们希望的结果,而我们需要的从当前web应用类路径加载,问题的症结也便在此:配置文件加载优先级问题,并非maven问题。

公司项目是web项目,显然更适合在Servlet上下文监听器(ServletContextListener)注册这个属性,如果项目比较紧急,到这一步已经基本算是临时性解决问题了,其他的留作后期代码重构再解决。

根本性解决方法

话到此处,有一些洁癖的程序员心里嘀咕着:这一点也不优雅,一点都不适合我的作风,这样不行,肯定有更好的办法。当然,如果能写出精炼、纯粹、直接的代码是最好不过了;好吧,笔者也是这么认为(窃笑中.gif),能有别人封装好的成熟代码最好不过,

不要重复造轮子;当然写博文这个时间算是充足,考虑到项目用了spring,以往经常用spring来托管log4j。spring针对log4j 1.x有一个Log4jConfigListener

  1. package org.springframework.web.util;
  2.  
  3. import javax.servlet.ServletContextEvent;
  4. import javax.servlet.ServletContextListener;
  5.  
  6. /**
  7. * Bootstrap listener for custom log4j initialization in a web environment.
  8. * Delegates to {@link Log4jWebConfigurer} (see its javadoc for configuration details).
  9. *
  10. * <b>WARNING: Assumes an expanded WAR file</b>, both for loading the configuration
  11. * file and for writing the log files. If you want to keep your WAR unexpanded or
  12. * don't need application-specific log files within the WAR directory, don't use
  13. * log4j setup within the application (thus, don't use Log4jConfigListener or
  14. * Log4jConfigServlet). Instead, use a global, VM-wide log4j setup (for example,
  15. * in JBoss) or JDK 1.4's {@code java.util.logging} (which is global too).
  16. *
  17. * <p>This listener should be registered before ContextLoaderListener in {@code web.xml}
  18. * when using custom log4j initialization.
  19. *
  20. * @author Juergen Hoeller
  21. * @since 13.03.2003
  22. * @see Log4jWebConfigurer
  23. * @see org.springframework.web.context.ContextLoaderListener
  24. * @see WebAppRootListener
  25. * @deprecated as of Spring 4.2.1, in favor of Apache Log4j 2
  26. * (following Apache's EOL declaration for log4j 1.x)
  27. */
  28. @Deprecated
  29. public class Log4jConfigListener implements ServletContextListener {
  30.  
  31. @Override
  32. public void contextInitialized(ServletContextEvent event) {
  33. Log4jWebConfigurer.initLogging(event.getServletContext());
  34. }
  35.  
  36. @Override
  37. public void contextDestroyed(ServletContextEvent event) {
  38. Log4jWebConfigurer.shutdownLogging(event.getServletContext());
  39. }
  40.  
  41. }

通过跟踪代码,我们发现spring也是扩展了ServletContextListener监听器接口,采用迂回的方式,返回到我们刚刚提及的Log4jManager类,初始化了日志功能。

当然,在spring4.2.1以后,文档说已经过期,将使用log4j2系列日志,各位酌情使用,时间太晚,笔者没有再继续追下去。

我们只需要在web.xml中配置一个监听器和上下文参数,即可解决问题

  1. <context-param>
  2. <param-name>log4jConfigLocation</param-name>
  3. <param-value>classpath:log4j.properties</param-value>
  4. </context-param>
  5.  
  6. <listener>
  7. <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
  8. </listener>

当然,通过查看Log4jWebConfigurer的文档注释,我们也可以指定日志的存放路,放到当前应用的上下文路径中。

  1. log4j.appender.myfile.File=${webapp.root}/WEB-INF/demo.log

至此,日志兼容性问题便解决,可爱的log4j日志又在控制台活蹦乱跳了,眼前便豁然开朗。

java传统web项目添加maven管理jar包,log4j无法正常输出日志的更多相关文章

  1. intellij 创建java web项目(maven管理的SSH)

    intellij 创建java web项目(maven管理的SSH) 环境intellij IDEA14.MAVEN.Spring.Struts2.Hibernate.Java Web.工程搭建. 1 ...

  2. Maven管理jar包依赖常出现的不能实例化类的问题

    you'ji 在maven管理jar包依赖时,存在一种常见的问题. pom.xml文件配置没问题,通过eclipse里中的maven dependencies查看,也确实有这个jar 包,或者这个类. ...

  3. IntelliJ IDEA基于maven构建的web项目找不到jar包

    基于maven构建的springMVC项目,下载好jar包import后,运行提示ClassNotFoundException: java.lang.ClassNotFoundException: o ...

  4. maven项目添加db2的jar包

    安装完DB2后,SQLLIB文件夹下的java目录下有对应的jar包,我的SQLLIB文件夹位置在 D:\Program Files\IBM\SQLLIB\java 处. 此目录直接添加到CLASSP ...

  5. 转!java web项目 build path 导入jar包,tomcat启动报错 找不到该类

    在eclipse集成tomcat开发java web项目时,引入的外部jar包,编译通过,但启动tomcat运行web时提示找不到jar包内的类,需要作如下配置,将jar包在部署到集成的tomcat环 ...

  6. Web项目添加Maven支持

    很多时候,进入到某个项目组,并非项目刚刚开始:同样,很多时候,项目并非一开始就有Maven支持: 对现有的项目支持Maven,需要修改以下地方: 1. 将以下代码拷贝到工程根路径下的  .projec ...

  7. 已有Web项目添加Maven支持

    IDE:MyEclipse 当我们在现有的Web开发项目中集成 Maven 的时候,需要修改以下几个地方: 1.将以下代码拷贝到工程根路径下的 .project 文件中的 <buildSpec& ...

  8. IDEA maven项目添加自己的jar包依赖

    在pom中添加<dependency> <groupId>com.sim</groupId> <artifactId>SM-1.60</artif ...

  9. springboot项目用maven打jar包

    clean package -Dmaven.test.skip=true idea eclipse STS

随机推荐

  1. ArcGIS案例学习笔记2_2_等高线生成DEM和三维景观动画

    ArcGIS案例学习笔记2_2_等高线生成DEM和三维景观动画 计划时间:第二天下午 教程:Pdf/405 数据:ch9/ex3 方法: 1. 创建DEM SA工具箱/插值分析/地形转栅格 2. 生成 ...

  2. C#中Graphics的画图代码【转】

    我要写多几个字上去 string str = "Baidu"; //写什么字? Font font = Font("宋体",30f); //字是什么样子的? B ...

  3. 吴裕雄 python oracle子查询的用法(3)

    import cx_Oracle conn = cx_Oracle.connect("scott/admin@localhost:1521/orcl")cursor = conn. ...

  4. 内存占用过高 kill 调整mysql内存占用

    通过 /var/log/messages  查看  被系统kill掉的进程   如果是自己崩溃会产生 hs_err_ 修改mysql  my.cnf    innodb_buffer_pool_siz ...

  5. pip 离线安装

    pip download ansible -d . --trusted-host mirrors.aliyun.com pip install ansible-2.7.5.tar.gz  --user ...

  6. console.log() 字体颜色

    console.log("%c%s", "color: #fff; background: #20B2AA; font-size: 24px;", " ...

  7. CSS 美化radio checkbox

    CSS 样式 <style type="text/css"> .option-input { -webkit-appearance: none; -moz-appear ...

  8. cd-hit软件

    参考网址:https://www.jianshu.com/p/57af07b9e986 1.安装 wget https://github.com/weizhongli/cdhit/releases/d ...

  9. 一个查询指定错误记录数据表错误记录条数的shell脚本

    #!/bin/bash #author:skycheng #parameters db_user=dbuser db_pass=dbpass db_host=xxx.xxx.xxx.xxx datab ...

  10. MySQL driver for Node

    [MySQL driver for Node] 1.安装 2.一个示例 From this example, you can learn the following: Every method you ...