`

APDPlat中业务日志和监控日志的设计与实现

阅读更多

APDPlat提供了业务日志监控日志,以便对用户操作进行审计、对系统性能进行调优。

 

业务日志主要包括数据的增删改日志、备份恢复日志以及用户登录注销日志。监控日志主要包括用户请求响应时间、内存使用情况、全文索引重建情况、系统启动关闭事件。

 

设计目标:

 

1、灵活,可以很容易地启用或停用

2、性能,不对正常的业务操作造成影响

3、开放,容易和第三方系统整合

 

下面阐述具体的设计及实现:

 

1、在灵活性方面,可以在配置文件config.properties或config.local.properties中指定选项来启用(true)或停用(false),如下所示:

 

   

    配置:

#是否启用系统监控模块中的功能
monitor.memory=true
monitor.performance=true
monitor.runing=true
monitor.login=true
monitor.index=true
monitor.backup=true
#是否启用业务日志记录功能
log.create=true
log.delete=true
log.update=true

   

    代码:

memoryMonitor=PropertyHolder.getBooleanProperty("monitor.memory");        
if(memoryMonitor){
	LOG.info("启用内存监视日志");
	LOG.info("Enable memory monitor log", Locale.ENGLISH);
}else{
	LOG.info("禁用内存监视日志");
	LOG.info("Disable memory monitor log", Locale.ENGLISH);
}

  

2、在性能方面,使用内存缓冲区来临时存储日志对象,可以节省磁盘和网络开销,缓冲区的大小可以配置,当缓冲区满了或是人工强制执行的时候才会对日志进行持久化或其他处理,这样不但提高了吞吐量(批量提交、批量处理),而且对用户业务处理的影响非常小,因为产生日志对象之后只需要将日志对象加入缓冲区即可(无阻塞、内存操作)。除此之外,当对缓冲区中的日志对象进行持久化或其他处理的时候,会有独立的线程池中的线程来完成,不会阻塞用户业务处理线程(线程复用、异步非阻塞),如下所示:

 

MemoryState logger=new MemoryState();
try {
 logger.setServerIP(InetAddress.getLocalHost().getHostAddress());
} catch (UnknownHostException ex) {
 LOG.error("获取服务器地址出错",ex);
 LOG.error("Can't get server's internet address", ex, Locale.ENGLISH);
}
logger.setAppName(SystemListener.getContextPath());
logger.setRecordTime(new Date());
logger.setMaxMemory(max);
logger.setTotalMemory(total);
logger.setFreeMemory(free);
logger.setUsableMemory(logger.getMaxMemory()-logger.getTotalMemory()+logger.getFreeMemory());
BufferLogCollector.collect(logger);

 

    首先,构造了一个日志对象logger,设置相关信息,然后调用BufferLogCollector.collect(logger)将日志对象加入内存缓冲区,BufferLogCollector.collect方法如下:

 

public static <T extends Model> void collect(T t){
	LOG.debug("将日志加入缓冲区:\n"+t.toString());
	buffers.add(t);
	//判断缓冲区是否达到限制
	if(buffers.size() > logBufferMax){
		LOG.info("缓冲区已达到限制数:"+logBufferMax+" ,处理日志");
		handleLog();
	}
}

 

    buffers是类ConcurrentLinkedQueue的实例,不限制大小,不会有日志存不下而暂停阻塞的情况发生,支持多线程并发操作,链表结构,尤其适合增删操作。加入缓冲区之后就会判断缓冲区是否已满,如满则会处理,logBufferMax的值从哪里来的呢?

 

private static final int logBufferMax = PropertyHolder.getIntProperty("log.buffer.max");

 

     log.buffer.max的值需要在配置文件config.properties或config.local.properties中指定,值越大,吞吐量越好,对用户的影响越小(除了批量处理的时候,发生次数很少),当然内存的占用也越大,需要根据实际情况权衡:

 

log.buffer.max=1000

 

    如果缓冲区满了,怎么处理日志呢?看看handleLog方法:

 

public static void handleLog(){
	if(shoudHandle()){
		executorService.submit(handleLogRunnable);
	}
}

 

     先判断是否应该处理,看是否有logHandlers,对缓冲区大小做了检查,如下所示:

 

private static boolean shoudHandle(){
	if(logHandlers.isEmpty()){    
		LOG.error("未找到任何LogHandler");
		return false;
	}
	int len=buffers.size();
	if(len==0){
		LOG.info("没有日志需要保存");
		LOG.info("No logs need to save:"+len, Locale.ENGLISH);
		return false;
	}
	return true;
}

 

    这里使用了ExecutorService来对线程进行管理,如检查通过,则将具体的日志处理逻辑(封装在handleLogRunnable中)提交给executorService 

 

private static final ExecutorService executorService = Executors.newSingleThreadExecutor();

 

    handleLog方法把具体的日志处理逻辑(封装在handleLogRunnable中)提交给executorService (线程执行服务),对线程的提交和执行做了解耦,复用线程池,提高了性能。由于具体的日志处理逻辑运行于独立的线程中,故不会阻塞用户业务处理线程。

 

3、怎么理解开放呢?先接着上面看看提交给线程执行服务的日志的处理逻辑:

 

private static final HandleLogRunnable handleLogRunnable = new HandleLogRunnable();

 

    在线程中调用了私有静态内部类LogSaver的save方法,如下所示:

 

private static class HandleLogRunnable implements Runnable{
	@Override
	public void run() {
		LOG_SAVER.save();
	}    
}

 

private static final LogSaver LOG_SAVER = new LogSaver();

 

    save里面究竟是怎么处理的呢?请看:

 

private static class LogSaver{
	public void save(){
		int len=buffers.size();
		List<Model> list=new ArrayList<>(len);
		for(int i=0;i<len;i++){
			list.add(buffers.remove());            
		}        
		//把日志交给LogHandler处理
		for(LogHandler logHandler : logHandlers){
			logHandler.handle(list);
		}            
	}
}

 

    首先把缓冲区里面的数据全部取出来(取出来之后缓冲区里面就没有了)构成一个链表,数目不超过规定的大小(配置文件中指定的log.buffer.max的值),然后把链表分别交给每一个注册的LogHandler来处理。注意这里不用多个线程来让多个LogHandler并行执行的原因:一是会增大系统负荷,对用户业务处理造成影响;二是多个LogHandler并行执行的话就满足不了需要先后执行顺序的要求了。

 

    接着看LogHandler,这是一个接口:

 

/**
 * 日志处理接口:
 * 可将日志存入独立日志数据库(非业务数据库)
 * 可将日志传递到activemq\rabbitmq\zeromq等消息队列
 * 可将日志传递到kafka\flume\chukwa\scribe等日志聚合系统
 * @author 杨尚川
 */
public interface LogHandler {
    public <T extends Model> void handle(List<T> list);
}

 

    那么logHandlers是怎么来的呢?

 

private static final List<LogHandler> logHandlers = new ArrayList<>();

 

@Service
public class BufferLogCollector  implements ApplicationListener {

 

    BufferLogCollector实现了Spring的ApplicationListener接口,当Spring的所有对象正确完整地装配完成后会回调BufferLogCollector实现的方法:

 

@Override
public void onApplicationEvent(ApplicationEvent event){
	if(event instanceof ContextRefreshedEvent){
		LOG.info("spring容器初始化完成,开始解析LogHandler");
		String handlerstr = PropertyHolder.getProperty("log.handlers");
		if(StringUtils.isBlank(handlerstr)){
			LOG.info("未配置log.handlers");
			return;
		}
		LOG.info("handlerstr:"+handlerstr);
		String[] handlers = handlerstr.trim().split(";");
		for(String handler : handlers){
			LogHandler logHandler = SpringContextUtils.getBean(handler.trim());
			if(logHandler != null){
				logHandlers.add(logHandler);
				LOG.info("找到LogHandler:"+handler);
			}else{
				LOG.info("未找到LogHandler:"+handler);
			}
		}
	}
}

 

    怎么跟Spring扯上关系了呢?因为logHandlers是从Spring的容器中获得的。从这里可以得知,logHandlers是由配置文件config.properties或config.local.properties中的log.handlers选项指定的,如下所示:

 

#日志缓冲区的最大值,只有达到最大值或手工强制刷新时,日志才会被持久化
#当用户在管理界面查看任意一种日志时,会强制刷新
#log.handlers可指定多个,用;分割,值为spring的bean名称
#如:databaseLogHandler;fileLogHandler;consoleLogHandler
log.buffer.max=1000
log.handlers=databaseLogHandler;

 

    开放的含义体现在:LogHandler定义了统一的接口,允许任意的扩展,LogHandler的实现由Spring来管理,通过在配置文件中指定log.handlers的值为托管在Spring中的多个bean name,可以有序地调用多个LogHandler实现,调用顺序就是配置文件中指定的先后顺序。

  

 

最后来看几个LogHandler的实现:

 

1、DatabaseLogHandler(将日志保存到关系数据库,使用JPA)

 

@Service
public class DatabaseLogHandler implements LogHandler{
    private static final APDPlatLogger LOG = new APDPlatLogger(DatabaseLogHandler.class);
    //使用日志数据库
    @Resource(name = "serviceFacadeForLog")
    private ServiceFacade serviceFacade;

    /**
     * 打开日志数据库em
     * @param entityManagerFactory 
     */
    private static void openEntityManagerForLog(EntityManagerFactory entityManagerFactory){        
        EntityManager em = entityManagerFactory.createEntityManager();
        TransactionSynchronizationManager.bindResource(entityManagerFactory, new EntityManagerHolder(em));
        LOG.info("打开ForLog实体管理器");
    }
    /**
     * 关闭日志数据库em
     * @param entityManagerFactory 
     */
    private static void closeEntityManagerForLog(EntityManagerFactory entityManagerFactory){
        EntityManagerHolder emHolder = (EntityManagerHolder)TransactionSynchronizationManager.unbindResource(entityManagerFactory);
        LOG.info("关闭ForLog实体管理器");
        EntityManagerFactoryUtils.closeEntityManager(emHolder.getEntityManager());
    }
    @Override
    public <T extends Model> void handle(List<T> list) {
        int len = list.size();
        LOG.info("需要保存的日志数目:"+len);
        LOG.info("The number of logs to be saved:"+len, Locale.ENGLISH);
        long start=System.currentTimeMillis();
        EntityManagerFactory entityManagerFactoryForLog = SpringContextUtils.getBean("entityManagerFactoryForLog");
        openEntityManagerForLog(entityManagerFactoryForLog);
        //保存日志
        serviceFacade.create(list);
        closeEntityManagerForLog(entityManagerFactoryForLog);
        long cost=System.currentTimeMillis()-start;
        LOG.info("成功保存 "+len+" 条日志, 耗时: "+ConvertUtils.getTimeDes(cost));
        LOG.info("Success to save "+len+" logs, elapsed: "+ConvertUtils.getTimeDes(cost), Locale.ENGLISH);
    }
}

 

2、FileLogHandler(将日志保存到本地文件)

 

@Service
public class FileLogHandler implements LogHandler{
    private static int count = 1;

    @Override
    public <T extends Model> void handle(List<T> list) {
        StringBuilder str = new StringBuilder();
        for(T t : list){
            str.append(count++).append(":\n").append(t.toString());
        }
        FileUtils.createAndWriteFile("/WEB-INF/logs/log-"+DateTypeConverter.toDefaultDateTime(new Date()).replace(" ", "-").replace(":", "-")+".txt", str.toString());
    }
}

 

3、ConsoleLogHandler(将日志在控制台输出)

 

@Service
public class ConsoleLogHandler implements LogHandler{
    private static int count = 1;
    @Override
    public <T extends Model> void handle(List<T> list) {
        for(T t : list){
            System.out.println((count++) + ":");
            System.out.println(t.toString());
        }
    }
}

 

 

 

APDPlat托管在Github

1
2
分享到:
评论
2 楼 yangshangchuan 2014-02-26  
sealv 写道
这些代码的位置在哪 我在Github 下的源码(22MB) 没找到这些类 是我我下不对么

代码主页:https://github.com/ysc/APDPlat
入门指南:https://github.com/ysc/APDPlat/wiki/%E5%85%A5%E9%97%A8%E6%8C%87%E5%8D%97
使用Netbeans的 编辑 -> 在项目中查找 -> 输入类名称,就找出来了
1 楼 sealv 2014-02-26  
这些代码的位置在哪 我在Github 下的源码(22MB) 没找到这些类 是我我下不对么

相关推荐

    APDPlat中数据库备份恢复的设计与实现

    在APDPlat中,数据库备份恢复的设计与实现是一个关键功能,它旨在简化数据库的维护工作...通过这些设计和实现,APDPlat创建了一个强大且灵活的数据库备份恢复解决方案,降低了数据库运维的复杂性,提升了系统的可靠性。

    APDPlat应用级产品开发平台_共27张UML设计图

    APDPlat应用级产品开发平台是一个专门用于构建企业级应用程序的高效开发工具,它集成了多种UML(统一建模语言)设计图,旨在帮助开发者以更清晰、规范的方式规划和实现软件项目。该平台包含27张不同的UML设计图,...

    应用级产品开发平台APDPlat.zip

    APDPlat提供了应用容器、多模块架构、代码生成、安装程序、认证授权、备份恢复、数据字典、web service、系统监控、操作审计、统计图、报表、机器绑定、防止破解、数据安全、内置搜索、数据转换、maven支持、WEB...

    制定CA6140车床拨叉的加工工艺,设计钻φ5孔的钻床夹具设计.rar

    制定CA6140车床拨叉的加工工艺,设计钻φ5孔的钻床夹具设计.rar

    128 基于STM32的儿童误锁车内远程报警系统【QT上位机源码】.zip

    这是 《128 基于STM32的儿童误锁车内远程报警系统【QT上位机源码】》 项目的Qt上位机上位机源码包。 这是一个Qt工程,采用QT5.12.6版本开发的源码。支持生成Windows系统运行程序。也支持生成Android手机APP。 对应项目的博客链接:https://blog.csdn.net/xiaolong1126626497/article/details/132015856 注意 注意 注意!!!: 如果不需要修改上位机源码,就不用下载本资源 (本项目的STM32源码包里就包含了上位机APP安装包,可以直接使用),在设计文档里也写了上位机的核心代码。 如果想学习本项目的上位机开发,学习上位机的源码,修改源。那么可以下载。 最好自己具备一定的Qt开发基础。

    水泥粉磨生产工艺流程图.zip

    水泥粉磨生产工艺流程图.zip

    ParagonHFS+forWin v14.0.24 x64.rar

    WINDOWS系统读取苹果分区的利器,支持HFS+及APFS分区。

    基于Ryu 控制器和 Mininet 实现软件定义网络(SDN)负载均衡解决方案,用于网络模拟.zip

    基于Ryu 控制器和 Mininet 实现软件定义网络(SDN)负载均衡解决方案,用于网络模拟.zip

    20250415API翻譯

    20250415API翻譯

    Git知识学习(尚硅谷)

    Git知识学习(尚硅谷)

    手机充电器的模具设计.zip

    手机充电器的模具设计.zip

    原神弹琴脚本,让风告诉你,只需要pynput库即可使用

    python

    基于SpringBoot的体育商品推荐系统(源码+数据库+万字文档+ppt)535

    基于SpringBoot的体育商品推荐系统,系统包含两种角色:管理员、用户主要功能如下。 【用户功能】 1. **首页:** 浏览体育商品推荐系统的主要信息。 2. **商品信息:** 查看系统推荐的体育商品。 3. **交流论坛:** 参与用户间的体育商品讨论和交流。 4. **公告资讯:** 查看系统发布的重要通知和体育商品资讯。 5. **留言板:** 发表个人意见和留言,参与系统互动。 6. **购物车:** 查看已选购的体育商品,进行结算和下单。 7. **个人中心:** 管理个人信息,查看订单历史和进行相关操作。 【管理员功能】 1. **首页:** 查看体育商品推荐系统。 2. **个人中心:** 修改密码、管理个人信息。 3. **用户管理:** 审核和管理注册用户的信息。 4. **商品分类管理:** 管理体育商品的分类信息。 5. **商品信息管理:** 监管和管理体育商品的信息。 6. **交流论坛:** 管理用户间的讨论和交流,包括删除不当内容。 7. **留言板:** 管理用户的留言,进行适当的处理。 8. **系统管理:** - **轮播图管理:** 管理系统首页的轮播图,包括添加、编辑和删除。 - **关于我们:** 编辑和更新关于体育商品推荐系统的介绍。 - **公告资讯:** 发布、编辑和删除系统的通知和公告。 - **系统简介:** 提供体育商品推荐系统的简要介绍。 9. **订单管理:** - **已退款订单:** 查看和管理已退款的订单信息。 - **未支付订单:** 查看和管理未支付的订单信息。 - **已发货订单:** 查看和管理已发货但未完成的订单信息。 - **已支付订单:** 查看和管理已支付但未完成的订单信息。 - **已完成订单:** 查看和管理已完成的订单信

    ### 【物联网操作系统】LiteOS从入门到实战:开发环境搭建、内核解析及网络编程详解、LiteOS简介

    内容概要:本文详细介绍了LiteOS这一轻量级物联网操作系统,涵盖其特点、应用场景、开发环境搭建、内核机制、实战演练及进阶学习。LiteOS由华为开发,专为资源受限设备设计,具备轻量级、高效性、安全性和开放性等特点,适用于智能家居、工业自动化、智能穿戴和智能城市建设等领域。文章逐步讲解了Windows和Linux系统下搭建LiteOS开发环境的具体步骤,包括安装交叉编译器、HiSpark Studio、配置Python环境、下载并配置LiteOS SDK等。深入探讨了LiteOS内核的任务管理和内存管理机制,并通过Hello World程序展示了创建任务、编写代码、编译和烧录的完整流程。最后,介绍了SAL及socket编程,提供了丰富的学习资源,包括官方文档、技术论坛和开源代码库。 适合人群:具备一定编程基础,尤其是对物联网开发感兴趣的开发者,以及希望深入了解嵌入式操作系统原理的技术人员。 使用场景及目标:①学习如何在资源受限的设备上开发高效稳定的应用程序;②掌握LiteOS的任务管理、内存管理等核心机制;③通过实战演练和进阶学习,提高物联网设备的网络通信能力,如使用SAL及socket编程实现设备与服务器之间的TCP通信。 其他说明:本文不仅提供了理论知识,还结合具体代码示例和实际操作步骤,帮助读者更好地理解和应用LiteOS。物联网技术正处于快速发展阶段,掌握LiteOS开发技能将为开发者在智能家居、工业自动化、智能穿戴等领域提供强大的竞争力。

    Android14请求存储权限适配demo源码

    Android开发14版本请求存储权限,它有部分允许权限,有一点难度。

    在vs集成开发环境中,使用C/C++开发的游戏:球球大作战(注意要使用EasyX库)

    在vs集成开发环境中,使用C/C++开发的游戏:球球大作战(注意要使用EasyX库)

    【露天矿山边坡安全监测】基于技术规范的金属非金属露天矿山边坡安全监测系统设计与实施:变形、应力、爆破振动及水文气象监测方法和要求

    内容概要:本文档为《露天矿山边坡安全监测技术规范》,旨在规定金属非金属露天矿山采场边坡安全监测的原则、内容、方法和技术要求,涵盖变形监测、采动应力监测、爆破振动监测、降雨和地下水监测、视频监控、在线监测系统等方面。文档详细介绍了监测系统的安装、维护和监测资料的整理分析等管理要求。通过定义边坡分类、安全监测分级、监测要求和具体监测方法,确保露天矿山边坡的安全性和稳定性。 适用人群:适用于从事露天矿山边坡安全监测的设计、施工、管理和研究人员,以及矿山企业的安全管理人员。 使用场景及目标:①用于指导露天矿山边坡的安全监测工作,确保监测系统的设计、安装、调试和运行符合标准;②通过对边坡变形、应力、爆破振动、水文气象等进行监测,预防和控制边坡失稳事故的发生;③利用在线监测系统和数据分析,实现对边坡安全状况的实时监控和预警。 其他说明:本文档不适用于与煤共生、伴生的金属非金属露天矿山采场边坡。文档提供了详细的监测方法和要求,强调了监测系统的兼容性、可扩展性和数据的安全存储。此外,还特别强调了定期巡查和信息反馈机制的重要性,以确保监测系统的有效运行和及时响应异常情况。

    acacia_door_bottom.png

    acacia_door_bottom

    Android开发不用存储权限进行拍照demo源码

    Android开发不用存储权限进行拍照,得到拍照后的图片效果。有一点难度,关键是存储路径的定义。

    27910240_g.zip

    27910240_g.zip

Global site tag (gtag.js) - Google Analytics