分佈式鏈路追蹤Skywalking集成log4j對現有系統改造

系統框架

Springboot + DUbbo + Mybatis-plus
日誌框架爲:log4j2.x

問題描述

目前項目中都是log.info(“xxxxxxxx”)或者log.error,但是由於探針埋點的包都是特定的,所以尤其是我們在項目中的這些日誌信息就無法體現在Skywalking的UI鏈路上,甚至異常由於我們會自己拋出相應的異常,而原始的異常堆棧信息無法打印到Skywalking裏。這不是我們想的要效果,我們希望開發的過程中,只需要用traceId即可查詢出所有需要看到的信息,大家才能快速的解決問題,同時,結合Skywalking提供的鏈路增加用法,那種代碼侵入式的做法是不現實的,我們並不能大面積的去改動。

解決思路

鑑於上述問題描述的效果,以及我們面對的改動面積,所以達到效果的同時又能最小變動成爲我們的目標!那就是在調用log.info方法的時候,我們能同時執行下ActiveSpan.info然後收集到Skywalking裏面去。瞭解過Slf4j和log4j的關係以及log4j的使用後,我們有下面兩種方案!

解決方案一:實現我們自己的LogApender,然後更改log4j的xml文件

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.apache.skywalking.apm.toolkit.trace.ActiveSpan;

import java.io.Serializable;

/**
 * @author wangguodong
 */
@Plugin(name = "SkywalkingLog", category = "Core", elementType = "appender", printObject = true)
public class LogAppender extends AbstractAppender {
    protected LogAppender(String name, Filter filter, Layout<? extends Serializable> layout, boolean ignoreExceptions, Property[] properties) {
        super(name, filter, layout, ignoreExceptions, properties);
    }

    @Override
    public void append(LogEvent logEvent) {
        // 這裏對log信息重新調用放到鏈路裏
        if (Level.ERROR.equals(logEvent.getLevel())){
            ActiveSpan.error(logEvent.getMessage().getFormattedMessage());
            ActiveSpan.error(logEvent.getThrown());
        } else if (Level.INFO.equals(logEvent.getLevel())) {
            ActiveSpan.info(logEvent.getMessage().getFormattedMessage());
        } else if (Level.DEBUG.equals(logEvent.getLevel())) {
            ActiveSpan.debug(logEvent.getMessage().getFormattedMessage());
        }
    }

    @PluginFactory
    public static LogAppender createAppender(@PluginAttribute("name") String name,
                                             @PluginElement("Filter") final Filter filter,
                                             @PluginElement("Layout") Layout<? extends Serializable> layout,
                                             @PluginAttribute("ignoreExceptions") boolean ignoreExceptions) {
        if (name == null) {
            LOGGER.error("No name provided for MyCustomAppenderImpl");
            return null;
        }
        if (layout == null) {
            layout = PatternLayout.createDefaultLayout();
        }
        return new LogAppender(name, filter, layout, ignoreExceptions, null);
    }
}


