From 787489f6c16cb832a4444dbf493761ddfcdf6de6 Mon Sep 17 00:00:00 2001 From: janghw Date: Tue, 18 Aug 2026 15:55:00 +0900 Subject: [PATCH] Format JSON payloads for readable HTTP logging --- .../dap/biz/mcp/toolclient/HttpToolClient.java | 18 +++++++++++++++--- .../biz/mcp/transport/http/McpController.java | 2 +- .../mcp/transport/http/McpExchangeFilter.java | 16 ++++++++++++++-- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/shinhanlife/dap/biz/mcp/toolclient/HttpToolClient.java b/src/main/java/io/shinhanlife/dap/biz/mcp/toolclient/HttpToolClient.java index 99509ff..7ca07b8 100644 --- a/src/main/java/io/shinhanlife/dap/biz/mcp/toolclient/HttpToolClient.java +++ b/src/main/java/io/shinhanlife/dap/biz/mcp/toolclient/HttpToolClient.java @@ -58,7 +58,7 @@ public class HttpToolClient implements ToolClient { request.toolName(), request.version(), request.endpoint(), - request.arguments()); + prettyJson(request.arguments())); RestClient.RequestBodySpec spec = requestSpec(request, context); var entity = spec.retrieve() @@ -68,12 +68,13 @@ public class HttpToolClient implements ToolClient { throw statusException(response.getStatusCode().value(), request.toolName()); }) .toEntity(String.class); - JsonNode responseBody = parseResponse(entity.getBody(), entity.getHeaders().getContentType()); log.info( + JsonNode responseBody = parseResponse(entity.getBody(), entity.getHeaders().getContentType()); + log.info( "TEMP_TOOL_HTTP_RESPONSE_BODY direction=tool_server_to_mcp toolName={} version={} statusCode={} body={}", request.toolName(), request.version(), entity.getStatusCode().value(), - responseBody); + prettyJson(responseBody)); return new ToolResponse(entity.getStatusCode().value(), responseBody); } catch (ToolClientException exception) { throw exception; @@ -88,6 +89,17 @@ public class HttpToolClient implements ToolClient { } } + /** + * 임시 Tool 본문 로그를 보기 쉽게 출력하기 위해 JSON 값을 개행된 문자열로 변환합니다. + * 직렬화 실패 시에도 Tool 호출 결과는 바꾸지 않고 문자열 변환 결과만 로그에 남깁니다. + */ + private String prettyJson(JsonNode value) { + try { + return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(value); + } catch (Exception exception) { + return String.valueOf(value); + } + } /** * URI, bypass 헤더와 JSON body를 조합해 실행 직전 POST 요청 객체를 만듭니다. Agent Builder가 보낸 correlation·사원 식별자는 이름과 값을 바꾸지 않고 그대로 실어 보냅니다. 사원 식별자는 암호문이며 MCP는 복호화하지 않으므로, 이 * 경계에서는 전달만 하고 해석하지 않습니다. diff --git a/src/main/java/io/shinhanlife/dap/biz/mcp/transport/http/McpController.java b/src/main/java/io/shinhanlife/dap/biz/mcp/transport/http/McpController.java index 020438e..a636d7d 100644 --- a/src/main/java/io/shinhanlife/dap/biz/mcp/transport/http/McpController.java +++ b/src/main/java/io/shinhanlife/dap/biz/mcp/transport/http/McpController.java @@ -95,7 +95,7 @@ public class McpController { */ private String toJson(Object value) { try { - return objectMapper.writeValueAsString(value); + return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(value); } catch (Exception exception) { return String.valueOf(value); } diff --git a/src/main/java/io/shinhanlife/dap/biz/mcp/transport/http/McpExchangeFilter.java b/src/main/java/io/shinhanlife/dap/biz/mcp/transport/http/McpExchangeFilter.java index 2dae8b1..5953c3c 100644 --- a/src/main/java/io/shinhanlife/dap/biz/mcp/transport/http/McpExchangeFilter.java +++ b/src/main/java/io/shinhanlife/dap/biz/mcp/transport/http/McpExchangeFilter.java @@ -107,12 +107,13 @@ public class McpExchangeFilter extends OncePerRequestFilter { "path", request.getRequestURI(), "mcpMethod", - mcpMethod); log.info( + mcpMethod); + log.info( "TEMP_MCP_HTTP_REQUEST_BODY direction=agent_to_mcp httpMethod={} path={} mcpMethod={} body={}", request.getMethod(), request.getRequestURI(), mcpMethod, - cachedRequest.bodyText()); + prettyJson(cachedRequest.bodyText())); try { protocolVersionValidator.validatePostInitializeRequest(request, mcpMethod); @@ -176,6 +177,17 @@ public class McpExchangeFilter extends OncePerRequestFilter { * * @return method 이름. 읽을 수 없으면 {@code null} */ + /** + * 임시 요청 본문 로그를 보기 쉽게 출력하기 위해 JSON이면 개행된 문자열로 변환합니다. + * JSON 파싱이 실패하면 원문 문자열을 그대로 돌려 요청 처리 흐름에는 영향을 주지 않습니다. + */ + private String prettyJson(String body) { + try { + return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(objectMapper.readTree(body)); + } catch (Exception exception) { + return body; + } + } private String extractMethod(CachedBodyHttpServletRequest request) { try { JsonNode envelope = objectMapper.readTree(request.getInputStream());