繼承AbstractAppender實現log4j的自定義標籤,完成集羣日誌集中展示

我們很多機器是集羣部署,如果單獨去每臺機器上面去找日誌會很麻煩。另外很多生產上機器會對部分人員進行限制,但卻需要他們去定位生產上的問題。這裏通過繼承AbstractAppender實現log4j的自定義標籤來發送到同一服務器集中處理。

1、首先定義一個類繼承AbstractAppender

/**
 * name 爲log.xml裏面配置標籤
 * @author yin
 * @Method
 */
@Plugin(name = "LOG_SEND", category = "Core", elementType = "appender", printObject = true)
public class LogAppender extends AbstractAppender {

    protected LogAppender(String name, Filter filter, Layout<? extends Serializable> layout) {
        super(name, filter, layout);
    }

    /**
     *  加載類之前會調用的方法
     */
    @Override
    public void start() {
        //QueueUtil.execSys();
        super.start();
    }

    /**
     * 類結束之前調用方法
     */
    @Override
    public void stop() {
        super.stop();
    }


    /**
     * 日誌輸入的具體操作方式
     * @param logEvent
     */
    @Override
    public void append(LogEvent logEvent) {


        try {
            ThrowableProxy thrownProxy = logEvent.getThrownProxy();
            LogData logData;
            if (Objects.isNull(thrownProxy)) {
                logData= new LogData(logEvent.getMessage().getFormattedMessage(), logEvent.getThreadName(),
                        logEvent.getLevel().name(), logEvent.getTimeMillis(), logEvent.getLoggerName(),MachineIp.getIp());

            }else {
                StringBuilder sb = new StringBuilder(logEvent.getMessage().getFormattedMessage());
                sb.append("\n");
                dealErrorContent(thrownProxy, sb);
                for (ThrowableProxy throwableProxy = thrownProxy.getCauseProxy(); !Objects.isNull(throwableProxy); throwableProxy = thrownProxy.getCauseProxy()) {
                    sb.append("Caused by: ");
                    dealErrorContent(thrownProxy, sb);
                }
                logData= new LogData(sb.toString(), logEvent.getThreadName(),
                        logEvent.getLevel().name(), logEvent.getTimeMillis(), logEvent.getLoggerName(),MachineIp.getIp());

            }
            //System.out.println("========================================logData====" + logData);
            QueueUtil.getInstance().offer(logData);
        } catch (Exception e) {
            System.out.println("log4j httpAppender error !" + e);
        }

    }

    /**
     * 通過配置工廠來創建創建  LogAppender
     * @param filter
     * @param layout
     * @param name
     * @return
     */
    @PluginFactory
    public static LogAppender createLogAppender(@PluginElement("Filter") Filter filter, @PluginElement("layout") Layout<? extends Serializable> layout, @PluginAttribute("name") String name) {
        if (name == null) {
            LOGGER.error("No name provided for LogAppender");
            return null;
        }
        if (layout == null) {
            layout = PatternLayout.createDefaultLayout();
        }
        return new LogAppender(name, filter, layout);
    }

    private StringBuilder dealErrorContent(ThrowableProxy throwableProxy,StringBuilder sb ){
        sb.append(throwableProxy.getName());
        sb.append(": ");
        sb.append(throwableProxy.getMessage());
        sb.append("\n");
        StackTraceElement[] elements = throwableProxy.getStackTrace();

        for (int i = 0, elementLength = elements.length; i < elementLength; i++) {
            sb.append("\t");
            StackTraceElement element = elements[i];
            sb.append(String.valueOf(element));
            sb.append("\n");
        }
        return sb;
    }
}

2、定義了標籤需要在log4j.xml添加對應標籤,需要增加

<LOG_SEND  name="log_send">
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] [%-5p] {%F:%L} - %m%n" />
        </LOG_SEND>

<AppenderRef ref="log_send"/>

下面是完整的log4j.xml的內容

