java - 如何解决 Java 中的事务日志?

标签 java spring logging transactions

我想实现以下目标:

在事务内部,我想生成多个日志消息。仅当事务成功提交时才应写入这些日志消息。如果事务回滚,则不得记录日志消息。

我找不到任何东西来实现这一点(使用 spring、hibernate、atomikos),所以我写了这个小包装器(我遗漏了一些方便的方法):

public class TransactionLogger {
    private Logger logger;
    private Map<Long, LinkedList<LogRecord>> threadBuffers =
        new HashMap<Long, LinkedList<LogRecord>>();

    public TransactionLogger(Logger logger) {
        this.logger = logger;
    }

    private void addRecord(LogRecord rec) {
        LinkedList<LogRecord> list =
            threadBuffers.get(Thread.currentThread().getId());
        if (list == null) {
            list = new LinkedList<LogRecord>();
            threadBuffers.put(Thread.currentThread().getId(), list);
        }
        list.add(rec);
    }

    private LinkedList<LogRecord> getRecords() {
        if (threadBuffers.containsKey(Thread.currentThread().getId())) {
            return threadBuffers.remove(Thread.currentThread().getId());
        } else {
            return new LinkedList<LogRecord>();
        }
    }

    public void commit() {
        for (LogRecord rec : getRecords()) {
            rec.setLoggerName(logger.getName());
            logger.log(rec);
        }
    }

    public void rollback() {
        getRecords();
    }

    /**
     * If the resulting log entry should contain the sourceMethodName
     * you should use logM(Level,String,String) instead,
     * otherwise TransactionLogger.commit() will get
     * inferred as sourceMethodName.
     */
    public void log(Level l, String sourceClassName, String msg) {
        LogRecord rec = new LogRecord(l, msg);
        rec.setSourceClassName(sourceClassName);
        addRecord(rec);
    }

    /**
     * Same as log(Level,String,String), but the sourceMethodName gets set.
     */
    public void logM(Level l, String sourceClassName, String msg) {
        StackTraceElement[] trace = Thread.currentThread().getStackTrace();
        LogRecord rec = new LogRecord(l, msg);
        rec.setSourceClassName(sourceClassName);
        if (trace != null && trace.length > 1) {
            rec.setSourceMethodName(trace[2].getMethodName());
        }
        addRecord(rec);
    }
}

您如何看待这种方法?它是否有任何重大或次要的缺陷或问题?
或者更好的是,是否有任何现成的解决方案?

更新:

因为我也在使用 JTA,所以我有了一个新想法。将 TransactionLogger 实现为事务感知消息队列的接收器会解决时间问题还是只会使事情变得更复杂?

更新:

我认为记录到数据库,然后按照评论中的建议,定期将该数据库中的日志条目写入文件中的定期任务,这是解决此问题的一个很好的方法:

