천객만래 [千客萬來] (It has an interminable succession of visitors)


파일쓰기 예제

void CFileioView::OnWritefile()
{
    CFile Wfile;
    if(!Wfile.Open("TestFile.txt", CFile::modeCreate |
                     CFile::modeWrite))
    {
        MessageBox("Can't Create testfile.txt !", "Warning",
                        MB_OK | MB_ICONHAND);
        return;
    }
    char* ps = new char[27];
    char* ps2 = ps;
    for(int i=0;i<26;i++)
        *ps2++ = 'A'+i;
    *ps2 = NULL;    // NULL 문자로 끝나게 한다.
    Wfile.Write(ps,27);
    Wfile.Close();
    delete ps;
}
 
파일읽기 예제
 
void CFileioView::OnReadfile()
{
    CFile Rfile;
    if(!Wfile.Open("TestFile.txt", CFile::modeRead))
    {
        MessageBox("Can't Open testfile.txt !", "Warning",
                        MB_OK | MB_ICONHAND);
        return;
    }
    UINT FileLength = (UINT)Rfile.GetLength();
    char* ps = new char[FileLength];
    Rfile.Read(ps,FileLength);
    Rfile.Close();
 
    CClientDC dc(this);
    dc.TextOut(0,0,ps,lstrlen(ps));
    delete ps;
}

랜덤 파일 처리 예제
 
void CFileioView::OnAddfile()
{
    CFile Afile;
    if(!Afile.Open("TestFile.txt", CFile::modeRead |
                     CFile::modeWrite))
    {
        MessageBox("Can't Create testfile.txt !", "Warning",
                        MB_OK | MB_ICONHAND);
        return;
    }
    Afile.Seek(-1,CFile::end);
    ~~~~~~~~~~~~~
    char* ps = new char[27];
    char* ps2 = ps;
    for(int i=0;i><26;i++)
        *ps2++ = 'a'+i;
    *ps2 = NULL;    // NULL 문자로 끝나게 한다.
    Wfile.Write(ps,27);
    Wfile.Close();
 
    delete ps;
}

파일 예외처리

extern char* pFileName;
     TRY
     {
          CFile f(pFileName, CFile::modeCreate | CFile::modeWrite);
     }
     CATCH(CFileException, e)
     {
          switch(e->m_cause)
          {
               case CFileException::fileNotFound :
 
                    MessageBox("File not found");
                    break;
               case CFileException::diskFull :
 
                    MessageBox("Disk is full");
                    break;
                    :
                    :
          }
     }
     END_CATCH

 

Posted by SB패밀리



모든 애플리케이션은 에러처리를 가지고 있다. 로컬 애플케이션에서는 처리할 수 없는 에러가 발생하였을때 이를 알림을 받을 수 없지만 웹에서는 이를 할 수 있다는 장점이 있다. ASP.NET은 이러한 에러를 처리할 수 있는 다양한 방법이 있다. 또한 에러를 처리하는 방법뿐 아니라 에러정보를 제공하는 방식도 다르다. 예로 기존의 ASP에서 Server.GetLastError를 사용하면 ASPError 개체를 반환하였다. 여전히 닷넷에서도 Server.GetLastError를 사용할 수 있지만 이는 System.Exception 형식을 반환한다.

문제점

여러분의 애플리케이션에 언제가는 에러가 발생할 것이다. 여러분은 try-catch 블럭을 사용하여 가능한한 많은 에러를 처리하려고 할 것이다. (기존의 ASP에서는 on error resume next가 유일한 방법이다.) 하지만 모든 예외들을 다 처리할 수는 없다. 처리할 수 없는 에러가 발생하였을때 어떠한 일이 발생하는가? 일반적으로 사용자는 IIS의 기본 에러 페이지를 보게 된다. (보통 c:\winnt\help\iishelp\common에 위치한다.) 이에 대해서 여러분은 할 수 있는 것이 아무것도 없으며 여러분의 사이트 이미지에도 좋지 않은 영향을 미친다. 에러는 개발적인 요소이어서 이 에러를 보다 좋은 방향으로 처리하거나 없애기 위해서 노력한다. 다음의 요소들을 염두해주다.

