outcoldman
outcoldman Denis Gladkikh

Конфигурирование log4net runtime & Получаем больше информации для тестов

.NET, C#, TDD, Log System, and log4net

Для логгирования приложений, написанных под .NET большинство разработчиков используют log4net (а другие разработчики используют log4 под другие платформы). Но так, к сожалению, бывает не везде. Так, например, в нашем проекте до сегодняшнего дня мы использовали свою самописную библиотеку для логгирования. Была она, видимо, написана давно нашим team leader, но так как нас сейчас покидает текущий team leader, мы решили так же распрощаться с некоторым legacy кодом, который был дорог нашему лидеру, но который не особо то мы хотим поддерживать. Сразу уточню, что код написан отлично, и наш team leader – отличный кодер, но log4net имеет больше преимуществ, а текущий логгер нам приходится частенько дописывать функционалом, который уже давно есть в log4net.

Большинство конфигурируют log4net при помощи config файла (попросту обычный xml файл), и скорее всего у фирм такой файл уже написан давно, и его просто копируют из проекта в проект, изменяя названия файлов или подправляя формат сообщений. А чтобы написать хороший конфиг рекомендую посмотреть на примеры, который предоставляет сама библиотека log4net. Самый используемый Appender – это, наверное, RollingFileAppender. Для этого сайта у меня, к примеру, такой упрощенный config:

<log4net>
    <!-- contexts:
    -->
    <!-- Setup the root category, add the appenders and set the default priority 
            Off, Fatal, Error, Warn, Info, Debug, All.-->
    <root>
        <level value="Error" />
        <appender-ref ref="RollingFileAppender" />
        <!--<appender-ref ref="TraceAppender" />-->
    </root>
    <appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
        <param name="File" value="App_Data\\Logs\\personalweb.log" />
        <param name="AppendToFile" value="true" />
        <param name="RollingStyle" value="Size" />
        <param name="MaxSizeRollBackups" value="10" />
        <param name="MaximumFileSize" value="1MB" />
        <param name="StaticLogFileName" value="true" />
        <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
        <layout type="log4net.Layout.PatternLayout">
            <header value="[Application Starts]&#13;&#10;" />
            <footer value="[Application Stops]&#13;&#10;" />
            <param name="ConversionPattern" value="%date{yyyy-dd-MM HH:mm:ss} [%thread] %-5level %logger{3} - %message%newline" />
        </layout>
    </appender>
    <appender name="TraceAppender" type="log4net.Appender.TraceAppender">
        <layout type="log4net.Layout.PatternLayout">
            <param name="ConversionPattern" value="%date{yyyy-dd-MM HH:mm:ss} [%thread] %-5level %logger{3} - %message%newline" />
        </layout>
    </appender>
 </log4net>

Логгер по большому счету должен спасать тогда, когда на сайте или в приложении валятся ошибки уже на live инсталяции, а понять о сути их происхождения сложно, тогда грамотно расставленные записи в лог помогут справиться нам с этой задачей. Но можно получить от него и больше пользы еще до этого, для этого мы заставим наш код, который выполняется в тестах, писать в Output окно Visual Studio. Я делаю это потому, что я тот программист, который запускает проект в режиме отладки только в крайнем случае, сначала я пишу код/тесты, проверяю их, потом строю приложение – смотрю, что все работает и с легкостью комичу код. И только в случае непонятных мне ошибок, странного поведения или memory-leak я начинаю отлаживать приложение. Такой подход очень сильно мне экономит время.

Так вот, тесты в моем процессе разработки играют большую роль, и, конечно же, я не хочу запускать тесты в режиме отладке, но хочу видеть как можно больше информации в output окне, потому мой конфигуратор для log4net выглядит следующим образом

public static class Log4NetUtils
{    private static bool _isConfigurated;
    private static readonly object _locker = new object();
     /// <summary>
    ///     Load configuration from config file
    /// </summary>
    public static void InitLogger()
    {        InitLogger(false);
    }
     /// <summary>
    ///     Load configuration from config file when <paramref name = "useBasicConfiguration" /> = false 
    ///     or set basic configuration with trace appender if <paramref name = "useBasicConfiguration" /> = true. 
    ///     Method is thread safe only for x86 and x64 architectures and Microsoft .NET platform.
    /// </summary>
    /// <param name = "useBasicConfiguration"></param>
    public static void InitLogger(bool useBasicConfiguration)
    {        if (!_isConfigurated)
        {            lock (_locker)
            {                if (!_isConfigurated)
                {                    if (useBasicConfiguration)
                        ConfigureRunTimeFromBasic();                    else
                        ConfigureFromFile();                    _isConfigurated = true;
                }
            }
        }
    }
     private static void ConfigureRunTimeFromBasic()
    {        TraceAppender traceAppender = new TraceAppender();
         PatternLayout patternLayout = new PatternLayout
                                          {
                                              ConversionPattern =                                                  "%date{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{2} - %message%newline"
                                          };
        patternLayout.ActivateOptions();
 
        traceAppender.Layout = patternLayout;
 
        BasicConfigurator.Configure(traceAppender);
    }
     private static void ConfigureFromFile()
    {        string fileConfiguration = ConfigurationManager.AppSettings.Get("Log4NetConfigurationFile");
        if (string.IsNullOrWhiteSpace(fileConfiguration))
            fileConfiguration = "log4net.config";
        XmlConfigurator.ConfigureAndWatch(new FileInfo(AppDomain.CurrentDomain.BaseDirectory + fileConfiguration));
    }
}

Соответственно для самого приложения вызывается метод Log4NetUtils.InitLogger() в Global.asax для веб приложения или в Main функции Windows-приложения, а вот в SetUp() для тестов я вызываю Log4NetUtils.InitLogger(true), который и включает мне логгирование всего в Output окно VS для тестов:

[TestFixture]class ParserSpec
{    // ReSharper disable InconsistentNaming
    [SetUp]    public void SetUp()
    {        Log4NetUtils.InitLogger(false);
    }
 
    [Test]    public void parse_string_to_int()
    {        int i = "1".To<int>();
        Assert.AreEqual(i, 1);
    }    //......
     // ReSharper enable InconsistentNaming
}

На самом деле у меня есть еще файл BaseSpec, в котором уже существует SetUp метод и делает он не только регистрацию log4net. А в целом получается очень удобно:

VS Output

P.S. Америку я тут не открывал, просто сохранил для потомков (будущих проектов) :)

Have feedback or questions? Looking for consultation?

My expertise: MongoDB, ElasticSearch, Splunk, and other databases. Docker, Kubernetes. Logging, Metrics. Performance, memory leaks.

Send me an email to public@denis.gladkikh.email.

The content on this site represents my own personal opinions and thoughts at the time of posting.

Content licensed under the Creative Commons CC BY 4.0.