Введение в журнал событий Windows и ETW — как использовать стандартные механизмы ОС для логов бизнес-приложения

· · CSharp, .NET, Журнал событий, ETW, EventSource, Проектирование логов, Исследование ошибок, Разработка Windows

При разработке бизнес-приложений или служб Windows на C# обычно уже ведутся повседневные записи самописным файловым логгером или библиотекой Serilog/NLog. Значит ли это, что журнал событий Windows и ETW (Event Tracing for Windows) больше не нужны? Нет. Это не «замена файловому логу», а записи другого уровня, которые видны операционному персоналу и стандартным средствам ОС.

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

1. Вывод сначала

  • Три механизма не заменяют друг друга, а разделяют работу. Файловый лог — запись для разработчиков, чтобы позже проследить детали; журнал событий — запись, по которой операционный персонал и стандартные средства ОС, такие как Event Viewer и мониторинг, замечают проблему; ETW — высокочастотная трасса, включаемая по требованию для исследования производительности или трудно воспроизводимых ошибок. Обычно применяйте все три, меняя объём информации в каждом.
  • В журнал событий пишите только «старт, остановку и фатальные ошибки». Если направить туда каждую строку лога, аномалии, которые должны видеть операторы, утонут. Файловый лог оставляйте подробным, а журнал событий сужайте.
  • Регистрация источника событий требует прав администратора. Начиная с Windows Vista EventLog.CreateEventSource нельзя вызвать без прав администратора. Не проектируйте регистрацию источника при первом доступе во время работы: зарегистрируйте его заранее в установщике. 1
  • ETW не означает «постоянный сбор». Даже если события определены через EventSource, ничего не записывается, пока их не слушает подписчик — dotnet-trace, PerfView или инструмент на основе ETW. Базовый сценарий — указать нужный provider и собирать только при анализе производительности или конкретной ошибки. 2
  • Пишите сообщения шаблонами, а не конкатенацией или интерполяцией. Запись logger.LogInformation("Заказ {OrderId} завершился ошибкой", orderId) позволяет искать в структурированном логе по имени заполнителя OrderId. Конкатенация и интерполяция теряют эту структурную информацию. 3
  • Проблема разрастания журнала часто начинается с малого значения по умолчанию. Предельный размер журнала событий по умолчанию — всего 512 КБ. Если бизнес-приложение регулярно использует журнал событий, явно увеличьте размер и заранее решите, что делать при заполнении: перезаписывать старые записи или отбрасывать новые. 4

Ниже — таблица выбора.

Аспект Файловый лог Журнал событий ETW
Основной читатель Разработчики и сопровождающие Операционный персонал, средства мониторинга, стандартный Event Viewer Исследователи производительности, инженеры поддержки
Примерный объём Подробный: Trace/Debug допустимы Небольшой: только старт, остановка и фатальные ошибки Большой только при включении; по умолчанию ничего не записывается
Время жизни / хранение Легко хранить долго в зависимости от ротации Старые записи перезаписываются или новые отбрасываются после достижения лимита Ограничено сессией; после сбора сохраняется как .etl/.nettrace
Интеграция со стандартными средствами ОС Нет, нужен собственный просмотрщик Просмотр в Event Viewer, wevtutil и Get-WinEvent Просмотр через dotnet-trace, PerfView, WPR/WPA
Права Только право записи у пользователя, под которым работает приложение Для регистрации источника нужны права администратора; после регистрации писать может стандартный пользователь Для запуска сбора часто требуются права администратора

2. Основы журнала событий Windows

В Windows по умолчанию есть три журнала: Application, System и Security; Security доступен только на чтение. Бизнес-приложения и службы пишут в Application или в собственный журнал (канал). По соглашению драйверы устройств пишут в System. 5

Единица записи — «источник событий». Источник — имя, идентифицирующее приложение; его заранее регистрируют через EventLog.CreateEventSource. Начиная с Windows Vista регистрация источника требует прав администратора. Проверка уникальности имени требует поиска по всем журналам, включая Security, к которому стандартный пользователь доступа не имеет. 1

using System.Diagnostics;

// Выполняйте это из установщика или процесса настройки, пока доступны права администратора
const string SourceName = "KomuraSoft.OrderService";
const string LogName = "Application";

if (!EventLog.SourceExists(SourceName))
{
    EventLog.CreateEventSource(SourceName, LogName);
}

