From 687d4798d4d4647bf5c16fd6ee77eaaea903c9f7 Mon Sep 17 00:00:00 2001
From: Rinjae
Date: Mon, 22 Jun 2026 15:39:59 +0900
Subject: [PATCH] =?UTF-8?q?-=20error=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?=
=?UTF-8?q?=EA=B0=9C=EB=B0=9C=20=ED=99=98=EA=B2=BD=EC=9A=A9=20=EC=83=81?=
=?UTF-8?q?=EC=84=B8=20=EC=98=A4=EB=A5=98=20=EC=A0=95=EB=B3=B4=20=EC=B6=94?=
=?UTF-8?q?=EA=B0=80=20-=20=EC=8A=A4=ED=83=9D=20=ED=8A=B8=EB=A0=88?=
=?UTF-8?q?=EC=9D=B4=EC=8A=A4=20=EB=B0=8F=20=EC=98=88=EC=99=B8=20=ED=81=B4?=
=?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EB=85=B8=EC=B6=9C=20=EB=A1=9C=EC=A7=81=20?=
=?UTF-8?q?=EA=B5=AC=ED=98=84=20-=20activeProfile=20=ED=91=9C=EC=8B=9C=20?=
=?UTF-8?q?=EB=B0=8F=20=EC=8A=A4=ED=83=80=EC=9D=BC=20=EC=97=85=EB=8D=B0?=
=?UTF-8?q?=EC=9D=B4=ED=8A=B8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../exception/PortalErrorController.java | 42 ++++++++++++
src/main/resources/templates/views/error.html | 68 +++++++++++++++++--
2 files changed, 106 insertions(+), 4 deletions(-)
diff --git a/src/main/java/com/eactive/apim/portal/common/exception/PortalErrorController.java b/src/main/java/com/eactive/apim/portal/common/exception/PortalErrorController.java
index 86a2fe6..6337764 100644
--- a/src/main/java/com/eactive/apim/portal/common/exception/PortalErrorController.java
+++ b/src/main/java/com/eactive/apim/portal/common/exception/PortalErrorController.java
@@ -11,6 +11,8 @@ import org.springframework.web.servlet.ModelAndView;
import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;
+import java.io.PrintWriter;
+import java.io.StringWriter;
/**
* Created by Sungpil Hyun
@@ -43,6 +45,15 @@ public class PortalErrorController implements ErrorController {
modelAndView.addObject("errorTitle", resolveTitle(status));
modelAndView.addObject("errorDescription", resolveDescription(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;
}
@@ -54,6 +65,37 @@ public class PortalErrorController implements ErrorController {
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) {
HttpStatus httpStatus = resolveHttpStatus(status);
if (httpStatus == null) {
diff --git a/src/main/resources/templates/views/error.html b/src/main/resources/templates/views/error.html
index 5a7d0bd..42fb5a2 100644
--- a/src/main/resources/templates/views/error.html
+++ b/src/main/resources/templates/views/error.html
@@ -150,6 +150,47 @@
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 */
@media screen and (max-width: 600px) {
.error-container {
@@ -219,10 +260,29 @@
잠시 후 다시 시도해 주세요.
-
-
-
오류 상세
-
+
+
+
+
+ ⚠ 아래 상세 정보는 개발 편의를 위한 것으로,
+ 운영 환경에서는 표시되지 않습니다.
+ 현재 환경: -
+
+
+
+
+
+
+