<?xml version="1.0" encoding="UTF-8"?>
<configuration status="WARN">
   <Properties>
   	  <property name="APP_NAME">bigtree-invoice</property>
	  <property name="LOGGER_LEVEL">INFO</property>
	  <property name="LOGGER_PATH">/data/logs</property>    
    
      <Property name="LOG_HOME">${LOGGER_PATH}/${APP_NAME}</Property>
      <Property name="FILE_SIZE">10M</Property>
      <Property name="log_pattern">%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] %-5level %logger{36} - %msg%n</Property>
      <Property name="rolling_file_name">-%d{yyyy-MM-dd}.%i.zip</Property>
      <Property name="rollover_strategy_max">30</Property>

      <Property name="LOG_HOME_PROJECT">${LOG_HOME}/${APP_NAME}-project</Property>
      <Property name="LOG_HOME_PROJECT_ERROR">${LOG_HOME}/${APP_NAME}-project-error</Property>
      <Property name="LOG_HOME_MSDP">${LOG_HOME}/${APP_NAME}-msdp</Property>
      <Property name="LOG_HOME_SQL">${LOG_HOME}/${APP_NAME}-sql</Property>
      <Property name="LOG_HOME_ACCESS">${LOG_HOME}/${APP_NAME}-access</Property>
      <Property name="LOG_HOME_ACCESS_ERROR">${LOG_HOME}/${APP_NAME}-access-error</Property>
        </Properties>
   <appenders>
      <Console name="Console" target="SYSTEM_OUT">
         <PatternLayout pattern="${log_pattern}" />
      </Console>
      <RollingRandomAccessFile  name="projectRolling"
         fileName="${LOG_HOME_PROJECT}.log"
         filePattern="${LOG_HOME_PROJECT}${rolling_file_name}"
         immediateFlush="false" append="true">
         <PatternLayout>
            <Pattern>${log_pattern}</Pattern>
            <Charset>UTF-8</Charset>
         </PatternLayout>
         <Policies>
            <SizeBasedTriggeringPolicy size="${FILE_SIZE}"/>
         </Policies>
         <DefaultRolloverStrategy max="${rollover_strategy_max}" />
      </RollingRandomAccessFile>


	   <RollingRandomAccessFile  name="projectErrorRolling"
         fileName="${LOG_HOME_PROJECT_ERROR}.log"
         filePattern="${LOG_HOME_PROJECT_ERROR}${rolling_file_name}"
         immediateFlush="false" append="true">
         <Filters>
            <ThresholdFilter level="${LOGGER_LEVEL}" onMatch="ACCEPT" onMismatch="DENY" />
         </Filters>
         <PatternLayout>
            <Pattern>${log_pattern}</Pattern>
            <Charset>UTF-8</Charset>
         </PatternLayout>
         <Policies>
            <SizeBasedTriggeringPolicy size="${FILE_SIZE}"/>
         </Policies>
         <DefaultRolloverStrategy max="${rollover_strategy_max}" />
      </RollingRandomAccessFile>

	   <RollingRandomAccessFile  name="msdpRolling"
         fileName="${LOG_HOME_MSDP}.log"
         filePattern="${LOG_HOME_MSDP}${rolling_file_name}"
         immediateFlush="false" append="true">
         <PatternLayout>
            <Pattern>${log_pattern}</Pattern>
            <Charset>UTF-8</Charset>
         </PatternLayout>
         <Policies>
            <SizeBasedTriggeringPolicy size="${FILE_SIZE}"/>
         </Policies>
         <DefaultRolloverStrategy max="${rollover_strategy_max}" />
      </RollingRandomAccessFile>
      
      
      <RollingRandomAccessFile  name="sqlRolling"
         fileName="${LOG_HOME_SQL}.log"
         filePattern="${LOG_HOME_SQL}${rolling_file_name}"
         immediateFlush="false" append="true">
         <PatternLayout>
            <Pattern>${log_pattern}</Pattern>
            <Charset>UTF-8</Charset>
         </PatternLayout>
         <Policies>
            <SizeBasedTriggeringPolicy size="${FILE_SIZE}"/>
         </Policies>
         <DefaultRolloverStrategy max="${rollover_strategy_max}" />
      </RollingRandomAccessFile>

	   <-- 添加我們自己的apender -->
       <SkywalkingLog name="SkywalkingLog">
           <PatternLayout pattern="${log_pattern}" />
       </SkywalkingLog>
   </appenders>

   <loggers>
      <AsyncLogger name="org.springframework" level="${LOGGER_LEVEL}"  additivity="false">
	  		<appender-ref ref="Console"/>
	  		<appender-ref ref="projectRolling"/>
	  		<appender-ref ref="projectErrorRolling"/>
	  </AsyncLogger>
      <AsyncLogger name="com.alibaba.dubbo" level="${LOGGER_LEVEL}"  additivity="false">
	  		<appender-ref ref="Console"/>
	  		<appender-ref ref="projectRolling"/>
	  		<appender-ref ref="projectErrorRolling"/>
	  </AsyncLogger>
      <AsyncLogger name="druid.sql" level="${LOGGER_LEVEL}"  additivity="false">
	  		<appender-ref ref="Console"/>
	  		<appender-ref ref="sqlRolling"/>
	  </AsyncLogger>
      <AsyncLogger name="org.mybatis" level="${LOGGER_LEVEL}"  additivity="false">
      		<appender-ref ref="Console"/>
	  		<appender-ref ref="sqlRolling"/>
      </AsyncLogger>
      <AsyncLogger name="com.jeedev.msdp" level="${LOGGER_LEVEL}"  additivity="false">
      		<appender-ref ref="Console"/>
	  		<appender-ref ref="msdpRolling"/>
      </AsyncLogger>
      <AsyncLogger name="com.tansun" level="${LOGGER_LEVEL}"  additivity="false">
      		<appender-ref ref="Console"/>
	  		<appender-ref ref="projectRolling"/>
	  		<appender-ref ref="projectErrorRolling"/>
      </AsyncLogger>
      <AsyncLogger name="com.bigtreefinance" level="${LOGGER_LEVEL}"  additivity="false">
      		<appender-ref ref="Console"/>
	  		<appender-ref ref="projectRolling"/>
	  		<appender-ref ref="projectErrorRolling"/>
      </AsyncLogger>

       <-- 添加我們自己的apender到下面,注意這裏不能使用異步,否則Skywalking控制檯仍然無法看到日誌信息 -->
       <Logger name="com.bigtreefinance.invoice" level="${LOGGER_LEVEL}"  additivity="false">
           <appender-ref ref="Console"/>
           <appender-ref ref="projectRolling"/>
           <appender-ref ref="projectErrorRolling"/>
           <appender-ref ref="SkywalkingLog"/>
       </Logger>

	  <AsyncRoot  level="${LOGGER_LEVEL}">
         <appender-ref ref="Console"/>
         <appender-ref ref="projectRolling" />
         <appender-ref ref="projectErrorRolling" />
         <-- 這裏也注意加一下 -->
          <appender-ref ref="SkywalkingLog"/>
      </AsyncRoot>
   </loggers>
