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