Запись логов в базу. Как это написать коротко и правильно?
От: SteeLHeaD  
Дата: 16.12.13 09:51
Оценка:
Хочу из своей WinForms программы писать лог ошибок в базу.
Проблема: не могу написать компактный и красивый код, потому что проверки разных условий приводят к длинным IF — ам.
Но вообще советы приму с благодарностью.

Как делал:
1) в Program.cs добавил

static void Main()
    {
      Application.EnableVisualStyles();
      Application.SetCompatibleTextRenderingDefault(false);
      try {
        Application.Run(new LoginForm());
      }
      catch (Exception ex) {
        Logger.WriteLog(ex);
      }
    }



В SQL — сделал таблицу

CREATE TABLE [dbo].[LogTable](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [dt] [datetime] NOT NULL,
    [Source] [varchar](255) NULL,
    [Message] [varchar](255) NULL,
    [StackTrace] [varchar](3000) NULL)


Вначале казалось, что все просто:


public static class WriteLog {
    
  private static SqlConnection cn;

  public static void WriteLog(Exception ex=null) {
    cn = CheckSqlConn(cn); // Проверяет, если коннекшн нулевой или не открытый - делает правильный коннекшн
      try
      {
        string sql = @"insert into [LogTable] ([Dt], [Source], [Message], [StackTrace], ) values (@Dt, @Source, @Message, @StackTrace)";
        SqlCommand sCmd1 = new SqlCommand(sql, cn);
        sCmd1.Parameters.Add(new SqlParameter("@dt", DateTime.Now));
        sCmd1.Parameters.Add(new SqlParameter("@Source", ex.Source));
        sCmd1.Parameters.Add(new SqlParameter("@Message", ex.Message));
        sCmd1.Parameters.Add(new SqlParameter("@StackTrace", ex.StackTrace));
        sCmd1.ExecuteScalar();
      }
      catch (Exception ex2)
      {
        // Ну не шмогла... 
      }
  }
}


2) Второй подход к сняряду — учитываем, что exception может быть null, и отдельные его поля могут быть null
получается что то вроде


public static void WriteLog(Exception ex=null) {
    cn = CheckSqlConn(cn); // Проверяет, если коннекшн нулевой или не открытый - делает правильный коннекшн
      try {
        string sql = @"insert into [LogTable] ([Dt], [Source], [Message], [StackTrace], ) values (@Dt, @Source, @Message, @StackTrace)";
        SqlCommand sCmd1 = new SqlCommand(sql, cn);
        sCmd1.Parameters.Add(new SqlParameter("@dt", DateTime.Now));
        if (ex != null) {
          sCmd1.Parameters.Add(new SqlParameter("@Source", ex.Source ?? String.Empty));
          sCmd1.Parameters.Add(new SqlParameter("@Message", ex.Message ?? String.Empty));
          sCmd1.Parameters.Add(new SqlParameter("@StackTrace", ex.StackTrace ?? String.Empty));
        }
        else {
          sCmd1.Parameters.Add(new SqlParameter("@Source", String.Empty));
          sCmd1.Parameters.Add(new SqlParameter("@Message", String.Empty));
          sCmd1.Parameters.Add(new SqlParameter("@StackTrace", String.Empty));
        }
        sCmd1.ExecuteScalar();
      }
      catch (Exception ex2) {
        // Ну не шмогла... 
      }
}


3) Теперь вспоминаю, что строки могут быть слишком длинными для нашей таблицы, и тут уже получается совсем некрасиво:


public static void WriteLog(Exception ex=null) {
    cn = CheckSqlConn(cn); // Проверяет, если коннекшн нулевой или не открытый - делает правильный коннекшн
      try {
        string sql = @"insert into [LogTable] ([Dt], [Source], [Message], [StackTrace], ) values (@Dt, @Source, @Message, @StackTrace)";
        SqlCommand sCmd1 = new SqlCommand(sql, cn);
        sCmd1.Parameters.Add(new SqlParameter("@dt", DateTime.Now));
        if (ex != null) {
          string s1 = ex.Source ?? String.Empty ;
          if (s1.Length > 255)
            s1 = s1.Substring(0, 255);
          sCmd1.Parameters.Add(new SqlParameter("@Source",  s1 ));

          string s2 = ex.Message ?? String.Empty;
          if (s2.Length > 255)
            s2 = s2.Substring(0, 255);
          sCmd1.Parameters.Add(new SqlParameter("@Message", s2 ));

          string s3 = ex.StackTrace ?? String.Empty;
          if (s3.Length > 3000)
            s2 = s2.Substring(0, 3000);
          sCmd1.Parameters.Add(new SqlParameter("@StackTrace", s3));
        }
        else {
          sCmd1.Parameters.Add(new SqlParameter("@Source", String.Empty));
          sCmd1.Parameters.Add(new SqlParameter("@Message", String.Empty));
          sCmd1.Parameters.Add(new SqlParameter("@StackTrace", String.Empty));
        }
        sCmd1.ExecuteScalar();
      }
      catch (Exception ex2) {
        // Ну не шмогла... 
      }
}