</configuration>

解決方案二:我們實現slf4j的實現,取代log4j,然後我們在自己的實現中調用log4j和skywalking達到目的

在這裏插入圖片描述
注意,org.slf4j.impl下面的StaticLoggerBinder是固定寫法,這是日誌決定是使用log4j還是logback等的要點。
代碼如下:

package org.slf4j.impl;

import com.bigtreefinance.core.log.NewLog4jLoggerFactory;
import org.slf4j.ILoggerFactory;
import org.slf4j.spi.LoggerFactoryBinder;

/**
 * @author wangguodong
 */
public class StaticLoggerBinder
        implements LoggerFactoryBinder {
    public static String REQUESTED_API_VERSION = "1.6";
    private static final String LOGGER_FACTORY_CLASS_STR = NewLog4jLoggerFactory.class.getName();
    private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
    private final ILoggerFactory loggerFactory = new NewLog4jLoggerFactory();

    private StaticLoggerBinder() {
    }

    public static StaticLoggerBinder getSingleton() {
        return SINGLETON;
    }

    @Override
    public ILoggerFactory getLoggerFactory() {
        return this.loggerFactory;
    }

    @Override
    public String getLoggerFactoryClassStr() {
        return LOGGER_FACTORY_CLASS_STR;
    }
}

package org.slf4j.impl;

import org.apache.logging.slf4j.Log4jMarkerFactory;
import org.slf4j.IMarkerFactory;
import org.slf4j.spi.MarkerFactoryBinder;

public class StaticMarkerBinder  implements MarkerFactoryBinder {
    public static final StaticMarkerBinder SINGLETON = new StaticMarkerBinder();
    private final IMarkerFactory markerFactory = new Log4jMarkerFactory();

    public StaticMarkerBinder() {
    }

    @Override
    public IMarkerFactory getMarkerFactory() {
        return this.markerFactory;
    }

    @Override
    public String getMarkerFactoryClassStr() {
        return Log4jMarkerFactory.class.getName();
    }
}


package org.slf4j.impl;

import org.apache.logging.slf4j.Log4jMDCAdapter;
import org.slf4j.spi.MDCAdapter;

public class StaticMDCBinder {
    public static final StaticMDCBinder SINGLETON = new StaticMDCBinder();

    private StaticMDCBinder() {
    }

    public MDCAdapter getMDCA() {
        return new Log4jMDCAdapter();
    }

    public String getMDCAdapterClassStr() {
        return Log4jMDCAdapter.class.getName();
    }
}

package com.bigtreefinance.core.log;

import org.apache.logging.log4j.message.FormattedMessage;
import org.apache.logging.log4j.spi.ExtendedLogger;
import org.apache.logging.slf4j.Log4jLogger;
import org.apache.skywalking.apm.toolkit.trace.ActiveSpan;
import org.slf4j.Marker;
import org.slf4j.spi.LocationAwareLogger;

import java.io.Serializable;

public class NewLog4jLogger implements LocationAwareLogger, Serializable {
    private Log4jLogger log4jLogger;

    public NewLog4jLogger(ExtendedLogger logger, String name) {
        log4jLogger = new Log4jLogger(logger, name);
    }

    @Override
    public void log(Marker marker, String s, int i, String s1, Object[] objects, Throwable throwable) {
        log4jLogger.log(marker, s, i, s1, objects, throwable);
    }

    @Override
    public String getName() {
        return log4jLogger.getName();
    }

    @Override
    public boolean isTraceEnabled() {
        return log4jLogger.isTraceEnabled();
    }

    @Override
    public void trace(String s) {
        log4jLogger.trace(s);

    }

    @Override
    public void trace(String s, Object o) {
        log4jLogger.trace(s, o);
    }

