Mule ESB Http項目轉換爲Tomcat項目(9) 日誌問題處理

     Mule ESB項目的日誌輸出有兩種方式,能夠在流程中添加Logger組件輸出日誌,也能夠在自定義的代碼中添加日誌輸出。Mule ESB日誌使用Log4j2庫進行輸出,Mule ESB 企業版使用的log4j2版本是2.1。java

     咱們在ESB項目中拖入一個Logger控件,輸出通過Transformer轉化後的Json 報文。
web

這裏Logger控件裏的Message內容爲#[message.payloadAs(java.lang.String)],使用的是MEL(Mule Expression Language),等效於message.getPayloadAsString()spring

拖拽Logger控件後,在項目的src/main/resources目錄下生成了log4j2.xml文件,用於配置Log4j2的日誌輸出express

log4j2.xml的內容以下:apache

<?xml version="1.0" encoding="utf-8"?>
<Configuration>
    <Appenders>
        <RollingFile name="file" fileName="${sys:mule.home}${sys:file.separator}logs${sys:file.separator}testproject.log" 
                 filePattern="${sys:mule.home}${sys:file.separator}logs${sys:file.separator}testproject-%i.log">
            <PatternLayout pattern="%d [%t] %-5p %c - %m%n" />
            <SizeBasedTriggeringPolicy size="10 MB" />
            <DefaultRolloverStrategy max="10"/>
        </RollingFile>
    </Appenders>
    <Loggers>
        <!-- CXF is used heavily by Mule for web services -->
        <AsyncLogger name="org.apache.cxf" level="WARN"/>

        <!-- Apache Commons tend to make a lot of noise which can clutter the log-->
        <AsyncLogger name="org.apache" level="WARN"/>

        <!-- Reduce startup noise -->
        <AsyncLogger name="org.springframework.beans.factory" level="WARN"/>

        <!-- Mule classes -->
        <AsyncLogger name="org.mule" level="INFO"/>
        <AsyncLogger name="com.mulesoft" level="INFO"/>

        <!-- Reduce DM verbosity -->
        <AsyncLogger name="org.jetel" level="WARN"/>
        <AsyncLogger name="Tracking" level="WARN"/>
        
        <AsyncRoot level="INFO">
            <AppenderRef ref="file" />
        </AsyncRoot>
    </Loggers>
</Configuration>

能夠看出Mule ESB的日誌輸出採用的是異步方式。json

以Debug方式啓動ESB項目, 調用ESB接口,控制檯輸出了Json報文日誌api

INFO  2016-06-29 15:15:33,335 [[testproject].HTTP_Listener_Configuration.worker.01] org.mule.api.processor.LoggerMessageProcessor: {"students":[{"name":"張三","id":"197","class":"1年1班"},{"name":"李四","id":"198","class":"1年2班"},{"name":"趙五","id":"199","class":"1年3班"}]}

同時查看項目對應的日誌文件(位置在Anypoint Studio的workspace目錄的.mule/logs子目錄下)tomcat

打開testproject.log文件,能夠看到上述的日誌信息也寫入了日誌文件。服務器

從日誌信息能夠看出,Logger控件的日誌是在org.mule.api.processor.LoggerMessageProcessor類中輸出的,具體是在log(MuleEvent event)方法中輸出的app

protected void log(MuleEvent event) 
{
    if (event == null) 
    {
        logWithLevel(null);
    } 
    else 
    {
        if (StringUtils.isEmpty(message)) 
        {
            logWithLevel(event.getMessage());
        } else 
        {
            LogLevel logLevel = LogLevel.valueOf(level);
            if (LogLevel.valueOf(level).isEnabled(logger)) 
            {
                logLevel.log(logger, expressionManager.parse(message, event));
            }
        }
    }
}

