Отладка ошибок в фоновых службах

Это не вопрос, а ответ, который, надеюсь, поможет другим людям.

Те, кто ранее писал службу Windows, знают, в какой миссии можно найти ошибку, особенно если это происходит только в живой среде. В моем случае у меня была служба, которая работала гладко в течение нескольких часов, а затем упала из-за ошибки. Нет трассировки стека. Удачи найти иглу в стоге сена.

Служба действительно создавала файл журнала, и код был завален записью журнала, но, как было указано, было создано файлы журнала размером 500 МБ! Вы едва могли открыть файл, не считая его анализа. Но как вы обошли эту проблему? Вы можете попытаться создать файлы журналов с меньшей информацией или автоматически удалить старые записи журналов, поскольку новые записи записываются, но затем вы теряете важный контекст ошибки.

Решение - это файл журнала, который будет отслеживать циклы в вашем коде и автоматически удалять записи журнала для каждой успешной итерации этого цикла. Таким образом, вы можете поддерживать файл с задержанным долгом, который остается относительно небольшим одновременно. Когда ваш сервис ломается, ваш файл журнала скажет вам, где именно произошло, плюс весь необходимый контекст, чтобы объяснить, как и почему это произошло.

Вы можете загрузить этот генератор журнала из http://sourceforge.net/projects/smartl/files/?source=navbar. Это самостоятельный класс и все его методы статичны. Примерный класс, чтобы показать вам, как правильно использовать методы ведения журнала:

    public void ExampleMethod()
    {           
        SmartLog.EnterMethod("ExampleMethod()"); 
        try
        {
            SmartLog.Write("Some code happening before the loop");

            Guid exampleLoopID = SmartLog.RegisterLoop("exampleLoopID");
            for (int i = 0; i < 10; i++)
            {
                SmartLog.IncrementLoop(exampleLoopID);

                SmartLog.Write("Some code happening inside the loop.");

            }
            SmartLog.CompleteLoop(exampleLoopID);

            SmartLog.Write("Some code happening after the loop.");

            SmartLog.LeaveMethod("ExampleMethod()");
        }
        catch (Exception ex)
        {
            SmartLog.WriteException(ex);
            SmartLog.LeaveMethod("ExampleMethod()");
            throw;
        }
    }

Убедитесь, что ваше приложение имеет доступ на чтение и запись в своей корневой папке.

Если вы выполняете код и вы его разбиваете в цикле, файл журнала будет выглядеть примерно так:

. ENTER METHOD: FirstMethod()
some code happening here.
Calling a different method:

. . ENTER METHOD: ExampleMethod()
some code happening before the loop.

LOOP: doWorkLoopID [4135a8ed-05b7-45de-b887-b2ab3c638faa] - CURRENT ITERATION: 20
some code happening inside the loop.

Как только цикл будет завершен, его содержимое будет удалено, и ваш файл журнала будет выглядеть следующим образом:

. ENTER METHOD: FirstMethod()
some code happening here.
Calling a different method:

. . ENTER METHOD: ExampleMethod()
some code happening before the loop.

LOOP: doWorkLoopID [4135a8ed-05b7-45de-b887-b2ab3c638faa] - TOTAL ITERATIONS: 22

some code happening after the loop.
. . LEAVING METHOD: ExampleMethod()

some code happening here.
some code happening here.
. LEAVING METHOD: FirstMethod()

Надеюсь, это поможет кому-то решить эту проблему, которая в противном случае могла занять недели. Он наверняка помогло.

Ответ 1

Вот мое статическое решение для регистрации. Полезно для ВСЕХ проектов не только услуг:

Начало приложения:

MyLog.Reset();

. Каждый статический или нестатический метод начинается с:

System.Diagnostics.StackTrace stackTrace = new System.Diagnostics.StackTrace(); MyLog.Log("", stackTrace.GetFrame(0).GetMethod().DeclaringType.ToString(), stackTrace.GetFrame(0).GetMethod().Name, 0);

Результат - источник диаграммы graphviz и выглядит так: Обратите внимание, что последнее закрывающее фигурное скобки должно быть добавлено вручную, когда вы скопируете текст из log.text, чтобы создать диаграмму GraphViz.

digraph G{arrowsize=2.0; ratio=fill; node[fontsize=24];graph [fontsize=24] edge [fontsize=24] node [fontsize=24] ranksep = 1.5 nodesep = .25 edge [style="setlinewidth(3)"]; 

subgraph cluster_Riogrande_UI { node [style=filled]; label = "Riogrande_UI"; color=red  
subgraph cluster_UsersForm { node [style=filled]; _ctor_UF; label = "UsersForm"; color=blue }}
subgraph cluster_Riogrande_DL { node [style=filled]; label = "Riogrande_DL"; color=red  
subgraph cluster_DataAccessUsers { node [style=filled]; _ctor_DAU; label = "DataAccessUsers"; color=blue    }}
_ctor_UF -> _ctor_DAU;
}

Это диаграмма, полученная из GraphViz:

The result

Это класс, который я использую:

namespace Riogrande
{
    public class MyLog
    {
        private static int MaximAcceptedLevel = 5;
        private static string lastMethodName = string.Empty;
        private static string filePath = "log.txt";