    @Override
    public void trace(String s, Object o, Object o1) {
        log4jLogger.trace(s, o, o1);
    }

    @Override
    public void trace(String s, Object... objects) {
        log4jLogger.trace(s, objects);
    }

    @Override
    public void trace(String s, Throwable throwable) {
        log4jLogger.trace(s, throwable);
    }

    @Override
    public boolean isTraceEnabled(Marker marker) {
        return log4jLogger.isTraceEnabled(marker);
    }

    @Override
    public void trace(Marker marker, String s) {
        log4jLogger.trace(marker, s);
    }

    @Override
    public void trace(Marker marker, String s, Object o) {
        log4jLogger.trace(marker, s, o);
    }

    @Override
    public void trace(Marker marker, String s, Object o, Object o1) {
        log4jLogger.trace(marker, s, o, o1);
    }

    @Override
    public void trace(Marker marker, String s, Object... objects) {
        log4jLogger.trace(marker, s, objects);
    }

    @Override
    public void trace(Marker marker, String s, Throwable throwable) {
        log4jLogger.trace(marker, s, throwable);
    }

    @Override
    public boolean isDebugEnabled() {
        return log4jLogger.isDebugEnabled();
    }

    @Override
    public void debug(String s) {
        log4jLogger.debug(s);
        ActiveSpan.debug(s);
    }

    @Override
    public void debug(String s, Object o) {
        log4jLogger.debug(s, o);
        ActiveSpan.info(new FormattedMessage(s, o).getFormattedMessage());
    }

    @Override
    public void debug(String s, Object o, Object o1) {
        log4jLogger.debug(s, o, o1);
        ActiveSpan.info(new FormattedMessage(s, o, o1).getFormattedMessage());
    }

    @Override
    public void debug(String s, Object... objects) {
        log4jLogger.debug(s, objects);
        ActiveSpan.info(new FormattedMessage(s, objects).getFormattedMessage());
    }

    @Override
    public void debug(String s, Throwable throwable) {
        log4jLogger.debug(s, throwable);
    }

    @Override
    public boolean isDebugEnabled(Marker marker) {
        return log4jLogger.isDebugEnabled(marker);
    }

    @Override
    public void debug(Marker marker, String s) {
        log4jLogger.debug(marker, s);
    }

    @Override
    public void debug(Marker marker, String s, Object o) {
        log4jLogger.debug(marker, s, o);
    }

    @Override
    public void debug(Marker marker, String s, Object o, Object o1) {
        log4jLogger.debug(marker, s, o, o1);
    }

    @Override
    public void debug(Marker marker, String s, Object... objects) {
        log4jLogger.debug(marker, s, objects);
    }

    @Override
    public void debug(Marker marker, String s, Throwable throwable) {
        log4jLogger.debug(marker, s, throwable);
    }

    @Override
    public boolean isInfoEnabled() {
        return log4jLogger.isInfoEnabled();
    }

    @Override
    public void info(String s) {
        log4jLogger.info(s);
        ActiveSpan.info(s);
    }

    @Override
    public void info(String s, Object o) {
        log4jLogger.info(s, o);
        ActiveSpan.info(new FormattedMessage(s, o).getFormattedMessage());
    }

    @Override
    public void info(String s, Object o, Object o1) {
        log4jLogger.info(s, o, o1);
        ActiveSpan.info(new FormattedMessage(s, o, o1).getFormattedMessage());

    }

    @Override
    public void info(String s, Object... objects) {
        log4jLogger.info(s, objects);
        ActiveSpan.info(new FormattedMessage(s, objects).getFormattedMessage());

    }

    @Override
    public void info(String s, Throwable throwable) {
        log4jLogger.info(s, throwable);//{},o,o

        ActiveSpan.info(new FormattedMessage(s, throwable).getFormattedMessage());

    }

    @Override
    public boolean isInfoEnabled(Marker marker) {
        return log4jLogger.isInfoEnabled(marker);
    }

    @Override
    public void info(Marker marker, String s) {
        log4jLogger.info(marker, s);
    }

    @Override
    public void info(Marker marker, String s, Object o) {
        log4jLogger.info(marker, s, o);
    }

    @Override
    public void info(Marker marker, String s, Object o, Object o1) {
        log4jLogger.info(marker, s, o, o1);
    }

    @Override
    public void info(Marker marker, String s, Object... objects) {
        log4jLogger.info(marker, s, objects);
    }

    @Override
    public void info(Marker marker, String s, Throwable throwable) {
        log4jLogger.info(marker, s, throwable);
    }

    @Override
    public boolean isWarnEnabled() {
        return log4jLogger.isWarnEnabled();
    }

