log4j.xml里实现读取变量(spring容器篇)
- 需求背景
公司日志系统不太完善,集群的服务器日志散落在不同的服务器上,要从公司层面解决这个问题不太现实,目前的项目进度也不允许,当前系统会有很多对接,有上游下游,生产环境调试看日志太麻烦了, 所以准备自己动手做一个workaround。
另一方面,公司开发、上线有好几套环境,不可能每套环境都去改一次,用变量可以把对应信息写在zookeeper里,这样就可以一劳永逸了。
2. 前期调研
之前用过mongodb,而且capped collection用来做非永久性日志非常合适,于是就从这方面入手。四处找了找下来分析下来觉得还是用log4j来把日志输入,一方面这个 org.log4mongo 包装了写mongodb的所有方面用起来方便,另一方面,一个logger.info就可以做到这样的效果对代码的入侵是很小的。
3. 说干咱就干
首先pom里引入所需要的jar包,这里注意log4mongo只适用log4j 版本1.2.15 及以上。
<!-- for MongoDbAppender start --> <dependency> <groupId>org.mongodb</groupId> <artifactId>mongo-java-driver</artifactId> <version>2.12.4</version> </dependency> <dependency> <groupId>org.log4mongo</groupId> <artifactId>log4mongo-java</artifactId> <version>0.7.4</version> </dependency> <!-- for MongoDbAppender end --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.15</version> </dependency>
log4j.xml里加入以下:
<appender name="mongoDBAppender" class="org.log4mongo.MongoDbAppender"> <param name="hostname" value="${hostName}" /> <param name="port" value="${port}" /> <param name="databaseName" value="${databaseName}" /> <param name="collectionName" value="${collectionName}" /> </appender> <appender name="asynAppender" class="org.apache.log4j.AsyncAppender"> <param name="bufferSize" value="100000" /> <appender-ref ref="mongoDBAppender" /> </appender> <logger name="com.xxx.xxx" additivity="false"> <level value="INFO" /> <appender-ref ref="CONSOLE" /> <appender-ref ref="asynAppender" /> </logger>
注意上面的log4j.xml里我用到了类似这样的 ${hostName} 变量读取。
这里,查阅了google , stackoverflow,查看源代码会看到
org.apache.log4j.helpers.OptionConverter 里
static String DELIM_START = "${"; static char DELIM_STOP = '}'; /** Very similar to <code>System.getProperty</code> except that the {@link SecurityException} is hidden. @param key The key to search for. @param def The default value to return. @return the string value of the system property, or the default value if there is no property with that key. @since 1.1 */ public static String getSystemProperty(String key, String def) { try { return System.getProperty(key, def); } catch(Throwable e) { // MS-Java throws com.ms.security.SecurityExceptionEx LogLog.debug("Was not allowed to read system property \""+key+"\"."); return def; } }
从上面的代码里可以看出,log4j的处理逻辑会去解析xml里的 ${} 变量,之后会去取System的property。
也就是说,log4j.xml里是可以用变量去取代的。OK,既然能读我们就去读。
那么问题来了。。。。。。
问题1:System的property里面是没有我们需要的hostName的
Easy,没有就塞进去呗,System.setProperty("hostName","11.11.11.11") 注意区分大小写
好了,现在系统变量里面也有了,总应该OK了吧。
问题2:因为我们用的是Spring容器,System.setProperty("hostName","11.11.11.11")放在哪里才能让log4j读到呢?(有人会说在tomcat启动参数里加,确实可行,但是,你总不能Ip变了要去改启动参数吧,扩展性不好)
Easy,因为log4j一般都会配置成由Spring listener来启动,会先于整个spring框架先启动,所以,最开始log4j启动的时候 mongoDbAppender是读不到hostName等属性的,那么我们就监听Spring容器,等容器里所有的bean都初始化好了的时间结点上去让Log4j重新读一遍配置文件,这样我的设置的System propery就可以被log4j读到。
说干就干 ,看了一下log4j的源码,启动读取log4j.xml或者log4j.properties是由
org.apache.log4j.xml.DOMConfigurator
org.apache.log4j.PropertyConfigurator
两个类来实现的,根据log4j官网的api描述
The PropertyConfigurator does not handle the advanced configuration features supported by the DOMConfigurator such as support custom ErrorHandlers, nested appenders such as the AsyncAppender, etc.
PropertyConfigurator 不能像 DOMConfigurator那样处理像 AsyncAppender 这样的,所以,一般的
log4j要实现高级一些功能还是用 log4j.xml。
https://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/PropertyConfigurator.html
上代码(这里之所以要用变量,是因为公司开发有好几套环境,这样的情况下不要每次上环境都改xml,只要在zoomkeeper里配置好就可以了)
@Component public class MongoDbLoggerInitializer implements ApplicationListener { public final String LOG_LOCATIONS = "/log/log4j.xml"; public void reloadLog4j() { Logger.info(this, "load lion properties", ""); System.setProperty("hostName","11.11.11.11"); System.setProperty("port","2121"); System.setProperty("databaseName","xxx"); System.setProperty("collectionName","xxx"); URL url = this.getClass().getResource(LOG_LOCATIONS); if (url == null) { Logger.info(this, "No resources found for [%s]!", LOG_LOCATIONS); return; } String path = url.toString(); if (StringUtils.isNotEmpty(path)) { path = path.substring(path.indexOf(":") + 1, path.length()); Logger.info(this, "DOMConfigurator.configure--start path=%s", path); DOMConfigurator.configure(path); Logger.info(this, "DOMConfigurator.configure--finished", ""); } } @Override public void onApplicationEvent(ApplicationEvent event) { // 容器启动完成之后load if (event instanceof ContextRefreshedEvent) { if (((ContextRefreshedEvent) event).getApplicationContext().getParent() == null) { reloadLog4j(); } } } }
关键点 DOMConfigurator.configure(path);
到此,目的达到了,想要的功能可以了。
上个mongodb的数据
{ "_id" : ObjectId("54b4fa4de4b011c54ede7f85"), "timestamp" : ISODate("2015-01-13T10:58:21.584Z"), "level" : "INFO", "thread" : "main", "message" : "MongoDBAppenderInitialize ----------", "loggerName" : { "fullyQualifiedClassName" : "com.xx.xx.MongoDBAppenderInitialize", "package" : ["xx", "xx", "xx", "xx", "xx", "xx", "xx", "MongoDBAppenderInitialize"], "className" : "MongoDBAppenderInitialize" }, "fileName" : "?", "method" : "?", "lineNumber" : "?", "class" : { "fullyQualifiedClassName" : "?", "package" : ["?"], "className" : "?" }, "host" : { "process" : "15703@xx.xx.xx", "name" : "xxx", "ip" : "127.0.0.1" } }
数据里还有host相关信息,对之后的查错提供帮助。
问题3. 用mongodb来做log,如果mongodb挂了,会不会影响到整个工程
这一点,我自己在机子上测试过,当我把mongodb服务器shutdown后,tomcat这边只会抛出一个Connect timeout的exception , 后续的log不会每次都抛出这个exception。
看了一下代码原来是做了一个errorHandler只会抛出第一次的exception,可以借鉴到以后的代码里。
org.apache.log4j.helpers.OnlyOnceErrorHandler
public void error(String message, Exception e, int errorCode, LoggingEvent event) { if (e instanceof InterruptedIOException || e instanceof InterruptedException) { Thread.currentThread().interrupt(); } if(firstTime) { LogLog.error(message, e); firstTime = false; } }
MongoDbAppender还是会每次去连接
org.log4mongo.MongoDbAppender
@Override public void append(DBObject bson) { if (initialized && bson != null) { try { getCollection().insert(bson, getConcern()); } catch (MongoException e) { errorHandler.error("Failed to insert document to MongoDB", e, ErrorCode.WRITE_FAILURE); } } }
写在后面的话:
1.
调试过程中遇到了很多的坑,原来tomcat里的catalina.out 文件会输出一些你其它日志里看不到的Runtime exception信息(不会记录到logger里),对调试错误很有帮助。
比如,我去getResource(log/log4j.xml) 的时候,如果文件拿不到会有IllegalArgumentException,
javax.xml.parsers.DocumentBuilder.parse(File)
public Document parse(File f) throws SAXException, IOException { if (f == null) { throw new IllegalArgumentException("File cannot be null"); } //convert file to appropriate URI, f.toURI().toASCIIString() //converts the URI to string as per rule specified in //RFC 2396, InputSource in = new InputSource(f.toURI().toASCIIString()); return parse(in); }
2.
还有就是,调试的时候,可以把log4j的debug信息打出来
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/" threshold="null" debug="true">
3.
重新认识了System下的property和env
* <p><a name="EnvironmentVSSystemProperties"><i>System * properties</i> and <i>environment variables</i></a> are both * conceptually mappings between names and values. Both * mechanisms can be used to pass user-defined information to a * Java process. Environment variables have a more global effect, * because they are visible to all descendants of the process * which defines them, not just the immediate Java subprocess. * They can have subtly different semantics, such as case * insensitivity, on different operating systems. For these * reasons, environment variables are more likely to have * unintended side effects. It is best to use system properties * where possible. Environment variables should be used when a * global effect is desired, or when an external system interface * requires an environment variable (such as <code>PATH</code>). * * <p>On UNIX systems the alphabetic case of <code>name</code> is * typically significant, while on Microsoft Windows systems it is * typically not. For example, the expression * <code>System.getenv("FOO").equals(System.getenv("foo"))</code> * is likely to be true on Microsoft Windows.
从JDK文档里写的来看,property可以看成是jvm里的参数,而env则是操作系统级别的,不同的操作系统还可以大小写不敏感。
所以,更推荐使用property.
It is best to use system properties where possible.
好了,中午没睡觉把这个写了,好困。。。
参考链接:
http://blog.sina.com.cn/s/blog_4b81125f0100fo95.html
相关推荐
要在项目中使用Log4j,首先需要将`log4j.jar`添加到项目的类路径中,然后创建或引用`log4j.xml`配置文件。在代码中,通过以下方式创建和使用Logger: ```java import org.apache.log4j.Logger; public class ...
这里是log4j.xml详细的配置,在使用MyBatis框架时必要的一种配置。
这里提到的四个关键配置文件——`spring-mvc.xml`、`spring-mybatis.xml`、`web.xml`以及`log4j.properties`,对于一个基于Java的Web应用来说至关重要,特别是使用Spring MVC和MyBatis框架的时候。接下来,我们将...
Log4j提供了多种配置方式,其中最常用的有两种:XML格式的`log4j.xml`和properties格式的`log4j.properties`。接下来,我们将深入探讨如何通过这两种配置文件来设置日志输出的目标、级别以及格式。 1. **XML配置** ...
本文将详细讲解如何将Log4j整合到Web应用并通过web.xml进行配置,以实现日志信息的输出和管理。 1. **Log4j介绍** - Log4j由Apache软件基金会开发,提供了一种简单但功能强大的日志记录API,可以控制日志信息的...
《深入解析log4j.xml配置》 在Java开发中,日志记录是不可或缺的一部分,而Log4j作为一款广泛使用的日志框架,它的配置文件log4j.xml扮演着至关重要的角色。本文将深入探讨log4j.xml配置文件的结构、用途及其中的...
Log4j2 配置模板学习笔记 Log4j2 是 Java 语言中一种流行的日志记录工具,它提供了灵活的日志记录管理功能。下面我们将学习 Log4j2 配置模板的使用和配置。 引入 Log4j2 依赖 在使用 Log4j2 之前,需要在 Maven ...
config.properties:数据库配置文件 log4j.properties:mybatis日志文件 spring-mvc.xml:spring-MVC配置文件 spring-mybatis.xml:mybatis的配置文件 spring.xml
**日志框架Log4j详解** 在Java开发中,日志记录是一项不可或缺的功能,它能够帮助开发者追踪程序运行状态,定位错误,优化性能,并为后期维护提供重要信息。Log4j是Apache组织开发的一个强大的、灵活的日志记录框架...
log4j.xml配置范例log4j.xml配置范例log4j.xml配置范例log4j.xml配置范例log4j.xml配置范例
在实际应用中,将`log4j.jar`添加到项目的类路径中,并根据项目需求定制`log4j.properties`,即可实现有效的日志管理。这不仅有助于问题排查,也有利于系统维护和性能优化,因为过多的日志可能会占用大量磁盘空间,...
log4j.xml配置文件
在这个场景中,我们将讨论如何通过`log4j.xml`配置文件来实现日志的自动化配置。 首先,我们创建了一个名为`Log4jConfigListener`的类,它实现了`ServletContextListener`接口。这个监听器的作用是在Web应用启动时...
本篇文章将深入探讨这两个关键组件:日志配置文件`log4j.xml`和MySQL数据库驱动文件`mysql-connector-java-5.1.39-bin.jar`。 首先,我们来了解`log4j.xml`。Log4j是Apache组织提供的一款开源的日志记录框架,广泛...
一个比较通用的log4j.xml配置模板
这可以通过在部署环境中覆盖`log4j.properties`或`log4j.xml`文件来实现,或者使用环境变量或命令行参数动态设置。 总的来说,通过合理配置Log4j,可以轻松地实现跨平台的日志管理,使得在Windows和Linux系统中日志...
log4j2.xml文件
spring5取消Log4jConfigListener,运用Log4jServletContextListener代替Log4jConfigListener(log4j2.xml的配置)
使用log4j2.xml实现对日志的精准控制,对整个开发过程百利有之!但是也要也要注意使用的误区,具体可才看本博客下的Java异常和日志管理!
总结,Log4j2通过XML配置文件实现了与MySQL数据库的集成,允许开发者将日志信息存储在数据库中,便于长期保存和分析。理解Log4j2的配置和使用是每个Java开发者必备的技能,这对于日后的故障排查和系统维护至关重要。...