继承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);
        }
    }
}

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