Собираюсь начинать новый проект и постепенно приближаюсь к стадии написания некоторых базовых вещей. Решил собрать и систематизировать свои знания об некоторых аспектах разработки ПО на платформе ASP.Net, полученные за более чем год коммерческой разработки. В результате получилась вот такая статья. Она не претендует на принципиально новые вещи, это все давно знают, в определенном смысле это своеобразные best practices. Все, что написано ниже, скорее всего пригодится новичкам, но и опытные разработчики смогут почерпнуть для себя что-нибуть интересное.
Мы с вами живем во времена продвинутых средств разработки, отладчиков, поддерживающих отладку многопоточных приложений, и многих других чрезвычайно полезных вещей. Но как и всякое другое явление, такой прогресс имеет и свои минусы - на не самых быстрых машинах процесс пошаговой отладки может превратится в кошмар разработчика. Все виснет, дебаггер намекает вам, что пора бы и проапгрейдить машину, после получасового путешевствия по коду вашего детища вы в который раз жмете F10 и с ужасом летите вместе с выброшенным где-то в глубинах кода исключением на самый верхний уровень, в заботливо подставленный catch. Сообщение исключения говорит вам, что в метод пришли неверные аргументы, но абсолютно непонятно, откуда и каким образом они взялись. Стиснув зубы и вооружившись терпением вы в который раз начинаете охоту за мерзким багом…
У меня за спиной несколько лет занятий олимпиадами по программированию, которые сопровождались написанием кода на самых разных инструментах от Turbo Pascal до Visual Studio 2008 в самых разных условиях. Кто не знает, олимпиады обычно проходят в различных ВУЗах. Иногда убитые студентами компьютеры висли до невозможности, а количество вирусов на машине превышало всякие разумные рамки. Впрочем, в таких условиях пребывают все учасники соревнования, так что жаловатся нету времени - нужно решать поставленные задачи. Так вот, за эти годы я усвоил очень важную вещь: едва ли не лучший дебаггер - это консоль. Да-да, вот та самая, обычная черная штука, по которой вверх ползут серые буквы. Впрочем, цвета зависят от фантазии пользователя.
Даже на быстрых машинах консоль удобна при отладке длинных итеративных или рекурсивных вычислений - она выдает информацию намного быстрее обычного отладчика, сразу всю, вместе с промежуточными результатами, достаточно только вписать в интересующих вас местах Console.WriteLine, printf, system.out.println или аналог на языке, на котором вы пишете свои приложения. Консоль - это тот же лог, но для его просмотра не надо лезть в файлы, она у вас на экране и вы можете видеть сразу все, что вас интересует. В конце-концов, я думаю, что многие из разработчиков использовали ее хоть раз в жизни для отладки.
Сегодня я вам покажу на примере ASP.Net MVC приложения, как можно использовать консоль при отладке и логгировании. Итак, приступим.
Во-первых, нам нужно инизиализировать саму консоль. Для этого опишем небольший статический класс ConsoleManager, в который импортнем AllocConsole из kernel32.dll. Также добавим метод, который будет инициализировать консоль, устанавливать ее вывод и чистить ее перед стартом приложения:
public static class ConsoleManager
{
[DllImport("kernel32.dll", EntryPoint = "AllocConsole", CharSet = CharSet.Unicode)]
private static extern bool AllocConsole();
public static void InitializeConsoleManager()
{
#if CONSOLE
try
{
AllocConsole();
Console.SetOut(new TextWriter(new StreamWriter(Console.OpenStandardOutput(), Encoding.Default, 100)));
Console.Clear();
}
catch (Exception)
{
}
#endif
}
}
Поскольку мы не хотим, чтобы консоль была видна во время нормальной работы приложения на сервере, код, инициализирущий ее, мы заключили в директиву условной компиляции и конечно же не забыли определить символ CONSOLE в Debug конфигурации проекта. Теперь лезем в Global.asax и инициализируем консоль на старте приложения:
protected void Application_Start()
{
ConsoleManager.InitializeConsoleManager();
// ...
}
Ура, теперь при старте приложения у нас появляется черное окошко приложения! Отлично, но работа не этом не закончена - теперь мы немного украсим ее. Практически непременным атрибутом любого веб-приложения являються логи - поскольку часто они являються практически единственной уликой, по которой можно отследить ошибку на работающем сервере. Грех не дублировать сообщения логгера в нашу консоль - это экономит тучу времени при отладке. Для того, чтобы различать, какие сообщения надо писать в и в файл и в консоль, а какие только на консоль опишем перечисление:
public enum Severity
{
None,
Event,
Error,
Debug,
}
Все просто:
- None - маловажная информация, в файл не пишеться, на консоль выводится темно-серым цветом, чтоб не отвлекала; пример использования - логгирование http-реквеста;
- Event - событие в жизни сайта - пишется в файл, на консоль выводится зеленым цветом; пример - пользователь залогинился;
- Error - где-то случилась беда - пишется в файл, на консоль ярко-красным цветом; пример использования - необработанное исключение;
- Debug - почти то же что и None, но более важное - выводится только на консоль голубым цветом; пример использования - промежуточные результаты длинного вычисления, которое нужно проверить.
Для определения цвета, соответствующего определенному типу сообщения напишем простенький extension-метод:
public static ConsoleColor GetLogEntryColor(this Severity severity)
{
switch (severity)
{
case Severity.None:
return ConsoleColor.DarkGray;
case Severity.Event:
return ConsoleColor.Green;
case Severity.Error:
return ConsoleColor.Red;
case Severity.Debug:
return ConsoleColor.Cyan;
default:
throw new ArgumentException(string.Format("Unknown severity: '{0}'", severity));
}
}
Тут надо заметить, что намного красивее был бы декларативный подход:
public enum Severity
{
[SeverityColor(ConsoleColor.DarkGray)]
None,
[SeverityColor(ConsoleColor.Green)]
Event,
[SeverityColor(ConsoleColor.Red)]
Error,
[SeverityColor(ConsoleColor.Cyan)]
Debug,
}
но из соображений производительности я от него отказался. Вопрос спорный и возможно в будущем я к нему еще вернусь. Теперь собственно осталось только описать наш логгер, не забыв, что ASP.Net приложения многопоточные:
public static class Logger
{
[ThreadStatic]
private static Severity m_CurrentSeverity;
/// <summary>
/// Writes debug message to log
/// </summary>
public static void WriteToLog(string message)
{
WriteToLog(message, Severity.Debug);
}
public static void WriteToLog(string message, Severity severity)
{
lock (typeof(Logger))
{
m_CurrentSeverity = severity;
WriteLineStart();
WriteLine(message);
}
}
private static void WriteLine(string message)
{
Write(message + Environment.NewLine);
}
private static void Write(string message, params object[] parameters)
{
Write(string.Format(message, parameters));
}
private static void Write(string message)
{
Console.ForegroundColor = m_CurrentSeverity.GetLogEntryColor();
Console.Write(message);
if(m_CurrentSeverity == Severity.Error // m_CurrentSeverity == Severity.Event)
{
// file logging
}
}
private static void WriteLineStart()
{
Write("{0} -> ", DateTime.Now);
}
}
Все намеренно упрощенно для большего понимания. Консоль во время отладки выглядит применно вот так:
По собственному опыту скажу, что с такой консолью жизнь девелопера становится намного легче.
Спасибо.
UPD. Мне тут подсказали, что существует альтернативный инструмент - программа Debug View, которая реализует практически идентичную функциональность.