stack trace가 무엇인가요? 애플리케이션 에러를 디버깅할 때 그것을 어떻게 활용해야 하나요?

조회수 19452회

가끔씩 프로그램을 실행할 때 다음과 같은 에러가 발생합니다:

Exception in thread "main" java.lang.NullPointerException
        at com.example.myproject.Book.getTitle(Book.java:16)
        at com.example.myproject.Author.getBookTitles(Author.java:25)
        at com.example.myproject.Bootstrap.main(Bootstrap.java:14)

사람들은 이것을 "stack trace"라고 하더라고요. stack trace가 무엇인가요? 프로그램에서 발생한 에러에 대해서 무엇을 알려주는 것인가요? 에러메시지의 의미도 알고 싶습니다.

1 답변

  • 좋아요

    4

    싫어요
    채택 취소하기

    stack trace는 익셉션이 발생하였을 때 프로그램이 실행중에 호출한 메소드의 리스트 입니다.

    간단한 예

    stack trace는 프로그램에서 익셉션이 발생한 위치를 정확하게 알려줍니다.

    다음의 stack trace를 보세요:

    Exception in thread "main" java.lang.NullPointerException
            at com.example.myproject.Book.getTitle(Book.java:16)
            at com.example.myproject.Author.getBookTitles(Author.java:25)
            at com.example.myproject.Bootstrap.main(Bootstrap.java:14)
    

    이것은 매우 간단한 stack trace입니다. "at..."으로 시작하는 첫 메시지가 에러가 발생한 곳입니다. 그 위치가 프로그램에서 가장 마지막에 호출된 메소드인 것이지요.

    at com.example.myproject.Book.getTitle(Book.java:16)
    

    그러면 디버깅하기 위해서 Book.java 소스파일을 열어, 16번째 줄을 살펴보겠지요.

    public String getTitle() {
        System.out.println(title.toString()); <-- line 16
        return title;
    }
    

    이 에러메시지는 위의 코드에서 titlenull이라는 것을 의미합니다.

    익셉션 체인의 예

    프로그램은 때때로 익셉션은 처리할 수도 있고 (catch), 다른 익셉션을 다시 던질 수도 있습니다.

    전형적으로 다음과 같은 구조로 구현합니다:

    try {
    ....
    } catch (NullPointerException e) {
      throw new IllegalStateException("A book has a null property", e)
    }
    

    이 경우의 stack trace는 다음과 같겠지요:

    Exception in thread "main" java.lang.IllegalStateException: A book has a null property
            at com.example.myproject.Author.getBookIds(Author.java:38)
            at com.example.myproject.Bootstrap.main(Bootstrap.java:14)
    Caused by: java.lang.NullPointerException
            at com.example.myproject.Book.getId(Book.java:22)
            at com.example.myproject.Author.getBookIds(Author.java:35)
            ... 1 more
    

    위의 stack trace가 다른 stack trace와 다른 점은 "Caused by" 입니다. 때때로 익셉션들은 다수의 "Caused by" 섹션을 가질 수도 있습니다. 이러한 stack trace에서 문제의 "근본 원인"을 찾고자 한다면, stack trace의 가장 아래의 "Caused by" 섹션을 보시면 됩니다.

    Caused by: java.lang.NullPointerException <-- root cause
            at com.example.myproject.Book.getId(Book.java:22) <-- important line
    

    그러면 이 메시지를 이해했다면, NullPointerException이 발생한 Book.java 소스파일의 22번째 줄을 확인해 봐야 겠지요.

    라이브러리 소스코드를 사용한 좀 더 어려운 예제

    보통의 stack trace는 위의 두 가지 예제보다 훨씬 복잡합니다. 다음은 그러한 예제입니다:

    javax.servlet.ServletException: Something bad happened
        at com.example.myproject.OpenSessionInViewFilter.doFilter(OpenSessionInViewFilter.java:60)
        at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
        at com.example.myproject.ExceptionHandlerFilter.doFilter(ExceptionHandlerFilter.java:28)
        at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
        at com.example.myproject.OutputBufferFilter.doFilter(OutputBufferFilter.java:33)
        at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
        at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:388)
        at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
        at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:182)
        at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:765)
        at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:418)
        at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
        at org.mortbay.jetty.Server.handle(Server.java:326)
        at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:542)
        at org.mortbay.jetty.HttpConnection$RequestHandler.content(HttpConnection.java:943)
        at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:756)
        at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:218)
        at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:404)
        at org.mortbay.jetty.bio.SocketConnector$Connection.run(SocketConnector.java:228)
        at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:582)
    Caused by: com.example.myproject.MyProjectServletException
        at com.example.myproject.MyServlet.doPost(MyServlet.java:169)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:820)
        at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:511)
        at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1166)
        at com.example.myproject.OpenSessionInViewFilter.doFilter(OpenSessionInViewFilter.java:30)
        ... 27 more
    Caused by: org.hibernate.exception.ConstraintViolationException: could not insert: [com.example.myproject.MyEntity]
        at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:96)
        at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66)
        at org.hibernate.id.insert.AbstractSelectingDelegate.performInsert(AbstractSelectingDelegate.java:64)
        at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2329)
        at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2822)
        at org.hibernate.action.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:71)
        at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:268)
        at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:321)
        at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:204)
        at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:130)
        at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:210)
        at org.hibernate.event.def.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:56)
        at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:195)
        at org.hibernate.event.def.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:50)
        at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:93)
        at org.hibernate.impl.SessionImpl.fireSave(SessionImpl.java:705)
        at org.hibernate.impl.SessionImpl.save(SessionImpl.java:693)
        at org.hibernate.impl.SessionImpl.save(SessionImpl.java:689)
        at sun.reflect.GeneratedMethodAccessor5.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
        at org.hibernate.context.ThreadLocalSessionContext$TransactionProtectionWrapper.invoke(ThreadLocalSessionContext.java:344)
        at $Proxy19.save(Unknown Source)
        at com.example.myproject.MyEntityService.save(MyEntityService.java:59) <-- relevant call (see notes below)
        at com.example.myproject.MyServlet.doPost(MyServlet.java:164)
        ... 32 more
    Caused by: java.sql.SQLException: Violation of unique constraint MY_ENTITY_UK_1: duplicate value(s) for column(s) MY_COLUMN in statement [...]
        at org.hsqldb.jdbc.Util.throwError(Unknown Source)
        at org.hsqldb.jdbc.jdbcPreparedStatement.executeUpdate(Unknown Source)
        at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeUpdate(NewProxyPreparedStatement.java:105)
        at org.hibernate.id.insert.AbstractSelectingDelegate.performInsert(AbstractSelectingDelegate.java:57)
        ... 54 more
    

    이 예제는 메시지가 굉장히 많습니다. 이제 이 메시지에서 사용자가 정의한 com.example.myproject 패키지의 소스코드에서 문제가 발생한 곳을 찾게 될 것입니다. 위의 두 번째 예제에서 살펴봤듯이 문제의 근본적인 원인을 제일 먼저 찾아야 겠지요. 그것은 다음과 같습니다:

    Caused by: java.sql.SQLException
    

    그러나 모든 메소드는 라이브러리 코드 내에서 호출됩니다. 그래서 마지막 "Caused by"부터 위의 "Caused by"를 하나씩 살펴보면서 사용자가 정의한 코드의 메소드가 처음 호출되는 위치를 찾아야 합니다. 위의 예제에서는 다음과 같은 메시지가 이에 해당하겠지요.

    at com.example.myproject.MyEntityService.save(MyEntityService.java:59)
    

    이전의 예제들처럼, 디버깅하기 위해서 MyEntityService.java의 59번째 줄을 살펴봐야 할 것입니다. 왜냐면 이 위치가 에러가 발생된 곳이기 때문이지요.

답변을 하려면 로그인이 필요합니다.

프로그래머스 커뮤니티는 개발자들을 위한 Q&A 서비스입니다. 로그인해야 답변을 작성하실 수 있습니다.

(ಠ_ಠ)
(ಠ‿ಠ)