Если пытаться создавать источник при первом доступе уже в работающем приложении, этот вызов в production завершится неудачей всякий раз, когда программа работает от обычного пользователя. Общий вопрос, когда действительно нужны права администратора, разобран в статье «Когда Windows действительно требует прав администратора?»; регистрация источника — именно такой случай. Безопасная схема — зарегистрировать источник установщиком, а приложению оставить только запись в уже зарегистрированный источник.

У каждого события есть две идентифицирующие части: «уровень» и «идентификатор события». Уровень — это EventLogEntryType (Information, Warning, Error, SuccessAudit, FailureAudit); он определяет значок в Event Viewer и стандартные фильтры. Идентификатор события — целое число, определяемое приложением, которое позднее служит ключом поиска и агрегации однотипных случаев.

3. Запись в журнал событий из .NET

Из .NET есть два основных пути записи в журнал событий.

3.1 Прямое использование System.Diagnostics.EventLog

Это низкоуровневый API для случаев, где нужен детальный контроль: категории, присоединение двоичных данных и т. п. В .NET, в отличие от .NET Framework, нужно сослаться на NuGet-пакет System.Diagnostics.EventLog.

using System.Diagnostics;

public sealed class OrderServiceEventLog
{
    private const string SourceName = "KomuraSoft.OrderService";

    public void WriteServiceStarted()
    {
        EventLog.WriteEntry(SourceName, "OrderService запущен.",
            EventLogEntryType.Information, eventID: 1000);
    }

    public void WriteFatalError(Exception ex)
    {
        EventLog.WriteEntry(SourceName,
            $"Произошла фатальная ошибка, и обработка не может быть продолжена. Подробности смотрите в файловом логе. {ex.GetType().Name}: {ex.Message}",
            EventLogEntryType.Error, eventID: 1999);
    }
}

3.2 Использование ILogger + AddEventLog

Если структурированное логирование уже построено вокруг ILogger, AddEventLog из пакета Microsoft.Extensions.Logging.EventLog естественно встраивается в существующий конвейер. В ASP.NET Core и Worker Service на основе Generic Host provider EventLog включён по умолчанию при работе в Windows. 6

using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.EventLog;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Logging.AddEventLog(settings =>
{
    settings.SourceName = "KomuraSoft.OrderService";
    settings.LogName = "Application";
    // Помимо стандартных фильтров категорий направляйте в журнал событий
    // только Warning и более высокие уровни (подробности оставляйте в файловом логе)
    settings.Filter = (_, level) => level >= LogLevel.Warning;
});

using IHost host = builder.Build();
await host.RunAsync();

Если не указывать EventLogSettings, LogName по умолчанию равен "Application", а SourceName".NET Runtime". 7 Оставляя общее имя .NET Runtime, в Event Viewer трудно понять, относится ли запись к вашему или к чужому .NET-приложению, поэтому для собственной программы всегда задавайте SourceName явно.

Для пакетной обработки в службе Windows или Task Scheduler практичный баланс — писать в журнал событий только «старт», «остановку» и «фатальную ошибку», оставляя всё остальное файловому логу. Построение и эксплуатация службы разобраны в «Создание и эксплуатация служб Windows», а исследование Task Scheduler — в «Когда задача планировщика не запускается или завершается с 0x1». В обоих случаях журнал событий часто становится первой точкой проверки операционного персонала, а наличие там ключевых фактов определяет скорость первичного исследования.

4. Что такое ETW: основа трассировки во всей ОС

Event Tracing for Windows (ETW) — механизм трассировки, на общей основе охватывающий всё от ядра до приложений пользовательского режима. Его главное достоинство — возможность записывать и анализировать события ядра, например дисковый I/O и создание процессов, вместе с собственными событиями приложения на одной временной шкале. 8

В .NET собственный ETW-provider определяется классом, наследующим System.Diagnostics.Tracing.EventSource. ETW использует модель publisher/subscriber: без подписчика события не пишутся. То есть одно создание EventSource ничего не делает — записи появятся только при явном включении сбором. 2

using System.Diagnostics.Tracing;

[EventSource(Name = "KomuraSoft-OrderService")]
internal sealed class OrderServiceEventSource : EventSource
{
    public static readonly OrderServiceEventSource Log = new();

