zoukankan      html  css  js  c++  java
  • 简单日志集成

    文章出现在个人博客首页时,博客园Markdown支持有问题,请点击标题再阅读,避免展示错误

    我们在开发应用时,为了便于调试和业务需要,在业务逻辑中加入了许多日志,通常这样会给人一种感觉:业务和日志耦合了,因此我们很自然的剔除掉了许多日志,并采用AOP实现日志切面。
    然而在许多情况下,我们并不能保证每个程序开发人员的代码都能做到整齐划一,逻辑复杂程度类似,某些和业务逻辑代码紧密关联的日志是无可避免的,毕竟太理想化的场景对业务和程序开发本身要求都比较高。

    本文对这两种情形的极端情况都提供了处理策略.

    1,完全的AOP切面提供日志,业务逻辑不存在任何日志代码
    2,扩展log4j Appender,所有日志都分布在业务逻辑代码当中

    为了方便日志接入到第三方系统,本文采用Activemq消息服务器接收日志.

    AOP日志集成#

    AOP切面方式实在是太热门,这种方式的优点是无侵入,下文将通过简单的业务场景,展示这种实践过程。

    温馨提示:请先行启动外置的Activemq(从官网下载安装包,默认条件启动即可),如果未启动,请打开Spring配置文件中内置broker服务

    业务流##

    执行业务方法getCustomer ---> 被日志切面拦截:执行正常业务处理pjp.proceed(),然后发送日志给目标队列(demo.business.log) ---> 监听器(demo.business.log)收到消息

    样例代码##

    业务服务

    package org.wit.ff.business;
    
    import org.springframework.stereotype.Service;
    import org.wit.ff.model.Customer;
    
    /**
     * Created by F.Fang on 2015/11/10.
     * Version :2015/11/10
     */
    
    @Service
    public class CustomerBusiness {
    
        public Customer getCustomer(int appId, int customerId){
            Customer customer = new Customer();
            customer.setCompanyId(10010);
            customer.setId(customerId);
            customer.setTitle("hnb");
            customer.setName("cxb");
            customer.setLevel(Integer.MAX_VALUE);
            return new Customer();
        }
    
        public void saveCustomer(int appId, Customer customer){
            System.out.println("appId is:"+appId);
            System.out.println("customer is:"+customer);
        }
    
    }
    

    备注:无任何Log4j日志代码

    模型

    package org.wit.ff.model;
    
    /**
     * Created by F.Fang on 2015/11/10.
     * Version :2015/11/10
     */
    public class Customer {
    
        private int id;
    
        private int companyId;
    
        private String name;
    
        private int level;
    
        private String title;
    
        public int getCompanyId() {
            return companyId;
        }
    
        public void setCompanyId(int companyId) {
            this.companyId = companyId;
        }
    
        public String getTitle() {
            return title;
        }
    
        public void setTitle(String title) {
            this.title = title;
        }
    
        public int getLevel() {
            return level;
        }
    
        public void setLevel(int level) {
            this.level = level;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    }
    
    

    日志切面

    package org.wit.ff.log;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.jms.core.JmsTemplate;
    import org.springframework.jms.core.MessageCreator;
    import org.wit.ff.util.JsonUtil;
    
    import javax.jms.Destination;
    import javax.jms.JMSException;
    import javax.jms.Message;
    import javax.jms.Session;
    import java.io.PrintWriter;
    import java.io.StringWriter;
    
    /**
     * Created by F.Fang on 2015/11/10.
     * Version :2015/11/10
     */
    @Aspect
    public class BusinessLogAspect {
    
        @Autowired
        private JmsTemplate jmsTemplate;
    
        @Autowired
        private Destination destination;
    
        @Around("execution(* org.wit.ff.business.*.*(..))")
        public Object record(ProceedingJoinPoint pjp) throws Throwable {
            try {
                Object result = pjp.proceed();
                // 添加正常处理的日志.
                sendMsg(buildLog(pjp, null));
                return result;
            } catch (Throwable e) {
                // 增加异常处理的日志.
                sendMsg(buildLog(pjp, e));
                throw e;
            }
        }
    
        private TraceLog buildLog(ProceedingJoinPoint pjp, Throwable e) {
            TraceLog log = new TraceLog();
            // 要保证所有的逻辑方法在调用参数上做限定,必须保证第一个参数是appId.
            if (pjp.getArgs() != null && pjp.getArgs().length >= 1) {
                log.setAppId((int) pjp.getArgs()[0]);
            }
            log.setOperation(pjp.getSignature().getName());
            if (null != e) {
                String msg = getStackTrace(e);
                if(msg.length()>256){
                    log.setDetails(msg.substring(0,256));
                } else{
                    log.setDetails(msg);
                }
            }
            return log;
        }
    
        private void sendMsg(final TraceLog log) {
            jmsTemplate.send(destination, new MessageCreator() {
                @Override
                public Message createMessage(Session paramSession) throws JMSException {
                    return paramSession.createTextMessage(JsonUtil.objectToJson(log));
                }
            });
        }
    
        /**
         * 获取目标异常栈信息.
         * 由于异常栈信息可能过长,如果考虑将数据入库或其它介质,最好考虑最大长度不超过一个阀值.
         *
         * @param throwable 目标异常.
         * @return
         */
        private String getStackTrace(Throwable throwable) {
            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw);
            try {
                throwable.printStackTrace(pw);
                return sw.toString();
            } finally {
                pw.close();
            }
        }
    
    }
    
    

    日志模型

    package org.wit.ff.log;
    
    import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
    import org.apache.commons.lang3.builder.ToStringStyle;
    
    /**
     * Created by F.Fang on 2015/11/10.
     * Version :2015/11/10
     */
    public class TraceLog {
    
        private String operation;
    
        /**
         * 任何一个操作都需要一个应用,此属性用于标识不同的应用数据接入.
         */
        private int appId;
    
        /**
         * 详细信息.
         */
        private String details;
    
        public String getOperation() {
            return operation;
        }
    
        public void setOperation(String operation) {
            this.operation = operation;
        }
    
        public int getAppId() {
            return appId;
        }
    
        public void setAppId(int appId) {
            this.appId = appId;
        }
    
        public String getDetails() {
            return details;
        }
    
        public void setDetails(String details) {
            this.details = details;
        }
    
        @Override
        public String toString() {
            return ReflectionToStringBuilder.toString(this, ToStringStyle.DEFAULT_STYLE);
        }
    }
    
    

    消息监听

    package org.wit.ff.log;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.wit.ff.util.JsonUtil;
    
    import javax.jms.JMSException;
    import javax.jms.Message;
    import javax.jms.MessageListener;
    import javax.jms.TextMessage;
    
    /**
     * Created by F.Fang on 2015/11/10.
     * Version :2015/11/10
     */
    public class BusinessLogMessageListener implements MessageListener{
    
        private static final Logger LOGGER = LoggerFactory.getLogger(BusinessLogAspect.class);
    
        @Override
        public void onMessage(Message message) {
            // 处理消息.
            TextMessage txtMsg = (TextMessage) message;
            try {
                TraceLog log = JsonUtil.jsonToObject(txtMsg.getText(), TraceLog.class);
                LOGGER.info("business log:"+log.toString());
            } catch (JMSException e) {
                LOGGER.error("处理业务日志发生异常!", e);
            }
        }
    }
    

    日志配置

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
    <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
        <!-- ===================================================================== -->
        <!--  以下是appender的定义                                                 -->
        <!-- ===================================================================== -->
    
        <!-- org.apache.log4j.ConsoleAppender -->
        <appender name="PROJECT-CONSOLE" class="org.apache.log4j.ConsoleAppender">
            <layout class="org.apache.log4j.PatternLayout">
                <param name="ConversionPattern" value="%d %-5p %c{2} - %m%n"/>
            </layout>
        </appender>
       
        <appender name="businessAppender" class="org.apache.log4j.DailyRollingFileAppender">
            <param name="file" value="logs/business.log"/>
            <!-- 若配置为true,表示在原有日志上继续append -->
            <param name="append" value="true"/>
            <!-- 若配置为false,表示清空原有日志 -->
            <!-- <param name="append" value="false"/> -->
            <param name="encoding" value="UTF-8"/>
            <layout class="org.apache.log4j.PatternLayout">
                <param name="ConversionPattern" value="%d %-5p %c{2} - %m%n"/>
            </layout>
        </appender>
        
        <!-- 定义logger,链接多个Appender表示信息将输出到多个目标(可以是文件,也可以是控制台或其它) -->
        <logger name="org.wit.ff.business" additivity="false">
            <level value="INFO"/>
            <appender-ref ref="businessAppender"/>
            <appender-ref ref="PROJECT-CONSOLE"/>
        </logger>
        
        <!-- ===================================================================== -->
        <!--  Root logger的定义                                                    -->
        <!-- ===================================================================== -->
        <root>
        <!--  DEBUG < INFO < WARN < ERROR < FATAL -->
            <level value="INFO"></level>
            <!-- <level value="WARN"/> -->
            <appender-ref ref="PROJECT-CONSOLE"/>
        </root>
    </log4j:configuration>
    

    Spring配置文件(spring-log-aop.xml)

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xmlns:context="http://www.springframework.org/schema/context"  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/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    
    
        <!-- 本地内置的代理服务, 如果外置的Activemq已启动,请注释 -->
        <!--
        <bean id="localBroker" class="org.apache.activemq.broker.BrokerService"
              init-method="start" destroy-method="stop">
            <property name="brokerName" value="mainBroker" />
            <property name="persistent" value="false" />
            <property name="transportConnectorURIs">
                <list>
                    <value>tcp://localhost:61616</value>
                </list>
            </property>
        </bean>
        -->
    
        <!-- 客户端连接工厂 -->
        <bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
            <property name="brokerURL">
                <value>tcp://localhost:61616</value>
            </property>
        </bean>
    
        <!-- Jms模版 -->
        <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
            <property name="connectionFactory" ref="connectionFactory" />
        </bean>
    
        <!-- 目标队列 -->
        <bean id="destination" class="org.apache.activemq.command.ActiveMQQueue">
            <constructor-arg value="demo.business.log" />
        </bean>
    
        <!-- 监听器. -->
        <bean id="businessLogListenerContainer"
              class="org.springframework.jms.listener.DefaultMessageListenerContainer">
            <property name="connectionFactory" ref="connectionFactory" />
            <property name="destinationName" value="demo.business.log" />
            <property name="messageListener" ref="messageListener" />
        </bean>
    
        <bean id="messageListener" class="org.wit.ff.log.BusinessLogMessageListener" />
    
        <!-- 日志Aspect扫描 -->
        <bean id="logAspect" class="org.wit.ff.log.BusinessLogAspect" />
    
    
        <aop:aspectj-autoproxy proxy-target-class="true"/>
    
        <!-- 启动service扫描 -->
    	<context:component-scan base-package="org.wit.ff.business"/>
    </beans>
    
    

    测试

    package org.wit.ff.business;
    
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    import org.wit.ff.model.Customer;
    
    /**
     * Created by F.Fang on 2015/11/10.
     * Version :2015/11/10
     */
    
    @ContextConfiguration(locations = "classpath:spring-log-aop.xml")
    @RunWith(SpringJUnit4ClassRunner.class)
    public class BusinessLogTest extends AbstractJUnit4SpringContextTests{
    
        @Autowired
        private CustomerBusiness customerBusiness;
    
        @Test
        public void demo() throws Exception {
            customerBusiness.getCustomer(1,1);
            Thread.sleep(10000);
        }
    
    }
    
    

    控制台日志

    2015-11-11 00:58:19,672 INFO  log.BusinessLogAspect - business log:org.wit.ff.log.TraceLog@191a9961[operation=getCustomer,appId=1,details=<null>]
    

    扩展log4j appender#

    扩展log4j appender是非常廉价的,自定义一个Appender即可,log4j的体系结构中,appender对应了一个目标输出介质,可以是文件、控制台、数据库。

    业务流##

    每一条日志都导向了CommonLogAppender ---> CommonBusiness执行getCustomer()方法,内部执行LOGGER.info(xxx)方法,实际日志内容是getCutomer,appId=1 --> CommonLogAppender执行append方法,并发送日志到队列(demo.common.log) ---> 监听器接收日志并打印到控制台。

    样例代码##

    自定义Appender

    package org.wit.ff.log;
    
    import org.apache.activemq.ActiveMQConnectionFactory;
    import org.apache.activemq.pool.PooledConnectionFactory;
    import org.apache.log4j.AppenderSkeleton;
    import org.apache.log4j.spi.LoggingEvent;
    
    import javax.jms.*;
    
    /**
     * Created by F.Fang on 2015/11/10.
     * Version :2015/11/10
     */
    public class CommonLogAppender extends AppenderSkeleton {
    
        private static final String COMMON_LOG_QUEUE = "demo.common.log";
        private PooledConnectionFactory pooledConnectionFactory;
    
        @Override
        public void activateOptions() {
            ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory();
            connectionFactory.setBrokerURL("tcp://localhost:61616");
            pooledConnectionFactory = new PooledConnectionFactory(connectionFactory);
            pooledConnectionFactory.setMaxConnections(1);
            pooledConnectionFactory.setMaximumActiveSessionPerConnection(2);
        }
    
        @Override
        protected void append(LoggingEvent event) {
            Connection connection = null;
            Session session = null;
            try {
                connection = pooledConnectionFactory.createConnection();
                session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
                MessageProducer producer = session.createProducer(session.createQueue(COMMON_LOG_QUEUE));
                if(event.getMessage()!=null){
                    TextMessage txtMsg = session.createTextMessage(event.getMessage().toString());
                    producer.send(txtMsg);
                }
            } catch (JMSException e) {
                e.printStackTrace();
            } finally {
                if (session != null) {
                    try {
                        session.close();
                    } catch (JMSException e) {
                        e.printStackTrace();
                    }
                }
                if (connection != null) {
                    try {
                        connection.close();
                    } catch (JMSException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        @Override
        public void close() {
            System.out.println("close!!!");
            pooledConnectionFactory.stop();
        }
    
        @Override
        public boolean requiresLayout() {
            return true;
        }
    }
    
    

    业务服务

    package org.wit.ff.business;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Service;
    import org.wit.ff.model.Customer;
    
    /**
     * Created by F.Fang on 2015/11/10.
     * Version :2015/11/10
     */
    @Service
    public class CommonBusiness {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(CommonBusiness.class);
    
        public Customer getCustomer(int appId, int customerId){
            LOGGER.info("getCustomer, appId="+appId);
            Customer customer = new Customer();
            customer.setCompanyId(10010);
            customer.setId(customerId);
            customer.setTitle("hnb");
            customer.setName("cxb");
            customer.setLevel(Integer.MAX_VALUE);
            return new Customer();
        }
    
        public void saveCustomer(int appId, Customer customer){
            LOGGER.info("saveCustomer, appId="+appId);
            System.out.println("appId is:"+appId);
            System.out.println("customer is:"+customer);
        }
    
    }
    
    

    log4j配置

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
    <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
        <!-- ===================================================================== -->
        <!--  以下是appender的定义                                                 -->
        <!-- ===================================================================== -->
    
        <!-- org.apache.log4j.ConsoleAppender -->
        <appender name="PROJECT-CONSOLE" class="org.wit.ff.log.CommonLogAppender">
            <layout class="org.apache.log4j.PatternLayout">
                <param name="ConversionPattern" value="%d %-5p %c{2} - %m%n"/>
            </layout>
        </appender>
       
        <appender name="businessAppender" class="org.apache.log4j.DailyRollingFileAppender">
            <param name="file" value="logs/business.log"/>
            <!-- 若配置为true,表示在原有日志上继续append -->
            <param name="append" value="true"/>
            <!-- 若配置为false,表示清空原有日志 -->
            <!-- <param name="append" value="false"/> -->
            <param name="encoding" value="UTF-8"/>
            <layout class="org.apache.log4j.PatternLayout">
                <param name="ConversionPattern" value="%d %-5p %c{2} - %m%n"/>
            </layout>
        </appender>
        
        <!-- 定义logger,链接多个Appender表示信息将输出到多个目标(可以是文件,也可以是控制台或其它) -->
        <logger name="org.wit.ff.business" additivity="false">
            <level value="INFO"/>
            <appender-ref ref="businessAppender"/>
            <appender-ref ref="PROJECT-CONSOLE"/>
        </logger>
        
        <!-- ===================================================================== -->
        <!--  Root logger的定义                                                    -->
        <!-- ===================================================================== -->
        <root>
        <!--  DEBUG < INFO < WARN < ERROR < FATAL -->
            <level value="INFO"></level>
            <!-- <level value="WARN"/> -->
            <appender-ref ref="PROJECT-CONSOLE"/>
        </root>
    </log4j:configuration>
    

    Spring配置

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xmlns:context="http://www.springframework.org/schema/context"  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/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    
    
        <!-- 本地内置的代理服务 -->
        <!--
        <bean id="localBroker" class="org.apache.activemq.broker.BrokerService"
              init-method="start" destroy-method="stop">
            <property name="brokerName" value="mainBroker" />
            <property name="persistent" value="false" />
            <property name="transportConnectorURIs">
                <list>
                    <value>tcp://localhost:61616</value>
                </list>
            </property>
        </bean>
        -->
    
        <!-- 客户端连接工厂 -->
        <bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
            <property name="brokerURL">
                <value>tcp://localhost:61616</value>
            </property>
        </bean>
    
        <!-- 监听器. -->
        <bean id="businessLogListenerContainer"
              class="org.springframework.jms.listener.DefaultMessageListenerContainer">
            <property name="connectionFactory" ref="connectionFactory" />
            <property name="destinationName" value="demo.common.log" />
            <property name="messageListener" ref="messageListener" />
        </bean>
    
        <bean id="messageListener" class="org.wit.ff.log.CommonLogMessageListener" />
    
        <!-- 启动service扫描 -->
    	<context:component-scan base-package="org.wit.ff.business"/>
    </beans>
    

    测试

    package org.wit.ff.business;
    
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    /**
     * Created by F.Fang on 2015/11/10.
     * Version :2015/11/10
     */
    @ContextConfiguration(locations = "classpath:spring-log-expand-log4j.xml")
    @RunWith(SpringJUnit4ClassRunner.class)
    public class CommonLogTest extends AbstractJUnit4SpringContextTests {
    
        @Autowired
        private CommonBusiness commonBusiness;
    
        @Test
        public void demo() throws Exception {
            commonBusiness.getCustomer(1, 1);
            Thread.sleep(10000);
        }
    }
    

    日志记录

    getCustomer, appId=1
    

    QA#

  • 相关阅读:
    php高效率写法
    php经典bug
    cideogniter部署到阿里云服务器出现session加载错误
    linux gcc编译protocol
    linux权限问题
    http协议详解
    哈希表
    c语言函数
    socket相关函数
    构建之法阅读笔记05
  • 原文地址:https://www.cnblogs.com/fangfan/p/4955687.html
Copyright © 2011-2022 走看看