프로그램을 개발하거나 운영하는데 있어 로깅만큼 중요한 기능도 없을것이다. 시스템이나 어플리케이션에서 발생한 문제 해결의 출발점이
'에러의 재현' 과 '로그 분석'이기 때문이다.
Java 진영에서는, 이미 오픈소스에서 발전해 온, Log4J라는 막강한 로깅 프레임웍이 사실상의 표준(de facto standard)으로 존재하고 있다.
( Commons Logging 도 있지만 Log4J의 Wrapper 정도라고 생각하면 된다. )
그럼, MS진영에는 어떤 로깅 프레임웍이 있을까?
지금 회사에서 진행중인 프로젝트가 .Net 기반의 윈도우즈 어플리케이션이라 MS계열에서 널리쓰이고 있을것 같은(?) 로깅툴을 찾아볼 필요가 있었다.
그런데 아니나 다를까.. 이미 이쪽 진영에까지 Log4J의 영향력이 미치고 있을줄이야..
이미 Log4J를 닷넷용으로 포팅한 Log4Net이 있는 것이다.
Log4J를 사용해 봤다면 Log4Net역시 어렵지 않게 사용 할 수 있다.
이번 포스팅에서는 Log4Net을 활용하여 .Net 어플리케이션을 로깅하는 방법을 살펴보자.
Log4Net 공식 사이트 :
http://logging.apache.org/log4net/index.html라이센스 :
Apache License, Ver 2.0 ( 상용어플리케이션에 이용할 수 있다!! )
지원하는 프레임워크 : 2008년 1월 현재
MS .Net Framework 1.0 , MS .Net Framework 1.1 , MS .Net Framework 2.0 ,
MS .Net Compact Framework 1.0 , Mono 1.2.3, MS Shared Source CLI 1.0, CLI 1.0 Compatible
6개의 프레임워크를 지원.
Appenders 일람
Appender |
.NET Framework 1.0 |
.NET Framework 1.1 |
.NET Framework 2.0 |
.NET CF 1.0 |
Mono 1.2 |
Shared Source CLI 1.0 |
CLI 1.0 Compatible |
AdoNetAppender |
x |
x |
x |
x |
x |
x |
AnsiColorTerminalAppender |
x |
x |
x |
x |
x |
x |
x |
AspNetTraceAppender |
x |
x |
x |
x |
x |
BufferingForwardingAppender |
x |
x |
x |
x |
x |
x |
x |
ColoredConsoleAppender |
x |
x |
x |
ConsoleAppender |
x |
x |
x |
x |
x |
x |
x |
DebugAppender |
x |
x |
x |
x |
x |
x |
x |
EventLogAppender |
x |
x |
x |
x |
x |
FileAppender |
x |
x |
x |
x |
x |
x |
x |
ForwardingAppender |
x |
x |
x |
x |
x |
x |
x |
LocalSyslogAppender |
x |
x |
x |
x |
x |
MemoryAppender |
x |
x |
x |
x |
x |
x |
x |
NetSendAppender |
x |
x |
x |
OutputDebugStringAppender |
x |
x |
x |
x |
RemoteSyslogAppender |
x |
x |
x |
x |
x |
x |
x |
RemotingAppender |
x |
x |
x |
x |
x |
x |
RollingFileAppender |
x |
x |
x |
x |
x |
x |
x |
SmtpAppender |
x |
x |
x |
x |
x |
SmtpPickupDirAppender |
x |
x |
x |
x |
x |
x |
x |
TelnetAppender |
x |
x |
x |
x |
x |
x |
x |
TraceAppender |
x |
x |
x |
x |
x |
x |
x |
UdpAppender |
x |
x |
x |
x |
x |
x |
x |
Log4Net은 최종으로 로그가 기록될 장소( 파일, DB, 메일 등..)를 Appender를 이용하여 구분하고 있다.
사용할 Appender를 Log4Net 설정파일에 기술하면 Log4Net이 해당 Appender를 사용하여 로그를 기록하게 된다.
사용방법은 이렇다.
1. 어플리케이션 소스 코드를 작성한다.
소스코드 보기..
[CODE]
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
//1. Log4Net 네임스페이스를 using한여 Log4Net을 사용 할 수 있도록 한다..
using log4net;
using log4net.Config;
namespace Log4NetSample
{
/// <summary>
/// 본 소스 코드는 C# 코드에서 Log4Net을 이용하여 로깅하는 예제를 보여주고 있습니다.
/// author : 신윤섭
/// </summary>
public partial class Window1 : System.Windows.Window
{
//3. Log4Net 인터페이스를 취득하여 해당 클래스에서 Log4Net 클래스를 사용 할 수 있도록 한다.
//private static readonly ILog logger = LogManager.GetLogger(typeof(Window1));
// XmlConfigurator.Configure() 실행 이후 시점부터 일반 클래스에서 로거객체 취득방법.
/// <summary>
/// default constructor
/// </summary>
public Window1()
{
InitializeComponent();
// 2. 어플리케이션 초기 구동시 Log4Net이 참조할 환경 설정파일을 Log4Net이 알 수 있도록 한다.
// 한번만 실행하여 설정 파일이 메모리에 로드되도록하면 됨.
//XmlConfigurator.Configure(); // <- Application base 디렉토리의 Window1.exe.config 파일을
// 읽어들일 때.
XmlConfigurator.Configure(new System.IO.FileInfo("log4net.xml")); // <- Application base
// 디렉토리의 log4net.xml을 읽어들일 때.
// 3.Log4Net 인터페이스를 취득하여 해당 클래스에서 Log4Net 클래스를 사용 할 수 있도록 한다.
ILog logger = LogManager.GetLogger(typeof(Window1));
// 혹은ILog logger = LogManager.GetLogger( Type.GetType("Log4NetSample.Window1") );
// 4. 로거 인터페이스를 이용하여 로깅 레벨 별로 로그 기록
if (logger.IsDebugEnabled)
{
logger.Debug( this.ToString()+ "인스턴스 생성 됨" );
}
}
/// <summary>
/// EventHandler
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void button_Click(object sender, RoutedEventArgs e)
{
ILog logger = LogManager.GetLogger(typeof(Window1));
// 디버그 메세지.
if (logger.IsDebugEnabled) logger.Debug("OnMouseButtonClick 이벤트 발생!!");
try
{
// 일부러 예외상황을 발생 합니다.
int i = Int32.Parse("exception");
}
catch (Exception ex)
{
// 에러 메세지.
if (logger.IsErrorEnabled) logger.Error("에러 발생!!!", ex);
}
}
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
base.OnClosing(e);
ILog logger = LogManager.GetLogger(typeof(Window1));
try
{
// 인포메이션 메세지.
if (logger.IsInfoEnabled) logger.Info("어플리케이션 종료");
// 5. Log4Net을 종료하고 리소스를 해제 한다.
LogManager.Shutdown();
}
catch (Exception ex)
{
}
}
}
}
[/CODE]
2. 다운로드 받은 Log4Net을 압축해제하여 나온 디렉토리중 개발을 진행하고 있는 닷넷 프레임워크 버전에 해당하는
log4net.dll을 Reference 한다.
3. Log4Net 설정하기
- 설정파일을 작성하여 어플리케이션이 빌드되는 디렉토리에 배치한다.
log4net.xml 보기..
[CODE]
<?xml version="1.0" encoding="utf-8" ?>
<!-- This section contains the log4net configuration settings -->
<log4net>
<!-- 본 예제에서는 콘솔과 파일 Appender를 이용해 보겠습니다. 나머지 Appender에 대한 설정 예제는
http://logging.apache.org/log4net/release/config-examples.html 를 참조하시면 됩니다.-->
<appender name="Console" type="log4net.Appender.ConsoleAppender">
<layout type="log4net.Layout.PatternLayout">
<!-- Pattern to output the caller's file name and line number -->
<conversionPattern value="%d [%t] %-5p %c - %m%n" />
</layout>
</appender>
<appender name="RollingFile" type="log4net.Appender.RollingFileAppender">
<file value="C:\Window1.log" />
<appendToFile value="true" />
<datePattern value="-yyyy-MM-dd" />
<rollingStyle value="Date" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%d [%t] %-5p %c - %m%n" />
</layout>
</appender>
<!-- root 엘리먼트에 로깅레벨과 사용할 Appender를 기술합니다. -->
<root>
<level value="DEBUG" />
<appender-ref ref="Console" />
<appender-ref ref="RollingFile" />
</root>
</log4net>
[/CODE]
4. 어플리케이션을 빌드후 구동하여 로그가 잘 남는것을 확인한다. ^^
첨부한 프로젝트는 WPF 어플리케이션으로 작성 되었습니다. 비주얼 스튜디오 2008이라면 바로 빌드가 가능하겠지만 VS2005인 경우 실행을 위해서는
http://www.yunsobi.com/blog/280 포스트를 참조하여 WPF개발 환경을 구축 한 후 실행 하셔야 합니다.
좀 더 들여다 볼까요?.퍼포먼스 ( Perfomance ) 로깅에서 퍼포먼스를 이야기할 때 다음 세가지 이슈사항을 고려할 수 있습니다.
1. Logging을 하지 않을 경우( turned off ) 로깅코드에 의한 퍼포먼스로깅레벨을
OFF 로 설정한 경우라면 메소드 호출 비용이 듭니다. 하지만 메소드 생성에는 파라메터 생성에따르는 숨겨진 비용도 포함되어 있음을 잊지 마세요. 예를들어 다음과 같은 코드
log.Debug("Entry number: " + i + " is " + entry[i].ToString());
에서 i 와 entry[i] 를 string으로 치환하는 비용과 각 string을 더하는 비용이 소모됩니다. 파라메터를 생성하는 비용은 파라메터의 갯수와 생성되는 횟수에 큰 영향을 받습니다.
파라메터 생성 비용을 없애고 싶다면 아래와 같은 코드를 작성 하세요.
if(log.IsDebugEnabled)
{
log.Debug("Entry number: " + i + " is " + entry[i].ToString());
}
디버깅 모드가 꺼져 있다면 위 코드는 파라메터 생성에드는 비용은 0이 될것입니다. 반면에 디버그기능이 켜져 있다면 두배의 비교 시간이 필요합니다. '디버그기능이 켜져있는지(log.IsDebugEnabled)'와 '로거가 디버그 모드인지(log.Debug())' . 하지만 이를 비교하는데 필요한시간은 매우 미미해서 실제 로그를 기록하는데 드는 비용의 1%정도에 해당합니다.
어떤 개발자들은 전처리나 컴파일타임의 테크닉등을 이용하여 로깅에대한 모든 상태를 조절하기도합니다.이는 퍼포먼스 측면에서 보면 완벽하다고 할 수 있겠지만, 로그레벨을 변경하기가 매우 까다로워 집니다. 이렇게 하는것에 대해 많은 사람들이 작은 성능향상을 위해 너무 많은것을 잃고 있다고 조언 합니다.
2. 로깅기능이 켜져 있을 경우 로그레벨에 의한 '로그작성'과 '로그작성 않기'의 판단에 따른 퍼포먼스
이는 기본적으로 로거 Hierarchy의 동작 성능에 따릅니다. 로깅이 켜져(turn on)있다면 log4net은 로그레벨과 로거의레벨을 비교하는 작업이 필요합니다. 하지만 로거 자체에는 레벨이 할당되어 있지 않습니다. 로그레벨은 로거 Hierarch로부터 상속받게 됩니다. 로그레벨을 상속받기 이전에 로거는 상위클래스를 확인 할 필요가 있겠지요.
여기에는 가능하면 짧은시간으로 동작케하기위한 노력이 담겨있습니다. 예를들어, 자식 로거들은 그들의 부모하고만 연결이 됩니다. 이전에 살펴본 BasicConfigurator의 예에서, Com.Foo.Bar 라는 이름의 로거는 루트로거와 직접 연결됩니다. 이것으로 존재하지 않을 Com 이나 Com.Foo 로거를 찾는 수고를 피할 수 있습니다. 이것으로 산재한 (sparse) hierarchies 속에서 상당한 속도향상을 얻을 수 있습니다.
로그레벨 의한 로깅 여부 판단은, IsXXXEnable 프로퍼티를 조사하는것에비해 약 3배정도 느리다고 볼 수 있습니다.
3. 로그메세지를 실제 기록하기
이는 로그메세지를 포메팅하는것과 목표에 실제로 내보내는 비용이 발생합니다. 여기에는 포메터와 Appender가 가능한 빨리 동작하도록 노력을 기울이는 수밖에 없겠지요.
어찌돼었든, log4net의 많은 기능들은 '속도'의 관점에서 디자인되었습니다. 몇몇 log4net 컴포넌트들은 성능향상을 위해 몇번이고 재작성 되었으며, 여전히 옵티마이즈되고 있으며 자주 갱신되고 있습니다. 이것만은 알아주세요.
SimpleLayout의 퍼포먼스 테스트에서 log4net은 System.Console.WriteLine 만큼의 성능을 보여주고 있다는 것을.
PatterLayout 수정하기
log4net의 출력형식을 조정하기위해서는 PatternLayout과 Conversion Character를 이용할 수 있습니다.
예를들어 log4net.xml에 아래와 같은 패턴을 정의하여 남긴 로그를 보면
[code]
<appender name="RollingFile" type="log4net.Appender.RollingFileAppender">
<file value="C:\Window1.log" />
<appendToFile value="true" />
<datePattern value="-yyyy-MM-dd" />
<rollingStyle value="Date" />
<layout type="log4net.Layout.PatternLayout"> <!-- 레이아웃으로 패턴레이아웃을 지정했습니다. -->
<conversionPattern value="%d [%t] %-5p %c - %m%n" /> <!-- conversion 캐릭터를 기술했습니다. -->
</layout>
</appender>
[/code]
아래와 같은 형식으로 남습니다.
[code]
2008-01-08 13:30:18,938 [10] DEBUG Log4NetSample.Window1 - Log4NetSample.Window1인스턴스 생성 됨
2008-01-08 13:30:24,792 [10] DEBUG Log4NetSample.Window1 - OnButtonMouseDown 이벤트 발생!!
2008-01-08 13:30:24,856 [10] ERROR Log4NetSample.Window1 - 에러 발생!!!
System.FormatException: 입력 문자열의 형식이 잘못되었습니다.
위치: System.Number.StringToNumber(String str, NumberStyles options, NumberBuffer& number,
NumberFormatInfo info, Boolean parseDecimal)
위치: System.Number.ParseInt32(String s, NumberStyles style, NumberFormatInfo info)
위치: System.Int32.Parse(String s)
위치: Log4NetSample.Window1.button_Click(Object sender, RoutedEventArgs e) 파일 D:\Project2007
\emodioroot\Samples\Log4NetSample\Log4NetSample\Window1.xaml.cs:줄 68[/code]
출력 결과물 형식을 변경하기위해선 Conversion Character를 변경할 필요가 있는데요.
이용가능한 Conversion Character 일람은 아래와 같습니다.
Conversion Character |
Effect |
a |
Used to output the frienly name of the AppDomain where the logging event was generated. |
c |
Used to output the logger of the logging event. The logger conversion specifier can be optionally followed by precision specifier, that is a decimal constant in brackets.
If a precision specifier is given, then only the corresponding number of right most components of the logger name will be printed. By default the logger name is printed in full.
For example, for the logger name "a.b.c" the pattern %c{2} will output "b.c". |
C |
Used to output the fully qualified class name of the caller issuing the logging request. This conversion specifier can be optionally followed by precision specifier, that is a decimal constant in brackets.
If a precision specifier is given, then only the corresponding number of right most components of the class name will be printed. By default the class name is output in fully qualified form.
For example, for the class name "log4net.Layout.PatternLayout", the pattern %C{1} will output "PatternLayout".
WARNING Generating the caller class information is slow. Thus, it's use should be avoided unless execution speed is not an issue. |
d |
Used to output the date of the logging event. The date conversion specifier may be followed by a date format specifier enclosed between braces. For example, %d{HH:mm:ss,fff} or %d{dd MMM yyyy HH:mm:ss,fff}. If no date format specifier is given then ISO8601 format is assumed (ISO8601DateFormatter).
The date format specifier admits the same syntax as the time pattern string of the ToString.
For better results it is recommended to use the log4net date formatters. These can be specified using one of the strings "ABSOLUTE", "DATE" and "ISO8601" for specifying AbsoluteTimeDateFormatter, and respectively ISO8601DateFormatter. For example, %d{ISO8601} or %d{ABSOLUTE}.
These dedicated date formatters perform significantly better than ToString. |
F |
Used to output the file name where the logging request was issued.
WARNING Generating caller location information is extremely slow. It's use should be avoided unless execution speed is not an issue. |
l |
Used to output location information of the caller which generated the logging event.
The location information depends on the CLI implementation but usually consists of the fully qualified name of the calling method followed by the callers source the file name and line number between parentheses.
The location information can be very useful. However, it's generation is extremely slow. It's use should be avoided unless execution speed is not an issue. |
L |
Used to output the line number from where the logging request was issued.
WARNING Generating caller location information is extremely slow. It's use should be avoided unless execution speed is not an issue. |
m |
Used to output the application supplied message associated with the logging event. |
M |
Used to output the method name where the logging request was issued.
WARNING Generating caller location information is extremely slow. It's use should be avoided unless execution speed is not an issue. |
n |
Outputs the platform dependent line separator character or characters.
This conversion character offers practically the same performance as using non-portable line separator strings such as "\n", or "\r\n". Thus, it is the preferred way of specifying a line separator. |
p |
Used to output the level of the logging event. |
P |
Used to output the an event specific property. The key to lookup must be specified within braces and directly following the pattern specifier, e.g. %X{user} would include the value from the property that is keyed by the string 'user'. Each property value that is to be included in the log must be specified separately. Properties are added to events by loggers or appenders. By default no properties are defined. |
r |
Used to output the number of milliseconds elapsed since the start of the application until the creation of the logging event. |
t |
Used to output the name of the thread that generated the logging event. Uses the thread number if no name is available. |
u |
Used to output the user name for the currently active user (Principal.Identity.Name).
WARNING Generating caller information is extremely slow. It's use should be avoided unless execution speed is not an issue. |
W |
Used to output the WindowsIdentity for the currently active user.
WARNING Generating caller WindowsIdentity information is extremely slow. It's use should be avoided unless execution speed is not an issue. |
x |
Used to output the NDC (nested diagnostic context) associated with the thread that generated the logging event. |
X |
Used to output the MDC (mapped diagnostic context) associated with the thread that generated the logging event. The key to lookup must be specified within braces and directly following the pattern specifier, e.g. %X{user} would include the value from the MDC that is keyed by the string 'user'. Each MDC value that is to be included in the log must be specified separately. |
% |
The sequence %% outputs a single percent sign. |
정말 괜찮은 프로그램이군요. 한번 써봐야 겠습니다.