stack trace가 무엇인가요? 애플리케이션 에러를 디버깅할 때 그것을 어떻게 활용해야 하나요?
조회수 19466회
가끔씩 프로그램을 실행할 때 다음과 같은 에러가 발생합니다:
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 답변
-
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; }
이 에러메시지는 위의 코드에서
title
이null
이라는 것을 의미합니다.익셉션 체인의 예
프로그램은 때때로 익셉션은 처리할 수도 있고 (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번째 줄을 살펴봐야 할 것입니다. 왜냐면 이 위치가 에러가 발생된 곳이기 때문이지요.
댓글 입력