<?xml version="1.0" encoding="UTF-8"?>
<!--日誌級別以及優先級排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration後面的status,這個用於設置log4j2自身內部的信息輸出,可以不設置,當設置成trace時,你會看到log4j2內部各種詳細輸出-->
<!--monitorInterval:Log4j能夠自動檢測修改配置 文件和重新配置本身,設置間隔秒數-->
<configuration  status="warn"  monitorInterval="500">

    <!--這裏相當於配置變量,後面在 <Appenders>  標籤引入 用${}-->
    <Properties>
        <Property name="LOG_HOME">logs</Property>
        <!-- 控制檯默認輸出格式,"%d":表示打印的時間 "%5level":日誌級別,
        %thread 表示打印日誌的線程 "%c":表示類 "%L":表示行號 "%msg" 表示打印的信息  %n 換行  %throwable 表示錯誤信息
        %style 和{bright,green} 結合表示展示顏色   %highlight
        所以影響日誌輸出的性能 -->
        <!--<Property name="PATTERN">[%style{%d{yyyy-MM-dd HH:mm:ss:SSS}}] |-%-5level [%thread] %c [%L] -| %msg%n</Property>-->
        <Property name="CONSOLE">[%style{%d{yyyy-MM-dd HH:mm:ss:SSS}}{bright,green}] | [%highlight{%5level}] [%thread] [%style{%c}{bright,yellow}] [%style{%L}{bright,blue}] -| %highlight{%msg}%n%style{%throwable}{red}</Property>
        <Property name="FILE">%date{yyy-MM-dd HH:mm:ss.SSS} %level [%file:%line] : %msg%n</Property>
    </Properties>


    <Appenders>

        <LOG_SEND  name="log_send">
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] [%-5p] {%F:%L} - %m%n" />
        </LOG_SEND>

        <!--生成文件的文件名,當天生成日誌 log.log ,保存其他天日誌爲log-%d{yyyy-MM-dd}.log-->
        <RollingFile name="file" fileName="${LOG_HOME}/log.log" filePattern="${LOG_HOME}/log-%d{yyyy-MM-dd}.log">
            <!--${FILE} 引入<Property> 標籤的文件格式-->
            <PatternLayout pattern="${FILE}"/>
            <Policies>
                <!--基於時間的觸發策略。該策略主要是完成周期性的log文件封存工作。有兩個參數:
               interval,integer型,指定兩次封存動作之間的時間間隔,modulate,boolean型,說明是否對封存時間進行調製。-->
                <!--設置每天打包日誌一次-->
                <TimeBasedTriggeringPolicy modulate="true" interval="1"></TimeBasedTriggeringPolicy>
            </Policies>

            <DefaultRolloverStrategy>
                <!--要訪問的目錄的最大級別數。值爲0表示僅訪問起始文件,2表示能訪問一下兩級目錄-->
                <Delete basePath="${LOG_HOME}" maxDepth="2">
                    <IfFileName glob="*/*.log">
                        <!--刪除超過十天文件-->
                        <IfLastModified age="10d"/>
                    </IfFileName>
                </Delete>
            </DefaultRolloverStrategy>
        </RollingFile>


        <!--添加一個控制檯追加器,添加之後在AppenderRef 中引用,就會生效-->
        <Console name="CONSOLE" target="SYSTEM_OUT" follow="true">
            <PatternLayout pattern="${CONSOLE}"/>
        </Console>

    </Appenders>

    <Loggers>


        <!-- 生產環境下,將此級別配置爲適合的級別,以免日誌文件太多或影響程序性能 -->
        <!--這裏指代生效的日誌級別和 輸出的內容,這裏代表生效的是 debug ,
        文件和控制檯都會輸出,如果是在環境留下file就可以了,file和CONSOLE 來自於 <Appenders> 配置的標籤 -->
        <Root level="info">
            <AppenderRef ref="file"/>
            <AppenderRef ref="CONSOLE"/>
            <AppenderRef ref="log_send"/>
        </Root>

    </Loggers>
</configuration >

3、定義一個我們需要關於日誌基本的類

@Data
@NoArgsConstructor
@AllArgsConstructor
public class LogData {
    private String message;
    private String threadName;

    private String logLevel;

    private long time;

    private String className;

    private String hostIp;

 
}

4、自定義一個守護線程不斷髮送日誌到日誌服務器統一管理

public class QueueUtil {
    private static Logger logger = LoggerFactory.getLogger(QueueUtil.class);

    private static final Integer MAX_RETRY = 3;