    [Event(1, Level = EventLevel.Informational, Message = "Начата обработка заказа. OrderId={0}")]
    public void OrderProcessingStart(string orderId)
    {
        if (IsEnabled())
        {
            WriteEvent(1, orderId);
        }
    }

    [Event(2, Level = EventLevel.Informational, Message = "Обработка заказа завершена. OrderId={0}")]
    public void OrderProcessingStop(string orderId)
    {
        if (IsEnabled())
        {
            WriteEvent(2, orderId);
        }
    }
}

Вызывающая сторона использует его так. Проверка IsEnabled() до записи события позволяет избежать ненужных накладных расходов, когда никто не собирает данные.

OrderServiceEventSource.Log.OrderProcessingStart(order.Id);
try
{
    ProcessOrder(order);
}
finally
{
    OrderServiceEventSource.Log.OrderProcessingStop(order.Id);
}

Самый простой способ собрать эти события — кроссплатформенный инструмент dotnet-trace. 9

dotnet-trace collect --providers KomuraSoft-OrderService -- OrderService.exe

Собранный файл .nettrace открывают в Visual Studio или PerfView. Для более серьёзного расследования производительности, например с событиями ядра, используется Windows Performance Recorder (WPR) из Windows Assessment and Deployment Kit (ADK), а результат просматривается в Windows Performance Analyzer (WPA). 10 Но рамки этой статьи заканчиваются на вопросах «что умеет ETW и когда его применять», без детальной процедуры анализа в PerfView/WPA. Для разработки и сопровождения бизнес-приложения достаточно уметь определить provider через EventSource и собирать его dotnet-trace.

5. Практика структурированного логирования

Шаблон сообщения ILogger — фиксированная строка с именованными заполнителями вроде {PlaceHolder}. Это не только стиль: передача аргументов при неизменном шаблоне позволяет фреймворку сохранить связь между именами заполнителей и значениями как структурированные данные. 11

// Рекомендуется: сохраняйте шаблон неизменным и передавайте аргументы отдельно
logger.LogWarning("Не удалось зарезервировать товарный запас для заказа {OrderId}. Склад={WarehouseId}", orderId, warehouseId);

// Не рекомендуется: конкатенация или интерполяция строк нарушает соответствие между шаблоном и его значениями
logger.LogWarning("Заказ " + orderId + ": не удалось зарезервировать товарный запас. Склад=" + warehouseId);
logger.LogWarning($"Не удалось зарезервировать товарный запас для заказа {orderId}. Склад={warehouseId}");

Правило анализатора CA2254 обнаруживает такой нежелательный вариант, где шаблон меняется при каждом вызове. 3 На горячих путях с частыми вызовами можно пойти дальше и использовать source generation через LoggerMessageAttribute, чтобы избежать стоимости boxing и разбора шаблона. 12

using Microsoft.Extensions.Logging;

internal static partial class Log
{
    [LoggerMessage(
        EventId = 2001,
        Level = LogLevel.Warning,
        Message = "Не удалось зарезервировать товарный запас для заказа {OrderId}. Склад={WarehouseId}")]
    public static partial void StockReservationFailed(
        this ILogger logger, string orderId, string warehouseId);
}

// Вызывающий код
logger.StockReservationFailed(orderId, warehouseId);

При такой схеме один вызов лога можно направлять в разные места: детали — в файловый лог через sink Serilog/NLog, сокращённую версию — в журнал событий, дешёвую трассу — в ETW через EventSourceLoggerProvider. Базовый паттерн — зарегистрировать несколько ILoggerProvider и с помощью Filter назначить разный уровень для каждого. Минимальные требования к самописному логгеру собраны в статье о минимальных требованиях и интеграционном чек-листе; связывание логов и дампов при падении — в статье о сохранении логов и дампов при падении Windows-приложения.

6. Сбор и исследование: как читать написанные логи

В Event Viewer через «Filter» или «Custom Views» из списка журнала можно выделить только определённый источник, уровень или идентификатор события. Сохранение часто используемых условий как custom view ускоряет последующие расследования. Custom view определяется XPath-запросом; например, запрос, выбирающий события конкретного источника, выглядит так. 13

<QueryList>
  <Query Id="0" Path="Application">
    <Select Path="Application">
      *[System[Provider[@Name='KomuraSoft.OrderService'] and (Level=2 or Level=3)]]
    </Select>
  </Query>
</QueryList>