        public static void Log(string namespaceName, string className, string methodName, int logLevel)
        {
            if (logLevel < MaximAcceptedLevel)
            using (StreamWriter w = File.AppendText(filePath))
            {
                string namespceName = className.Substring(0, className.LastIndexOf('.')).Replace('.', '_');

                if (className.Contains('.'))
                {
                    className = className.Substring(className.LastIndexOf('.') + 1);
                }
                if (className.Contains('+'))
                {
                    className = className.Substring(0, className.LastIndexOf('+'));
                }
                className = className.Replace('.', '_');
                string cls = "";
                for (int i = className.Length-1; i > -1; i--)
                {
                    if (Char.IsUpper(className[i]))
                    {
                        if (cls.Length < 3)
                        {
                            cls = className[i] + cls;
                        }
                    }
                }
                string currentMethodName = methodName.Replace('.', '_') + "_" + cls;
                w.WriteLine("subgraph cluster_" + namespceName + " { node [style=filled]; label = \"" + namespceName + "\"; color=red   ");
                w.WriteLine("subgraph cluster_" + className + " { node [style=filled]; " + currentMethodName + "; label = \"" + className + "\"; color=blue }}");
                if (!string.IsNullOrEmpty(lastMethodName))
                {
                    w.WriteLine(lastMethodName + " -> " + currentMethodName + ";");
                }
                lastMethodName = currentMethodName;
            }
        }

        public static void Reset()
        {
            File.Delete(filePath);
            using (StreamWriter w = File.AppendText(filePath))
            {
                w.WriteLine("digraph G{arrowsize=2.0; ratio=fill; node[fontsize=24];graph [fontsize=24] edge [fontsize=24] node [fontsize=24] ranksep = 1.5 nodesep = .25 edge [style=\"setlinewidth(3)\"]; ");
                w.WriteLine();
            }
        }    
    }
}

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

Ответ 2

Хорошая работа, но если настоящая проблема работает с большими журналами или даже с огромными журналами, вы должны взглянуть на microsoft LogParser. Пока вы правильно отформатируете файлы журнала, вы сможете запросить журнал так же, как и в SQL. Вы даже можете создавать csv файлы из результатов и анализировать их или использовать библиотеки для выполнения сложных операций в приложении .net.

Например, скажите, что вы хотите выбрать все записи IIS для данной страницы и сохранить результаты как CSV, вы можете сделать что-то вроде этого

logparser "select * into LANDINGPAGEHITS.CSV from [yourlogfile] where cs-uri-stem like '/home.aspx"

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

Ответ 3

Решение - это файл журнала, который будет отслеживать циклы в вашем коде и автоматически удалять записи журнала для каждой успешной итерации этого цикла. Таким образом, вы можете поддерживать файл с задержанным долгом, который остается относительно небольшим одновременно. Когда ваш сервис ломается, ваш файл журнала скажет вам, где именно произошло, плюс весь необходимый контекст, чтобы объяснить, как и почему это произошло.

Вы можете загрузить этот генератор журнала из http://sourceforge.net/projects/smartl/files/?source=navbar. Это самостоятельный класс и все его методы статичны. Примерный класс, чтобы показать вам, как правильно использовать методы ведения журнала:

public void ExampleMethod()
{           
    SmartLog.EnterMethod("ExampleMethod()"); 
    try
    {
        SmartLog.Write("Some code happening before the loop");

        Guid exampleLoopID = SmartLog.RegisterLoop("exampleLoopID");
        for (int i = 0; i < 10; i++)
        {
            SmartLog.IncrementLoop(exampleLoopID);

            SmartLog.Write("Some code happening inside the loop.");

        }
        SmartLog.CompleteLoop(exampleLoopID);

        SmartLog.Write("Some code happening after the loop.");

        SmartLog.LeaveMethod("ExampleMethod()");
    }
    catch (Exception ex)
    {
        SmartLog.WriteException(ex);
        SmartLog.LeaveMethod("ExampleMethod()");
        throw;
    }
}

Убедитесь, что ваше приложение имеет доступ для чтения и записи в своей корневой папке.

Если вы выполняете код и вы его разбиваете в цикле, файл журнала будет выглядеть примерно так:

. ENTER METHOD: FirstMethod()
some code happening here.
Calling a different method:

. . ENTER METHOD: ExampleMethod()
some code happening before the loop.

LOOP: doWorkLoopID [4135a8ed-05b7-45de-b887-b2ab3c638faa] - CURRENT ITERATION: 20
some code happening inside the loop.

Как только цикл будет завершен, его содержимое будет удалено, и ваш файл журнала будет выглядеть следующим образом:

. ENTER METHOD: FirstMethod()
some code happening here.
Calling a different method:

. . ENTER METHOD: ExampleMethod()
some code happening before the loop.

LOOP: doWorkLoopID [4135a8ed-05b7-45de-b887-b2ab3c638faa] - TOTAL ITERATIONS: 22

some code happening after the loop.
. . LEAVING METHOD: ExampleMethod()

some code happening here.
some code happening here.
. LEAVING METHOD: FirstMethod()