Мониторинг приложений на платформе .NET

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


Логирование
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;

namespace Poligon
{
    /// <summary>
    /// Файл ведения логов в формате RAMO
    /// </summary>
    public class Logger
    {
        /// <summary>
        /// Ведение логов
        /// </summary>
        public Logger() { }

        /// <summary>
        /// Ведение логов
        /// </summary>
        /// <param name="source">Имя программы</param>
        /// <param name="logPath">Директория лог-файлов</param>
        /// <param name="eventLog">Запись в системный лог</param>
        public Logger(string source, string logPath, EventLog eventLog)
        {
            Source = source;
            Log = eventLog;
            Log.Source = Source;
            LogPath = logPath;
            try {
                if (!EventLog.SourceExists(Source))
                    EventLog.CreateEventSource(Source, "Application");
            }  catch { }
        }

        /// <summary>
        /// Запись в системный лог
        /// </summary>
        public EventLog Log { get; set; }
        /// <summary>
        /// Директория лог-файлов
        /// </summary>
        public string LogPath { get; set; }
        /// <summary>
        /// Имя сервиса
        /// </summary>
        public string Source { get; set; }

        /// <summary>
        /// Трассировка ошибок
        /// </summary>
        /// <param name="message"></param>
        public void TraceError(string message)
        {
            TraceMessage(message, EventLogEntryType.Error);
        }

        /// <summary>
        /// Трассировка ошибок аудита
        /// </summary>
        /// <param name="message"></param>
        public void TraceFailed(string message)
        {
            TraceMessage(message, EventLogEntryType.FailureAudit);
        }

        /// <summary>
        /// Трассировка информационных сообщений
        /// </summary>
        /// <param name="message"></param>
        public void TraceInformation(string message)
        {
            TraceMessage(message, EventLogEntryType.Information);
        }

        /// <summary>
        /// Трассировка успешного аудита
        /// </summary>
        /// <param name="message"></param>
        public void TraceSuccess(string message)
        {
            TraceMessage(message, EventLogEntryType.SuccessAudit);
        }

        /// <summary>
        /// Трассировка предупреждений
        /// </summary>
        /// <param name="message"></param>
        public void TraceWarning(string message)
        {
            TraceMessage(message, EventLogEntryType.Warning);
        }

        private void TraceMessage(string message, EventLogEntryType entryType)
        {
            try { Log.WriteEntry(message, entryType); } catch {  }
            try {
                if ((entryType == EventLogEntryType.Error) ||
                    (entryType == EventLogEntryType.FailureAudit))
                    _errors.Add(message);
                var logPath = LogPath;
                var fileName = string.Format("{0}_{1}.log",
                    Source, DateTime.Now.ToString("yyyyMMdd"));
                if (string.IsNullOrEmpty(logPath))
                    fileName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName);
                else {
                    fileName = Path.Combine(logPath, fileName);
                    if (!Directory.Exists(logPath))
                        Directory.CreateDirectory(logPath);
                }
                using (var fs = new FileStream(fileName, FileMode.Append, FileAccess.Write)) {
                    string prefix;
                    switch (entryType) {
                        case EventLogEntryType.Error:
                            prefix = "E"; break;
                        case EventLogEntryType.FailureAudit:
                            prefix = "F"; break;
                        case EventLogEntryType.Information:
                            prefix = "I"; break;
                        case EventLogEntryType.SuccessAudit:
                            prefix = "S"; break;
                        case EventLogEntryType.Warning:
                            prefix = "W"; break;
                        default:
                            prefix = "U"; break;
                    }
                    message = string.Format("{0}:{1}>{2}\r\n", prefix,
                        DateTime.Now.ToString("HH:mm:ss"), message);
                    var buffer = Encoding.Default.GetBytes(message);
                    fs.Write(buffer, 0, buffer.Length);
                }
            }
            catch (Exception ex) {
                try { Log.WriteEntry(ex.Message, EventLogEntryType.Error); } catch { }
            }
        }

        private readonly List<string> _errors = new List<string>();
        /// <summary>
        /// Список ошибок
        /// </summary>
        public IList<string> Errors { get { return _errors.AsReadOnly(); } }

        /// <summary>
        /// Сброс ошибок
        /// </summary>
        public void ResetErrors() { _errors.Clear(); }
    }
}
Уведомление
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Mail;
using System.Text;

namespace Poligon
{
    /// <summary>
    /// Отправка сообщений
    /// Стандартная настройка SMTP в config-файле
    /// <system.net>
    ///     <mailSettings>
    ///         <smtp deliveryMethod="Network">
    ///             <network host="..."
    ///                      port="25" 
    ///                      userName="..." 
    ///                      password="..." />
    ///         </smtp>
    ///     </mailSettings>
    /// </system.net>
    /// </summary>
    public class Mailer
    {
        /// <summary>
        /// Отправка сообщений
        /// </summary>
        public Mailer() { }
        /// <summary>
        /// Отправка сообщений
        /// </summary>
        /// <param name="from">Отправитель</param>
        /// <param name="recipients">Получатели</param>
        public Mailer(string from, string recipients)
        {
            From = from;
            Recipients = recipients;
        }
        /// <summary>
        /// Отправка сообщений
        /// </summary>
        /// <param name="from">Отправитель</param>
        /// <param name="recipients">Получатели</param>
        /// <param name="errorRecipient">Получатель ошибок</param>
        public Mailer(string from, string recipients, string errorRecipient)
            : this(from, recipients)
        {
            ErrorRecipient = errorRecipient;
        }

