Должны ли потоки файлов журнала открываться / закрываться при каждой записи или оставаться открытыми в течение срока службы приложения для настольного компьютера? - PullRequest
34 голосов
/ 02 октября 2008

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

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

Ответы [ 13 ]

20 голосов
/ 02 октября 2008

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

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

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

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

16 голосов
/ 03 октября 2008

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

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

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

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

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

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

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

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

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

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

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

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

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 ГБ (и изменение) при резервном копировании (без сжатия) и 1 ГБ (и изменение) при восстановлении. Антисоциально, но возможно.

4 голосов
/ 02 октября 2008

Для производительности держать открытым . В целях безопасности часто смывайте .

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

3 голосов
/ 02 октября 2008

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

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

2 голосов
/ 02 октября 2008

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

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

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

2 голосов
/ 02 октября 2008

Обычно лучше держать их открытыми.

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

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

1 голос
/ 02 октября 2008

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

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

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

1 голос
/ 02 октября 2008

Не вижу смысла закрывать его.

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

1 голос
/ 02 октября 2008

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

0 голосов
/ 03 октября 2008

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

...