public enum LogLevel
{
   INFO
   {
      @Override
      public void log(Log logger, Object object) 
      {
         logger.info(object);
      }

而記錄日誌的logger對象是在ESB項目啓動,加載Mule容器時調用LoggerMessageProcessor類的initLogger方法構造的,構造Logger的大體流程是這樣的:

這裏的流程圖只描繪了LoggerMessageProcessor的logger對象構建的幾個主要類和方法,能夠看出Logger控件的日誌輸出與Mule容器的啓動和初始化密切相關。若是ESB項目遷移到Web項目,則實際運行環境變成了Tomcat環境,加載類變成了org.mule.config.builders.MuleXmlBuilderContextListener,而咱們查看MuleXmlBuilderContextListener類的初始化方法

public void initialize(ServletContext context)
    {
        String config = context.getInitParameter(INIT_PARAMETER_MULE_CONFIG);
        ....................
        try
        {
            muleContext = createMuleContext(config, context);
            context.setAttribute(MuleProperties.MULE_CONTEXT_PROPERTY, muleContext);
            muleContext.start();
        }
        ....................

能夠看出這裏沒有對MuleContainer的初始化方法調用,Logger Component使用的Logger對象沒有被初始化,所以在Web項目裏使用Logger組件將不會輸出日誌,不管是控制檯仍是文件,咱們須要自定義Logger類輸出日誌。

咱們在用於轉換的Transformer類中添加Log4j2的Logger對象

private static Logger logger = LogManager.getLogger(CustomJsonTransformer.class);

再在json報文轉換結束後使用這個logger對象輸出轉換後的json報文。

try {
	            String jsonMessage = message.getPayloadAsString();	
	            //添加信息
	            JSONObject jsonMap = updateStudentInfos(jsonMessage);
	            transformJsonStr = jsonMap.toJSONString();
	            if(!Strings.isBlank(transformJsonStr))
	            {
	            	logger.info("The json message after transformation is:" + transformJsonStr);
	            }
	        } catch (Exception e) {
	        		e.printStackTrace();
	        }

由於咱們使用的是Log4j2在Tomcat容器中進行日誌輸出,根據查閱的資料,咱們須要引入log4j-web這個jar包,由於Mule默認使用的log4j-core版本是2.1,咱們引入的log4j-web也使用2.1版本,將這個jar文件拷貝到mule_libs/opt目錄下。

此外咱們須要修改log4j2.xml文件,ESB項目建立的log4j2.xml的日誌文件輸出到mule的workspace目錄下,咱們將其修改成輸出到tomcat的logs目錄下,修改後的log4j2.xml文件

<?xml version="1.0" encoding="utf-8"?>
<Configuration>
	<properties>
    	<property name="logPath">${sys:catalina.home}/logs/</property>
  	</properties>

 	 <Appenders>
 	 	<Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d [%t] %-5p %c - %m%n"/>
        </Console>
        <!-- <RollingFile name="file" fileName="${logPath}/testproject.log"
            filePattern="${logPath}/testproject-%d{MM-dd-yyyy}-%i.log"> -->
        <RollingFile name="file" fileName="${logPath}/testproject.log" 
                 filePattern="${logPath}/testproject-%i.log" append="true" immediateFlush="true">
            <PatternLayout pattern="%d [%t] %-5p %c - %m%n" />
            <!-- <Policies>
                <TimeBasedTriggeringPolicy />
                <SizeBasedTriggeringPolicy size="10 MB"/>
            </Policies> -->
            <SizeBasedTriggeringPolicy size="10 MB" />
            <DefaultRolloverStrategy max="10"/>
        </RollingFile>
    </Appenders>
    <Loggers>
    
    	 <!-- Apache Commons tend to make a lot of noise which can clutter the log-->
        <AsyncLogger name="org.apache" level="WARN"/>
    
    	<!-- Reduce startup noise -->
        <AsyncLogger name="org.springframework.beans.factory" level="WARN"/>
        
        <!-- Mule classes -->
        <AsyncLogger name="org.mule" level="INFO"/>
        <AsyncLogger name="com.mulesoft" level="INFO"/>        
        <Logger name="com.mule.spring" level="INFO"/>
        
        <Root level="INFO">
        	<AppenderRef ref="Console"/>
            <AppenderRef ref="file" />
        </Root>
    </Loggers>   
</Configuration>

這裏的${sys:catalina.home}指的是當前運行的tomcat根目錄,另外基於咱們自定義的代碼包路徑,咱們添加了一個Logger。

須要注意的是在web項目中,log4j2.xml文件必須放置在WEB-INF根目錄下,和web.xml同一級目錄,爲此咱們須要將log4j2.xml文件移動到src/main/app目錄下。

修改完成後,咱們部署從新生成的web項目到tomcat環境,調用接口。

能夠看到Tomcat的運行時窗口輸出了日誌信息:

同時在tomcat的logs目錄下生成了testproject.log文件,

testproject.log文件中輸出了控制檯窗口輸出的json報文日誌

     使用自定義Logger,咱們能夠將須要的程序運行信息輸出到控制檯和日誌文件,對於自定義代碼中拋出的異常,咱們能夠直接輸出日誌,但若是是流程運行過程當中拋出的異常信息,該如何捕捉異常信息,並將其輸出呢?

      咱們須要使用Mule的Catch Exception Strategy控件。

     咱們在流程文件中加入Catch Exception Strategy控件,放置在Error Handling下

 咱們在這個控件中拖入兩個控件,Set Payload和Logger控件,

<catch-exception-strategy doc:name="Catch Exception Strategy">
     <set-payload value="#[exception.cause.message]" encoding="UTF-8" mimeType="text/plain" doc:name="Set Payload"/>
     <logger message="#[exception.cause.message]" level="ERROR" doc:name="Logger"/>
</catch-exception-strategy>

    Set Payload控件將異常信息設置爲Mule Message的Payload,返回給調用端(不然Mule Message的Payload仍然是請求的Payload),logger控件則將異常信息做爲日誌輸出。

    #[message.cause.exception]一樣是MEL表達式,表示異常的Root Cause信息。

   添加完異常處理控件後,咱們修改自定義的Transformer類的transformMessage方法,將原先返回的轉換好的json報文替換爲null。這樣當運行到Data Weaver數據映射時,流程將會拋出異常,咱們能夠查看異常信息是如何被Catch Exception Strategy控件捕捉並處理的。

   調用ESB接口後,系統輸出的異常日誌爲:

ERROR 2016-06-29 18:20:30,035 [[testproject].HTTP_Listener_Configuration.worker.01] org.mule.api.processor.LoggerMessageProcessor: The object transformed is of type: 
"SimpleDataType{type=org.mule.transport.NullPayload, mimeType='*/*', encoding='null'}", 
but the expected return type is "SimpleDataType{type=java.lang.String, mimeType='application/json', encoding='UTF-8'}".

   調用端返回的響應消息是:

   這裏輸出的異常信息僅顯示了異常的Root Cause信息,若是要詳細的堆棧信息,咱們須要修改#[message.cause.exception]爲#[org.mule.util.ExceptionUtils.getFullStackTrace(exception)]

<catch-exception-strategy doc:name="Catch Exception Strategy">
     <set-payload value="#[org.mule.util.ExceptionUtils.getFullStackTrace(exception)]" encoding="UTF-8" mimeType="text/plain" doc:name="Set Payload"/>
     <logger message="#[org.mule.util.ExceptionUtils.getFullStackTrace(exception)]" level="ERROR" doc:name="Logger"/>
</catch-exception-strategy>

修改後再調用接口,異常的堆棧信息被輸出到日誌和調用端

因爲添加了Catch Exception Strategy控件,流程運行過程當中的異常被捕捉了,返回的響應狀態代碼變成了200,這顯然不是服務器端真實的狀態,所以咱們須要從新設置響應的Status Code.

咱們在Catch Exception Strategy控件中添加設置Status Code狀態的代碼

<set-property propertyName="http.status" value="#[500]" doc:name="Set Http Status" />

再次調用接口,能夠看到返回的響應Status Code變成了500

在Web項目中,咱們不能使用Logger輸出日誌,咱們有三種方式輸出日誌:

1)自定義Transformer中添加Logger輸出日誌。

2)自定義Component中添加Logger輸出日誌。

3)自定義MessageProcessor在處理Mule Event時輸出日誌。