    private QueueUtil(){
        initTask();
    }
    private static final Executor executor = Executors.newSingleThreadExecutor(new ThreadFactory() {

        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r);
            thread.setName("logAppendQueue");
            thread.setDaemon(true);
            return thread;

        }
    });

    private static final ScheduledExecutorService scheduled_thread_pool = Executors.newScheduledThreadPool(1, new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r);
            thread.setName("scheduled thread pool");
            thread.setDaemon(true);
            return thread;
        }
    });

   /* private static final DateTimeFormatter formtter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    public static  void execSys(){
        scheduled_thread_pool.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                System.out.println("sdfdfdfdf"+formtter.format(LocalDateTime.now()));
            }
        }, 0L, 1L, TimeUnit.SECONDS);
    }*/

    private static final BlockingQueue<LogData> LOG_QUEUE = new LinkedBlockingQueue<>(2000);

    private static ExecutorService executor1=new ThreadPoolExecutor(1, 1,
                                   0L,TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>(1),new ThreadFactory() {

        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r);
            thread.setName("logAppendQueue");
            thread.setDaemon(true);
            return thread;

        }
    });


    private void initTask(){
        executor1.execute(new Runnable() {
            @Override
            public void run() {
               List<LogData> logDatas = new ArrayList<>();
                while (true) {
                    while (true) {

                        try {
                            List<LogData> tmpLst = new ArrayList<>(100);
                            LOG_QUEUE.drainTo(tmpLst, 100);
                            if (!CollectionUtils.isEmpty(tmpLst)) {
                                logDatas.addAll(tmpLst);
                                if (logDatas.size() >= 500) {
                                    sendMessageRetry(logDatas,0);
                                    sleepSeconds(1);
                                }
                            }else {
                                if (logDatas.size() > 0) {
                                    sendMessageRetry(logDatas,0);
                                    sleepSeconds(1);

                                }

                            }
                        } catch (Exception e) {
                            logger.error("log appender send message fail:", e);
                            sleepSeconds(2);
                        }

                    }
                }
            }
        });
    }

    public boolean offer(LogData logData) {
        return LOG_QUEUE.offer(logData);
    }

    public static QueueUtil getInstance(){
        return QueueUtil.QueueUtilHelper.queueUtil;
    }
    private void sleepSeconds(int time) {
        try {
            TimeUnit.SECONDS.sleep(time);
        } catch (InterruptedException e) {
            logger.error("sleep Interrupt", e);
        }

    }



    private void sendMessageRetry(List<LogData> logDataLst,int currentTime) {
        try {
            System.out.println("發送消息"+logDataLst);
        } catch (Exception e) {
            logger.error("sendMessageRetry fail ,current time is :{} time", currentTime, e);
            if(currentTime>=MAX_RETRY){
                return;
            }
            sendMessageRetry(logDataLst, ++currentTime);
            sleepSeconds(2);

        }finally {
            if (!CollectionUtils.isEmpty(logDataLst)) {
                logDataLst.clear();
            }
        }

    }


    public static class QueueUtilHelper {
        private static QueueUtil queueUtil=new QueueUtil();

        public QueueUtilHelper() {
        }
    }


}

6、補充上需要獲取當前機器的Ip的工具類

public class MachineIp {

    private static volatile String ipAddr;

    public static String getHostIp() {

        String sIP = "";
        InetAddress ip = null;
        try {
            boolean bFindIP = false;
            Enumeration<NetworkInterface> netInterfaces = NetworkInterface.getNetworkInterfaces();
            while (netInterfaces.hasMoreElements()) {
                if (bFindIP){
                    break;
                }

                NetworkInterface ni = netInterfaces.nextElement();
                Enumeration<InetAddress> ips = ni.getInetAddresses();
                while (ips.hasMoreElements()) {
                    ip = ips.nextElement();
                    if (!ip.isLoopbackAddress()
                            && ip.getHostAddress().matches("(\\d{1,3}\\.){3}\\d{1,3}")) {
                        bFindIP = true;
                        break;
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (null != ip){
            sIP = ip.getHostAddress();
        }
        return sIP;
    }

    public static String getIp() {
        if (null != ipAddr) {
            return ipAddr;
        } else {
            Enumeration netInterfaces;
            try {
                netInterfaces = NetworkInterface.getNetworkInterfaces();
            } catch (SocketException var6) {
                throw new HostException(var6);
            }

            String localIpAddress = null;

            while(netInterfaces.hasMoreElements()) {
                NetworkInterface netInterface = (NetworkInterface)netInterfaces.nextElement();
                Enumeration ipAddresses = netInterface.getInetAddresses();

                while(ipAddresses.hasMoreElements()) {
                    InetAddress ipAddress = (InetAddress)ipAddresses.nextElement();
                    if (isPublicIpAddress(ipAddress)) {
                        String publicIpAddress = ipAddress.getHostAddress();
                        ipAddr = publicIpAddress;
                        return publicIpAddress;
                    }

                    if (isLocalIpAddress(ipAddress)) {
                        localIpAddress = ipAddress.getHostAddress();
                    }
                }
            }

            ipAddr = localIpAddress;
            return localIpAddress;
        }
    }

    private static boolean isPublicIpAddress(InetAddress ipAddress) {
        return !ipAddress.isSiteLocalAddress() && !ipAddress.isLoopbackAddress() && !isV6IpAddress(ipAddress);
    }

    private static boolean isLocalIpAddress(InetAddress ipAddress) {
        return ipAddress.isSiteLocalAddress() && !ipAddress.isLoopbackAddress() && !isV6IpAddress(ipAddress);
    }

    private static boolean isV6IpAddress(InetAddress ipAddress) {
        return ipAddress.getHostAddress().contains(":");
    }

    public static String getHostName() {
        try {
            return InetAddress.getLocalHost().getHostName();
        } catch (UnknownHostException var1) {
            throw new HostException(var1);
        }
    }
}

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