在“基于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); }