— и вот эта конструкция из четырех строк и одной переменной только для того, чтобы проверить, что строка не null и не слишком длинная — меня совсем не радует...
То есть если есть более правильный путь — можно мне его рассказать?
Я понимаю, что есть готовые фреймворки для записи логов, но хочется научиться писать ПРАВИЛЬНЫЙ код...
Re: Запись логов в базу. Как это написать коротко и правильно?
От: catbert  
Дата: 16.12.13 12:26
Оценка:
Здравствуйте, SteeLHeaD, Вы писали:

SLH>3) Теперь вспоминаю, что строки могут быть слишком длинными для нашей таблицы, и тут уже получается совсем некрасиво:


SLH>
SLH>public static void WriteLog(Exception ex=null) {
SLH>    cn = CheckSqlConn(cn); // Проверяет, если коннекшн нулевой или не открытый - делает правильный коннекшн
SLH>      try {
SLH>        string sql = @"insert into [LogTable] ([Dt], [Source], [Message], [StackTrace], ) values (@Dt, @Source, @Message, @StackTrace)";
SLH>        SqlCommand sCmd1 = new SqlCommand(sql, cn);
SLH>        sCmd1.Parameters.Add(new SqlParameter("@dt", DateTime.Now));
SLH>        if (ex != null) {
SLH>          string s1 = ex.Source ?? String.Empty ;
SLH>          if (s1.Length > 255)
SLH>            s1 = s1.Substring(0, 255);
SLH>          sCmd1.Parameters.Add(new SqlParameter("@Source",  s1 ));

SLH>          string s2 = ex.Message ?? String.Empty;
SLH>          if (s2.Length > 255)
SLH>            s2 = s2.Substring(0, 255);
SLH>          sCmd1.Parameters.Add(new SqlParameter("@Message", s2 ));

SLH>          string s3 = ex.StackTrace ?? String.Empty;
SLH>          if (s3.Length > 3000)
SLH>            s2 = s2.Substring(0, 3000);
SLH>          sCmd1.Parameters.Add(new SqlParameter("@StackTrace", s3));
SLH>        }
SLH>        else {
SLH>          sCmd1.Parameters.Add(new SqlParameter("@Source", String.Empty));
SLH>          sCmd1.Parameters.Add(new SqlParameter("@Message", String.Empty));
SLH>          sCmd1.Parameters.Add(new SqlParameter("@StackTrace", String.Empty));
SLH>        }
SLH>        sCmd1.ExecuteScalar();
SLH>      }
SLH>      catch (Exception ex2) {
SLH>        // Ну не шмогла... 
SLH>      }
SLH>}

SLH>


SLH>- и вот эта конструкция из четырех строк и одной переменной только для того, чтобы проверить, что строка не null и не слишком длинная — меня совсем не радует...

SLH>То есть если есть более правильный путь — можно мне его рассказать?
SLH>Я понимаю, что есть готовые фреймворки для записи логов, но хочется научиться писать ПРАВИЛЬНЫЙ код...

Попробуйте вынести общий функционал в отдельный приватный метод. Вот так допустим:

string Normalize(string source, int maxCount)
{
    source = source ?? String.Empty;
    if (source.Length > maxCount)
        source = source.Substring(0, 255);
    
    return source;
}
Re[2]: Запись логов в базу. Как это написать коротко и правильно?
От: SteeLHeaD  
Дата: 17.12.13 05:42
Оценка:
Здравствуйте, catbert,
спасибо — это был простой и полезный совет!
Re: Запись логов в базу. Как это написать коротко и правильно?
От: Artem Korneev США https://www.linkedin.com/in/artemkorneev/
Дата: 18.12.13 09:29
Оценка: +1
Здравствуйте, SteeLHeaD, Вы писали:

SLH>Хочу из своей WinForms программы писать лог ошибок в базу.


А готовые решения рассматриваете?
log4net умеет писать логи в SQL-базу.
С уважением, Artem Korneev.
Re: Запись логов в базу. Как это написать коротко и правильно?
От: Sinatr Германия  
Дата: 18.12.13 13:58
Оценка:
Здравствуйте, SteeLHeaD, Вы писали:

