Ошибка нехватки памяти при архивировании файла журнала

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

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

Однако с тех пор, как я запустил работу BOT, у меня возникали проблемы с ошибками System Out of Memory, когда я пытался архивировать файл.

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

Я оборачиваю все в 3 попытки/уловы

  1. I/O
  2. Системе не хватает памяти
  3. стандартное исключение

Однако я всегда получаю исключение OutOfMemoryException, например

Ошибка System.OutOfMemoryException: было создано исключение типа «System.OutOfMemoryException».;

Чтобы дать вам пример размера 100 000 строк журнала, это файл размером около 11 МБ.

Стандартный полный файл журнала может иметь размер от 1/2 ГБ до 2 ГБ.

Мне нужно знать следующее:

а) какой размер стандартного текстового файла вызовет ошибку нехватки памяти при попытке использовать File.ReadAllText или пользовательскую функцию StreamReader, которую я вызываю ReadFileString, например

public static string ReadFileString(string path)
{
    // Use StreamReader to consume the entire text file.
using (StreamReader reader = new StreamReader(path))
{
    return reader.ReadToEnd();
    }
}

б) это память моего компьютера (у меня 16 ГБ ОЗУ - 8 ГБ используется во время копирования) или объекты, которые я использую в С#, не работают при открытии и копировании файлов.

При архивировании я сначала пытаюсь использовать свою пользовательскую функцию ReadFileString (см. выше), если она возвращает 0 байтов содержимого, я пробую File.ReadAllText, а затем, если это не удается, я пробую пользовательскую функцию, чтобы получить последние 100 000 строк, чего действительно недостаточно для отладки ошибок ранее в тот же день.

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

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

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

Например, какой-то способ зацикливания строки за строкой, пока не произойдет исключение, а затем сохранение того, что у меня есть.

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

private bool GetHundredThousandLines(string logpath, string archivepath)
{
    bool success = false;

    int numberOfLines = 100000;


    if (!File.Exists(logpath))
    {
    this.LogDebug("GetHundredThousandLines - Cannot find path " + logpath + " to archive " + numberOfLines.ToString() + " lines");
    return false;
    }

    var queue = new Queue<string>(numberOfLines);

    using (FileStream fs = File.Open(logpath, FileMode.Open, FileAccess.Read, FileShare.Read))
    using (BufferedStream bs = new BufferedStream(fs))  // May not make much difference.
    using (StreamReader sr = new StreamReader(bs))
    {
    while (!sr.EndOfStream)
    {
        if (queue.Count == numberOfLines)
        {
        queue.Dequeue();
        }

        queue.Enqueue(sr.ReadLine() + "\r\n");
    }
    }

    // The queue now has our set of lines. So print to console, save to another file, etc.
    try
    {

    do
    {        
        File.AppendAllText(archivepath, queue.Dequeue(), Encoding.UTF8);
    } while (queue.Count > 0);


    }
    catch (IOException exception)
    {
    this.LogDebug("GetHundredThousandLines - I/O Error accessing daily log file with ReadFileString: " + exception.Message.ToString());
    }
    catch (System.OutOfMemoryException exception)
    {
    this.LogDebug("GetHundredThousandLines - Out of Memory Error accessing daily log file with ReadFileString: " + exception.Message.ToString());
    }
    catch (Exception exception)
    {
    this.LogDebug("GetHundredThousandLines - Exception accessing daily log file with ReadFileString: " + exception.Message.ToString());
    }


    if (File.Exists(archivepath))
    {
    this.LogDebug("GetHundredThousandLines - Log file exists at " + archivepath);
    success = true;
    }
    else
    {
    this.LogDebug("GetHundredThousandLines - Log file DOES NOT exist at " + archivepath);
    }

    return success;

}

Любая помощь приветствуется.

Спасибо


