Чтение журналов Windows эффективно и быстро - PullRequest
0 голосов
/ 22 ноября 2018

Я пытаюсь выполнить приложение на C #, которое будет читать журналы из журналов событий Windows и сохранять их где-то еще.Это должно быть быстро, так как некоторые устройства, на которых он будет установлен, генерируют большое количество логов / с.

До сих пор я пробовал три подхода:

Локальный WMI: он нене работают, слишком много ошибок и исключений, вызванных размером коллекций, которые нужно загрузить.EventLogReader: я думаю, что это было идеальное решение, поскольку оно позволяет запрашивать журнал событий, как вам нравится, с помощью выражений XPath.Проблема заключается в том, что когда вы хотите получить содержимое сообщения для каждого журнала (путем вызова FormatDescription ()), требуется слишком много времени для длинных коллекций.Например: я могу прочитать 12k журналов за 0.11 с, если просто просмотреть их.Если я добавляю строку для хранения сообщения для каждого журнала, то для выполнения точно такой же операции требуется почти 6 минут, что совершенно безумно для такого небольшого количества журналов.Я не знаю, была ли какая-либо оптимизация, которая могла бы быть сделана с EventLogReader, чтобы получить сообщение быстрее, я не мог найти ничего ни в документации MS, ни в Интернете.

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

EventLog eventLog = EventLog.GetEventLogs().FirstOrDefault(el => el.Log.Equals("Security", StringComparison.OrdinalIgnoreCase));
var newEntries = (from entry in eventLog.Entries.OfType()
orderby entry.TimeWritten ascending
where entry.TimeWritten > takefrom
select entry);

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

Кто-нибудь может мне помочь с этим?Я не могу найти обходных путей или подходов для достижения чего-то подобного.

Спасибо!.

Ответы [ 2 ]

0 голосов
/ 23 ноября 2018

Вы можете попробовать класс EventLogReader.См. https://docs.microsoft.com/en-us/previous-versions/bb671200(v=vs.90).

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

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

Вотприложение, которое показывает, как вы можете распараллелить чтение:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Eventing.Reader;
using System.Linq;
using System.Threading.Tasks;

namespace EventLogReading
{
    class Program
    {
        static volatile bool myHasStoppedReading = false;

        static void ParseEventsParallel()
        {
            var sw = Stopwatch.StartNew();
            var query = new EventLogQuery("Application", PathType.LogName, "*");

            const int BatchSize = 100;

            ConcurrentQueue<EventRecord> events = new ConcurrentQueue<EventRecord>();
            var readerTask = Task.Factory.StartNew(() =>
            {
                using (EventLogReader reader = new EventLogReader(query))
                {
                    EventRecord ev;
                    bool bFirst = true;
                    int count = 0;
                    while ((ev = reader.ReadEvent()) != null)
                    {
                        if ( count % BatchSize == 0)
                        {
                            events.Enqueue(ev);
                        }
                        count++;
                    }
                }
                myHasStoppedReading = true;
            });

            ConcurrentQueue<KeyValuePair<string, EventRecord>> eventsWithStrings = new ConcurrentQueue<KeyValuePair<string, EventRecord>>();

            Action conversion = () =>
            {
                EventRecord ev = null;
                using (var reader = new EventLogReader(query))
                {
                    while (!myHasStoppedReading || events.TryDequeue(out ev))
                    {
                        if (ev != null)
                        {
                            reader.Seek(ev.Bookmark);
                            for (int i = 0; i < BatchSize; i++)
                            {
                                ev = reader.ReadEvent();
                                if (ev == null)
                                {
                                    break;
                                }
                                eventsWithStrings.Enqueue(new KeyValuePair<string, EventRecord>(ev.FormatDescription(), ev));
                            }
                        }
                    }
                }
            };

            Parallel.Invoke(Enumerable.Repeat(conversion, 8).ToArray());

            sw.Stop();
            Console.WriteLine($"Got {eventsWithStrings.Count} events with strings in {sw.Elapsed.TotalMilliseconds:N3}ms");
        }

        static void ParseEvents()
        {
            var sw = Stopwatch.StartNew();
            List<KeyValuePair<string, EventRecord>> parsedEvents = new List<KeyValuePair<string, EventRecord>>();

            using (EventLogReader reader = new EventLogReader(new EventLogQuery("Application", PathType.LogName, "*")))
            {
                EventRecord ev;
                while ((ev = reader.ReadEvent()) != null)
                {
                    parsedEvents.Add(new KeyValuePair<string, EventRecord>(ev.FormatDescription(), ev));
                }
            }

            sw.Stop();
            Console.WriteLine($"Got {parsedEvents.Count} events with strings in {sw.Elapsed.TotalMilliseconds:N3}ms");
        }

        static void Main(string[] args)
        {
            ParseEvents();
            ParseEventsParallel();
        }
    }
}
Got 20322 events with strings in 19,320.047ms
Got 20323 events with strings in 5,327.064ms

Это дает приличное ускорение в 4 раза. Мне нужно было использовать некоторые приемы, чтобы ускориться, потому что для некоторых странноПричина, по которой класс ProviderMetadataCachedInformation не является поточно-ориентированным и использует внутреннюю блокировку (this) для метода Format, которая предотвращает чтение паралелей.Ключевой трюк заключается в том, чтобы снова открыть журнал событий в потоках преобразования, а затем прочитать несколько событий запроса через закладку событий Api.Таким образом, вы можете отформатировать строки независимо.

0 голосов
/ 22 ноября 2018

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

 var eventLog = new EventLog("Security");
 for (int i = 0; i < eventLog.Entries.Count; i++)
 {
      Console.WriteLine($"{eventLog.Entries[i].Message}");
 }

Это может быть не самым чистым (с точки зрения производительности) способомделать это, но я сомневаюсь, что любой другой будет быстрее, как вы сами уже поняли, пробуя различные методы.Небольшой дуэт редактирования в пост Алоиса: EventLogReader не быстрее из коробки, чем EventLog, особенно при использовании механизма for-loop, показанного в блоке кода выше, я думаю, EventLog быстрее - он только получает доступэлементы внутри цикла, использующие их индекс, коллекция Entries является просто ссылкой, тогда как при использовании EventLogReader она сначала выполнит запрос и выполнит цикл по этому результату, который должен быть медленнее.Как прокомментировал пост Алоиса: если вам не нужно использовать опцию запроса, просто используйте вариант EventLog.Если вам нужен запрос, используйте запрос EventLogReader as is на более низком уровне, чем при использовании EventLog (только запросы LINQ, что, конечно, медленнее, чем запрос при выполнении поиска).

Чтобы не допустить повторения этой проблемы в будущем, и поскольку вы сказали, что запускаете службу, я бы использовал событие EntryWritten класса EventLog :

    var eventLog = new EventLog("Security")
    {
        EnableRaisingEvents = true
    };
    eventLog.EntryWritten += EventLog_EntryWritten;

    // .. read existing logs or do other work ..

    private static void EventLog_EntryWritten(object sender, EntryWrittenEventArgs e)
    {
        Console.WriteLine($"received new entry: {e.Entry.Message}");
    }

Обратите внимание, что необходимо установить для EnableRaisingEvents значение true, чтобы событие срабатывало при регистрации новой записи.Также будет хорошей практикой (также с точки зрения производительности) запускать (например) Task , чтобы система не блокировалась при постановке в очередь вызовов на ваше событие.

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

...