SLH>Хочу из своей WinForms программы писать лог ошибок в базу.

SLH>То есть если есть более правильный путь — можно мне его рассказать?

Все банально и просто: id (время + уникальное чего-то, чтобы не было дупликатов) + blob (сериализованный exception + extra).

Такая запись всегда создастся (т.к. ид и блоб можно создать).

Фильтр по дате есть, остальное фильтровать при десериализации (более медленно, но кого это заботит).
---
ПроГLамеры объединяйтесь..
Re[2]: Запись логов в базу. Как это написать коротко и правильно?
От: abibok  
Дата: 18.12.13 20:45
Оценка:
S>Все банально и просто: id (время + уникальное чего-то, чтобы не было дупликатов) + blob (сериализованный exception + extra).

Могут быть проблемы с десериализацией и версионностью.
Re[3]: Запись логов в базу. Как это написать коротко и правильно?
От: Sinatr Германия  
Дата: 19.12.13 08:09
Оценка:
Здравствуйте, abibok, Вы писали:

A>Могут быть проблемы с десериализацией и версионностью.


В каком месте? Юзаю бинари и xml сериализацию без проблем, в блобе должно быть поле/свойство Version, десериализовать, проверять версию, разруливать если старая. Естествено при изменении версии блоба поддерживать совместимость снизу-вверх.

Насчет сериализации, в бинари есть [OptionalField] решает при добавлении новых полей. В xml все проще, но нужно быть осторожным с enum (никогда не убивать старые значения в enum).

Дополнительный бонус, это то, что блобы сложно редактировать — защищенность данных от изменения.

Минусы — это скорость фильтра по полям/свойствам блоба (т.к. нужно все блобы десериализовать).
---
ПроГLамеры объединяйтесь..
Re: Запись логов в базу. Как это написать коротко и правильно?
От: fmiracle  
Дата: 20.12.13 22:42
Оценка:
Здравствуйте, SteeLHeaD, Вы писали:
SLH> [StackTrace] [varchar](3000) NULL)
А если не влезет? Обидно будет изучать stacktrace, который вдруг неожиданно обрывается. 3000 для stacktrace не так уж много. varchar(8000) облегчают проблему, varchar(max) решают кардинально.

SLH>- и вот эта конструкция из четырех строк и одной переменной только для того, чтобы проверить, что строка не null и не слишком длинная — меня совсем не радует...


Постоянно повторяется что-то — выделяй метод.

SLH>То есть если есть более правильный путь — можно мне его рассказать?

SLH>Я понимаю, что есть готовые фреймворки для записи логов, но хочется научиться писать ПРАВИЛЬНЫЙ код...

Есть мнение, что писать правильный код — это использовать правильные фреймворки там, где они хорошо подходят и решают задачу лучше самописных изобретений.

На один момент обращу, однако, внимание:

private static SqlConnection cn;
...
cn = CheckSqlConn(cn);


По коду дальше соединение нигде не закрывается. Тогда после разовой записи у тебя висит это соединение открытым к sql серверу. Зачем?

Лучше всегда используй
using( var cn = new SqlConnection(connStr) ){
 ...
}


Соединения будут браться из пула и возвращаться обратно после использования. Это в целом стандартный и правлиьный подход в .net
Re[3]: Запись логов в базу. Как это написать коротко и правильно?
От: мыщъх США http://nezumi-lab.org
Дата: 21.12.13 04:44
Оценка:
Здравствуйте, abibok, Вы писали:

S>>Все банально и просто: id (время + уникальное чего-то, чтобы не было дупликатов) + blob (сериализованный exception + extra).


A>Могут быть проблемы с десериализацией и версионностью.

проблемы с версионностью решаются просто. в бинарном представлении записывается размер структуры и номер версии. при выходе следующей версии -- размер увеличивается. старые версии читают только те данные, которые они могут прочитать, отбрасывая остальные.

в принципе, достаточно одного размера без указания версии. но версия позволит располагать данные более компактно. любое представление данных в каком-то смысле является бинарным, даже если оно текстовое. скажем, версия 1.0 понимала только десятичные числа. в версии 2.0 добавили хекс, а в версии 3.0 еще и уникод поддержали. что может сделать версия 1.0 с файлом в уникоде, в котором числа записаны хексом?

ЗЫ. известный мне софтвер, пишущий логи в базу, удаляет старые логи при смене мажорных версий.
americans fought a war for a freedom. another one to end slavery. so, what do some of them choose to do with their freedom? become slaves.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.