1. 언제 에러가 발생하는가?
2. 어디서 에러가 발생하는가?
3. 에러가 무엇인가?

이벤트 로그나 데이터베이스 또는 다른 로그 파일들에 기록된 에러들은 이 문제들을 나중에 해결하기 위한 기초가 된다.

IIS는 기본적으로 훌륭한 에러처리 기능을 가지고 있다. 하지만 이런 방식을 사용하는데는 몇몇 문제점이 있다. 가끔씩 에러가 발생하였을때 사이트의 기본 에러 페이지를 오버라이딩 하지 않고서는 말끔하게 처리할 수 없는 경우가 있다.(이는 IIS의 커스템 에러 페이지를 사용하여 처리한다.) 예로 인증이 필요한 경우 여러분은 아마 에러 페이지가 아닌 로그인 페이지로 넘어가도록하게 하고 싶을 것이다. 또한 여러분이 웹 호스팅을 받고 있는 상황이라면 문제가 된다. 웹 호스팅을 받고 있다면 일반적으로 여러분이 IIS 설정을 바꿀수가 없다. 따라서 커스텀 에러 페이지를 설정하는 방법은 기존의 ASP에서는 불가능한 것이었다. ASP.NET으로 오면서 이러한 문제는 해결되었으며 앞으로 이에 대해서 보도록 할 것이다.

 

 

 

앞장에서 언급한 것과 같은 문제점을 해결하는 것을 간단하다. ASP.NET에서 다음의 세부분의 영역에서 처리되지 않은 예외를 다룰 수 있다.

  • web.config 파일의 customError 영역
  • global.asax 파일의 Application_Error 이벤트 핸들러 영역
  • aspx 파일이나 이에 연결된 코드 비하인드 페이지의 Page_Error 이벤트 핸들러

실제 예외 처리는 순서는 다음과 같다.

    1. 페이지의 Page_Error 이벤트에서 자체 처리
    2. global.asax의 Application_Error 이벤트
    3. web.config 파일

Page_Error나 Application_Error에서 에러가 계속 쌓이는 것을 방지하기 위해서 이벤트 내에서 Server.ClearError를 호출한다. 이에 대해서는 나중에 설명할 것이다.

애플리케이션에서 에러가 발생하였을때 System.Exception으로부터 상속받은 개체가 생성되고 다음의 멤버들이 생긴다.

HelpLink 해당 에러에 대한 Help 파일의 경로를 얻거나 지정한다.
InnerException 현재 에러의 인스턴스를 얻는다.
Message 현재 에러에 대한 정보 메시지를 얻는다.
Source 에러가 발생한 애플리케이션이나 개체 이름을 얻는다.
StackTrace 현재 에러가 발생한 시간에 호출된 스택의 흐름을 문자로 얻는다.
TargetSite 현재 에러를 넘긴 메서드를 얻는다.

Page_Error 또는 OnError 이벤트 사용

예외처리의 첫번째는 페이지 수준에서 발생한다. 여러분은 MyBase.Error 프로시져를 오버라이드하여 아래의 첫번째 함수처럼 사용 할 수 있다. (여러분은 비쥬얼 스튜디오에서 베이스 클래스나 오버라이드된 클래스 모두 이벤트를 수정할 수 있다. 또한 아래의 두 함수 중 하나만 사용할 수 있고 같이는 사용할 수 없다.)

