英文原文:Asynchronous logging using Log4j, ActiveMQ and Spring
我的团队和我正在创建一个由一组RESTful JSON服务组成的服务平台,该平台中的每个服务在平台中的作用就是分别提供一些独特的功能和/或数据。由于平台中产生的日志四散各处,所以我们想,要是能将这些日志集中化处理一下,并提供一个能够让我们查看、过滤、排序和搜索我们所有的日志的基本型的日志查看工具就好了。我们还想让我们的日志是异步式的,因为我们可不想在写日志的时候(比方说,可能会将日志直接写入数据库),让我们提供的服务因为写日志而暂时被阻挡住。
实现这个目标的策略非常简单明了。
安装ActiveMQ创建一个log4j的日志追加器,将日志写入队列(log4j自带了一个这样的追加器,不过现在让我们自己来写一个吧。)写一个消息侦听器,从MQ
服务器
上所设置的JMS队列中读取日志并将日志持久化
下面让我们分步来看这个策略是如何得以实现的。
安装ActiveMQ
安装一个外部的ActiveMQ服务器简单极了。这个链接http://servicebus.blogspot.com/2011/02/installing-apache-active-mq-on-ubuntu.html是在Ubuntu上安装ActiveMQ的一个非常棒的指南。你还可以选择在你的应用中嵌入一个消息代理,采用Spring就可以非常轻松实现。 我们将在后文中详谈具体的实现方法。
创建一个Lo4j的JMS日志追加器
首先,我们来创建一个log4j的JMS日志追加器。log4j自带了一个这样的追加器(该追加器没有将日志写入一个队列,而是写给了一个话题)
01 |
import javax.jms.DeliveryMode;
|
02 |
import javax.jms.Destination;
|
03 |
import javax.jms.MessageProducer;
|
04 |
import javax.jms.ObjectMessage;
|
05 |
import javax.jms.Session;
|
07 |
import org.apache.activemq.ActiveMQConnectionFactory;
|
08 |
import org.apache.log4j.Appender;
|
09 |
import org.apache.log4j.AppenderSkeleton;
|
10 |
import org.apache.log4j.Logger;
|
11 |
import org.apache.log4j.PatternLayout;
|
12 |
import org.apache.log4j.spi.LoggingEvent;
|
15 |
* JMSQueue appender is a log4j appender that writes LoggingEvent to a queue.
|
19 |
public class JMSQueueAppender extends AppenderSkeleton implements Appender{
|
21 |
private static Logger logger = Logger.getLogger( "JMSQueueAppender" );
|
23 |
private String brokerUri;
|
24 |
private String queueName;
|
32 |
public boolean requiresLayout() {
|
37 |
protected synchronized void append(LoggingEvent event) {
|
41 |
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(
|
44 |
// Create a Connection
|
45 |
javax.jms.Connection connection = connectionFactory.createConnection();
|
49 |
Session session = connection.createSession( false ,Session.AUTO_ACKNOWLEDGE);
|
51 |
// Create the destination (Topic or Queue)
|
52 |
Destination destination = session.createQueue( this .queueName);
|
54 |
// Create a MessageProducer from the Session to the Topic or Queue
|
55 |
MessageProducer producer = session.createProducer(destination);
|
56 |
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
|
58 |
ObjectMessage message = session.createObjectMessage( new LoggingEventWrapper(event));
|
60 |
// Tell the producer to send the message
|
61 |
producer.send(message);
|
66 |
} catch (Exception e) {
|
71 |
public void setBrokerUri(String brokerUri) {
|
72 |
this .brokerUri = brokerUri;
|
75 |
public String getBrokerUri() {
|
79 |
public void setQueueName(String queueName) {
|
80 |
this .queueName = queueName;
|
83 |
public String getQueueName() {
|
下面让我们看看这里面发生了什么事情。
第19行:We我们实现了的Log4J日志追加器接口,该接口要求我们实现三个方法:requiresLayout, close和append。我们将暂时简化处理过程,实现所需的append方法。在对logger进行调用时这个方法就会被调用。
第37行: log4j将一个LoggingEvent对象作为参数对append方法进行调用,这个LoggingEvent对象表示了对logger的一次调用,它封装了每一个日志项的所有信息。
第41和42行:将指向JMS的uri作为参数,创建一个连接工厂对象,在我们的情况下,该uri指向的是我们的ActiveMQ服务器。
第45, 46和49行: 我们同JMS服务器建立一个连接和会话。会话有多种打开模式。在Auto_Acknowledge模式的会话中,消息的应答会自动发生。Client_Acknowledge 模式下,客户端需要对消息的接收和/或处理进行显式地应答。另外还有两种其它的模式。有关细节,请参考文档http://download.oracle.com/javaee/1.4/api/javax/jms/Session.html
第52行: 创建一个队列。将队列的名字作为参数发送给连接
第56行: 我们将发送模式设置为Non_Persistent。另一个可选的模式是Persistent ,在这种模式下,消息会持久化到一个持久性存储系统中。持久化模式会降低系统速度,但能增加了消息传递的可靠性。
第58行: 这行我们做了很多事。首先我将一个LoggingEvent对象封装到了一个LoggingEventWrapper对象之中。这么做是因为LoggingEvent对象有一些属性不支持序列化,另外还有一个原因是我想记录一些额外的信息,比如IP地址和主机名。接下来,使用JMS的会话对象,我们把一个对象(LoggingEventWrapper对象)做好了发送前的准备。
第61行: 我将该对象发送到了队列中。
下面所示是LoggingEventWrapper的代码。
01 |
import java.io.Serializable;
|
02 |
import java.net.InetAddress;
|
03 |
import java.net.UnknownHostException;
|
05 |
import org.apache.log4j.EnhancedPatternLayout;
|
06 |
import org.apache.log4j.spi.LoggingEvent;
|
09 |
* Logging Event Wraps a log4j LoggingEvent object. Wrapping is required by some information is lost
|
10 |
* when the LoggingEvent is serialized. The idea is to extract all information required from the LoggingEvent
|
11 |
* object, place it in the wrapper and then serialize the LoggingEventWrapper. This way all required data remains
|
17 |
public class LoggingEventWrapper implements Serializable{
|
19 |
private static final String ENHANCED_PATTERN_LAYOUT = "%throwable" ;
|
20 |
private static final long serialVersionUID = 3281981073249085474L;
|
21 |
private LoggingEvent loggingEvent;
|
23 |
private Long timeStamp;
|
25 |
private String logger;
|
26 |
private String message;
|
27 |
private String detail;
|
28 |
private String ipAddress;
|
29 |
private String hostName;
|
31 |
public LoggingEventWrapper(LoggingEvent loggingEvent){
|
32 |
this .loggingEvent = loggingEvent;
|
34 |
//Format event and set detail field
|
35 |
EnhancedPatternLayout layout = new EnhancedPatternLayout();
|
36 |
layout.setConversionPattern(ENHANCED_PATTERN_LAYOUT);
|
37 |
this .detail = layout.format( this .loggingEvent);
|
40 |
public Long getTimeStamp() {
|
41 |
return this .loggingEvent.timeStamp;
|
44 |
public String getLevel() {
|
45 |
return this .loggingEvent.getLevel().toString();
|
48 |
public String getLogger() {
|
49 |
return this .loggingEvent.getLoggerName();
|
52 |
public String getMessage() {
|
53 |
return this .loggingEvent.getRenderedMessage();
|
56 |
public String getDetail() {
|
60 |
public LoggingEvent getLoggingEvent() {
|
64 |
public String getIpAddress() {
|
66 |
return InetAddress.getLocalHost().getHostAddress();
|
67 |
} catch (UnknownHostException e) {
|
68 |
return "Could not determine IP" ;
|
72 |
public String getHostName() {
|
74 |
return InetAddress.getLocalHost().getHostName();
|
75 |
} catch (UnknownHostException e) {
|
76 |
return "Could not determine Host Name" ;
|
消息侦听器
消息侦听器会对队列(或话题)进行“侦听”。一旦有新消息添加到了队列中,onMessage 方法就会得到调用。
01 |
import javax.jms.JMSException;
|
02 |
import javax.jms.Message;
|
03 |
import javax.jms.MessageListener;
|
04 |
import javax.jms.ObjectMessage;
|
06 |
import org.apache.log4j.Logger;
|
07 |
import org.springframework.beans.factory.annotation.Autowired;
|
08 |
import org.springframework.stereotype.Component;
|
11 |
public class LogQueueListener implements MessageListener
|
13 |
public static Logger logger = Logger.getLogger(LogQueueListener. class );
|
16 |
private ILoggingService loggingService;
|
18 |
public void onMessage( final Message message )
|
20 |
if ( message instanceof ObjectMessage )
|
23 |
final LoggingEventWrapper loggingEventWrapper = (LoggingEventWrapper)((ObjectMessage) message).getObject();
|
24 |
loggingService.saveLog(loggingEventWrapper);
|
26 |
catch ( final JMSException e)
|
28 |
logger.error(e.getMessage(), e);
|
29 |
} catch (Exception e) {
|
30 |
logger.error(e.getMessage(),e);
|
第23行: 检查从队列中拿到的对象是否是ObjectMessage的实例
第26行: 从消息中提取出LoggingEventWrapper对象
第27行: 调用服务方法将日志持久化
Spring配置
01 |
<? xml version = "1.0" encoding = "UTF-8" ?>
|
02 |
< beans xmlns = "http://www.springframework.org/schema/beans" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" xmlns:p = "http://www.springframework.org/schema/p" xmlns:context = "http://www.springframework.org/schema/context" xmlns:jms = "http://www.springframework.org/schema/jms" xmlns:amq = "http://activemq.apache.org/schema/core" xmlns:aop = "http://www.springframework.org/schema/aop" xsi:schemaLocation = "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms-3.0.xsd http://activemq.apache.org/schema/core http://activemq.apache.org/schema/core/activemq-core-5.5.0.xsd" >
|
03 |
<!-- lets create an embedded ActiveMQ Broker --> |
04 |
<!-- uncomment the tag below only if you need to create an embedded broker --> |
05 |
<!-- amq:broker useJmx="false" persistent="false"> |
06 |
<amq:transportConnectors>
|
07 |
<amq:transportConnector uri="tcp://localhost:61616" />
|
08 |
</amq:transportConnectors>
|
10 |
<!-- ActiveMQ destinations to use --> |
11 |
< amq:queue id = "destination" physicalName = "logQueue" />
|
12 |
<!-- JMS ConnectionFactory to use, configuring the embedded broker using XML --> |
13 |
< amq:connectionFactory id = "jmsFactory" brokerURL = "tcp://localhost:61616" />
|
14 |
< bean id = "connectionFactory" class = "org.springframework.jms.connection.CachingConnectionFactory" >
|
15 |
< constructor-arg ref = "jmsFactory" />
|
16 |
< property name = "exceptionListener" ref = "JMSExceptionListener" />
|
17 |
< property name = "sessionCacheSize" value = "100" />
|
19 |
<!-- Spring JMS Template --> |
20 |
< bean id = "jmsTemplate" class = "org.springframework.jms.core.JmsTemplate" >
|
21 |
< constructor-arg ref = "connectionFactory" />
|
23 |
<!-- listener container definition using the jms namespace, concurrency |
24 |
is the max number of concurrent listeners that can be started --> |
25 |
< jms:listener-container concurrency = "10" >
|
26 |
< jms:listener id = "QueueListener" destination = "logQueue" ref = "logQueueListener" />
|
27 |
</ jms:listener-container >
|
第5到9行: 使用代理标签建立一个嵌入式消息代理。既然我用的是外部消息代理,所以我就不需要它了。
第12行: 给出你想要连接的队列的名字
第14行: 代理服务器的URI
第15到19行: 连接工厂的设置
第26到28行: 消息侦听器的设置,这里可以指定用于从队列中读取消息的并发现线程的个数
当然,上面的例子做不到让你能够拿来就用。你还需要包含所有的JMS依赖库并实现完成日志持久化任务的服务。但是,我希望本文能够为你提供一个相当不错的思路。
分享到:
相关推荐
6. **Log4j**: 这是一个日志记录库,用于记录应用程序运行过程中的各种信息,便于问题追踪和调试。在Spring和ActiveMQ整合中,日志记录是必不可少的。 7. **activemq-all-5.15.0.jar**: 这是ActiveMQ的全功能JAR包...
此外,还需要引入JMS规范的实现包`jms-1.1.jar`,以及其他日志相关的库,如`log4j-1.2.15.jar`和`slf4j`系列的库。 ### 集成步骤 集成的关键在于Spring配置文件的编写。配置文件应遵循Spring的XML命名空间和schema...
- 日志可以通过log4j配置文件(conf/log4j.properties)进行定制。 9. **开发集成**: - 开发人员可以使用Java、C#、Python等多种语言的API与ActiveMQ交互。 - 对于Web应用,可以利用Spring框架集成ActiveMQ,...
同时,Spring项目还需要如`spring-beans-2.5.6.jar`, `spring-context-2.5.6.jar`, `spring-jms-2.5.6.jar`等核心jar包,以及其他依赖的jar文件,例如JMS API和log4j等。 **集成步骤** 1. **添加ActiveMQ库**:将...
6. **log4j-1.2.17.jar**:这是一个流行的日志记录库,用于收集和记录应用程序运行时的信息,便于调试和监控。 7. **commons-net-3.3.jar**:Apache Commons Net库提供了各种网络协议的实现,如FTP、SMTP等,可能在...
3. `log4j-1.2.17.jar`:这是一个流行的Java日志框架,用于记录应用程序运行过程中的事件,帮助开发者调试和监控系统状态。 4. `spring-core-3.0.7.RELEASE.jar` 和 `spring-aop-3.0.7.RELEASE.jar`:分别代表...
6. **log4j-1.2.14.jar**:经典的日志记录库,用于应用程序的日志输出和管理。 7. **activemq-core-5.0.0.jar**:ActiveMQ的主要核心库,包含消息处理和传输的关键代码。 8. **jaxb-api-2.0.jar**:Java ...
Camel使用EIP(Enterprise Integration Patterns)来设计和实现集成方案,通过简单的DSL(Domain Specific Language)来定义路由规则。 **Spring框架** 是Java开发中的一个基石,它简化了企业级应用的开发,提供了...
6. **Log4j**:`log4j-1.2.17.jar`是Apache的流行日志记录框架,它为ActiveMQ提供了日志记录服务,帮助开发者跟踪和诊断应用程序中的问题。 7. **Commons Net**:`commons-net-3.3.jar`是Apache Commons的一个子...
6. "log4j-1.2.17.jar":经典的日志记录库,用于记录应用程序的运行日志,便于调试和监控。 7. "spring-aop-3.2.4.RELEASE.jar":Spring AOP模块,提供了面向切面编程的支持,可以定义横切关注点,如日志、事务管理...
6. **log4j-1.2.17.jar**:这是一个日志记录库,用于收集和处理应用程序的运行时日志信息,提供灵活的日志配置和输出格式。 7. **spring-core-3.1.3.RELEASE.jar** 和 **spring-aop-3.1.3.RELEASE.jar**:进一步的...
log4j-1.2.14.jar是著名的日志记录框架,它允许ActiveMQ记录和管理各种级别的日志信息,对于调试和监控系统的运行状态非常有用。 commons-lang-2.4.jar和commons-net-2.0.jar分别提供了Java语言工具类和网络通信...
4. `log4j-1.2.14.jar`:日志处理库,提供灵活的日志记录机制,便于调试和监控ActiveMQ的运行状态。 5. `spring-osgi-core-1.2.1.jar`:Spring对OSGi(开放服务网关协议)的支持,允许在模块化环境中部署和管理...
5. `log4j-1.2.14.jar`:日志记录框架,用于记录应用程序的运行日志,便于调试和监控。 6. `commons-lang-2.1.jar`:Apache Commons Lang库,提供了许多实用的Java类库扩展,如字符串处理、日期时间操作等。 7. `...
- `log4j-1.2.14.jar`:一个广泛使用的日志记录框架,为Java应用程序提供灵活的日志记录功能。 - `activeio-core-3.1.2.jar`:ActiveMQ的内核组件,提供高性能的网络I/O服务,是ActiveMQ底层通信的基础。 4. **...
5. `log4j-1.2.14.jar`:这是Log4j的日志记录库,用于记录应用程序的调试和日志信息。 6. `backport-util-concurrent-2.1.jar`:提供了Java 5并发特性的回移植,以便在不支持这些特性的JVM上使用。 7. `xbean-spring...
- 日志库:如log4j、slf4j,提供日志记录功能。 - 验证框架:如Hibernate Validator,用于数据验证。 - 缓存库:如Ehcache,提供缓存机制以提高性能。 - 消息队列客户端:如ActiveMQ,支持消息传递和异步处理。 ...
本文将深入探讨如何使用`ApplicationEvent`、注解和消息队列(MQ)来实现日志记录的公用逻辑。我们将重点关注Java日志的高级实践,涉及到的服务实现、接口设计、消息中间件的运用,以及相关的代码结构。 首先,`...