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