person MonkeyMagix    schedule 08.04.2016    source источник
comment
Зачем вы вообще читаете журнал, чтобы заархивировать его? Просто переименуйте его, скопируйте в папку архива, создайте новый пустой файл на следующий день и вуаля! Никакой нехватки памяти, никакого ненужного чтения всего файла, когда вам это не нужно, и никакой головной боли.   -  person Ken White    schedule 08.04.2016
comment
Почему бы просто не переименовать старый файл и не создать новый на следующий день? Кажется, что это слишком много усилий, которые можно было бы сэкономить - извините, только что видел, как Кен Уайт опередил меня в этом. Я согласен с ним.   -  person Ian Murray    schedule 08.04.2016
comment
Почему бы не назвать файл журнала датой и временем. никогда не переименовывать   -  person Claudius    schedule 08.04.2016
comment
Если вы архивируете файлы журнала, вы можете заархивировать их одновременно, чтобы сэкономить много места на диске. SharpZipLib позволяет вам использовать буфер для создания zip-файлов, поэтому вы можете ограничить используемую память, скажем, 32 КБ. Я ожидаю, что другие утилиты сжатия файлов имеют такие же возможности.   -  person Andrew Morton    schedule 08.04.2016
comment
Хорошо, да, но по моему опыту работы с классикой ASP, перемещение под обложками делает то же самое, что и File.Copy, а затем File.Delete в любом случае, поэтому содержимое все равно каким-то образом копируется. Если памяти недостаточно для копирования файла, то это не сработает. Я уверен, что много лет назад я пытался использовать этот более простой метод, но он продолжал терпеть неудачу из-за проблем с памятью, поэтому я перешел к этому 2-му (теперь 3-этапному) процессу. Однако я мог бы вернуться и посмотреть, будет ли FIle.Move работать снова, но я уверен, что это не так, поэтому мне пришлось перейти к этому в первую очередь.   -  person MonkeyMagix    schedule 09.04.2016
comment
Клавдий, мне нравится эта идея, у меня просто всегда был файл logcurrent.log за текущий день, а затем архивные файлы с датой в имени, которые я затем перебирал и удалял X самых старых, сохраняя 2 самых новых архивных файла. Однако иметь все их с одинаковой датой в формате имени может быть проще - Techies Law - подумайте об этом, хотя с кем-то, и самые сложные (в вашем уме) проблемы могут иметь самые простые решения.   -  person MonkeyMagix    schedule 09.04.2016


Ответы (1)


попробуйте: сохраните позицию очереди и потока в области класса, попробуйте GC.Collect() при выходе из исключения памяти и снова вызовите функцию. искать поток до последней позиции и продолжать. или: используйте одну базу данных, такую ​​​​как sqlite, и сохраняйте 100000 новейших записей в каждой таблице.

person mehrdad safa    schedule 08.04.2016
comment
Не могли бы вы объяснить, что делает GC.Collect (я понимаю, сбор мусора), как это помогает, если у меня нет памяти, чтобы открыть файл. Также мне нужен метод для получения всего файла НЕ только 100 000 строк. Итак, что было бы ЛУЧШИМ способом получить весь файл, даже если он выбрасывал ошибки памяти? Прокручивая файл, храните 100 000 файлов в БД, очищайте память (как? GC.Collect()?), а затем продолжайте, а затем объединяйте их все в куски в конце? - person MonkeyMagix; 09.04.2016
comment
Кстати, прошлой ночью у меня почему-то сработал первый способ. Размер файла составил 25828836 байт. Мне интересно, есть ли какой-то ИЗВЕСТНЫЙ размер, когда файлы СЛИШКОМ БОЛЬШИЕ, чтобы их можно было открывать и читать любым способом. Если да, то каков этот предел, поскольку у меня достаточно оперативной памяти на моем ПК, чтобы открыть файл в редакторе. Итак, каковы ограничения C# для каждого метода чтения файлов? - person MonkeyMagix; 09.04.2016
comment
А также из трех перечисленных здесь подходов для чтения файла, включая File.ReadAllText, функцию Stream и метод Queue, который лучше всего подходит для производительности/памяти/IO для извлечения содержимого файла? - person MonkeyMagix; 09.04.2016
comment
@MonkeyMagix Чтение всего файла сразу означает, что вам нужно иметь достаточно непрерывной виртуальной памяти - на самом деле вы не можете повлиять или предсказать это. Потоковая передача обычно является лучшей идеей. Также обратите внимание, что строки .NET имеют кодировку Unicode, поэтому, если вы читаете файл ASCII/ANSI, удвойте размер файла для требований к памяти. - person Luaan; 11.04.2016
comment
Просто чтобы вы знали, я переписал его, чтобы использовать метод CopyTo, и на данный момент он работает нормально. Поэтому я удалил GC.Collect() - person MonkeyMagix; 15.04.2016