`
sty2008boy
  • 浏览: 299408 次
  • 性别: Icon_minigender_1
  • 来自: 太原
社区版块
存档分类
最新评论

总结关于操作日志记录的实现

阅读更多
目 录
在数据库中建立操作记录(方式一)
建立操作记录(方法二)
使用LOG4J,通过配置LOG4J来获取业务日志(Apache Log4j)
用触发器生成SQL Server2000数据表的操作日志
基于拦截器的操作日志保存方式
Struts2拦截器(自定义拦截器)
Spring结合注解和反射利用拦截器实现对日志记录
Hibernate3拦截器

在数据库中建立操作记录(方式一)



这种方式主要是在每个方法中设置静态常量String 相当于对方法的注释,并建立数据库日志表,用户登陆ERP系统并进行操作时,读取用户登录信息Session,同方法中的静态常量,和时间一并写入数据库的日志表中,由此来达到建立ERP系统操作日志的目的

缺点:此方法较为比较麻烦需要在每个方法中定义静态常量,并且影响ERP系统的工作效率,当一个操作执行多次时ERP多次读取用户信息Sessions,为TOMCAT增加较高的负荷,但思路简单,代码量大,实现方便。也可以考虑将信息写入到XML或者TXT文档中去。

建立操作记录(方法二)

在SSH环境下,将数据库操作事务交给Spring管理
1、尽量使用注解声明事务;

2、写一个类扫描使用了事务的方法。根据楼主提出的技术需求分析,只有写入动作才需要记录,同样数据库只有写入才需要事务,读取不需要,所以在不需要事务的方法上面标注@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true),这样可以精确过滤出需要记录日志的方法;

3、利用AOP编程实现日志记录功能。
时间:AOP切入点处取系统时间
操作员和IP:控制层在session作用域里取得用户对象和request取IP地址传给切入点

操作:可以在操作数据库的DAO组件上(方法)用自定义注解标上,例如:@Operation=INSERT|DELETE|UPDATE...只要读取到这个配置就知道操作类型。当然也可以利用Hibernate来得知,得要看Hibernate的源代码。

结果:事务成功即成功,事务回滚即失败。

业务数据ID:这个有两种解决方法,一种笨拙的办法是所有的数据模型层的实体对象都抽取ID到父类;二是实体映射的配置方法采用注解方式,这样可以写一个类扫描出实体的ID字段和类型,自然能记录之。

粒度问题:首先只要应用到事务的地方都可以记录之,批量操作数据行实际是循环调用DAO组件,也就实现了批量记录。当然,如果送批量SQL语句到数据库,由DBMS来做那就无奈了。

这种方案设计可以一次编写,终身使用,不受项目的模块增减影响。代码量小,维护容易。

使用LOG4J,通过配置LOG4J来获取业务日志(Apache Log4j)

用触发器生成SQL Server2000数据表的操作日志
有时,我们想知道登录到数据库的用户做了什么,于是,记录用户执行的SQL语句就非常有必要,这将是重要的参考依据。我们先建一张日志表(DBLoger)用于保存用户执行的SQL语句:


程序代码
Create TABLE DBLoger(
    LoginName nvarchar(50),
    HostName nvarchar(50),
    EventInfo nvarchar(500),
    Parameters int,
    EventType nvarchar(100)
)

接着再建一个触发器,在用户对表进行增/删/改时触发,将执行的SQL语句记录到日志表中:


程序代码
Create TRIGGER Loger ON student
FOR Insert, Update, Delete
AS
    SET NOCOUNT ON
  
    Create TABLE #T(EventType nvarchar(100),Parameters int,EventInfo nvarchar(500))
    Insert #T exec('dbcc inputbuffer(' + @@spid + ')')
    --记录到日志表
    Insert INTO DBLoger(LoginName,HostName,EventInfo,Parameters,EventType) Select suser_sname(),host_name(),EventInfo,Parameters,EventType FROM #T

缺点:由于dbcc inputbuffer的EventInfo最多只能保存255个字符,所以一旦执行的SQL过长,日志表中将无法看到完整的SQL语句!

基于拦截器的操作日志保存方式
Struts2拦截器(自定义拦截器)
Struts2的内置拦截器只能完成一些通用功能,若要使用拦截器捕获业务信息,则需要自定义拦截器进行操作
例:Log日志的数据库保存。即使用Struts2拦截器的intercept方法,在方法里直接把操作记录保存到数据库中,而这时计算出的查询时间则是整个查询过程的时间,即读取用户输入后,进行判断,并进行查询的时间,这种计算方法比之以前更加合理,因为查询本身就包含判断,如果仅仅只是查询数据库那一个动作,在复杂的查询里并不能体现出整个查询所花的时间,而用拦截器,则相对轻松的解决了此问题。在随后显示的时间里,查询耗时确定比以前有了很大的延长。
如果达到与具体action无关,只与用户有关的话,那么这个拦截器很容易实扩展到在项目中的任何地方保存用户操作记录。

Spring结合注解和反射利用拦截器实现对日志记录
现今的框架都很灵活,对于日志的记录也有很多种方式,关键看很么样的方式更高效,更能体现系统的灵活和松散耦合。之前日志记录采用log4j的JDBCAppender,虽然配置一样很灵活,但是相对后来基于spring aop上的实现觉得还是比较复杂。比如我的表名是动态的,字段内容或则字段名字是动态的,这些配置对于log4j来说多了很多难度,而且还要管理缓存中的属性值。再则log4j没有实现jdbc的pool管理,虽然提供了相应接口。最终决定才用spring拦截器,又高效又灵活。对于数据库的操作同样还是使用自己的底层。
源码和配置记录:

package com.cmcc.common.controller.interceptor;
import java.lang.reflect.Method;
import java.util.Date;
import org.apache.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import com.cmcc.common.Global;
import com.cmcc.common.cache.PoolConfigInfoMap;
import com.cmcc.common.controller.interceptor.annotations.GenericLogger;
import com.cmcc.common.util.UserSessionObj;
import com.cmcc.framework.business.interfaces.corporation.ICompanyInfoManager;
import com.cmcc.framework.business.interfaces.log.IOperLogManager;
import com.cmcc.framework.model.log.OperateLog;
/**
*
* 记录系统日志的拦截器
*
* @author <a href="mailto:sun128837@126.com">conntsing</a>
*
* @version $Revision: 1.5 $
*
* @since 2009-3-23
*/
public class GenericLoggerInterceptor {
    /**
     * Logger for this class
     */
    private static final Logger logger = Logger
            .getLogger(GenericLoggerInterceptor.class);
    @SuppressWarnings("unchecked")
    public Object invoke(ProceedingJoinPoint joinPoint) throws Throwable {
        if (logger.isDebugEnabled()) {
            logger.debug("invoke(ProceedingJoinPoint) - start"); //$NON-NLS-1$
        }
        try {
            Object result = joinPoint.proceed();
            Class cl = joinPoint.getTarget().getClass();
            Method[] methods = cl.getMethods();
            GenericLogger genericLogger = null;
            UserSessionObj userSessionObj = null;
            String departmentNames = "";
            String employeeNames = "";
            for (Method m : methods) {
                if (m.getName().equals(joinPoint.getSignature().getName())) {
                    genericLogger = m.getAnnotation(GenericLogger.class);
                }
                if (m.getName().equalsIgnoreCase("getusersessioninfo")) {
                    userSessionObj = (UserSessionObj) m.invoke(joinPoint
                            .getTarget());
                }
            }
            OperateLog opLog = new OperateLog();
            if (null != genericLogger) {
                if (genericLogger.isOperateDepartment()) {
                    for (Method m : methods) {
                        if (m.getName().equalsIgnoreCase("getdepartmentnames")) {
                            departmentNames = (String) m.invoke(joinPoint
                                    .getTarget());
                            break;
                        }
                    }
                }
                if (genericLogger.isOperateEmployee()) {
                    for (Method m : methods) {
                        if (m.getName().equalsIgnoreCase("getemployeenames")) {
                            employeeNames = (String) m.invoke(joinPoint
                                    .getTarget());
                            break;
                        }
                    }
                }
                ICompanyInfoManager companyservice = (ICompanyInfoManager) Global._ctx
                        .getBean("companyservice");
                opLog.setEid(userSessionObj.getEid());
                opLog.setAdminId(userSessionObj.getId());
                opLog.setAdminName(userSessionObj.getLoginId());
                opLog.setC0(userSessionObj.getCode().toString());
                opLog.setOperateDesc(genericLogger.operateDescription());
                opLog.setOperateTime(new Date(System.currentTimeMillis()));
                opLog.setShortName(companyservice.get(
                        userSessionObj.getEid(),
                        PoolConfigInfoMap.get(userSessionObj.getEid())
                                .getPhysical_pool_id()).getShortName());
                if (null != employeeNames && null != departmentNames) {
                    if (employeeNames.equals("") && departmentNames.equals("")) {
                        opLog.setDeptName(opLog.getShortName());
                    }
                    else {
                        opLog.setEmployeeName(employeeNames);
                        opLog.setDeptName(departmentNames);
                    }
                }
                else {
                    opLog.setDeptName(opLog.getShortName());
                }
                IOperLogManager logmanage = (IOperLogManager) Global._ctx
                        .getBean("operLogManager");
                logmanage.saveOperateLog(opLog);
            }
            if (logger.isInfoEnabled()) {
                logger.info("OperateLog------adminid:    " + opLog.getAdminId());
                logger.info("OperateLog--operatedesc:    " + opLog.getOperateDesc());
                logger.info("OperateLog--operatetime:    " + opLog.getOperateTime());
                logger.info("OperateLog----shortName:    " + opLog.getShortName());
            }
            return result;
        }
        catch (Exception e) {
            e.printStackTrace();
            logger.warn("invoke(ProceedingJoinPoint) - exception ignored", e); //$NON-NLS-1$
        }
        finally {
        }
        if (logger.isDebugEnabled()) {
            logger.debug("invoke(ProceedingJoinPoint) - end"); //$NON-NLS-1$
        }
        return null;
    }
}
<aop:config  proxy-target-class="true">
      <aop:pointcut id="loggerService" expression="execution(* com.cmcc.framework.controller.action..*.*(..)) and
                    @annotation(com.cmcc.common.controller.interceptor.annotations.GenericLogger)"/>
      <aop:aspect ref="genericLoggerInterceptor">
          <aop:around pointcut-ref="loggerService" method="invoke"/>
      </aop:aspect>
    </aop:config>
    <bean id="genericLoggerInterceptor" class="com.cmcc.common.controller.interceptor.GenericLoggerInterceptor"/>
像我这里会有个分表分pool区的配置处理,如果是之前我还得交给log4j去动态的传入表名参数,而且还得重新实现appender。现在我只需要拦截器拦截我匹配表达式的方法,并使用注解传入相应描述,再依赖注入反射获取相应的描述字段值,一切都很灵活配置也很简单。
实际上,我拦截的方法只在controller层,所以同样可以通过实现struts2拦截器(同样支持注解)来拦截方法记录日志,不过使用spring拦截器更灵活易于扩展。

  
Hibernate3拦截器
    技术选型:
最土的,在所有的Dao方法中显示的编写日志记录代码
该项目以前是用.net这么干的,这种做法重复工作量太大,维护性差,并且也没实现字段级变更的记录,根本不予考虑。
数据库触发器 - 与数据库耦合
与数据库耦合,违背了使用hibernate的初衷,也不予考虑
原生的Hibernate Interceptor
优点:可以在hibernate对象操作的时候获取最为详细的运行期信息,字段名,原始值,修改后值等等。
缺点:在JPA  API的封装下很难获取到hibernate的session,不能进行持久化操作。
JPA callback / event-listener
优点:JPA规范,最为优雅简单
缺点:功能太弱,不能满足需求
很自然地,干这种事AOP似乎比较合适
优点:灵活,在spring容器中,可以访问所有spring bean
缺点:不能获取详细的运行期信息(字段名,原始值,等等),无法感知hibernate的事务执行,即使dao事务rollback,仍然会插入一条操作历史记录,破坏了“操作”和“历史”的一致性。
采用Hibernate 3的新特性 Event-listener
可以解决以上所有问题
能够取得运行期详细信息,除了能记录粗粒度的实体的保存删除操作外,还能精确追踪对实体字段修改、实体关联/级联关系的变更,能记录更新前的值、更新后的值,可以生成详细日志。
灵活解耦,跨数据库,不影响原有代码。
    Hibernate3 新特性事件处理框架是hibernate 2拦截器的一个补充或者替代,由拦截器被动拦截操作事件变成事件源的主动驱动,这是一个进步。Hibernate 事件框架官方文档.
    Hibernate3中定义了很多的事件,涵盖了持久化过程中不同的生命周期。简单说Session的一个方法(load, flush...)分别对应一个事件,当该方法被调用时,就会触发一个相应的事件,这个事件会被我们预先定义的事件监听器收到,再进行相应的处理。这种方式来做审计日志是再适合不过。
   但也有个缺点就是这样的Event-listener是脱离主容器(比如Spring IoC环境)单独实例化的,无法访问主容器的资源(比如要取得当前登录的用户信息就会比较麻烦)。这个暂时还没解决。

在这里我们选取PostInsertEventListener(插入后事件),PostUpdateEventListener(更新后事件),PostDeleteEventListener(删除后事件)接口作为CRUD方法的监听接口。hibernate3中事件是分为pre和post,表示该发生事件前、后。这里我们全部用Post,因为PostEvent只有在数据实际改变后才会触发,假如CRUD事务因为异常回滚,则不会触发事件。

首先定义一个mark接口Historiazable,实现该接口的entity类表明是需要做审计日志的。
然后编写我们自定义的EventListener类,实现上述的事件接口。
在事件接口实现方法里在根据不同的事件编写审计日志的代码。
Java代码
public class HistoryListener implements PostInsertEventListener,  
        PostUpdateEventListener, PostDeleteEventListener {  
    
    @Override 
    public void onPostInsert(PostInsertEvent event) {  
        if (event.getEntity() instanceof Historizable) {  
        //  保存 插入日志  
        }  
    }  
 
    @Override 
    public void onPostUpdate(PostUpdateEvent event) {  
        if (event.getEntity() instanceof Historizable) {  
        // 保存 修改日志  
        }  
    }  
 
    @Override 
    public void onPostDelete(PostDeleteEvent event) {  
        if (event.getEntity() instanceof Historizable) {  
        // 保存 删除日志  
        }  
    }  




配置EventListener
编辑hibernate.cfg.xml,配置监听器
Xml代码
<session-factory> 
    <listener type="post-insert" class="net.jeffrey.hibernate.history.HistoryListener"/> 
    <listener type="post-update" class="net.jeffrey.hibernate.history.HistoryListener"/> 
    <listener type="post-delete" class="net.jeffrey.hibernate.history.HistoryListener"/> 
</session-factory> 
分享到:
评论

相关推荐

    PHP实现工厂模式设计日志记录器

    总结来说,这个PHP实现的工厂模式设计日志记录器示例展示了如何利用设计模式提高代码的可维护性和可扩展性。通过工厂模式,我们可以轻松地切换日志的存储方式,只需要更改配置文件,而无需改动其他代码。这在实际...

    基于切面的日志记录SSMdemo

    总结来说,“基于切面的日志记录SSMdemo”是一个很好的学习案例,它展示了如何利用Spring的AOP特性在SSM框架中实现优雅的日志管理。通过这种方式,开发者可以专注于业务逻辑,而日志记录这一跨切面的需求则由AOP自动...

    SpringMVC 写操作日志

    总结来说,SpringMVC结合AOP可以有效地实现操作日志的记录。通过定义切面和切点,我们可以轻松地插入日志记录功能,提升代码的可维护性和可扩展性。同时,这种做法符合面向切面编程的思想,让关注点分离,提高了代码...

    日志记录器

    总结而言,ZHN.LogLib是一款轻量级的日志记录库,它以易用性为核心,提供了基本而实用的功能,特别适合那些追求效率和简洁性的开发者。无论你是初学者还是经验丰富的专业人士,ZHN.LogLib都能帮你轻松应对日志管理的...

    VB用记事本实现操作日志

    总结,VB用记事本实现操作日志是一个简单而实用的方法,尤其适用于小型项目或学习实践。通过掌握基本的文件操作和事件处理,你可以构建一个基本的日志系统,为进一步的软件开发打下基础。同时,随着技能的提升,可以...

    windows日志记录类

    总结来说,Windows日志记录是系统监控和调试的关键工具,开发者可以通过Windows API或者第三方库来实现定制化的日志功能。在VS2005中,可以利用提供的资源创建一个简单的日志记录类,从而更好地理解和实践这一技术。

    C# 实现键盘j记录与日志实例.rar

    总结来说,"C#实现键盘记录与日志实例"是一个关于如何在C#中创建后台服务程序,以监听并记录用户键盘输入的示例。它涵盖了键盘事件处理、Windows服务创建和日志文件操作等多方面的C#编程技术。通过学习和实践这个...

    linux下的异步日志记录类

    总结起来,Linux下的异步日志记录类`debugger`和`debug_msg`涉及到多线程编程、日志级别管理、日志消息封装、同步机制、性能优化等多个方面,是实现高效、稳定、易用的日志系统的基石。理解和掌握这些知识点对于开发...

    python的基于django的记录日志系统

    在本项目中,我们探索了如何使用Python的Django框架构建一个简单的日志记录系统。这个系统涵盖了用户登录、注册功能,并且在成功操作后能够跳转至博客界面查看内容。此外,它还利用了Django内置的数据库来实现日志的...

    真正通用的操作日志系统设计.zip

    总结来说,设计一个真正通用的操作日志系统,需要综合考虑日志的记录、存储、查询、分析、安全和维护等多个方面,通过合理的技术选型和设计策略,实现高效、可扩展、安全且易用的日志管理解决方案。这样的系统不仅...

    日志记录组件

    2. **DT.cpp**:这是一个C++源代码文件,很可能包含了具体的日志记录实现。cpp文件通常包含函数定义、类实现等,可能定义了如何记录、存储和格式化日志的方法。 3. **LogDealU.dll**:这是一个动态链接库文件,可能...

    上海繁易HMI软件操作记录功能使用教程.zip

    总结来说,上海繁易HMI软件的操作记录功能是一个强大的工具,它能帮助用户监控系统的运行情况,提供故障诊断依据,同时也能为系统优化和性能提升提供有价值的数据支持。通过深入学习和实践,您可以充分利用这一功能...

    SQL操作日志系统(asp)

    总结来说,通过ASP构建的SQL操作日志系统能够帮助我们监控数据库活动,提高数据库的安全性和可维护性。在实际应用中,可以根据具体需求进行功能扩展,例如添加日志查询、日志级别设置等功能,以更好地服务于业务场景...

    真正通用的操作日志系统设计

    通过上述设计,我们实现了一个高度灵活且易于集成的通用操作日志系统。该系统不仅减少了开发者的重复工作,还提高了系统的可维护性和可扩展性。未来还可以考虑引入更高级的功能,例如基于规则的日志过滤机制、更强大...

    SpringBoot使用AOP注解记录操作日志

    总结起来,Spring Boot结合AOP和事件监听机制,可以有效地实现操作日志的记录,同时保持代码的清晰和模块化。这不仅有助于调试和问题定位,也能提高系统的可维护性。通过扩展和定制这些组件,我们可以根据项目需求...

    Spring boot学习(六)Spring boot实现AOP记录操作日志.pdf

    在本文中,我们将深入探讨如何在Spring Boot项目中利用AOP(面向切面编程)来实现操作日志记录。AOP是一种编程范式,它允许程序员定义“切面”,这些切面可以在程序运行时被自动应用到多个点上,比如方法调用之前、...

    操作日志队列

    总结起来,"操作日志队列"是一个关键的日志管理技术,通过队列数据结构和相应的工具、框架,可以实现高效、可靠的日志记录和分析,为系统的稳定性和可维护性提供有力保障。博客中的内容可能涉及了具体实现、源码解析...

    一个记录程序日志的c#例子,可以按天分文件记录

    总结起来,这个C#日志记录器示例展示了如何有效地管理和组织日志信息,以提高软件的可维护性和调试效率。通过日期和文件大小的控制,它能够适应不同规模的项目需求,同时保持了日志数据的有序性和易读性。对于开发者...

    JSPSmart系统-权限管理与日志记录模块的设计与开发(源代码+论文).zip

    总结,JSPSmart系统在权限管理和日志记录方面的设计与实现,体现了现代Web应用的规范性和安全性。通过深入研究源代码和论文,开发者不仅可以学习到实用的开发技巧,还能掌握如何构建安全、高效的企业级应用。

    Laravel框架实现利用中间件进行操作日志记录功能

    特别是在使用Laravel框架时,通过中间件实现操作日志记录是一种高效且优雅的做法。Laravel框架提供了中间件的概念,这使得开发者可以在请求到达路由和控制器之前或之后,插入自定义的逻辑代码,从而实现对应用的高级...

Global site tag (gtag.js) - Google Analytics