概述
以ActiveMQ + Log4j + Spring的技術組合,實現基於消息隊列的統一日志服務。
與參考文章的比較
- 更新了技術的版本
e.g. Spring升級到4.2.0,ActiveMQ升級到5.13.2 - 更新了依賴
e.g. 使用activemq-client 5.13.2替換activemq-core 5.7.0,並取消了多余的spring-jms依賴 - 精簡了配置
e.g. 去掉spring.xml中的jmsTemplate - 其他略述
前提
為理解文章的內容,你可能需要先了解下面的知識:
- 了解基於Maven的項目結構
- 下載並運行ActiveMQ
- 了解log4j基於properties配置的簡單用法
- 了解基於Spring-webmvc的框架的搭建
當然,這只是建議......
技術版本
- ActiveMQ - 5.13.2
- Log4j - 1.2.17
- Spring - 4.2.4
結構圖
1.節點拓撲圖

說明:
- 應用系統基於log4j規范,通過JMSAppender將日志發送到ActiveMQ
- Log Server向ActiveMQ訂閱消息,並指定MessageListener的實現來接收ActiveMQ發布的消息
實現
1.Log Server
你可以從
amqlog-server拿到源代碼。
1.1.文件目錄結構
1 pom.xml 2 src/main/webapp/ 3 |---- WEB-INF/ 4 |---- web.xml 5 |---- index.jsp # 忽略 6 src/main/resources/ 7 |---- spring-beans.xml 8 |---- topic.properties # 集中管理修改概率比較高的屬性配置 9 src/main/java/ 10 |---- cn.sinobest.asj.logserver 11 |---- LogListener.java # 接收並輸出log message
1.2.文件內容
1.2.1. pom.xml
1 <project xmlns="http://maven.apache.org/POM/4.0.0" 2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4 <modelVersion>4.0.0</modelVersion> 5 <groupId>cn.sinobest.asj</groupId> 6 <artifactId>log-servler</artifactId> 7 <packaging>war</packaging> 8 <version>0.0.1-SNAPSHOT</version> 9 <name>amqlog-servler Maven Webapp</name> 10 <url>http://maven.apache.org</url> 11 <description>日志服務器,從ActiveMQ訂閱主題,從而獲取相關的日志數據</description> 12 <dependencies> 13 <dependency> 14 <groupId>junit</groupId> 15 <artifactId>junit</artifactId> 16 <version>3.8.1</version> 17 <scope>test</scope> 18 </dependency> 19 <!-- use to import spring-webmvc framework --> 20 <dependency> 21 <groupId>org.springframework</groupId> 22 <artifactId>spring-web</artifactId> 23 <version>4.2.4.RELEASE</version> 24 </dependency> 25 <dependency> 26 <groupId>org.springframework</groupId> 27 <artifactId>spring-jms</artifactId> 28 <version>4.2.4.RELEASE</version> 29 </dependency> 30 <!-- use to subscribe topic message from ActiveMQ --> 31 <dependency> 32 <groupId>org.apache.activemq</groupId> 33 <artifactId>activemq-client</artifactId> 34 <version>5.13.2</version> 35 </dependency> 36 <!-- use to extract log content from message --> 37 <dependency> 38 <groupId>log4j</groupId> 39 <artifactId>log4j</artifactId> 40 <version>1.2.17</version> 41 </dependency> 42 </dependencies> 43 <build> 44 <finalName>amqlog-servler</finalName> 45 </build> 46 </project>
1.2.2. web.xml
1 <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 2 xmlns="http://java.sun.com/xml/ns/javaee" 3 xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 4 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 5 id="WebApp_ID" version="3.0" metadata-complete="false"> 6 <display-name>Archetype Created Web Application</display-name> 7 <context-param> 8 <param-name>contextConfigLocation</param-name> 9 <param-value> 10 classpath:spring-beans.xml 11 </param-value> 12 </context-param> 13 <listener> 14 <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 15 </listener> 16 </web-app>
1.2.3. spring-beans.xml
裝配圖