В командной строке wevtutil экспортирует журналы и меняет настройки. Во время расследования сбоя им можно экспортировать журнал событий вокруг момента инцидента и передать его в поддержку. 14

:: Экспортировать весь журнал Application в файл evtx
wevtutil epl Application C:\logs\application_20260707.evtx

:: Показать только события указанного источника (10 самых последних)
wevtutil qe Application /q:"*[System[Provider[@Name='KomuraSoft.OrderService']]]" /c:10 /rd:true /f:text

При исследовании падения основная практика — сопоставить по времени три вещи: журнал событий, файловый лог и crash dump. Начиная с времени «фатальной ошибки» из журнала событий, сравнивают детали файлового лога того же момента и дамп, снятый WER/ProcDump. Как собирать дампы, разобрано в «Введение в сбор Windows crash dump: WER/ProcDump/WinDbg», а процедура анализа снятого дампа — в «Чтение crash dump с WinDbg + SOS».

7. Ловушки

  • Попытка записи с незарегистрированным источником. Если выполнить аналог RegisterEventSource с незарегистрированным именем источника, само событие попадает в Application, но из-за отсутствия DLL ресурсов сообщений Event Viewer не может показать описание и показывает ошибку. 15 А схема с автоматической регистрацией через CreateEventSource при запуске процесса обычным пользователем обычно падает исключением уже при первом старте. Всегда завершайте регистрацию источника в установщике.
  • Заблуждение «можно писать под чужим именем источника». При наличии права записи журнал событий позволяет писать под именем источника, которого ваше приложение не регистрировало. Имена источников должны быть уникальны на компьютере, но ОС не применяет это правило — дисциплина полностью остаётся на стороне приложений. 5 Слишком общее короткое имя без компании или продукта часто приводит к столкновению с приложением другого поставщика или непреднамеренному использованию его источника.
  • Разрастание журнала и политика хранения. Предельный размер журнала по умолчанию — всего 512 КБ. 4 При регулярном использовании бизнес-приложением явно увеличьте размер через wevtutil sl или установщик и осознанно выберите: «перезаписывать самые старые записи» или «отбрасывать новые». Помните также, что OverwriteOlder помечен устаревшим: его указание может фактически дать режим «не перезаписывать». 16
  • Отображение сообщений в многоязычной среде. Если настроены локализованные ресурсы сообщений (MessageResourceFile), просмотр лога в среде без нужной DLL или с другой версией показывает «The description for Event ID XX cannot be found». Это возникает, например, при просмотре перенаправленного журнала на другом сервере или когда после удаления приложения остаются только события. Запись строк напрямую через WriteEntry, без файла ресурсов, не вводит эту проблему изначально.
  • Случаи, когда запись невозможна из-за прав. Часто неверно считают, что администратор нужен всегда. На деле права администратора требуются для регистрации источника, но запись в уже зарегистрированный источник обычному пользователю доступна. Не запускайте всё приложение elevated, увидев «без администратора не работает»: сначала отделите операцию, которой эти права действительно нужны.

Связанные статьи

Связанные области консультаций

KomuraSoft LLC проводит технические консультации по проектированию основы логирования, журнала событий и ETW для Windows-бизнес-приложений, а также по определению точек наблюдаемости с учётом исследования сбоев.

