Ведение журналов в классе StringBuffer для нескольких экземпляров приложения.
Я боролся с этой проблемой в течение последних нескольких дней.
Требование В нашем приложении мы загружаем файл через пользовательский интерфейс, разработанный с помощью nodejs, а затем записи файлов обрабатываются с помощью простого рабочего процесса amazon (SWF). Вызов Amazon Amazon SWF происходит через приложение Spring, которое приложение nodejs будет вызывать при обработке файла. Требование заключается в том, что для каждого обрабатываемого файла приложению необходимо создать файл журнала, в котором подробно описывается, что происходило с записями по мере их обработки.
Как я реализовал? В приложении Spring, которое запускает SWF, я создал класс FileLogger, который будет поддерживать статическую переменную StringBuffer. Этот класс fileLogger установлен в область действия рабочего процесса, то есть класс будет создан для каждого выполнения рабочего процесса и уничтожен в конце его. Когда файл обрабатывается, я продолжаю добавлять журналы в StringBuffer в классе FileLogger и в конце обработки записывать в файл и сохранять его.
Описание проблемы Это решение, казалось, работало нормально, пока у нас был запущен только один экземпляр приложения. Как только мы развернули приложение в нескольких экземплярах amazon ec-2, кажется, что неполные журналы сохраняются в файле. Дальнейшее изучение этого вопроса показало, что каждый экземпляр приложения имеет свой собственный stringBuffer для ведения журнала, и когда мы пишем в приложение, только читает одно из содержимого буферов строк и, следовательно, неполные журналы. Излишне говорить, что шаблон журнала является случайным. Я заметил, что у нас будет N экземпляров StringBuffer, если мы развернем N экземпляров приложения.
Вот класс FileLogger
private static final Logger logger = LoggerFactory.getLogger(FileLogger.class);
//private static final Logger logger = LoggerFactory.getLogger(FileLogger.class);
private static final SimpleDateFormat logFileDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
private static final SimpleDateFormat fileNameDateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
private static final String COLON = ":";
private static StringBuffer logAppender = null;
public synchronized void debug(Date date, String logMessage){
if(appConfig.getLogLevel().equalsIgnoreCase(LogLevel.DEBUG.name())){
this.log(LogLevel.DEBUG.name(), date, logMessage);
}
}
public synchronized void info(Date date, String logMessage){
if(appConfig.getLogLevel().equalsIgnoreCase(LogLevel.INFO.name()) ||
appConfig.getLogLevel().equalsIgnoreCase(LogLevel.DEBUG.name())){
this.log(LogLevel.INFO.name(), date, logMessage);
}
}
public synchronized void error(Date date, String logMessage){
if(appConfig.getLogLevel().equalsIgnoreCase(LogLevel.ERROR.name()) ||
appConfig.getLogLevel().equalsIgnoreCase(LogLevel.DEBUG.name())){
this.log(LogLevel.ERROR.name(), date, logMessage);
}
}
private synchronized void log(String logLevel, Date date, String logMessage){
logger.info("logAppender Hashcode: "+logAppender.hashCode());
if(!logLevel.equalsIgnoreCase(LogLevel.NONE.name())){
//StringBuffer logAppender = getLogAppender();
getLogAppender().append(getLogAppender().hashCode());
getLogAppender().append(COLON);
getLogAppender().append(getFormattedDate(date, logFileDateFormat));
getLogAppender().append(COLON);
getLogAppender().append(logLevel);
getLogAppender().append(COLON);
getLogAppender().append(logMessage);
getLogAppender().append(System.getProperty("line.separator"));
}
}
private synchronized StringBuffer getLogAppender(){
logger.info("Getting logAppender .."+logAppender);
if(logAppender == null){
logger.info("Log appender is null");
logAppender = new StringBuffer();
}
return logAppender;
}
Вопрос Как убедиться, что в нескольких экземплярах моего приложения есть только один экземпляр StringBuffer (logAppender), к которому я могу продолжать добавлять журнал, а затем читать в конце и записывать содержимое в файл перед его сохранением?
2 ответа
Я просто хотел вернуться и упомянуть, что в качестве решения я, наконец, выбрал elastiCache Amazon (реализацию redis) для временного хранения моих журналов, а затем в конце операции, чтения всего из кэша и записи в файл в amazon s3. Надеюсь, это поможет.
Прежде всего, вы используете статическое поле в нестатическом методе: это плохая практика, либо используйте статические методы, либо используйте синглтон с нестатическим полем.
Во-вторых, если вас беспокоит производительность, лучше удалить все синхронизированные, когда они не нужны. Это нужно только в методе log(). Error(), warn() и info() не обращаются к общим данным способом, который требует синхронизации (только чтение). Кроме того, зачем вызывать getLogAppender() несколько раз в log()?
Тестирование уровня журнала с помощью equalsIgnoreCase() также неэффективно, хотя это не будет заметно. И у вас есть ошибка в error(): он не регистрирует, если уровень - INFO.
Теперь, к вашему мнению: как уже было сказано другими, JVM не делят пользовательские данные между экземплярами. И JVM, вероятно, не являются событиями в одном экземпляре ОС. Легкого выхода нет. Ведение журнала в БД может быть не таким уж плохим для производительности, если вы настроите ее правильно: вам вообще не нужна изоляция транзакций, поскольку вы не обновляете существующие данные. Вы также можете попробовать использовать сервер журналов: Unix-подобные ОС предлагают множество решений. Или когда вы очищаете свой буфер, очищайте все экземпляры вашего сервера.