Страницы

Поиск по вопросам

пятница, 27 декабря 2019 г.

Передача исключений и логирование

#c_sharp


Не так давно я поднял тему исключений.
Потом я задал вопрос про пробрасывание исключений, и там разговор зашел о логировании,
в связи с чем я эту тему и поднимаю.  

В этом вопросе я просто излагаю свои мысли и привожу в пример свой текущий, маленький,
обучающий проект, и прошу Вас сказать мне, где я ошибаюсь.  Так что вопроса, как такового,
здесь нет. Есть только призыв к обсуждению и критике. И так многа букав:



Почитал msdn и прочие источники, где наткнулся на различные стратегии обработки исключений.
Меня заинтересовали:  


"передача исключения в исключение-оболочку для сохранения содержимого и создание
нового исключения. Этот подход является реализацией шаблона трансляции исключений"
"регистрация исключения в журнале"


Пункт 1 это и есть тот проброс исключений, о котором я спрашивал ранее. А пункт 2
это то, на что меня натолкнули товарищи @VladD и @DreamChild (за что им и спасибо).
Сегодня я покопался во всем этом и вот, что получилось: 

Предположим, есть у нас метод, который выполняет запрос и заполняет датасет:  

    public bool Open_SQL(string query, string table)
    {
        try
        {
            query = "select * from asd";
            if (dataSet.Tables.Count > 0)
                _dataSet.Tables.Clear();
            SqlCommand sqlComand = new SqlCommand(query, sqlconnection);
            sqlDataAdapter.SelectCommand = sqlComand;
            sqlDataAdapter.Fill(_dataSet, table);
            return true;
        }
        catch (Exception e)
        {
            throw new Exception("Ошибка при попытке выполнить Sql запрос: " + query, e);
        }
    }  


Метод этот будет у нас находиться в самом низу пищевой цепочки, и чтобы путь исключения
был долгим, ошибку создадим именно в нем, подменив валидный запрос на "query = "select
* from asd";" (таблицы asd нет, так что ошибка 100%). Этот метод вызывается из другого
объекта (не буду расписывать метод полностью, а отражу самую суть):  

    // Подключение DataGrid к данным
    public bool ConnectDataGrid()
    {
        try
        {   
            ...
            if (sqlClient.Open_SQL(query, tableName))
            {
                 ...
            }
            ...
        }
        catch (Exception e)
        {
            throw new Exception("Ошибка при подключении набора данных к DataGridView", e);
        }
    }  


А этот метод в свою очередь вызывается из объекта на более высоком уровне. Да, архитектура
приложения получилась многоуровневой, так как мне показалось это удобным, ибо у каждого
объекта есть свой круг/уровень объектов с которыми он может взаимодействовать. Поэтому
в критических ситуациях, когда работа приложения не может быть продолжена (например
ошибка подключения к базе данных при старте), может выстроиться цепочка из 3-4 зависимых
исключений. 



На это закончим описание исключений, и перейдем к модулю логирования. А тут я написал
маленький класс:  

static public class uNix_Logger
{      
    static private void Read_and_writeToFile_Exception(Exception e, string logFile)
    {
        if (e.InnerException != null)
        {
            Read_and_writeToFile_Exception(e.InnerException, logFile);
        }

        StreamWriter stream = new StreamWriter(logFile, true);
        stream.WriteLine("Ошибка: " + e.Message);
        stream.WriteLine("Объект: " + e.Source);
        stream.WriteLine("Метод, вызвавший исключение: " + e.TargetSite);
        stream.WriteLine("Стэк: " + e.StackTrace);
        stream.WriteLine("====================================");
        stream.Close();
    }

    static public void CreateLog(Exception e)
    {
        if(!Directory.Exists(uNix_Const.errorLogPath))
        {
            Directory.CreateDirectory(uNix_Const.errorLogPath);
        }

        string logFile = string.Format(uNix_Const.error_template, DateTime.Now.ToString().Replace(":",
"."));
        Read_and_writeToFile_Exception(e, uNix_Const.errorLogPath + logFile);
    }
}  