Private Sub Page_Error(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Error

End Sub

또는 다음과 같이 사용할 수 있다.

Protected Overrides Sub OnError(ByVal e As System.EventArgs)

End Sub

이들 함수에서 에러를 처리하는 것은 간단하다. 단지 Server.GetLastError를 호출해서 에러를 반환해주면 된다. 만약 여러분이 다른 페이지로의 이동을 원한다면 Response.Rediret("HandleError.aspx")를 호출하면 된다. 이러한 방식의 처리는 다음과 같은 이유에 의해서 좋은 방법이라고 할 수 있다.

  • 필요하다면 Application_Error 나 web.config 파일의 customError를 설정 할 수 있다.
  • 개개의 페이지가 페이지 자신만의 예외처리를 가질 수 있으며 로그에 기록하여 특정 정보를 얻기 위해서 로깅 코드를 여기에 삽입하면 된다. 또한 에러 처리 프로세스를 취소하고 싶다면 Application_Error 나 customErrors까지 가지 않고서도 여기서 Server.ClearError를 호출하면 된다.

Global.asax 파일 사용

global.asax 파일의 사용은 예외처리의 두번째 단계이다. 에러가 발생하면 Application_Error가 발생하게 된다. 필자는 이 프로시져를 사용하여 로그를 기록하는 방식을 좋아한다. 또한 필자는 대부분의 애플리케이션에서 페이지 수준에서 에러들을 처리하지않고 애플리케이션 수준에서 처리하도록 한다. Page_Error 와 Application_Error 프로시져에서만이 실제로 여러분이 Server.GetLastError에 접근할 수 있는 영역이라는 것을 기억해두자.

Page_Error가 호출된후 Application_Error가 호출된다. 여기서도 역시 여러분은 에러를 로그에 기록하던가 다른 페이지로 이동을 시킬 수 있다. 필자는 이에 대해서 설명하지는 않겠다. 왜냐하면 기본적으로 Page_Error에서 하던것과 똑같기 때문이다. 단지 페이지 수준이냐 애플리케이션 수준이냐 하는것이 차이일 뿐이다.

 

 

web.config 파일의 customError 요소는 처리되지 않은 에러에 대비하는 마지막 방법이다. 여러분이 Application_Error나 Page_Error와 같은 다른 예외처리부분이 있다면 이들이 먼저 호출이 된다. 그리고 여기에 Response.Redirect나 Server.ClearError를 명시하지 않았다면 web.config 파일에 있는 정의대로 페이지를 이동하게 된다. web.config 파일에서 여러분은 500, 404 같은 에러코드들을 다룰 수 있고 또한 모든 에러를 처리하는 하나의 페이지를 사용할 수 있다. 이것이 다른 예외처리방법과 web.config 파일을 사용했을때와의 가장 큰 차이점이다. (물론 여러분은 다른 메서드에 Response.Redirect를 사용하여 비슷하게 처리할 수 있다.) web.config 파일을 열어 customError 부분을 보면 다음과 같은 형식으로 되어있다.

<customErrors defaultRedirect="url" mode="On|Off|RemoteOnly">
  <error statusCode="statuscode" redirect="url"/>
</customErrors>

잠시 mode의 속성에 대해서 보도록 하겠다.

  • "On"은 customError를 사용할 수 있는 상태라는 것이다. 만약 defaultRedirect에 URL을 명시하지 않는다면 사용자는 일반적인 에러를 보게된다.
  • "Off"는 customError를 사용하지 않는다는 것이다. 이를 사용하면 에러에 대한 자세한 정보를 출력해준다.
  • "RemoteOnly"를 지정하면 customError는 원격 클라이언트에게만 보여지게 되고 로컬 호스트에는 ASP.NET 에러가 보여지게 된다. 이것이 기본 값이다.

여러분이 웹 애플리케이션을 생성하였을때 기본적으로 다음과 같이 생성된다.

<customErrors mode="RemoteOnly" />

이 경우 일반적인 에러 페이지를 사용자에게 보여주게 된다. 에러페이지로 이동시키고 싶다면 다음과 같이 수정하면 된다.

<customErrors mode="On" defaultRedirect="error.htm" />

이제 에러가 발생하면 error.htm 페이지로 이동하게 된다.

특정 에러를 처리하고 싶고 여러분이 지정한 특정 에러페이지로 이동시키고 싶다면 다음과 같이 작성하면 된다.

<customErrors mode="On" defaultRedirect="error.htm">
  <error statusCode="500" redirect="error500.aspx?code=500"/>
  <error statusCode="404" redirect="filenotfound.aspx"/>
  <error statusCode="403" redirect="authorizationfailed.aspx"/>
</customErrors>

이러한 방식에는 몇가지 문제점이 있다. 일단 페이지 이동이 완료되고나면 에러 정보는 이동된 페이지에서는 더이상 유효하지 않다. 이는 IIS는 에러 페이지에 GET으로 요청하기 때문에 IIS 내장 에러 핸들러처럼 Server.Transfer를 사용할 수 없다.

그렇기 때문에 여러분이 얻을 수 있는 정보는 에러가 발생한 경로뿐이다. (ex : http://localhost/ErrorHandling/error500.aspx?aspxerrorpath=/ErrorHandling/WebForm1.aspx)customError를 사용하였을때 나타나는 또다른 재미있는 점은 여러분이 서로 다른 서브 디렉터리에 각각의 에러 페이지를 사용하도록 할 수 있다는 것이다.

예를들면 여러분이 루트 디렉터리와 떨어져 있는 Customers라는 로그인한 고객에게 제품 정보를 보여주는 정보를 담고있는 디렉터리를 가지고 있다고 하자. 여러분은 이 디렉터리에 대해 다른 에러 페이지 집합을 만들 수 있다. 주의할것은 redirect 속성은 여러분 사이트의 루트 디렉터리가 아닌 Customers 디렉터리에 대해 상대적인 경로를 가진다는 것이다. 필자는 다음 예에 MYDOMAIN\Customers만 파일에 액세스할 수 있도록 Security rule도 포함시켰다. 여러분은 web.config 파일에서 이들 에러에 대한 rule을 정의 할 수 있다.

<configuration>
  <system.web>
...
...
  </system.web>

    <!-- Customers 디렉터리에 대한 설정 -->
    <location path="Customers">
      <system.web>
        <customErrors mode="On" defaultRedirect="error.htm">
          <error statusCode="500" redirect="CustomerError500.aspx"/>
          <error statusCode="401" redirect="CustomerAccessDenied.aspx"/>
          <error statusCode="404" redirect="CustomerPageNotFound.htm"/>
          <error statusCode="403" redirect="noaccessallowed.htm"/>
        </customErrors>
        <authorization>
          <allow roles="MYDOMAIN\Customers" />
          <deny users="*" />
        </authorization>
      </system.web>
    </location>

필자가 개발중에 발견한 것이 있는데 그것은 이들 에러가 상속되는 것처럼 보인다는 것이다. 이게 무엇을 말하는가하면 여러분이 루트에 500 에러를 정의하였고 Customers 디렉터리에는 정의하지 않고 다만 defaultRedirect만 지정했다면 Customers 디렉터리에서 에러가 발생하였을때 루트에 있는 500 에러 핸들러가 호출된다.

 

 

코드 사용

저번장에서 애플리케이션의 셋팅에 대해서 보았다. 이번에는 어떻게 코드를 작성하는지에 대해서 볼 것이다. 첨부한 소스파일에는 두개의 프로젝트가 들어있다. 먼저 웹 프로젝트에서는 몇개의 버튼이 있고 각각의 버튼마다 다른 에러를 발생하도록 하였다. 또한 페이지, global.asax, web.config 파일을 통한 에러처리의 예도 넣었다. web.config 파일에서 주의깊게 보아야 할 부분이다.

<appSettings>
  <add key="ErrorLoggingLogToDB" value="True" />
  <add key="ErrorLoggingLogToEventLog" value="True" />
  <add key="ErrorLoggingLogToFile" value="True" />
  <add key="ErrorLoggingConnectString" value="Initial Catalog=DotNetErrorLog;Data Source=localhost;Integrated Security=SSPI;" />
  <add key="ErrorLoggingEventLogType" value="Application" />
  <add key="ErrorLoggingLogFile" value="c:\ErrorManager.log" />
</appSettings>

필자는 애플리케이션 설정을 위와 같이 하였다. web.config를 사용하면 여러분은 이러한 정보를 레지스트리에 유지할 필요가 없고 애플리케이션을 개발환경에서 실제 프로덕션 환경으로 옮기는데도 쉽다. 보안 측면을 고려한다면 닷넷의 암호화된 클래스를 사용하여 데이터베이스 연결정보를 암호화하고 이를 web.config에 저장하는 것이다. 이는 이 기사의 내용에서 벗어나는 것이기 때문에 생략하도록 하겠다. 설정의 내용은 다음과 같다.

  • 데이터베이스에 로그 기록
    1. ErrorLoggingLogToDB - True로 설정하면 로그를 데이터베이스에 기록할 수 있다.
    2. ErrorLoggingConnectString - 에러를 저장할 데이터베이스에 사용될 연결문자열

  • 이벤트 로그에 로그 기록
    1. ErrorLoggingLogToEventLog - True로 설정하면 이벤트 로그에 에러정보를 기록한다.
    2. ErrorLoggingEventLogType - 이벤트 로그의 종류의 이름(예: 시스템, 응용프로그램) 여러분은 웹 에러만을 위한 새로운 이벤트 로그 종류를 만들수도 있다.

  • 텍스트 파일에 로그 기록
    1. ErrorLoggingLogToFile - True로 설정하면 로그를 텍스트 파일에 기록할 수 있다.
    2. ErrorLoggingLogFile - 에러 로그를 저장할 파일의 경로

다음은 저장된 로그의 샘플이다.

-----------------12/20/2002 3:00:36 PM-----------------
SessionID:qwyvaojenw1ad1553ftnesmq
Form Data:
__VIEWSTATE - dDwtNTMwNzcxMzI0Ozs+4QI35VkUBmX1qfHHH8i25a/4g4A=
Button1 - Cause a generic error in the customer directory
1: Error Description:Exception of type System.Web.HttpUnhandledException was thrown.
1: Source:System.Web
1: Stack Trace: at System.Web.UI.Page.HandleError(Exception e)
1: at System.Web.UI.Page.ProcessRequestMain()
1: at System.Web.UI.Page.ProcessRequest()
1: at System.Web.UI.Page.ProcessRequest(HttpContext context)
1: at System.Web.CallHandlerExecutionStep.Execute()
1: at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
1: Target Site:Boolean HandleError(System.Exception)
2: Error Description:Object reference not set to an instance of an object.
2: Source:ErrorHandling
2: Stack Trace: at ErrorHandling.WebForm2.Button1_Click(Object sender, EventArgs e) in C:\Inetpub\wwwroot\ErrorHandling\Customers\WebForm2.aspx.vb:line 26
2: at System.Web.UI.WebControls.Button.OnClick(EventArgs e)
2: at System.Web.UI.WebControls.Button.System.Web.UI.IPostBackEventHandler.
RaisePostBackEvent(String eventArgument)
2: at System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler sourceControl, String eventArgument)
2: at System.Web.UI.Page.RaisePostBackEvent(NameValueCollection postData)
2: at System.Web.UI.Page.ProcessRequestMain()
2: Target Site:Void Button1_Click(System.Object, System.EventArgs)

먼저 날자와 시간이 로그에 기록된다. 그리고 사용자의 세션 ID가 기록된다. 세션 ID들은 모두 숫자였던 ASP 세션 ID와는 다른 모습이다. 다음 줄은 페이지의 폼 데이터를 담고 있다. 다음 줄에 1이라고 있는 것은 첫번째 에러이다. 그리고 에러에 대한 정보와 소스, 스택 추적 정보와 어떤 함수가 에러를 발생했는지 알 수 있다. 2로 시작되는 것은 1에 앞서 발생된 에러이다.

여기에 좀 더 아이디어를 추가한다면 에러를 계층형으로 보기 좋게 표현할 수도 있을 것이다. 그리고 SMTP 컴포넌트를 추가하여 에러가 발생하였을때 여러분에게 이메일로 통보할 수 있도록 할 수 있을 것이다. 이는 appSettings에서 이메일 주소를 명시하고 CErrorLog.GetErrorAsString을 사용하여 이메일로 보낼 문자를 얻을 수 있다.

  • 소스코드 다운

    - ErrorManager 프로젝트를 먼저 컴파일하고 ErrorHandling 프로젝트에서 이를 참조한 후 실행시킨다.

  • Posted by SB패밀리