- error 페이지 개발 환경용 상세 오류 정보 추가
eapim-portal CI / build (push) Has been cancelled
eapim-portal Test / test (push) Has been cancelled

- 스택 트레이스 및 예외 클래스 노출 로직 구현
- activeProfile 표시 및 스타일 업데이트
This commit is contained in:
Rinjae
2026-06-22 15:39:59 +09:00
parent 5adfd42751
commit 687d4798d4
2 changed files with 106 additions and 4 deletions
@@ -11,6 +11,8 @@ import org.springframework.web.servlet.ModelAndView;
import javax.servlet.RequestDispatcher; import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.io.PrintWriter;
import java.io.StringWriter;
/** /**
* Created by Sungpil Hyun * Created by Sungpil Hyun
@@ -43,6 +45,15 @@ public class PortalErrorController implements ErrorController {
modelAndView.addObject("errorTitle", resolveTitle(status)); modelAndView.addObject("errorTitle", resolveTitle(status));
modelAndView.addObject("errorDescription", resolveDescription(status)); modelAndView.addObject("errorDescription", resolveDescription(status));
modelAndView.addObject("errorMessage", resolveErrorMessage(request, status)); modelAndView.addObject("errorMessage", resolveErrorMessage(request, status));
modelAndView.addObject("activeProfile", resolveActiveProfile());
// include-stacktrace=always(로컬 디버깅 프로파일)일 때만 화면에 스택 트레이스까지 노출
if (isErrorDetailEnabled()) {
Throwable throwable = resolveThrowable(request);
if (throwable != null) {
modelAndView.addObject("errorException", throwable.getClass().getName());
modelAndView.addObject("errorStackTrace", stackTraceAsString(throwable));
}
}
} }
return modelAndView; return modelAndView;
} }
@@ -54,6 +65,37 @@ public class PortalErrorController implements ErrorController {
return environment.acceptsProfiles(Profiles.of("prod")); return environment.acceptsProfiles(Profiles.of("prod"));
} }
/**
* server.error.include-stacktrace=always 인 경우에만 화면에 스택 트레이스를 노출한다.
* (로컬 디버깅 프로파일에서만 해당 값을 always 로 설정 → 운영/스테이징에는 노출되지 않음)
*/
private boolean isErrorDetailEnabled() {
String includeStacktrace = environment.getProperty("server.error.include-stacktrace", "never");
return "always".equalsIgnoreCase(includeStacktrace);
}
private String resolveActiveProfile() {
String[] activeProfiles = environment.getActiveProfiles();
if (activeProfiles.length == 0) {
return "default";
}
return String.join(", ", activeProfiles);
}
private Throwable resolveThrowable(HttpServletRequest request) {
Object exception = request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
if (exception instanceof Throwable) {
return (Throwable) exception;
}
return null;
}
private String stackTraceAsString(Throwable throwable) {
StringWriter stringWriter = new StringWriter();
throwable.printStackTrace(new PrintWriter(stringWriter));
return stringWriter.toString();
}
private String resolveTitle(Object status) { private String resolveTitle(Object status) {
HttpStatus httpStatus = resolveHttpStatus(status); HttpStatus httpStatus = resolveHttpStatus(status);
if (httpStatus == null) { if (httpStatus == null) {
+64 -4
View File
@@ -150,6 +150,47 @@
white-space: pre-wrap; white-space: pre-wrap;
} }
/* Dev Error Detail (운영 미노출) */
.error-detail-wrap {
display: flex;
flex-direction: column;
gap: 14px;
width: 100%;
}
.error-dev-notice {
background-color: #fffaf0;
border: 1px solid #f6e05e;
border-radius: 8px;
padding: 12px 18px;
font-size: 14px;
line-height: 1.6;
color: #975a16;
text-align: left;
}
.error-dev-notice .error-env {
display: inline-block;
margin-top: 4px;
}
.error-dev-notice .error-env strong {
color: #c05621;
}
/* Stack Trace */
.error-stacktrace-text {
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
font-size: 12px;
color: #742a2a;
line-height: 1.5;
text-align: left;
white-space: pre;
overflow: auto;
max-height: 360px;
margin: 0;
}
/* Responsive */ /* Responsive */
@media screen and (max-width: 600px) { @media screen and (max-width: 600px) {
.error-container { .error-container {
@@ -219,10 +260,29 @@
잠시 후 다시 시도해 주세요. 잠시 후 다시 시도해 주세요.
</p> </p>
<!-- Error Message (서버에서 전달된 에러 메시지가 있을 경우 표시) --> <!-- 상세 오류 정보 (개발 환경 전용) - 운영(prod)에서는 표시되지 않음 -->
<div th:if="${errorMessage}" class="error-message-box"> <div th:if="${errorMessage}" class="error-detail-wrap">
<p class="error-message-label">오류 상세</p> <!-- 운영 미노출 안내 + 현재 환경 -->
<p class="error-message-text" th:text="${errorMessage}"></p> <p class="error-dev-notice">
⚠ 아래 상세 정보는 개발 편의를 위한 것으로,
<strong>운영 환경에서는 표시되지 않습니다.</strong>
<span class="error-env">현재 환경: <strong th:text="${activeProfile} ?: 'default'">-</strong></span>
</p>
<!-- 오류 메시지 -->
<div class="error-message-box">
<p class="error-message-label">오류 상세</p>
<p class="error-message-text" th:text="${errorMessage}"></p>
</div>
<!-- 스택 트레이스 (include-stacktrace=always 이고 예외가 있을 때만) -->
<div th:if="${errorStackTrace}" class="error-message-box">
<p class="error-message-label">
<span>스택 트레이스</span>
<span th:if="${errorException}" th:text="'(' + ${errorException} + ')'"></span>
</p>
<pre class="error-stacktrace-text" th:text="${errorStackTrace}"></pre>
</div>
</div> </div>
<!-- Action Buttons --> <!-- Action Buttons -->