基于log4j的消息流的实现之二消息传递

在“基于log4j的消息流的实现之一消息获取”中获取日志消息的部分,修改如下:

import org.apache.commons.collections.map.HashedMap;
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.spi.LoggingEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayDeque;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * AdapterAppender requires the layout as "%X - %m%n", and the message will be stored by identifier.
 * getMessageQueueByKey can be used to get the message by identifier
 * */
public class AdapterAppender extends AppenderSkeleton {
    private static final Logger LOG = LoggerFactory.getLogger(AdapterAppender.class);
    //to store message got from log4j by key
    private static ConcurrentHashMap<String, ArrayDeque<String>> messageHM = new ConcurrentHashMap<>();
    //to store the first time the  message got by key
    private Map<String,Long> messageTimestamp = new HashedMap();
    //last time we do the remove method
    private long lastCheckTimestamp = System.currentTimeMillis();
    //will do the remove by one minute
    private final int checkInterval = 60000;
    //messages which has lived more than 10 minute will be removed
    private final int liveDuration = 600000;

    /**
     * format the log message as <identifier - information>, store the <identifier,information>
     *
     * @param loggingEvent
     *         log message sent from log4j rootLogger
     * */
    @Override
    protected void append(LoggingEvent loggingEvent) {
        String message = this.layout.format(loggingEvent);
        if (message.contains("-")) {
            String key = message.substring(0, message.indexOf("-") - 1).trim();
            String value = message.substring(message.indexOf("-") + 1).trim();
            if (messageHM.containsKey(key)) {
                messageHM.get(key).add(value);
            } else {
                ArrayDeque<String> ad = new ArrayDeque<>(32);
                ad.add(value);
                messageHM.put(key, ad);
                messageTimestamp.put(key,System.currentTimeMillis());
            }
        }else {
            LOG.warn("Receive a wrong format message which does not have a '-' in it:{}",message);
        }

        //won't do the remove too frequently,will do it by the checkInterval
        long currentTimestamp = System.currentTimeMillis();
        if(currentTimestamp - this.lastCheckTimestamp > this.checkInterval){
            removeOldMessage(currentTimestamp);
            this.lastCheckTimestamp = currentTimestamp;
        }

    }

    /**
     * Remove the message which lives more than the liveDuration
     *
     * @param currentTime
     *         the check time
     * */
    private void removeOldMessage(long currentTime){
        for(String key : messageTimestamp.keySet()){
            long messageCreateTime = messageTimestamp.get(key);
            if(currentTime - messageCreateTime > this.liveDuration){
                if(messageHM.containsKey(key)){
                    messageHM.remove(key);
                    LOG.info("Remove message for {}",key);
                }
                messageTimestamp.remove(key);
            }

        }
    }

    /**
     * return the message got by this appender until now
     * @param key
     *         identifier which will exists in the log message
     * @return message returned when key is found, null returned when key is not found
     * */
    public static ArrayDeque<String> getMessageQueueByKey(String key) {
        if (messageHM.containsKey(key) == false) {
            return null;
        }
        ArrayDeque<String> ad = messageHM.get(key);
        messageHM.remove(key);
        return ad;
    }

    @Override
    public void close() {

    }

    /**
     * the layout should be "%X - %m%n"
     * @param
     * @return
     * */
    @Override
    public boolean requiresLayout() {
        return true;
    }
}

对外保留了一个static方法 getMessageQueueByKey,供获取消息。

注意看到,这个方法里,获取消息后,消息会被删掉,另外在removeOldMessage这个方法,也是为了删除过期的数据,避免日志过大,导致内存出问题。

如下的类,会去获取消息:

 1 import java.util.ArrayDeque;
 2 import java.util.concurrent.ConcurrentLinkedQueue;
 3 
 4 /**
 5  * LogGetter is supposed to be executed in a thread. It get message by the key and store it in the clq which
 6  * is got in the construction method.User can get message by clq.poll().
 7  *
 8  * */
 9 public class LogGetter extends Thread {
10     //identifier to get message
11     private String logKey;
12     //signal to stop the method run
13     private boolean isStop = false;
14     //a object to transfer message between LogGetter and the caller
15     private ConcurrentLinkedQueue<String> concurrentLinkedQueue;
16 
17     /**
18      * private construction prevents from wrong use of it.
19      */
20     private LogGetter() {
21     }
22 
23     /**
24      * @param key
25      *         identifier to get message
26      * @param clq
27      *         a ConcurrentLinkedQueue<String> object to transfer message between LogGetter and the caller
28      * */
29     public LogGetter(String key, ConcurrentLinkedQueue<String> clq) {
30         this.logKey = key;
31         this.concurrentLinkedQueue = clq;
32     }
33     /**
34      * set the signal to
35      * */
36     public void setStop(boolean stop) {
37         isStop = stop;
38     }
39 
40     /**
41      * get message from AdapterAppender by key and store it in clq
42      */
43     @Override
44     public void run() {
45         while (!isStop) {
46             
47             ArrayDeque<String> al = AdapterAppender.getMessageQueueByKey(this.logKey);
48             if (null == al) {
49 
50             } else {
51                 for (String str : al) {
52                     this.concurrentLinkedQueue.add(str);
53                 }
54             }
55             
56             try {
57                 Thread.sleep(100);
58             } catch (InterruptedException ie) {
59                 ie.printStackTrace();
60             }
61         }
62     }
63 }

这个类会循环获取消息,放在queue里。

实际使用的片段如下:

/**
        * send back the message keyed by mdcValue with responseObserver when execute future
        *
        * @param future
         *        a future which is already executed
        * @param mdcValue
         *        a value will be set into the MDC
         * @param responseObserver
         *        a observer which will send back message
        * **/
        private void sendFutureLog(Future future,
                                    String mdcValue,
                                    StreamObserver<AgentResultReply> responseObserver){

            ConcurrentLinkedQueue<String> messageQ = new ConcurrentLinkedQueue<>();
            LogGetter lg = new LogGetter(mdcValue, messageQ);
            this.executorService.submit(lg);

            String returnMessage;
            AgentResultReply agentResultReply;

            while (!future.isDone()) {
                while (messageQ.size() > 0) {
                    returnMessage = responseJson.getResponseJson(ResponseJson.MessageType.INFO,
                            messageQ.poll());
                    agentResultReply = AgentResultReply.newBuilder().setMessage(returnMessage).build();
                    responseObserver.onNext(agentResultReply);
                }
                try {
                    Thread.sleep(50);
                } catch (InterruptedException ie) {
                    LOG.error("Exception happened in sendFutureLog:{}",ie.getMessage());
                    ie.printStackTrace();
                }
            }

            lg.setStop(true);
        }

 

posted @ 2018-08-14 20:46  boiledwater  阅读(367)  评论(0编辑  收藏  举报