Класс простенький и все что он делает - это создает директорию и файлы в которые
рекурсивно записывает информацию об ошибках. Да, в таком виде он не очень практичен,
так как в стек может попасть критически важная информация, которую бы нам не очень
хотелось показывать. Но экранирование исключений это тема для отдельного вопроса...
 Но а наш класс вполне способен составить простой лог следующего вида (я не стал тут
писать данные стэка, так как много места занимают):

Ошибка: Недопустимое имя объекта "asd".
Объект: .Net SqlClient Data Provider
Метод, вызвавший исключение: Void OnError(System.Data.SqlClient.SqlException, Boolean,
System.Action`1[System.Action])
Стэк:    ...
====================================
Ошибка: Ошибка при попытке выполнить Sql запрос: select * from asd
Объект: Nix_Manager
Метод, вызвавший исключение: Boolean Open_SQL(System.String, System.String)
Стэк:    ...
====================================
Ошибка: Ошибка при подключении набора данных к DataGridView
Объект: Nix_Manager
Метод, вызвавший исключение: Boolean ConnectDataGrid()
Стэк:    ...
====================================
Ошибка: Ошибка начальной инициализации компонентов
Объект: Nix_Manager
Метод, вызвавший исключение: Void .ctor(System.String[])
Стэк:    ...
====================================  


Такой лог позволяет мне увидеть, что именно произошло, в каком месте, и что этому
предшествовало. А пользователю показывается простое человекопонятное: "Ошибка! Продолжение
работы программы невозможно! Попробуйте перезапустить её."
    


Ответы

Ответ 1



public static class Msg { public static void MessageInfo(string caption, string msg) { MessageBox.Show(msg, caption, MessageBoxButtons.OK, MessageBoxIcon.Information); } public static void MessageError(string caption, string msg) { MessageBox.Show(msg, caption, MessageBoxButtons.OK, MessageBoxIcon.Error); } public static void MessageError(Exception _ex, string caption) { MessageBox.Show(_ex.Message, caption, MessageBoxButtons.OK, MessageBoxIcon.Error); WriteLog(_ex.Message + "\n************************\n" + _ex.StackTrace + "\n******************\n\n"); } public static void ShowError(Exception _ex, string caption) { MessageBox.Show(_ex.Message, caption, MessageBoxButtons.OK, MessageBoxIcon.Error); } public static void ShowError(string txt, string caption) { MessageBox.Show(txt, caption, MessageBoxButtons.OK, MessageBoxIcon.Error); } public static DialogResult MessageQuery(string caption, string msg) { return MessageBox.Show(msg, caption, MessageBoxButtons.YesNo, MessageBoxIcon.Question); } public static void WriteLog(string msg) { if (string.IsNullOrEmpty(msg)) return; string path = Directory.GetCurrentDirectory() + "\\log.txt"; using (var outfile = new StreamWriter(path, true, Encoding.UTF8)) { outfile.WriteLine("***********************"); outfile.WriteLine("дата: {0}", DateTime.Now); outfile.WriteLine(); outfile.Write(msg); } } } Вызов: где-то там, где угодно.. private void boxDiam_SelectedIndexChanged(object sender, EventArgs e) { try { if (boxDiam.SelectedIndex > 0) { bindSteel1.Filter = "DIAMETR_BL = " + boxDiam.SelectedValue.ToString(); bindSteel2.Filter = "DIAMETR_BL = " + boxDiam.SelectedValue.ToString(); } else { bindSteel1.Filter = ""; bindSteel2.Filter = ""; } } catch (Exception ex) { Msg.MessageError(ex, this.Text); } } Появится окно в ошибкой и запишется в лог. Можно использовать тихий режим - без вывода ошибки, или только ошибка без записи в лог

Комментариев нет:

Отправить комментарий