第一種方式上面已經提到了,這裏再也不贅述,重點說一下第二種和第三種方式。

從Mule 3.8起,自定義Component須要實現接口org.mule.api.lifecycle.Callable的onCall方法

咱們自定義的Component類代碼以下:

package com.mule.spring.components;

import org.mule.api.MuleEventContext;
import org.mule.api.lifecycle.Callable;
import org.mule.util.ExceptionUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class CustomComponent implements Callable 
{

	private static Logger logger = LogManager.getLogger(CustomComponent.class);

	@Override
	public Object onCall(MuleEventContext eventContext) throws Exception {
		String exceptionMessage = 
				ExceptionUtils.getFullStackTrace(eventContext.getMessage().getExceptionPayload().getException());
		logger.error(exceptionMessage);	
        return eventContext.getMessage();
	}
}

在Catch Exception Strategy中引用這個Component以下:

<component class="com.mule.spring.components.CustomComponent" doc:name="Custom Component"/>

自定義MessageProcessor以下:

package com.mule.spring.messageprocessors;

import org.mule.api.MuleEvent;
import org.mule.api.MuleException;
import org.mule.api.processor.MessageProcessor;
import org.mule.util.ExceptionUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class LoggerMessageProcessor implements MessageProcessor {

	private static Logger logger = LogManager.getLogger(LoggerMessageProcessor.class);
	
	@Override
	public MuleEvent process(MuleEvent event) throws MuleException 
	{		
		String exceptionMessage = 
        		ExceptionUtils.getFullStackTrace(event.getMessage().getExceptionPayload().getException());
        logger.error(exceptionMessage);	
		return event;
	}

}

