Log4Net 설정 방법

java 내에 동작하는 각종 Log 내용을 쌓기 위한 모듈인데, 내부적으로 어떠한 설계로 구성되었는지는 살펴보지 않아, 잘은 모르지만, 성능적 저하 없이 많은 양의 Log 관리에 훌륭하게 대처 할 수 있다. 또, Log로 남겨지는 형태를 단순 Text 파일 형식에서 부터 DB 파일까지 그 대응적 능력이 우수하다.
.NET 프로그램에서도 이 강력한 log4j를 사용할 수 있는데, 그 도구가 바로 log4net 이다. ( http://logging.apache.org/log4net/ )
여기에 쓸 내용은 지금까지 Application 내에 적용하면서 진행되었던 사항들을 적용 순서 대로 나열할 예정이다. 그래서 그 순서가 log4net에 대한 기초와는 다르다. 그러다 보니, 독자가 현재 적용 중인 Project나 구성과는 사뭇 다를 수 있으며, 스스로 잘못 이해한 부분으로 인한 오류도 있다.
더욱이 Web-Base가 아닌 WinForm-Base로 제작하면서 익힌 기능이다 보니, Web 기반의 구성과 일부 차이가 발생할 수 도 있다. 이점을 이해하면서 살펴보면 좋을 것 같다. 또, 이 글 외에도 많은 log4net에 대한 예제, 구성 이야기들을 살펴볼 수 있으므로, 사뭇 달라도, 자신의 적용과 유사한 다른 글들을 찾아보기도 쉬울 것이다.

log4net 접근 시작

최초에는 독자적으로 log 파일을 쌓고 구성했다. 그런데, 문제는 Single Thread 기반의 Application이다 보니, 로그 쌓는데, 의외로 많은 Process Time을 잡아 먹는 문제점이 발생했다. 로그는 좀 더 Detail 하게 쌓고 싶었지만, 그 성능적 문제로 인해 log를 쌓기에 두려움까지 얻게 되었다.
그러다가, java 관련 프로젝트를 수행하면서 log4j 에 대한 접근을 좀 하고 나니, log 쌓는 작업에 대한 대치성에 대해서 고민하게 되었고, 잠시 짬을 두어 log4net을 살펴보았다.
오픈 소스다 보니, 소스 자체를 공개하고 있으며, 다양한 Platform에 대한 컴파일이 이미 되어 있어, 사실 자신이 원하는 프로젝트에 대한 Platform에 맞추어 미리 컴파일된 버전을 바로 연결해서 사용가능 하다.
http://logging.apache.org/log4net/download_log4net.cgi 에서 다운로드 페이지 내에, Binaries 중,
log4net-1.2.13-bin-newkey.zip 을 다운 받았다. 그 안을 열면 여러가지 폴더가 있는데, bin 폴더에 들어가, 자신의 .NET Framework 버전에 맞추어 들어가면, log4net.dll 이 있는데, 이 파일만 꺼내오면 된다.

이제 자신의 Project 내에 저 파일을 복사( 개인적으로는 Solution 폴더 바로 아래에 Assembly 폴더를 만들어서 그 안에 복사) 해준다.

이제 자신의 프로젝트에 참조를 건다.


참조 건 뒤에 빌드 할 때 마다 파일이 복사되서 전달 될 수 있도록 Copy Local 이 True인지도 살펴본다.
(간혹 이 부분을 누락해서, 배포할 때, DLL이 빠져서 실제 이 배포본을 받는 사람은 실행이 안되는 문제가 발생하기도 한다.)

그리고 로그를 쌓는 로직을 작성한다.
그리고 이제 소스 상에서 다음과 같이 추가한다.
여기서는 제일 만만한 Form1.cs 파일을 열고 수정한다.
최초로 열면 아래와 같은 소스이다.

  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Data;
  5. using System.Drawing;
  6. using System.Linq;
  7. using System.Text;
  8. using System.Windows.Forms;
  9. namespace MyTestProject
  10. {
  11.     public partial class Form1 : Form
  12.     {
  13.         public Form1()
  14.         {
  15.             InitializeComponent();
  16.         }
  17.     }
  18. }

이제 이 소스를 다음과 같이 수정한다.

  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Data;
  5. using System.Drawing;
  6. using System.Linq;
  7. using System.Text;
  8. using System.Windows.Forms;
  9. namespace MyTestProject
  10. {
  11.     public partial class Form1 : Form
  12.     {
  13.         private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
  14.         public Form1()
  15.         {
  16.             log.Info(“Form Init Start”);
  17.             InitializeComponent();
  18.             log.Info(“Form Init End”);
  19.         }
  20.     }
  21. }

이제 프로젝트를 실행해보자.
분명 로그를 쌓으라고는 했으나, 로그가 쌓이지는 않는다. 당연하다.
로그를 쌓기 위한 log 개체는 만들었다.

  1. private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

하지만, 위의 개체에다 아무리 로그를 쌓으라고 해도 어디다가 어떻게 쌓아야 될지에 대한 설정이 없으므로 쌓일리 없다.
즉 로그가 모두 무시되는 상황. 이를 위해서 설정을 해주어야 한다.
이 설정 방법은 크게 두 가지가 있는데, 하나는 정적인 방법이고, 다른 하나는 동적인 방법이다.
그 방법이 아래와 같다.

로그 쌓기 설정 그 방법 1.

구글을 통해 이런 저런 설정 관련 예제를 받으면 대부분은 .config 파일에다 그 설정을 넣는 방법을 제공한다.
즉 응용 프로그램 프로젝트의 경우 App.config 파일이 그 해당 파일인데 응용 프로그램 프로젝트를 최초로 만들 때는 없는 경우가 많다. 그 경우 새 항목을 추가해서 새로 만들어 주도록 한다.


이제 App.config 파일을 열어보면, 설정을 위한 XML 파일이 열린다.

  1. <?xml version=“1.0” encoding=“utf-8” ?>
  2. <configuration?>
  3. </configuration?>

이 안을 다음과 같이 설정한다.

  1. <?xml version=“1.0” encoding=“utf-8” ?>
  2. <configuration?>
  3.   <configsections?>
  4.     <section name=“log4net” type=“log4net.Config.Log4NetConfigurationSectionHandler, log4net” /?>
  5.   </configsections?>
  6.   <log4net?>
  7.     <appender name=“RollingFile” type=“log4net.Appender.RollingFileAppender”?>
  8.       <file value=“example.log” /?>
  9.       <appendtofile value=“true” /?>
  10.       <maximumfilesize value=“100KB” /?>
  11.       <maxsizerollbackups value=“2” /?>
  12.       <layout type=“log4net.Layout.PatternLayout”?>
  13.         <conversionpattern value=“%level %thread %logger – %message%newline” /?>
  14.       </layout?>
  15.     </appender?>
  16.     <!– Set root logger level to DEBUG and its only appender to A1 —?>
  17.     <root?>
  18.       <level value=“INFO” /?>
  19.       <appender-ref ref=“RollingFile” /?>
  20.     </root?>
  21.   </log4net?>
  22. </configuration?>

그리고 Properties 폴더를 열고, AssemblyInfo.cs 파일을 연다.

이제 맨 아래 줄에다가, 다음 줄을 삽입한다.

  1. [assembly: log4net.Config.XmlConfigurator(Watch = true)]

앞에서 로그 쌓는 로직이 정상적으로 작성되어 있다면, 이제 다시 프로젝트를 실행해보자.
실행 한 뒤에 Build 결과물 폴더를 열어보면 example.log 라는 파일이 생성되는데 그 안에 보면,
Form Init Start와 Form Init End가 보일 것이다.


설정이야 어쨌던 로그가 쌓인다.

로그 쌓기 그 방법 2

대부분의 경우는 위의 경우에 다 처리 될 수 있다. (상세 설정은 추가적인 검색을 하면 상세한 설정이 가능하다.)
그런데, 이런 저런 프로젝트에 적용하다가 보니, 이 로그 설정을 동적으로 설정해야 할 경우가 발생했다.
즉 app.config를 이용해서 설정하는 방식으로는 그 설정 내용을 마음대로 주무르기가 무척 힘들다는 사실이다.
예를 들면 로그 파일의 이름이나, 경로 같은 경우다. 경로가 딱 정해진 위치라면 상관 없지만, 프로그램이 실행된 뒤, 동적으로 변경되어야 하는 경우라면 이야기가 많이 달라진다.
예를 들면 Application 관련 설정이 다른 위치에 저장되고, 설정 창을 통해 로그 파일의 위치가 변경되는 경우라면 어떻게 할 것인가? 그럼 매번 app.config를 변경해서 재실행해야 할까?
이런 저런 생각에 동적(코드상)으로 변경하는 방식을 찾아보았고, 그 방법을 정리해 보았다.
먼저 동적인 방법으로 설정을 하기 위해서는 log 개체에 대한 구성체계를 가져와야 한다.

  1. log4net.Repository.Hierarchy.Hierarchy hierarchy = (log4net.Repository.Hierarchy.Hierarchy)log4net.LogManager.GetRepository();
  2. hierarchy.Configured = true;

이제 모든 설정은 hierarchy를 통해서 진행되게 된다.
먼저 제일 중요한 로직이 어디다가 어떻게 쌓을지에 대해서다.
log4net 에서 제공되는 쌓는 방식은 19가지 정도 되지만, 위의 예제도 그렇듯이, 파일로 제공하는 형태로 구성할 예정이다. 방법 1과 동일한 방식으로 쌓으려면 아래와 같은 로직을 통해 쌓는 방식에 대한 클래스를 생성한다.

  1. log4net.Appender.RollingFileAppender rollingAppender = new log4net.Appender.RollingFileAppender();
  2. rollingAppender.File = “C:\Temp\Test.log”// 전체 경로에 생성할 메인 로그 파일 이름
  3. rollingAppender.AppendToFile = true;
  4. rollingAppender.RollingStyle = log4net.Appender.RollingFileAppender.RollingMode.Date;
  5. rollingAppender.LockingModel = new log4net.Appender.FileAppender.MinimalLock();
  6. rollingAppender.DatePattern = “_yyyyMMdd\”.log\””// 날짜가 지나간 경우 이전 로그에 붙을 이름 구성
  7. log4net.Layout.PatternLayout layout = new log4net.Layout.PatternLayout(“%date [%property{buildversion}] %-5level %logger – %message%newline”);
  8. rollingAppender.Layout = layout;

여기에 있는 RollongFileAppender는 log 내용을 파일로 쌓는 역할을 제공하는 모듈이다.
이 모듈에 대한 개체를 생성했으면, 이제는 hierarchy에 붙이도록 한다.

  1. hierarchy.Root.AddAppender(rollingAppender);
  2. rollingAppender.ActivateOptions();

이제 로그를 쌓는 레벨을 설정한다.

  1. hierarchy.Root.Level = log4net.Core.Level.All;

이 로직을 로그가 쌓이기 전에 한번 실행 할 수 있도록 구성하면 된다. 다음 코드는 위의 부분 부분을 모두 합친 부분이다.

  1. log4net.Repository.Hierarchy.Hierarchy hierarchy = (log4net.Repository.Hierarchy.Hierarchy)log4net.LogManager.GetRepository();
  2. hierarchy.Configured = true;
  3. log4net.Appender.RollingFileAppender rollingAppender = new log4net.Appender.RollingFileAppender();
  4. rollingAppender.File = “C:\Temp\Test.log”// 전체 경로에 생성할 메인 로그 파일 이름
  5. rollingAppender.AppendToFile = true;
  6. rollingAppender.RollingStyle = log4net.Appender.RollingFileAppender.RollingMode.Date;
  7. rollingAppender.LockingModel = new log4net.Appender.FileAppender.MinimalLock();
  8. rollingAppender.DatePattern = “_yyyyMMdd\”.log\””// 날짜가 지나간 경우 이전 로그에 붙을 이름 구성
  9. log4net.Layout.PatternLayout layout = new log4net.Layout.PatternLayout(“%date [%property{buildversion}] %-5level %logger – %message%newline”);
  10. rollingAppender.Layout = layout;
  11. hierarchy.Root.AddAppender(rollingAppender);
  12. rollingAppender.ActivateOptions();
  13. hierarchy.Root.Level = log4net.Core.Level.All;

프로그램 제일 먼저 시작하는 로직에 추가하면 된다. 대개의 경우에는 program.cs 파일내 추가해주면 된다.

  1. static void Main()
  2. {
  3.     #region 로그 설정
  4.     log4net.Repository.Hierarchy.Hierarchy hierarchy = (log4net.Repository.Hierarchy.Hierarchy)log4net.LogManager.GetRepository();
  5.     hierarchy.Configured = true;
  6.     log4net.Appender.RollingFileAppender rollingAppender = new log4net.Appender.RollingFileAppender();
  7.     rollingAppender.File = “C:\Temp\Test.log”// 전체 경로에 생성할 메인 로그 파일 이름
  8.     rollingAppender.AppendToFile = true;
  9.     rollingAppender.RollingStyle = log4net.Appender.RollingFileAppender.RollingMode.Date;
  10.     rollingAppender.LockingModel = new log4net.Appender.FileAppender.MinimalLock();
  11.     rollingAppender.DatePattern = “_yyyyMMdd\”.log\””// 날짜가 지나간 경우 이전 로그에 붙을 이름 구성
  12.     log4net.Layout.PatternLayout layout = new log4net.Layout.PatternLayout(“%date [%property{buildversion}] %-5level %logger – %message%newline”);
  13.     rollingAppender.Layout = layout;
  14.     hierarchy.Root.AddAppender(rollingAppender);
  15.     rollingAppender.ActivateOptions();
  16.     hierarchy.Root.Level = log4net.Core.Level.All;
  17.     #endregion
  18.     Application.EnableVisualStyles();
  19.     Application.SetCompatibleTextRenderingDefault(false);
  20.     Application.Run(new Form1());
  21. }

이제 app.config 없이도 log가 쌓이게 된다.

정리

이 포스트의 목적은 바로 이 2번째 방법에 따른 방식을 소개하기 위한 글이다.
log4net의 기능의 강력함은 동작 중에 무시할 만큼의 리소스를 소모하면서 강력하게 로그를 쌓는 방식에 있을 것이다. 더욱이 자동으로 파일로 떨구기도 하고, 데이터베이스에 쌓을 수도 있기 때문에, 활용도에서도 우수하다.
개인적으로 현재 프로젝트를 모두 이 log4net 으로 전환하고, 쌓이는 로그를 WinTail(http://www.baremetalsoft.com/wintail/)  이라는 프로그램을 통해서 살펴보고 있다. 동작 중에 발생되는 각종 값에 대한 감시 및 데이터 체크는 모두 이를 통해서 하고 있다. 동적으로 프로그램을 실행하면서 디버깅을 할 때 의외로 편하다.
닷넷 프로그램으로 로깅을 남기는 작업을 해보고 있다면 한번 시도해보심이?

log4net을 이용한 Logging

log4net을 이용한 Logging
Apache log4net은 다양한 출력 타겟으로 로깅을 할 수 있는 .NET용 오픈소스 라이브러리이다. log4net은 Java에서 사용하는 log4j 라이브러리를 포팅한 것이다. log4net을 사용하여 로깅을 하기 위해서는 일반적으로 다음과 같은 절차를 따른다.

  1. log4net을 NuGet에서 다운받는다. 다운로드가 완료되면 자동으로 log4net 이 References 에 추가된다.PM> Install-Package log4net
    log4net는 아래 그림과 같이 여러 네임스페이스들로 나뉘어지고 각 네임스페이스에는 여러 클래스들을 제공하고 있다.
    log4net 네임스페이스
  2. 두번째로 App.config 파일에 log4net 관련 기본 셋팅들을 설정한다. (주: Desktop Application인 경우는 App.config를 를 사용하고, 웹인 경우는 Web.config를 사용. 하지만, 아래 Step 3의 XmlConfigurator 안에서 임의로 Config 파일을 지정할 수도 있음. 이렇게 Config 파일을 이용하지 않고 물론 C#에서 직접 핸들링할 수도 있음)App.config 설정은 먼저 configSections 안에 log4net 섹션 (주: 임의명칭 사용가능)을 추가하고 Type을 아래와 같이 Log4NetConfigurationSectionHandler 클래스로 지정한다.
    log4net은 콘솔, 파일, 데이타베이스 등 여러 곳에 로그를 출력시킬 수 있는데, 이들 각각의 출력 타겟을 appender로 지정한다. 예를 들어, 아래 샘플은 콘솔 로깅을 위한 appender (ConsoleAppender로)를 지정하고 있는데, 파일 로깅을 위해서는 FileAppender를, DB 로깅을 위해서는 AdoNetAppender 등을 지정하면 된다. 물론 복수 타겟으로 로깅할 수도 있다. appender에는 임의의 이름 (여기서는 ConsoleLog)을 지정하고 해당 로그 타켓의 Type을 지정한다. appender 안에는 여러 옵션들을 지정할 수 있는데, 아래의 경우 layout을 지정하고 있다. 이 layout의 ConversionPattern 옵션은 로그 출력 형식을 지정하게 되는데, 아래 예제에서는 현재 날짜/시간을 나타내는 %date과 로그 메서드 호출로부터 전달되는 메시지인 %message를 사용하여 출력 포맷을 정하고 있다.
    마지막으로 이러한 설정들 밑에 root 노드가 있는데, 여기서는 일반적으로 어느 레벨까지 출력한 것인가와 어떤 타겟에 출력할 것인가를 지정하게 된다. 레벨은 ALL, DEBUG, INFO, WARN, ERROR, FATAL, OFF 등 7가지가 있는데, ALL은 모두 로깅한다는 것을 의미하며, OFF 는 아무것도 로깅하지 않는다는 것을 의미한다. 따라서 실제 사용되는 로깅 레벨은 나머지 5가지로 봐도 무방하다. 아래 예제에선 DEBUG 레벨 이상을 출력하도록 하고 있는데, DEBUG 레벨이 가장 낮은 레벨이므로 사실상 모두 로깅하는 것과 같다. level 노드 밑에 있는 appender-ref 노드는 실제 타겟으로 사용할 appender 를 지정하게 된다. appender 노드를 설정했다하더라도 appender-ref 노드에서 해당 appender 명을 추가하지 않으면 해당 타겟으로 로깅을 수행하지 않는다.

    예제

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
        <configSections>
        <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
        </configSections>
      
        <log4net>
        <appender name="ConsoleLog" type="log4net.Appender.ConsoleAppender">
            <layout type="log4net.Layout.PatternLayout">
            <param name="ConversionPattern" value="%date : %message %newline"/>
            </layout>
        </appender>
        <root>
            <level value="DEBUG"/>
            <appender-ref ref="ConsoleLog"/>
        </root>
        </log4net>
      
    </configuration>
    
  3. 다음으로 소스코드에서 아래와 같이 XmlConfigurator()를 실행해야 하는데, 이는 XML Config 파일 (예: App.config)을 사용하여 log4net 관련 기본 셋팅들을 사용하도록 한다. 이 문장은 프로그램 내에서 한번만 실행하면 되며, 특별히 어떤 소스파일에 넣어야 한다는 규칙은 없지만, 주로 AssemblyInfo.cs나 Main() 메서드가 있는 소스 파일(예: Program.cs)에 넣어 준다.
    [assembly: log4net.Config.XmlConfigurator(Watch = true)]
    // 임의의 Config 파일을 지정할 경우:
    // [assembly: log4net.Config.XmlConfigurator(ConfigFile = "My.config", Watch = true)]
    
  4. 위에서 여러 log4net 관련된 기본 셋팅이 완료되면, 이제 C# 코드에서 LogManager.GetLogger()를 통해 ILog 인터페이스를 얻어 log4net 기능을 사용하면 된다.LogManager.GetLogger()를 매번 사용해도 되지만, 일반적으로 아래 예제와 같이 Static 필드를 만들어 사용하면 편리하다. LogManager.GetLogger()는 ILog를 인터페이스를 리턴하고 ILog는 Debug(), Info(), Warn(), Error(), Fatal() 등과 같은 레벨별 로깅 메서드들을 가지고 있다.

    예제

    using System;
    using log4net;  // log4net 네임스페이스
    // 프로그램 내 한번만 지정
    [assembly: log4net.Config.XmlConfigurator(Watch = true)]  
    namespace LogApp
    {
        class Program
        {
            // 로거 ILog 필드
            private static ILog log = LogManager.GetLogger("Program");
            static void Main(string[] args)
            {
                // 로거 사용 : 5가지 레벨의 메서드들
                log.Debug("Main() started"); 
                log.Info("My Info");
                log.Warn("My Warning");
                log.Error("My Error");
                log.Fatal("My Fatal Error");
            }
        }
    }
    
파일 로깅 (File Logging)
로그 데이타를 파일에 출력하기 위해서는 App.config (혹은 Web.config)에 아래와 같이 FileAppender 를 추가하고, root/appender-ref 에 해당 appender를 추가하면 된다. 아래 예제에서는 level을 Info 로 지정하였으므로, INFO 레벨 이상만 로그파일 Logs.txt에 로깅된다.

예제

<log4net>
<appender name="LogFileAppender" type="log4net.Appender.FileAppender">
  <param name="File" value="Logs.txt" />
  <param name="AppendToFile" value="true" />
  <layout type="log4net.Layout.PatternLayout">
    <param name="ConversionPattern" value="%date [%logger] %message %newline" />
  </layout>
</appender>
<root>
  <level value="Info"/>
  <appender-ref ref="LogFileAppender"/>
</root>
</log4net>

데이타베이스 로깅 (DB Logging)
로그 데이타를 DB에 출력하기 위해서는 App.config (혹은 Web.config)에 아래와 같이 AdoNetAppender 를 추가하고, root/appender-ref 에 해당 appender를 추가하면 된다. DB는 ADO.NET이 지원하는 모든 DB를 지원한다.
DB 로깅을 위해서는 미리 로그 테이블을 생성해 두어야 한다. 또한, App.config 파일에 해당 로그 테이블에 대한 INSERT문과 컬럼 데이타 타입을 appender 노드에 아래 예와 같이 정의해 주어야 한다.
아래 예제의 첫번째 CREATE TABLE문은 SQL Server에 Logs 라는 테이블을 만들 때 사용하는 SQL문 예제이다. 그리고 다음 두번째는 해당 테이블을 사용하는 AdoNetAppender를 App.config에 정의한 예이다.

예제

-- Logs  
CREATE TABLE [Logs]
(
[Id] INT IDENTITY(1, 1) NOT NULL,
[CreateDate] DATETIME NOT NULL,
[Level] VARCHAR(50) NULL,
[Message] VARCHAR(max) NULL
)
<log4net>
  <appender name="DBAppender" type="log4net.Appender.AdoNetAppender">
    <bufferSize value="1" />
    <connectionType value="System.Data.SqlClient.SqlConnection, System.Data, Version=2.0.50727.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
    <connectionString value="Data Source=(local);Initial Catalog=MyDB;Integrated Security=SSPI;" />
    <commandText value="INSERT Logs(CreateDate,Level,Message) VALUES (@CreateDate,@Level,@Message)" />
    <parameter>
      <parameterName value="CreateDate" />
      <dbType value="DateTime" />
      <layout type="log4net.Layout.RawTimeStampLayout" />
    </parameter>
    <parameter>
      <parameterName value="@Level" />
      <dbType value="String" />
      <size value="50" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%level" />
      </layout>
    </parameter>
    <parameter>
      <parameterName value="@Message" />
      <dbType value="String" />
      <size value="4000" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%message" />
      </layout>
    </parameter>
  </appender>
  <root>
    <level value="DEBUG"/>    
    <appender-ref ref="DBAppender" />
  </root>
</log4net>