Должны ли потоки файлов журнала открываться / закрываться при каждой записи или оставаться открытыми в течение времени жизни приложения для настольного компьютера?
Должны ли классы журналов открывать / закрывать поток файлов журнала при каждой записи в файл журнала или он должен сохранять поток файлов журнала открытым в течение всего времени жизни приложения до тех пор, пока не будет завершена вся регистрация?
Я спрашиваю в контексте настольного приложения. Я видел, как люди делали это в обоих направлениях, и мне было интересно, какой подход дает наилучшие всесторонние результаты для лесозаготовителей.
13 ответов
Если вы часто читаете / записываете, более эффективно сохранить файл открытым на всю жизнь с помощью одного открытия / закрытия.
Возможно, вы захотите сбрасывать периодически или после каждой записи, хотя в случае сбоя вашего приложения у вас могут не быть все данные, записанные в ваш файл. Используйте fflush в системах на основе Unix и FlushFileBuffers в Windows.
Если вы также работаете в Windows, вы можете использовать API CreateFile с FILE_FLAG_NO_BUFFERING для перехода непосредственно к файлу при каждой записи.
Также лучше держать файл открытым на всю жизнь, потому что каждый раз, когда вы открываете / закрываете, у вас может возникнуть сбой, если файл используется. Например, у вас может быть приложение резервного копирования, которое запускает и открывает / закрывает ваш файл во время его резервного копирования. И это может привести к тому, что ваша программа не сможет получить доступ к вашему собственному файлу. В идеале вы хотите, чтобы ваш файл всегда оставался открытым и указывали флаги общего доступа в Windows (FILE_SHARE_READ). В системах на основе Unix совместное использование будет по умолчанию.
В общем, как и все остальные, держите файл открытым для производительности (открытие - относительно медленная операция). Однако вам нужно подумать о том, что произойдет, если вы оставите файл открытым, и люди либо удалят файл журнала, либо усекают его. И это зависит от флагов, используемых в открытом времени. (Я обращаюсь к 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 ГБ (и изменение) при восстановлении. Антисоциально, но возможно.
Для производительности оставайтесь открытыми. В целях безопасности часто смывайте.
Это будет означать, что библиотека времени выполнения не будет пытаться буферизовать записи до тех пор, пока у нее не будет много данных - вы можете потерпеть крах до того, как они будут записаны!
Я бы предпочел оставить их открытыми, но открывать их с установленными разрешениями общего доступа к файлам, чтобы позволить другим читателям и проверять вывод журнала при каждом сообщении.
Я ненавижу программы, которые даже не позволяют вам просматривать файл журнала во время их работы или там, где файл журнала не очищается и не отстает от происходящего.
Обычно лучше держать их открытыми.
Если вас беспокоит возможность прочитать их из другого процесса, вам нужно убедиться, что режим общего доступа, который вы используете для их открытия / создания, позволяет другим читать их (но, очевидно, не записывать в них).
Если вы беспокоитесь о потере данных в случае сбоя, вы должны периодически очищать / фиксировать их буферы.
Это компромисс. Открытие и закрытие файла каждый раз повышает вероятность того, что файл будет обновлен на диске в случае сбоя программы. С другой стороны, есть некоторые накладные расходы, связанные с открытием файла, поиском до конца и добавлением в него данных.
В Windows вы не сможете перемещать / переименовывать / удалять файл, пока он открыт, поэтому открытие / запись / закрытие может быть полезно для длительного процесса, когда вы можете иногда захотеть заархивировать старое содержимое журнала, не прерывая писатель.
В большинстве случаев, когда я выполнял такую регистрацию, я оставлял файл открытым и использовал fflush(), чтобы повысить вероятность того, что файл обновился в случае сбоя программы.
Я могу вспомнить пару причин, по которым вы не хотите держать файл открытым:
- Если файл журнала используется несколькими различными приложениями, пользователями или экземплярами приложений, у вас могут возникнуть проблемы с блокировкой.
- Если вы не очищаете буфер потока правильно, вы можете потерять несколько последних записей, когда приложение выйдет из строя, и вам они понадобятся больше всего.
С другой стороны, открытие файлов может быть медленным, даже в режиме добавления. В конце концов, все сводится к тому, что делает ваше приложение.
Открыть и закрыть. Может спасти вас от поврежденного файла в случае сбоя системы.
Я не вижу причин, чтобы закрыть его.
С другой стороны, закрытие и повторное открытие занимает немного больше времени.
Как пользователь вашего приложения, я бы предпочел, чтобы оно не держало файлы открытыми, если это не является реальным требованием приложения. Еще одна вещь, которая может пойти не так в случае сбоя системы и т. Д.
Преимущество закрытия файла каждый раз заключается в том, что ОС гарантирует, что новое сообщение будет записано на диск. Если вы оставите файл открытым, и ваша программа выйдет из строя, вполне возможно, что не все будет написано. Вы также можете сделать то же самое, выполнив fflush() или любой другой эквивалент в языке, который вы используете.
Я бы открывал и закрывал каждую запись (или серию записей). Если это приводит к проблемам с производительностью в настольном приложении, возможно, вы пишете в файл журнала слишком часто (хотя я уверен, что могут быть веские причины для большого количества записей).
Для больших интенсивных приложений я обычно сохраняю файл журнала открытым на время работы приложения и имею отдельный поток, который периодически сбрасывает содержимое журнала в памяти на жесткий диск. Операции открытия и закрытия файлов требуют системных вызовов, что является большой работой, если вы посмотрите на более низкий уровень.