Справочные ссылки

  1. Microsoft Learn, EventLog.CreateEventSource Method. О том, почему с Windows Vista создание источника событий требует прав администратора: нужен поиск по всем журналам, включая Security.  2

  2. Microsoft Learn, Getting Started with EventSource. О модели publish-subscribe EventSource, при которой без ETW/EventPipe-подписчика события не записываются, и о сборе через dotnet-trace collect.  2

  3. Microsoft Learn, CA2254: Template should be a static expression. О том, что конкатенация и интерполяция в шаблоне лог-сообщения теряют связь между именами заполнителей и значениями.  2

  4. Microsoft Learn, EventLog.MaximumKilobytes Property. О предельном размере журнала событий по умолчанию 512 КБ.  2

  5. Microsoft Learn, EventLog Class. О трёх журналах по умолчанию (Application/System/Security), требовании уникальности источника на компьютере и механизме, по которому писать под любым зарегистрированным именем источника можно при наличии права записи.  2

  6. Microsoft Learn, Logging providers in .NET. О том, что стандартные providers Generic Host включают Console, Debug, EventSource и EventLog (только Windows). 

  7. Microsoft Learn, EventLogSettings Class. О значениях по умолчанию AddEventLog: LogName"Application", SourceName".NET Runtime"

  8. Microsoft Learn, Event Tracing. О ETW как механизме запуска, остановки, инструментирования и потребления событий трассировки приложений. 

  9. Microsoft Learn, dotnet-trace performance analysis utility. О dotnet-trace — кроссплатформенном инструменте сбора трасс на базе EventPipe — и его команде collect. 

  10. Microsoft Learn, Introduction to WPR. О Windows Performance Recorder как инструменте, расширяющем ETW для подробной записи системы и приложений. 

  11. Microsoft Learn, Logging in C# and .NET. О синтаксисе {PlaceHolder} в шаблонах и механизме, в котором ключи шаблона становятся именами свойств лога. 

  12. Microsoft Learn, High-performance logging in .NET. О том, что source-generated logging через LoggerMessageAttribute устраняет стоимость boxing и разбора шаблона. 

  13. Microsoft Learn, Comparison of ETW and EventLog logger functionality. О создании custom view Event Viewer XPath-запросом с фильтрацией по имени события и другим критериям. 

  14. Microsoft Learn, wevtutil. О командах работы с журналом событий, включая экспорт (epl), запрос (qe) и смену настройки (sl). 

  15. Microsoft Learn, Event Sources. О том, что запись под незарегистрированным именем попадает в Application, но без файла сообщений Event Viewer не может показать описание и сообщает ошибку. 

  16. Microsoft Learn, OverflowAction Enum. О поведении при достижении размера журнала — перезаписи или отбрасывании — и об устаревании OverwriteOlder. 

Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.

Не только appsettings.json — практическое руководство по управлению конфигурацией Windows-бизнес-приложений (настройки окружений, секреты и место записи)

Практический разбор управления конфигурацией Windows-бизнес-приложений: слои appsettings.json, выбор между IConfiguration и семейством IO...

Как сделать сайт заметным по запросам с названием района — практическое руководство по локальному SEO для малого и среднего бизнеса

Для компаний, которые не появляются по запросу «название района + отрасль», собран порядок исправлений для локального SEO: подготовка Goo...

Эти страницы показывают тему статьи в более широком контексте услуг и решений.

Статья напрямую связана со следующими услугами.

Частые вопросы

Вопросы, которые часто возникают при консультациях по теме статьи.

Если уже есть файловые логи, нужен ли журнал событий?
Для подробного исследования одних файловых логов часто достаточно. Но если операционный персонал должен замечать проблемы приложения через стандартные средства ОС — Event Viewer или системы мониторинга, уведомления о сбоях Task Scheduler, SCOM и т. п. — практично выводить в журнал событий хотя бы ключевые точки. Это не замена одного другим, а записи на разных уровнях для разных читателей.
Как выбрать правила нумерации идентификаторов событий?
Единственно правильного ответа нет, но три практических правила уменьшают ошибки: делите диапазоны по функциональным областям — например, 1000-е для запуска, 2000-е для связи; после выпуска не меняйте значение идентификатора и не используйте его повторно; оставляйте пробелы в нумерации. EventId в ILogger также служит ключом поиска структурированных логов, поэтому удобная для документации схема окупается при сопровождении.
Я использую Serilog или NLog — как это соотносится со статьёй?
Serilog и NLog — реализации под ILogger, которые направляют те же записи в несколько приёмников: файлы, журнал событий, ETW, внешние SaaS и т. д. Принципы статьи — оставлять в журнале событий только главное и не разрушать шаблон сообщения — одинаково применимы при прямом ILogger и при Serilog/NLog. Выбор между EventLog sink, например Serilog.Sinks.EventLog, и стандартным AddEventLog — вопрос реализации.
Насколько глубоко нужно изучать ETW?
Если главная цель — разработка и сопровождение бизнес-приложения, достаточно уметь определить собственный provider через EventSource и собирать его события с dotnet-trace. Анализ стека вызовов в PerfView и сбор событий ядра через WPR можно осваивать при специализации на расследовании производительности; эта статья намеренно оставляет их за рамками.

Об авторе

Страница с профилем автора статьи.

Го Комура

Представитель KomuraSoft LLC

Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.

Публичные ссылки

Вернуться в блог