    @Override
    public void warn(String s) {
        log4jLogger.warn(s);
    }

    @Override
    public void warn(String s, Object o) {
        log4jLogger.warn(s, o);
    }

    @Override
    public void warn(String s, Object... objects) {
        log4jLogger.warn(s, objects);
    }

    @Override
    public void warn(String s, Object o, Object o1) {
        log4jLogger.warn(s, o, o1);
    }

    @Override
    public void warn(String s, Throwable throwable) {
        log4jLogger.warn(s, throwable);
    }

    @Override
    public boolean isWarnEnabled(Marker marker) {
        return log4jLogger.isWarnEnabled(marker);
    }

    @Override
    public void warn(Marker marker, String s) {
        log4jLogger.warn(marker, s);
    }

    @Override
    public void warn(Marker marker, String s, Object o) {
        log4jLogger.warn(marker, s, o);
    }

    @Override
    public void warn(Marker marker, String s, Object o, Object o1) {
        log4jLogger.warn(marker, s, o, o1);
    }

    @Override
    public void warn(Marker marker, String s, Object... objects) {
        log4jLogger.warn(marker, s, objects);
    }

    @Override
    public void warn(Marker marker, String s, Throwable throwable) {
        log4jLogger.warn(marker, s, throwable);
    }

    @Override
    public boolean isErrorEnabled() {
        return log4jLogger.isErrorEnabled();
    }

    @Override
    public void error(String s) {
        log4jLogger.error(s);
        ActiveSpan.error(s);
    }

    @Override
    public void error(String s, Object o) {
        log4jLogger.error(s, o);
        ActiveSpan.error(new FormattedMessage(s, o).getFormattedMessage());
    }

    @Override
    public void error(String s, Object o, Object o1) {
        log4jLogger.error(s, o, o1);
        ActiveSpan.error(new FormattedMessage(s, o, o1).getFormattedMessage());
    }

    @Override
    public void error(String s, Object... objects) {
        log4jLogger.error(s, objects);
        ActiveSpan.error(new FormattedMessage(s, objects).getFormattedMessage());
    }

    @Override
    public void error(String s, Throwable throwable) {
        log4jLogger.error(s, throwable);
        ActiveSpan.error(s);
        ActiveSpan.error(throwable);
    }

    @Override
    public boolean isErrorEnabled(Marker marker) {
        return log4jLogger.isErrorEnabled(marker);
    }

    @Override
    public void error(Marker marker, String s) {
        log4jLogger.error(marker, s);
    }

    @Override
    public void error(Marker marker, String s, Object o) {
        log4jLogger.error(marker, s, o);
    }

    @Override
    public void error(Marker marker, String s, Object o, Object o1) {
        log4jLogger.error(marker, s, o, o1);
    }

    @Override
    public void error(Marker marker, String s, Object... objects) {
        log4jLogger.error(marker, s, objects);
    }

    @Override
    public void error(Marker marker, String s, Throwable throwable) {
        log4jLogger.error(marker, s, throwable);
    }
}

package com.bigtreefinance.core.log;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.spi.AbstractLoggerAdapter;
import org.apache.logging.log4j.spi.LoggerContext;
import org.apache.logging.log4j.util.StackLocatorUtil;
import org.slf4j.ILoggerFactory;
import org.slf4j.Logger;

/**
 * @author wangguodong
 */
public class NewLog4jLoggerFactory extends AbstractLoggerAdapter<Logger> implements ILoggerFactory {
    private static final String FQCN = NewLog4jLoggerFactory.class.getName();
    private static final String PACKAGE = "org.slf4j";

    public NewLog4jLoggerFactory() {
    }

    @Override
    protected Logger newLogger(String name, LoggerContext context) {
        String key = "ROOT".equals(name) ? "" : name;
        return new NewLog4jLogger(context.getLogger(key), name);
    }

    @Override
    protected LoggerContext getContext() {
        Class<?> anchor = StackLocatorUtil.getCallerClass(FQCN, "org.slf4j");
        return anchor == null ? LogManager.getContext() : this.getContext(StackLocatorUtil.getCallerClass(anchor));
    }
}

運行項目,我們會看到這樣的情景,但是不要慌:
在這裏插入圖片描述

沒錯,這裏加載了兩個StaticLoggerBinder這不會影響到項目的啓動,只是提醒你有衝突,但是隻會加載第一個被加載到的類!所以在pom文件中需要將我們自己寫的類的包移動到log4j的上面,這是基於maven的最短路徑加載的順序。

總結

上述兩種方案,都是需要有所改動,一個是調整log4j的xml配置文件,一個是調整pom文件,如果您有更好的方案,請留言告訴我。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章