        /// <summary>
        /// Отправитель
        /// </summary>
        public string From { get; set; }
        /// <summary>
        /// Получатетели
        /// </summary>
        public string Recipients { get; set; }
        /// <summary>
        /// Получатель ошибок
        /// </summary>
        public string ErrorRecipient { get; set; }

        private SmtpClient _client;
        protected virtual SmtpClient Client
        {
            get
            {
                if (_client != null)
                    return _client;
                _client = new SmtpClient();
                return _client;
            }
            set { _client = value; }
        }

        /// <summary>
        /// Отправить сообщение
        /// </summary>
        /// <param name="subject">Тема</param>
        /// <param name="body">Текст письма</param>
        /// <param name="attachments">Вложения</param>
        public void SendMail(string subject, string body, string[] attachments)
        {
            var message = new MailMessage(From, Recipients, subject, body);
            foreach (var fileName in attachments)
                message.Attachments.Add(new Attachment(fileName));
            Client.Send(message);
        }

        /// <summary>
        /// Отправить ошибки
        /// </summary>
        /// <param name="source">От кого</param>
        /// <param name="errors">Список ошибок</param>
        public void SendErrors(string source, IList<string> errors)
        {
            if (errors.Count == 0) return;
            var subject = string.Format(SubjectTemplate, source,
                DateTime.Now.ToString("dd.MM.yyyy hh:mm"));
            var body = new StringBuilder(ReportHeaderTemplate);
            body.AppendFormat(ReportNoteTemplate, subject)
                .Append(TableHeaderTemplate);
            foreach (var error in errors)
                body.AppendFormat(TableItemTemplate, error);
            body.Append(TableFooterTemplate)
                .AppendFormat(ReportFooterTemplate, errors.Count);
            Client.Send(From, ErrorRecipient, subject, body.ToString());
        }

        #region ErrorReportTemplates

        private const string SubjectTemplate = "Отчет {0} ({1})";

        private const string ReportHeaderTemplate =
            "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">"
            + "<html>"
            + "<head>"
            + "<meta http-equiv=\"content-type\" content=\"text/html; charset=windows-1251\">"
            + "<style type=\"text/css\">"
            + "BODY  { FONT-SIZE: small; FONT-FAMILY: Arial, Verdana, Helvetica, Tahoma, sans-serif}"
            + "TD    { FONT-SIZE: x-small; BACKGROUND-COLOR: lightgrey}"
            + "TH    { FONT-SIZE: x-small; COLOR: white; BACKGROUND-COLOR: silver;TEXT-ALIGN: left}"
            + "H1    { FONT-SIZE: small; FONT-WEIGHT: bold}"
            + "P     { FONT-SIZE: x-small}"
            + "</style>"
            + "</head>"
            + "<body>";

        private const string ReportNoteTemplate = "<h2>{0}</h2>";

        private const string TableHeaderTemplate =
              "<table cellspacing=\"1\" cellpadding=\"2\" width=\"100%\" border=\"0\">"
            + "<tbody>"
            + "<tr>"
            + "<th>Мониторинг выполнения</th>"
            + "</tr>";

        private const string TableItemTemplate =
              "<tr valign=top>"
            + "<td width=\"100%\">{0}</td>"
            + "</tr>";

        private const string TableFooterTemplate =
              "</tbody>"
            + "</table>"
            + "<hr />";

        private const string ReportFooterTemplate =
            "<p>Количество ошибок при выполнении: {0}</p>"
            + "<p>Данное сообщение сгенерированно и отправлено Вам программой, отвечать на него не нужно.</p>"
            + "</body>"
            + "</html>";

        #endregion
    }

    /// <summary>
    /// Ручная настройка SMTP
    /// </summary>
    public class CustomMailer : Mailer
    {
        /// <summary>
        /// Отправка сообщений
        /// </summary>
        public CustomMailer() { }
        /// <summary>
        /// Отправка сообщений
        /// </summary>
        /// <param name="from">Отправитель</param>
        /// <param name="recipients">Получатели</param>
        public CustomMailer(string from, string recipients)
            : base(from, recipients) { }
        /// <summary>
        /// Отправка сообщений
        /// </summary>
        /// <param name="from">Отправитель</param>
        /// <param name="recipients">Получатели</param>
        /// <param name="errorRecipient">Получатель ошибок</param>
        public CustomMailer(string from, string recipients, string errorRecipient)
            : base(from, recipients, errorRecipient) { }

        /// <summary>
        /// SMTP-сервер
        /// </summary>
        public string Host { get; set; }
        /// <summary>
        /// Порт
        /// </summary>
        public int Port { get; set; }
        /// <summary>
        /// Домен
        /// </summary>
        public string Domain { get; set; }
        /// <summary>
        /// ПОльзователь
        /// </summary>
        public string UserName { get; set; }
        /// <summary>
        /// Пароль
        /// </summary>
        public string Password { get; set; }

        protected override SmtpClient Client
        {
            get
            {
                if (base.Client != null)
                    return base.Client;
                base.Client = new SmtpClient(Host, Port) {
                    Credentials = new NetworkCredential {
                        Domain = Domain,
                        Password = Password,
                        UserName = UserName
                    }
                };
                return base.Client;
            }
            set { base.Client = value; }
        }
    }
}

Комментарии