說明:左上角標識由誰提供具體的實現,沒有標識的由自己提供實現。
內容
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:context="http://www.springframework.org/schema/context" 5 xsi:schemaLocation="http://www.springframework.org/schema/beans 6 http://www.springframework.org/schema/beans/spring-beans-4.2.xsd 7 http://www.springframework.org/schema/context 8 http://www.springframework.org/schema/context/spring-context-4.2.xsd"> 9 <context:property-placeholder location="classpath:topic.properties" /> 10 11 <bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"> 12 <property name="brokerURL" value="${topic.brokerURL}" /> 13 <!-- add trusted packages. see http://activemq.apache.org/objectmessage.html --> 14 <property name="trustedPackages"> 15 <list> 16 <value>org.apache.log4j.spi</value> 17 </list> 18 </property> 19 </bean> 20 <bean id="connectionFactory" 21 class="org.springframework.jms.connection.SingleConnectionFactory"> 22 <property name="targetConnectionFactory" ref="targetConnectionFactory" /> 23 </bean> 24 25 <bean id="destination" class="org.apache.activemq.command.ActiveMQTopic"> 26 <constructor-arg name="name" value="${topic.topicName}" /> 27 </bean> 28 29 <!-- define the message-listener to receive and dipose log data. --> 30 <bean id="messageListener" class="cn.sinobest.asj.logserver.LogListener" /> 31 32 <bean id="jmsContainer" 33 class="org.springframework.jms.listener.DefaultMessageListenerContainer"> 34 <property name="connectionFactory" ref="connectionFactory" /> 35 <property name="destination" ref="destination" /> 36 <property name="messageListener" ref="messageListener" /> 37 </bean> 38 </beans>
說明:
在targetConnectionFactory的屬性中,指定了trustedPackages。ActiveMQ自5.12.2版本之后,強制用戶指定一份可信任的packages白名單,以對付ObjectMessage存在的安全漏洞。具體內容可參考:
http://activemq.apache.org/objectmessage.html。
1.2.4. topic.properties
1 topic.brokerURL=tcp://localhost:61616 2 topic.topicName=demo
注意:brokerURL的值必須和ActiveMQ的監聽地址一致。
1.2.5. LogListener.java
1 package cn.sinobest.asj.logserver; 2 import javax.jms.JMSException; 3 import javax.jms.Message; 4 import javax.jms.MessageListener; 5 import org.apache.activemq.command.ActiveMQObjectMessage; 6 import org.apache.log4j.spi.LoggingEvent; 7 public class LogListener implements MessageListener { 8 private static final String TEMPLATE = "[%-5s] %s"; 9 public void onMessage(Message message) { 10 try { 11 // extract LoggingEvent from message 12 // you must set org.apache.log4j.spi into the trusted packages list 13 // see spring-beans.xml in classpath 14 LoggingEvent event = (LoggingEvent) ((ActiveMQObjectMessage) message) 15 .getObject(); 16 String content = String.format(TEMPLATE, event.getLevel() 17 .toString(), event.getMessage().toString()); 18 System.out.println(content); 19 } catch (JMSException e) { 20 e.printStackTrace(); 21 } 22 } 23 }
說明:這里的LoggingEvent來自package org.apache.log4j.spi,該package在spring-beans.xml的白名單中。
2.Log Client
log client模擬一般的應用系統。該應用系統有日志存儲的需要,將日志發送給ActiveMQ而不用關心日志最終的存儲方式。這里僅用一個簡單的JavaSE project來模擬,但是已經足夠提供完整的核心代碼。
你可以從
amqlog-client拿到源代碼。
2.1.文件目錄結構
1 pom.xml 2 src/main/resources/ 3 |---- log4j.properties # 配置日志輸出地點,及ActiveMQ的相關參數 4 |---- jndi.properties # 配置topic 5 src/main/java/ 6 |---- cn.sinobest.asj.logclient 7 |---- LogProducer.java # 生成並輸出日志
2.2.文件內容
2.2.1. pom.xml
1 <project xmlns="http://maven.apache.org/POM/4.0.0" 2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4 <modelVersion>4.0.0</modelVersion> 5 <groupId>cn.sinobest.asj</groupId> 6 <artifactId>amqlog-client</artifactId> 7 <version>0.0.1-SNAPSHOT</version> 8 <name>Simple app to send log to ActiveMQ</name> 9 <description>模擬一般的應用系統,通過log4j發送日志到ActiveMQ</description> 10 <dependencies> 11 <!-- use to write log --> 12 <dependency> 13 <groupId>log4j</groupId> 14 <artifactId>log4j</artifactId> 15 <version>1.2.17</version> 16 </dependency> 17 <dependency> 18 <groupId>commons-logging</groupId> 19 <artifactId>commons-logging</artifactId> 20 <version>1.1.1</version> 21 </dependency> 22 <!-- use to import class org.apache.activemq.jndi.ActiveMQInitialContextFactory 23 to write log to ActiveMQ --> 24 <dependency> 25 <groupId>org.apache.activemq</groupId> 26 <artifactId>activemq-client</artifactId> 27 <version>5.13.2</version> 28 </dependency> 29 </dependencies> 30 </project>
2.2.2. log4j.properties
1 # define the stand out appender 2 log4j.appender.stdout=org.apache.log4j.ConsoleAppender 3 log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 4 log4j.appender.stdout.layout.ConversionPattern=[%-5p] %-d{yyyy-MM-dd HH:mm:ss z}%n %m%n%n 5 6 # define the jms appender 7 log4j.appender.jms=org.apache.log4j.net.JMSAppender 8 log4j.appender.jms.InitialContextFactoryName=org.apache.activemq.jndi.ActiveMQInitialContextFactory 9 log4j.appender.jms.ProviderURL=tcp://localhost:61616 10 # TopicBindingName可以自由配置,只需要確保提供對應的jndi屬性即可 11 log4j.appender.jms.TopicBindingName=topicName 12 # TopicConnectionFactoryBindingName目前不能自由配置 13 log4j.appender.jms.TopicConnectionFactoryBindingName=ConnectionFactory 14 15 # define the logger 16 log4j.rootLogger=INFO, stdout, jms
注意:log4j.appender.jms.ProviderURL的值必須和ActiveMQ的監聽地址一致。
2.2.3. jndi.properties
1 topic.topicName=demo
注意:key的后半部分(topicName)必須與log4j.properties中的log4j.appender.jms.TopicBindingName一致。
屬性間的對應關系

2.2.4. LogProducer.java
1 package cn.sinobest.asj.logclient; 2 import org.apache.commons.logging.Log; 3 import org.apache.commons.logging.LogFactory; 4 public class LogProducer { 5 private static final Log log = LogFactory.getLog(LogProducer.class); 6 /** 7 * @param args 8 */ 9 public static void main(String[] args) { 10 log.debug("this is a debug message."); 11 log.info("this is a info message."); 12 log.warn("this is a warn message."); 13 log.error("this is a error message"); 14 System.exit(0); 15 } 16 }
說明:debug的內容不會發送到ActiveMQ。
測試
- 啟動ActiveMQ
cd到ActiveMQ的解壓縮目錄,在cmd執行bin\activemq start - 部署Log Server到Tomcat並啟動
- 運行Log Client的LogProducer main方法
- Log Server的Console會有: