1. 程式人生 > >如何隨心所欲地自定義log4j輸出格式

如何隨心所欲地自定義log4j輸出格式

在某種情況下,我們需要在不影響原有程式碼的基礎上自定義log4j的輸出格式。

例如這樣的需求,硬性規定了專案的日誌格式為:

 日期 日誌等級 ClassName:line - [版本號] [請求ip地址] [專案應用名稱] [服務介面模組] [模組方法] [業務引數1] [業務引數2] [業務引數3] 日誌詳細內容(必須為json格式)

示例:

2018-05-10 14:04:50,972  INFO ViolationService:51 - [v1.0.0] [192.168.137.47] [merchant-service.cx580.com] [OrderController] [messageList] [null] [] [] {"body":"訂單狀態訊息列表resp:{\"code\":1000,\"msg\":\"成功\"}"}


其中:

    版本號是指當前服務介面實際的版本資訊,例如V1.0.1;

    請求ip地址為使用者真實的請求ip;

    專案應用名稱為專案的名稱或者標識,例如支付服務定義應用名稱為payService;

    服務介面模組是指請求介面對應的模組程式碼,例如請求訂單介面,則介面模組為OrderControlller;

    模組方法是指介面對應的請求方法,例如下單介面對應模組方法為createOrder;

    業務引數1可根據實際情況寫入相應的業務資料,錄入訂單號orderId,該引數可為空;

    業務引數2同上;

    業務引數3同上;

   日誌詳細內容是指請求介面時需打印出來的描述資訊,例如建立訂單異常時,在異常捕捉方法體中描述異常詳細資訊,日誌內容需定義到一個json結構中。

以上是我遇到的場景,這時在不影響原有專案程式碼的基礎上,我們做出日誌格式的調整,使用如下方案:

1.通過log4j的佔位替換符%X{}配合MDC格式化日誌,使用AOP切面在請求執行緒開始處填充替換符變數

2.繼承log4j的具體appender類,重寫subAppend方法,修改日誌輸出的內容格式。

此時log4j檔案如下

log4j.rootCategory=INFO, stdout, file, errorfile
#log4j.category.com.cx=DEBUG
log4j.logger.error=errorfile

log4j.appender.stdout=com.test.common.GrayLogConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %5p %c{1}:%L%X{log_version}%X{log_ip}%X{log_item}%X{log_module}%X{log_method}%X{log_req_params} %m%n

log4j.appender.file=com.test.common.GrayLogDailyRollingFileAppender
log4j.appender.file.file=${log.dir}/${spring.application.name}.log
log4j.appender.file.DatePattern='.'yyyy-MM-dd
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %5p %c{1}:%L%X{log_version}%X{log_ip}%X{log_item}%X{log_module}%X{log_method}%X{log_req_params} %m%n

log4j.appender.errorfile=com.test.common.GrayLogDailyRollingFileAppender
log4j.appender.errorfile.file=${log.dir}/${spring.application.name}_error.log
log4j.appender.errorfile.DatePattern='.'yyyy-MM-dd
log4j.appender.errorfile.Threshold = ERROR
log4j.appender.errorfile.layout=org.apache.log4j.PatternLayout
log4j.appender.errorfile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %5p %c{1}:%L%X{log_version}%X{log_ip}%X{log_item}%X{log_module}%X{log_method}%X{log_req_params} %m%n

在log4j.properties檔案,我們做了兩個變動,一個是添加了%X{value}的變數,另一個則是將原本的DailyRollingFileAppender修改成了com.test.common.GrayLogConsoleAppender。

處理log4j的變數,對程式碼進行controller切面,在一個http請求java的入口中放入執行緒變數,該執行緒變數在當次http請求生命週期內生效。

切面程式碼如下:

@Around("execution(public * com.test.controller..*.*(..))")
    public Object aroundController(ProceedingJoinPoint joinPoint) {
        ServletRequestAttributes attributes =(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request =attributes.getRequest();
        String execIp = request.getHeader("X-Real-IP");
        if(StringUtils.isBlank(execIp)){
            execIp=request.getRemoteAddr();
        }
        String execClass= joinPoint.getTarget().getClass().getSimpleName();
        String execMethod = joinPoint.getSignature().getName();
        Map<String,String[]> map = request.getParameterMap();
        List<String> paramsList = new ArrayList<>();
        for(Map.Entry<String,String []> m : map.entrySet()){
            String [] value = m.getValue();
            paramsList.add( m.getKey() + "=" + StringUtils.join(value,","));
        }

        String execParams = "[" + StringUtils.join(paramsList,"&") + "] [] []";
        MDC.put("log_version"," - [V1.0.0]");
        MDC.put("log_item"," [violation-mini]");
        MDC.put("log_module"," [" + execClass + "]");
        MDC.put("log_method"," [" + execMethod+ "]");
        MDC.put("log_req_params"," " + execParams);
        MDC.put("log_ip"," [" + execIp+ "]");
        Object result= null;
        try {
            result = joinPoint.proceed();
        } catch (Throwable throwable) {
            LOGGER.error("方法異常:",throwable);
        }
        return result;
    }

至此,格式中的MDC變數都已被放入成功。

下一步,將原本的日誌內容套上json外套。

新建GrayLogConsoleAppender類繼承具體的appender類

程式碼如下:

package com.test.common;

import net.sf.json.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.spi.LoggingEvent;
import org.apache.log4j.spi.ThrowableInformation;

import java.lang.reflect.Field;

/**
 * @Author: Lxx
 * @Description:
 * @Date: Created in 17:29 2018/5/30
 */
public class GrayLogConsoleAppender extends ConsoleAppender {
    @Override
    protected void subAppend(LoggingEvent event) {
        try {
            Class<LoggingEvent> clazz = LoggingEvent.class;
            Field filed = clazz.getDeclaredField("throwableInfo");
            filed.setAccessible(true);
            Object exception = filed.get(event);
            JSONObject json = new JSONObject();
            if(exception != null){
                if(exception instanceof ThrowableInformation){
                    ThrowableInformation throwableInformation = (ThrowableInformation) exception;
                    String [] details = throwableInformation.getThrowableStrRep();
                    String error_msg = StringUtils.join(details,"\r\n");
                    json.put("exception",error_msg);
                }
            }
            filed.set(event,null);
            boolean flag = false;
            Field filed1 = clazz.getDeclaredField("message");
            filed1.setAccessible(true);
            Object message = filed1.get(event);
            if (message instanceof String) {
                String msg = (String) message;
                if (message != null) {
                    flag = true;
                }
                json.put("body", msg);
                filed1.set(event, json.toString());
            }
            if(!flag){
                Field filed2 = clazz.getDeclaredField("renderedMessage");
                filed2.setAccessible(true);
                Object message2 = filed2.get(event);
                if (message2 instanceof String) {
                    String msg = (String) message2;
                    json.put("body", msg);
                    filed2.set(event, json.toString());
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        super.subAppend(event);
    }
}

至此,已為日誌內容套上json外套,並且當有異常日誌時,將異常的堆疊資訊放入json的exception中輸出出來,不列印堆疊資訊。

最終結果:

2018-06-09 00:48:31,849  INFO LogAspect:65 - [V1.0.0] [223.88.53.135] [violation-mini] [TestController] [queryList] [appName=abc&authType=test&avatar=&nickName=&token=asdfasdfadsfasdf&userId=asdfasdfasdfasdf&userType=aaaaa] [] [] {"body":"結果為:ResponseResult{code='0', msg='null', errormsg='查詢成功', data={}, successFlag=false}"}