基于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这个方法,也是为了删除过期的数据,避免日志过大,导致内存出问题。
如下的类,会去获取消息:
import java.util.ArrayDeque;
import java.util.concurrent.ConcurrentLinkedQueue; /**
* LogGetter is supposed to be executed in a thread. It get message by the key and store it in the clq which
* is got in the construction method.User can get message by clq.poll().
*
* */
public class LogGetter extends Thread {
//identifier to get message
private String logKey;
//signal to stop the method run
private boolean isStop = false;
//a object to transfer message between LogGetter and the caller
private ConcurrentLinkedQueue<String> concurrentLinkedQueue; /**
* private construction prevents from wrong use of it.
*/
private LogGetter() {
} /**
* @param key
* identifier to get message
* @param clq
* a ConcurrentLinkedQueue<String> object to transfer message between LogGetter and the caller
* */
public LogGetter(String key, ConcurrentLinkedQueue<String> clq) {
this.logKey = key;
this.concurrentLinkedQueue = clq;
}
/**
* set the signal to
* */
public void setStop(boolean stop) {
isStop = stop;
} /**
* get message from AdapterAppender by key and store it in clq
*/
@Override
public void run() {
while (!isStop) { ArrayDeque<String> al = AdapterAppender.getMessageQueueByKey(this.logKey);
if (null == al) { } else {
for (String str : al) {
this.concurrentLinkedQueue.add(str);
}
} try {
Thread.sleep(100);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
}
}
}
这个类会循环获取消息,放在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);
}
基于log4j的消息流的实现之二消息传递的更多相关文章
- 基于log4j的消息流的实现之一消息获取
需求: 目前的程序中都是基于log4j来实现日志的管理,想要获取日志中的一部分消息,展示给用户. 约束: 由于程序中除了自己开发的代码,还会有层层依赖的第三方jar中的日志输出.需要展示给用户的消息, ...
- [蓝牙] 6、基于nRF51822的蓝牙心率计工程消息流Log分析(详细)
开机初始化Log Log编号 函数名 所在文件名 000001: main ..\main.c 000002: timers_init ..\main.c 000003: gpiote_init ...
- Android 基于Netty的消息推送方案之对象的传递(四)
在上一篇文章中<Android 基于Netty的消息推送方案之字符串的接收和发送(三)>我们介绍了Netty的字符串传递,我们知道了Netty的消息传递都是基于流,通过ChannelBuf ...
- 开源一个自己造的轮子:基于图的任务流引擎GraphScheduleEngine
GraphScheduleEngine是什么: GraphScheduleEngine是一个基于DAG图的任务流引擎,不同语言编写.运行于不同机器上的模块.程序,均可以通过订阅GraphSchedul ...
- BTARN 接收消息流以3A7为例
 1.RNIFReceive.aspx 页接收来自发起方的传入消息. (如果发起方是BizTalk则类似于:http://localhost/BTARNApp/RNIFSend.aspx?TPUrl ...
- 大数据平台消息流系统Kafka
Kafka前世今生 随着大数据时代的到来,数据中蕴含的价值日益得到展现,仿佛一座待人挖掘的金矿,引来无数的掘金者.但随着数据量越来越大,如何实时准确地收集并分析如此大的数据成为摆在所有从业人员面前的难 ...
- Knative 实战:基于 Kafka 实现消息推送
作者 | 元毅 阿里云智能事业群高级开发工程师 导读:当前在 Knative 中已经提供了对 Kafka 事件源的支持,那么如何基于 Kafka 实现消息推送呢?本文作者将以阿里云 Kafka 产品为 ...
- IM系统-消息流化一些常见问题
原创不易,求分享.求一键三连 之前说过IM系统的一些优化,但是在网络上传输数据对于数据的流化和反流化也是处理异常情况的重点环节,不处理好可能会出现一些消息发送成功,但是解析失败的情况,本文就带大家来一 ...
- 基于SignalR的消息推送与二维码描登录实现
1 概要说明 使用微信扫描登录相信大家都不会陌生吧,二维码与手机结合产生了不同应用场景,基于二维码的应用更是比较广泛.为了满足ios.android客户端与web短信平台的结合,特开发了基于Singl ...
随机推荐
- JDBC配置文件db.properties(Mysql) 及dbutils的编写
#数据库驱动driver=com.mysql.jdbc.Driver#数据库连接url=jdb:mysql://localhost:3306/newdb3?useUnicode=true&ch ...
- hibernate的CRUD操作
一对多关系映射的crud操作: 1.单项的保存操作 /** * 保存操作 * 正常的保存:创建一个联系人,需要关联客户 */ @Test public void test1(){ Session s= ...
- centos7-mongodb3.4.6集群的搭建
0.需要环境 安装包:mongodb-linux-x86_64-3.4.6.tgz 安装路径:/usr/mongodb 服务器: 192.168.177.131/132/133 mongos 2000 ...
- JS高级. 01 复习JS基础
1. JavaScript 包含: ____, ____, 和 ____. 2. JavaScript 的基本类型有 ____, ____, 和 ____. 3. JavaScript 的复合类型有 ...
- Python Web开发中,WSGI协议的作用和实现原理详解
首先理解下面三个概念: WSGI:全称是Web Server Gateway Interface,WSGI不是服务器,python模块,框架,API或者任何软件,只是一种规范,描述web server ...
- is和==,编码补充
一,is和==的区别: 1, 通过一个ID()可以查看到一个变量表示的值在内存中的地址. s = 'alex' print(id(s)) # 4326667072 s = "alex& ...
- python, pycharm, virtualenv 的使用
创建虚拟环境,一次安装多个库 pip freeze > requirements.txt (库的名字都在里面) 产生requirements.txt文件 在另一个环境下使用 pip instal ...
- FireDAC 连接Access (accdb)数据库
FireDAC可以方便连接数据库,但是要连接新版本的accdb数据库,要注意这样的事项(以Office2010版为例) 安装Office2010 x86版,注意,不能安装x64版,因为Delphi I ...
- vs13发布web程序 iis上
一.配置iis 1,找到控制面板--程序--启用或关闭Windows功能 2,从列表中选择Internet Infomation Services,并且把相应的功能条目勾选上,如果不清楚,可以全部选中 ...
- ubuntu设置ssh登陆
转: 默认请况下,ubuntu是不允许远程登陆的.(因为服务没有开,可以这么理解.) 想要用ssh登陆的话,要在需要登陆的系统上启动服务.即,安装ssh的服务器端 $ sudo apt-get ins ...