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 @@ 잠시 후 다시 시도해 주세요.

- -
-

오류 상세

-

+ +
+ +

+ ⚠ 아래 상세 정보는 개발 편의를 위한 것으로, + 운영 환경에서는 표시되지 않습니다. + 현재 환경: - +

+ + +
+

오류 상세

+

+
+ + +
+

+ 스택 트레이스 + +

+

+