在Catch Exception Strategy中引用這個Message Processor以下:

<custom-processor class="com.mule.spring.messageprocessors.LoggerMessageProcessor" />

因爲輸出堆棧信息時引用了common-lang的ExceptionUtils類(org.mule.utils.ExceptionUtils的父類),咱們須要在pom.xml中引入common-lang的jar包保證編譯經過。

<dependency>
     <groupId>commons-lang</groupId>
	 <artifactId>commons-lang</artifactId>
     <version>2.6</version>
     <scope>provided</scope>
</dependency>

Web項目最後的Catch Exception Strategy設置以下:

<catch-exception-strategy doc:name="Catch Exception Strategy">
     <set-payload value="#[org.mule.util.ExceptionUtils.getFullStackTrace(exception)]" encoding="UTF-8" mimeType="text/plain" doc:name="Set Payload"/>
     <!-- <component class="com.mule.spring.components.CustomComponent" doc:name="Custom Component"/> -->
     <custom-processor class="com.mule.spring.messageprocessors.LoggerMessageProcessor" />
     <set-property propertyName="http.status" value="#[500]" doc:name="Set Http Status" />
     <message-properties-transformer>
	    <add-message-property key="Content-Type" value="text/plain;charset=utf-8" />
	 </message-properties-transformer>        
</catch-exception-strategy>

這裏設置Content-Type爲text/plain,由於返回的異常堆棧信息是純文本形式,不是json或者xml形式。

從新編譯web項目並部署,調用接口,能夠看到返回的響應是500: Internal Server Error

 

在testproject.log文件中也顯示了異常堆棧信息

相關文章
相關標籤/搜索