#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); } } Появится окно в ошибкой и запишется в лог. Можно использовать тихий режим - без вывода ошибки, или только ошибка без записи в лог
Комментариев нет:
Отправить комментарий