优点:
  • 正常执行负担
  • 与 TransactionManager 集成
  • 日志文件中的日志条目可以按时间戳排序

  • 缺点:
  • 日志文件不是最新的(取决于周期任务间隔)
  • 依赖于数据库结构
  • 简单的非事务性事件的日志记录变得依赖于 dbconnection
  • 可能更大的整体日志开销

  • 以下是我发布的包装器的优缺点:

    优点:
  • 独立于数据库和框架
  • 简单实现
  • 日志文件始终是最新的

  • 缺点:
  • 日志文件中的日志条目不是按事件时间戳排序的,而是按“事务完成”-时间戳排序的(长事务会导致日志文件非常困惑。
  • rollback()commit()必须“手动”调用,这会导致编程错误(如果忘记调用这些方法,可能会出现 OutOfMemoryError)

  • 我认为这两者的组合,比如在“包装器”方法中缓冲日志记录比使用提到的两种方法之一更糟糕,因为可能存在不一致的日志文件(由于应用程序崩溃而忘记了日志条目)。

    我现在的决定是保留我的“包装器”。以下原因对这一决定至关重要(按重要性排序):
  • 与完美有序的日志条目相比,我更喜欢始终保持最新状态的日志文件
  • 在我的情况下,长交易非常罕见
  • 我能够减少 rollback() 的使用和 commit()只有几个方法。
  • 该解决方案现在已经存在。

  • 顺便说一句:我想提高我的英语。所以如果你发现我的文章中有任何错误,如果你指出它们,我会很高兴。

    更新:

    简单地说,我是这样使用它的:
    /*
     * This objects contains one or more TransactionLogger(s) and exposes rollback()
     * and commit() through rollbackLogs() and commitLogs().
     */
    @Autowired
    private ITransactionalServer server;
    
    public void someMethod(String someParam) {
        boolean rollback = false;
        try {
            /*
             * This method is for example annotated with @Transactional.
             */
            return server.someTransactionalMethod(someParam);
        } catch (Exception ex) {
            logError(ex);
            rollback = true;
        } finally {
            if (rollback) {
                server.rollbackLogs();
            } else {
                server.commitLogs();
            }
        }
    }
    

    这仍然不完美,但现在对我来说似乎是一个“足够好的解决方案”。下一步将使用方面来装饰我的事务方法。

    更新:

    我在我的问题中添加了这一点,因为我对接受自己的答案感到难过,尽管其他人让我上路了。

    我现在基本上使用 AOP 方法和以下 Logger。 (在我的实际应用程序中,我有不止一个这样的 Logger,所有这些 Logger 都由一个自定义的单例管理器管理。):
    public class AopLogger extends Logger {
    
        public static AopLogger getLogger(String name) {
            LogManager manager = LogManager.getLogManager();
            Object l = manager.getLogger(name);
            if (l == null) {
                manager.addLogger(new AopLogger(name));
            }
            l = manager.getLogger(name);
            return (AopLogger)l;
        }
    
        private Map<Long, LinkedList<LogRecord>> threadBuffers = new HashMap<Long, LinkedList<LogRecord>>();
    
        public AopLogger(String name) {
            super(name, null);
        }
    
        public void beginTransaction() {
            LinkedList<LogRecord> list = threadBuffers.get(Thread.currentThread().getId());
            if (list == null) {
                list = new LinkedList<LogRecord>();
                threadBuffers.put(Thread.currentThread().getId(), list);
            }
        }
    
        private void addRecord(LogRecord rec) {
            LinkedList<LogRecord> list = threadBuffers.get(Thread.currentThread().getId());
            if (list != null) {
                list.add(rec);
            } else {
                super.log(record);
            }
        }
    
        private LinkedList<LogRecord> getRecords() {
            if (threadBuffers.containsKey(Thread.currentThread().getId())) {
                return threadBuffers.remove(Thread.currentThread().getId());
            } else {
                return new LinkedList<LogRecord>();
            }
        }
    
        public void commit() {
            for (LogRecord rec : getRecords()) {
                rec.setMillis(System.currentTimeMillis());
                super.log(rec);
            }
        }
    
        public void rollback() {
            getRecords();
        }
    
        public void log(LogRecord record) {
            addRecord(record);
        }
    }
    

    和这个方面:
    import org.aspectj.lang.annotation.AfterReturning;
    import org.aspectj.lang.annotation.AfterThrowing;
    import org.aspectj.lang.annotation.Aspect;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.core.Ordered;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Service;
    
    @Service
    @Aspect
    @Order(10)
    public class AopLogManager implements Ordered {
    
        @Autowired
        private AopLogger logger;
        private Logger errorLogger = Logger.getLogger("ExceptionLogger");
    
        @Around("@annotation(org.springframework.transaction.annotation.Transactional)")
        public Object logTransaction(ProceedingJoinPoint pjp) throws Throwable {
            logger.beginTransaction();
            Exception ex = null;
            try {
                return pjp.proceed();
            } catch (Exception e) {
                ex = e;
                throw e;
            } finally {
                if (ex != null) {
                    logger.rollback();
                    errorLogger.severe(ex.getMessage());
                } else {
                    logger.commit();
                }
            }
        }
    
        private int mOrder;
    
        @Override
        public int getOrder() {
            return mOrder;
        }
    
        public void setOrder(int order) {
            mOrder = order;
        }
    }
    

    在我的 applicationContext.xml 我有以下几行:
    <aop:aspectj-autoproxy />
    <tx:annotation-driven transaction-manager="springTransactionManager" order="5"/>
    

    到目前为止,这工作正常。

    优点:
  • 独立于数据库和框架
  • 简单实现
  • 日志文件始终是最新的
  • rollback()commit()在每次交易后自动调用

  • 缺点:
  • (日志文件中的日志条目不是按事件时间戳排序,而是按“事务完成”-时间戳排序。我认为这不是一个大缺点,因为 DB 操作确实发生在事务提交和 LogRecords 的时间一笔交易的顺序仍然正确。)
  • 最佳答案

    为什么不创建一个方面并实现一个建议,尤其是在返回建议后,请检查自 2.0 以来可用的 spring 文档:

    Types of advice:
    
    Before advice: Advice that executes before a join point, but which does not have the ability to prevent execution flow proceeding to the join point (unless it throws an exception).
    
    After returning advice: Advice to be executed after a join point completes normally: for example, if a method returns without throwing an exception.
    
    After throwing advice: Advice to be executed if a method exits by throwing an exception.
    
    After (finally) advice: Advice to be executed regardless of the means by which a join point exits (normal or exceptional return).
    
    Around advice: Advice that surrounds a join point such as a method invocation. This is the most powerful kind of advice. Around advice can perform custom behavior before and after the method invocation. It is also responsible for choosing whether to proceed to the join point or to shortcut the advised method execution by returning its own return value or throwing an exception.
    

    如果你只需要记录一切是否正常,那么创建建议并让你的代码保持干净:
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.AfterReturning;
    
    @Aspect
    public class AfterReturningExample {
    
        @AfterReturning("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
        public void doAccessCheck() {
        // ...
        }
    }
    

    关于java - 如何解决 Java 中的事务日志?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9734165/

    相关文章:

    node.js - Winston 的多个日志文件?

    java - 如何在 Hibernate、Spring、JSP 中从数据库获取多行?

    java - 单击按钮红点应该闪烁

    java - apache tomcat 服务器上的数据库 postgresql

    java - SpringJUnit4ClassRunner : run test method multiple times with different input data

    ruby-on-rails - Rails 中随处可见的方法

    java - 重新部署应用程序时创建的新日志和锁定文件

    java - 在 android 中启动 Activity 时获取 NullPointerException

    java - 最短路径的 Floyd Warshall 算法

    java - 如何订购AOP切面和MVC拦截器?