ASP.NET: пишем в Event log от имени .NET Runtime
Хочу поделиться с вами своим опытом работы с журналом Windows и рассказать о том, как осуществлять туда запись из приложения ASP.NET, без осуществления каких-либо предварительных настроек.
Суть проблемы
На сервере имеется развернутое и работающее ASP.NET приложение. В ходе добавления определенной функциональности в данное приложение встал вопрос о ведении журнала, дабы иметь возможность при необходимости проанализировать результаты работы, например, с целью выявления ошибок.
Было принято решение воспользоваться стандартными средствами ведения журнала Windows (Windows event logs). Подробнее можно прочитать здесь: http://imar.spaanjaars.com/275/logging-errors-to-the-event-log-in-aspnet-applications.
Проблема при использовании данного метода заключается в том, что журнал перед использованием нужно надлежащим образом настроить, а именно создать пользовательский источник событий. Имя источника может быть совершенно произвольным и зачастую соответствует имени приложения, производящего запись в журнал (стоит, однако, отметить, что имя должно быть уникально в пределах Windows-машины).
Суть проблемы в том, что учетная запись, от имени которой запущен процесс IIS (обслуживающий приложения ASP.NET), не имеет права создавать пользовательские источники (читай, не имеет права писать в реестр). В связи с этим в голову сразу приходят следующие возможные решения проблемы:
- Предварительно создать пользовательский источник, используя привелигированную учетную запись (будь то ручная настройка, либо запуск программы-конфигуратора).
- Отредактировать существующую учетную запись, добавив недостающих прав.
- Использовать другие методы ведения журнала.
Первый и второй варианты сразу не подходят, так как экземпляры работающего приложения установлены на множестве серверов. Таким образом, накладные расходы на ручную/автоматическую конфигурацию будут достаточно высоки (стоит так же отметить, что политика компании в отношении подобного рода манипуляций на хостинговой площадке достаточно однозначна – все должно работать «из коробки» без дополнительных настроек со стороны системного администратора). Второй вариант плох еще и потому, что открывает потенциальную дыру в безопасности. Третий вариант так же был расценен как неприемлемый, ввиду того, что требует дополнительных знаний от человека, использующего журнал («где хранится журнал, в каком виде, как в нем представлена информация…» и т.д.).
Использование стандартного источника
Так как ни один из изложенных выше вариантов решения проблемы не удовлетворял требованиям, было принято решение использовать стандартный источник среды выполнения ASP.NET. О том, как это было достигнуто, речь пойдет ниже.
Первое, что требовалось выяснить – имя источника. На снимке выше можно заметить, что в списке присутствует предупреждение (о не перехваченном исключении), источником которого в данном случае является «ASP.NET 2.0.50727.0». Этим источником мы и воспользуемся. Однако просто подставить данное значение в код мы не можем (версия, как major, так и minor может отличаться), следовательно, ее нужно как-то сформировать. Формируется она следующим образом (обратите внимание на имя журнала, в который впоследствии будет производиться запись — “Application”):
public System.Diagnostics.EventLog GetEventLog() { string logName = "Application"; string runtimeVersion = string.Empty; System.Diagnostics.EventLog ret = null; System.Diagnostics.EventLog[] logs = null; logs = System.Diagnostics.EventLog.GetEventLogs(); if (logs != null && logs.Any()) { foreach (System.Diagnostics.EventLog l in logs) { if (string.Compare(l.Log, logName, StringComparison.InvariantCultureIgnoreCase) == 0) { if (string.IsNullOrEmpty(l.Source)) { runtimeVersion = System.Runtime.InteropServices.RuntimeEnvironment.GetSystemVersion(); if (string.IsNullOrEmpty(runtimeVersion)) { ret = l; runtimeVersion = runtimeVersion.Replace("v", string.Empty); ret.Source = string.Format("ASP.NET {0}.0", runtimeVersion); } } if(ret != null) break; } } } return ret; }
Сообщения и ресурсы
Итак, имя источника получено, можно начинать писать в журнал:
System.Diagnostics.EventLog log = GetLog(); log.WriteEntry("Index initialization has been completed.", System.Diagnostics.EventLogEntryType.Information, 0);
Однако если мы посмотрим на нашу «всежеиспеченную» запись, мы увидим следующее:
Как видно, система поставила в текст сообщения уведомление о не найденном строковом ресурсе. Суть в том, что выбранный нами источник связан с файлом ресурсов, в котором хранятся все текстовые сообщения, а строка, которую мы передаем – лишь аргумент для соответствующего строкового ресурса. Ответ очевиден: нужно передать такое значение «eventId» (который в выше приведенном примере имеет значение «0»), которое соответствует ресурсу вида «{0}», то есть ничего, кроме служебной информации не будет присутствовать в тексте сообщения.
Первое, что стоит сделать — это выяснить, где хранятся ресурсы. Открываем реестр и переходим в раздел «HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\Eventlog\[имя журнала]\[имя источника]». В нашем случае имя журнала – «Application», а имя источника – «ASP.NET 2.0.50727.0». Список ключей раздела имеет примерно следующий вид:
Следующим шагом скачиваем программу под названием «PE Explorer» и открываем в ней данный файл. Далее открываем обозреватель ресурсов (View -> Resources (CTRL + R)). В левой панели раскрываем узел «Message table» и по очереди изучаем каждый блок ресурсов. Скажу сразу, нас интересует ресурс со значением «%1»:
Переводим шестнадцатеричный идентификатор ресурса «0x4000046C» в десятичное число и в результате имеем следующий блок кода (обратите внимание, что мы теперь используем класс «EventInstance» для указания ресурса):
int categoryID = 3; long instanceID = 1073742956; System.Diagnostics.EventLog log = GetLog(); System.Diagnostics.EventInstance instance = new System.Diagnostics.EventInstance(instanceID, categoryID); instance.EntryType = System.Diagnostics.EventLogEntryType.Information; log.WriteEvent(instance, new object[] { "Index initialization has been completed." });
Кстати, идентификатор категории – 3 («Web event») так же был подсмотрен в файле ресурсов.
В результате получаем следующее:
Заключение
Данное решение не претендует на универсальность, и я не берусь утверждать, что оно будет работать во всех случаях. Буду рад, если кому-то оно так же может быть полезным, как и мне.