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

Должны ли классы журналов открывать/закрывать поток файла журнала при каждой записи в файл журнала или хранить его в течение всего жизненного цикла приложения до тех пор, пока все записи не будут завершены?

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

Ответ 1

Если у вас есть частые операции чтения/записи, более эффективно держать файл открытым для жизни с одним открытым/закрытым.

Возможно, вы захотите выполнить флеш периодически или после каждой записи, хотя в случае сбоя приложения у вас могут не быть все данные, записанные в ваш файл. Используйте fflush для систем на базе Unix и FlushFileBuffers в Windows.

Если вы работаете в Windows, вы также можете использовать API CreateFile с FILE_FLAG_NO_BUFFERING, чтобы перейти непосредственно к файлу при каждой записи.

Кроме того, лучше держать файл открытым для жизни, , потому что при каждом открытии/закрытии может произойти сбой, если файл используется. Например, у вас может быть приложение резервного копирования, которое запускает и открывает/закрывает ваш файл при его поддержке. И этот может привести к тому, что ваша программа не сможет получить доступ к вашему собственному файлу. В идеале вы хотели бы всегда держать свой файл открытым и указывать флаги обмена в Windows (FILE_SHARE_READ). В системах на основе Unix обмен будет использоваться по умолчанию.

Ответ 2

В общем, как все говорили, держите файл открытым для производительности (open - относительно медленная операция). Однако вам нужно подумать о том, что произойдет, если вы сохраните файл открытым, и люди либо удаляют файл журнала, либо обрезают его. И это зависит от флагов, используемых в открытое время. (Я обращаюсь к Unix - аналогичные соображения, вероятно, относятся к Windows, но я соглашусь на исправление тех, кто более осведомлен, чем я).

Если кто-то видит, что файл журнала растет, скажем, на 1 MiB, а затем удаляет его, приложение не будет более мудрым, а Unix будет хранить данные журнала в безопасности до тех пор, пока приложение не будет закрыто. Что еще, пользователи будут смущены, потому что они, вероятно, создали новый файл журнала с тем же именем, что и старый, и недоумевают, почему приложение "остановило ведение журнала". Конечно, это не так; это просто вход в старый файл, который никто не может получить.

Если кто-то замечает, что файл журнала вырос до, скажем, 1 MiB, а затем усекает его, приложение также не станет более мудрее. В зависимости от того, как был открыт файл журнала, вы можете получить странные результаты. Если файл не был открыт с помощью O_APPEND (POSIX-talk), программа продолжит записывать с текущим смещением в файле журнала, а первый 1 MiB файла будет отображаться как поток нулевых байтов, что является apt чтобы запутать программы, просматривающие файл.

Как избежать этих проблем?

  • Откройте файл журнала с помощью O_APPEND.
  • Периодически используйте fstat() в дескрипторе файла и проверяйте, равен ли st_nlink.

Если счетчик ссылок равен нулю, кто-то удалил ваш файл журнала. Время закрыть его и снова открыть новый. По сравнению с stat() или open(), fstat() должно быть быстрым; это в основном копирование информации непосредственно из материала, который уже находится в памяти, не требуется поиск имени. Таким образом, вы должны, вероятно, делать это каждый раз, когда собираетесь писать.

Предложения:

  • Убедитесь, что есть механизм, чтобы сообщить программе о переключении журналов.
  • Удостоверьтесь, что вы записали полные дату и время в сообщениях.

Я страдаю от приложения, которое выдает время, а не дату. Раньше сегодня у меня был файл сообщения, в котором были записи с 17 августа (одно из сообщений случайно включало дату в сообщение после времени), а затем некоторые записи с сегодняшнего дня, но я могу только сказать это, потому что я их создал. Если я просмотрю файл журнала через несколько недель, я не могу определить, в какой день они были созданы (хотя я бы знал время, когда они были созданы). Это раздражает.

Вы также можете посмотреть, какие системы, такие как Apache, у них есть механизмы для обработки файлов журналов и есть инструменты для работы с вращением журнала. Примечание: если приложение сохраняет один файл открытым, не использует режим добавления и не планирует устанавливать ограничения на журнал или размеры, тогда вы не можете многого сделать с файлами журналов, которые растут или будут иметь куски нулей в начале - другие чем перезапуск приложения периодически.

Вы должны убедиться, что все записи в журнал завершены как можно скорее. Если вы используете файловые дескрипторы, на самом деле существует только буферизация ядра; это может быть приемлемым, но рассмотрите опции O_SYNC или O_DSYNC для open(). Если вы используете ввод/вывод потока файлов, убедитесь, что за каждой записью следует fflush(). Если у вас многопоточное приложение, убедитесь, что каждый write() содержит полное сообщение; не пытайтесь писать отдельные части сообщения отдельно. При использовании ввода/вывода потока файлов вам может потребоваться использовать flockfile() и родственников для групповых операций вместе. С помощью ввода/вывода дескриптора файла вы можете использовать dprintf() для форматирования ввода-вывода в файловый дескриптор (хотя это не ясно, что dprintf() делает один вызов write()), или, возможно, writev() для записи отдельных сегментов данных в одиночная операция.

Кстати, блоки диска, которые содержат "нули", фактически не выделяются на диске. Вы действительно можете испортить стратегии резервного копирования людей, создавая файлы, которые составляют несколько GiB, но все, кроме самого последнего диска, содержат только нули. В принципе (проверка ошибок и генерация имени файла опущены для краткости):

int fd = open("/some/file", O_WRITE|O_CREATE|O_TRUNC, 0444);
lseek(fd, 1024L * 1024L * 1024L, 0);
write(fd, "hi", 2);
close(fd);

Это занимает один дисковый блок на диске - но 1 GiB (и изменение) в (несжатой) резервной копии и 1 ГБ (и изменение) при восстановлении. Антиобщественный, но возможно.

Ответ 3

Для производительности держать открытым. Для обеспечения безопасности флеш часто.

Это будет означать, что библиотека времени выполнения не будет пытаться буферизовать записи до тех пор, пока у нее не будет много данных - вы можете сработать до того, как это будет написано!

Ответ 4

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

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

Ответ 5

Как правило, лучше держать их открытыми.

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

Если вы беспокоитесь о потере данных в случае сбоя, вы должны периодически очищать/фиксировать свои буферы.

Ответ 6

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

В Windows вы не сможете перемещать/переименовывать/удалять файл во время его открытия, поэтому open/write/close может оказаться полезным для долговременного процесса, когда вы можете иногда архивировать старое содержимое журнала без прерывания записи.

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

Ответ 7

Открыть и закрыть. Может спасти вас от поврежденного файла в случае сбоя системы.

Ответ 8

Я не вижу причин закрывать его.

С другой стороны, закрытие и повторное открытие занимает немного дополнительного времени.

Ответ 9

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

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

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

Ответ 10

Преимущество закрытия файла каждый раз заключается в том, что ОС гарантирует, что новое сообщение будет записано на диск. Если вы оставите файл открытым и ваша программа выйдет из строя, возможно, все это не будет написано. Вы также можете выполнить одно и то же, выполнив fflush() или что-то еще эквивалентное на используемом вами языке.

Ответ 11

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

Ответ 12

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

Ответ 13

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