Как решить логирование транзакций в Java? - PullRequest
10 голосов
/ 16 марта 2012

Я хочу добиться следующего:

Внутри транзакции я хочу создать несколько сообщений журнала.Эти сообщения журнала должны быть записаны только в том случае, если транзакция успешно зафиксирована.Если транзакция откатывается, сообщения журнала не должны регистрироваться.

Я не мог найти ничего, чтобы достичь этого (используя 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 в качестве получателя очереди сообщений с поддержкой транзакций проблему времени или это просто усложнит ситуацию?

ОБНОВЛЕНИЕ:

Я думаю, что ведение журналав базу данных, а затем периодически записывать записи журнала из этой базы данных в файл в периодической задаче, как предлагается в комментариях, является очень хорошим решением этой проблемы:

PRO:

  • Нормальная реализация позволяет
  • интегрируется с TransactionManager
  • Записи журнала в файлах журнала можно упорядочить по метке времени

CON:

  • Файлы журналов не обновлены (в зависимости от интервала периодических задач)
  • зависит от структуры базы данных
  • регистрация простых нетранзакционных событий становится зависимой от dbconnection
  • вероятнобольшие общие затраты на ведение журнала

Вот плюсы и минусы, которые я вижу с размещенной мной оболочкой:

Плюсы:

  • база данных и фреймработа независимая
  • простая реализация
  • файлы журнала всегда актуальны

CONs:

  • Записи журнала в файлах журналане упорядочено по метке-события-времени, а по методу-транзакции-времени (длинные транзакции приводят к очень смешанным файлам журналов.
  • rollback() и commit() должны вызываться «вручную», что может привести кошибки программирования (и возможный OutOfMemoryError, если вызов этих методов забыт) * ​​1063 *

Я думаю, что комбинация этих двух, например буферизация записей журнала в подходе "обертка", хуже, чем использование одного из двух упомянутыхподходы, из-за возможных несовместимых файлов журнала (забытых записей журнала из-за сбоя приложения).

Мое решение сейчас - сохранить мою "оболочку".Следующие причины имеют решающее значение для этого решения (в порядке важности):

  1. Я предпочитаю файлы журналов, которые всегда обновляются по сравнению с идеально упорядоченными записями журнала
  2. Длинные транзакции очень редкив моем случае
  3. Я могу сократить использование rollback() и commit() до нескольких методов.
  4. Это решение уже существует.

Кстати: я бы хотел улучшить свой английский.Поэтому, если вы заметите какие-либо ошибки в моей статье, я буду рад, если вы укажете на них.

ОБНОВЛЕНИЕ:

Проще говоря, я использую это какэто:

/*
 * 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.(В моем реальном приложении у меня есть несколько подобных регистраторов, и все эти регистраторы управляются специальным одноэлементным менеджером.):

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"/>

До сих пор это прекрасно работало.

PRO:

  • независимы от базы данных и фреймворка
  • простая реализация
  • файлы журналов всегда актуальны
  • rollback() и commit() вызываются автоматически после каждой транзакции

CON:

  • (Записи журнала в файлах журнала упорядочены не по метке времени-события, а по метке-транзакции-завершения. Я думаю, что это не является большим недостатком, поскольку действия DB действительно имеют место во время совершения транзакции и записи LogRecordsодна транзакция все еще упорядочена правильно.)

Ответы [ 5 ]

4 голосов
/ 23 мая 2012

Если вы хотите, чтобы регистратор только «фиксировал» свои сообщения журнала при фиксации транзакции, то лучшим решением было бы, чтобы ваш регистратор был XAResource и использовал транзакции XA.Это означает, что ваш XALogger получает уведомление о подготовке / подтверждении от менеджера транзакций.Таким образом, ваш XAResource становится «Менеджером ресурсов», участвующим в XA-переходе.Ваш XALogger должен быть зарегистрирован в диспетчере транзакций так же, как зарегистрированы ресурсы JMS и JDBC.Вы можете написать реализацию JCA или (проще) просто зарегистрировать свой XALogger с текущим java.transaction.Transaction

transaction.enlistResource(myXALogger)

и заставить его реализовать XAResource (JavaDoc)

1 голос
/ 20 мая 2012

почему вы не создаете аспект и не выполняете рекомендации, особенно после возврата совета, проверьте документацию весны, которая доступна с 2.0:

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() {
    // ...
    }
}
0 голосов
/ 04 апреля 2012

Итак, я хочу узнать, где комментарии оставили вас :) @MichaelBorgwardt предлагает войти в базу данных, а @cherouvim добавил, что вы должны периодически записывать записи журнала из базы данных в файл.

Также возможно начать преобразование записей журнала в файл непосредственно после того, как транзакция была зафиксирована отдельным потоком (таким образом, избегая разрыва между фиксацией и сохранением ее в файле).

Другая возможностьбыть для записи файла журнала для каждой транзакции.При откате вы удаляете его.При 'commit' вы добавляете его в уже существующий лог-файл (или вы решаете не иметь ни одного лог-файла, кроме как logdir, а при 'commit' вы перемещаете один лог-файл).

0 голосов
/ 08 мая 2012

Есть раздел о ведении журнала в Spring Reference.

Показывает, как настроить различные каркасы ведения журналов, среди них log4j

В вашем случае последняя строка конфигурации будет:

log4j.logger.org.springframework.transactions=DEBUG
0 голосов
/ 16 марта 2012

Не ясно, чего вы пытаетесь достичь с помощью rec.setMillis(System.currentTimeMillis()); Зарегистрированное событие произошло во время создания записи, а не во время фиксации. Он просто буферизуется в оперативной памяти до момента фиксации. Переопределив его таким образом, вы регистрируете время фиксации, а не время возникновения события. При чтении журнала у вас должно быть какое-то указание на фактический порядок, иначе вы не сможете интерпретировать причинные связи.

Ваше решение также страдает от проблем в ситуациях аварийного восстановления. Менеджер транзакций обработает фиксацию для транзакционных ресурсов, но изменчивые сообщения журнала будут потеряны. Это может или не может быть терпимым, в зависимости от ваших требований.

...