Skip to content

Commit 649f265

Browse files
committed
Per request logging update
1 parent 3541159 commit 649f265

File tree

8 files changed

+93
-17
lines changed

8 files changed

+93
-17
lines changed

core/src/main/java/com/sap/ai/sdk/core/DeploymentResolver.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ void reloadDeployments(@Nonnull final String resourceGroup) {
4646
try {
4747
val apiClient = new DeploymentApi(service);
4848
val deployments = new HashSet<>(apiClient.query(resourceGroup).getResources());
49+
log.info("Found {} deployments in resource group '{}'", deployments.size(), resourceGroup);
4950
cache.put(resourceGroup, deployments);
5051
} catch (final RuntimeException e) {
5152
throw new DeploymentResolutionException(

core/src/main/java/com/sap/ai/sdk/core/common/ClientResponseHandler.java

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import org.apache.hc.core5.http.HttpEntity;
1919
import org.apache.hc.core5.http.io.HttpClientResponseHandler;
2020
import org.apache.hc.core5.http.io.entity.EntityUtils;
21+
import org.slf4j.MDC;
2122

2223
/**
2324
* Parse incoming JSON responses and handles any errors. For internal use only.
@@ -86,16 +87,10 @@ private T parseSuccess(@Nonnull final ClassicHttpResponse response) throws E {
8687
val content =
8788
tryGetContent(responseEntity)
8889
.getOrElseThrow(e -> exceptionFactory.build(message, e).setHttpResponse(response));
90+
logResponseSuccess(response);
91+
8992
try {
90-
final T value = objectMapper.readValue(content, successType);
91-
val timeHeaders = response.getHeaders("x-upstream-service-time");
92-
if (timeHeaders.length > 0) {
93-
log.info("LLM request success with duration:{}", timeHeaders[0].getValue());
94-
} else {
95-
log.info("LLM request success");
96-
}
97-
log.debug("Response content:\n{}", content);
98-
return value;
93+
return objectMapper.readValue(content, successType);
9994
} catch (final JsonProcessingException e) {
10095
log.error("Failed to parse response to type {}", successType);
10196
throw exceptionFactory.build("Failed to parse response", e).setHttpResponse(response);
@@ -177,4 +172,19 @@ private static String getErrorMessage(
177172
val message = Optional.ofNullable(additionalMessage).orElse("");
178173
return message.isEmpty() ? baseErrorMessage : "%s: %s".formatted(baseErrorMessage, message);
179174
}
175+
176+
private static void logResponseSuccess(final @Nonnull ClassicHttpResponse response) {
177+
val latency =
178+
Optional.ofNullable(response.getFirstHeader("x-upstream-service-time"))
179+
.map(h -> h.getValue() + "ms")
180+
.orElseGet(() -> "unknown");
181+
val entityLength = response.getEntity().getContentLength();
182+
val sizeInfo = entityLength >= 0 ? String.format("%.1fKB", entityLength / 1024.0) : "unknown";
183+
log.debug(
184+
"[reqId={}] {} request completed successfully with latency={}, size={}.",
185+
MDC.get("reqId"),
186+
MDC.get("service"),
187+
latency,
188+
sizeInfo);
189+
}
180190
}

foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiChatCompletionResponse.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@
77
import com.sap.ai.sdk.foundationmodels.openai.generated.model.CompletionUsage;
88
import com.sap.ai.sdk.foundationmodels.openai.generated.model.CreateChatCompletionResponse;
99
import com.sap.ai.sdk.foundationmodels.openai.generated.model.CreateChatCompletionResponseChoicesInner;
10+
import java.util.Collections;
1011
import java.util.List;
1112
import java.util.Objects;
13+
import java.util.Optional;
1214
import javax.annotation.Nonnull;
1315
import lombok.Getter;
1416
import lombok.RequiredArgsConstructor;
1517
import lombok.Setter;
1618
import lombok.Value;
19+
import lombok.extern.slf4j.Slf4j;
1720

1821
/**
1922
* Represents the output of an OpenAI chat completion. *
@@ -23,6 +26,7 @@
2326
@Value
2427
@RequiredArgsConstructor(access = PACKAGE)
2528
@Setter(value = NONE)
29+
@Slf4j
2630
public class OpenAiChatCompletionResponse {
2731
/** The original response from the OpenAI API. */
2832
@Nonnull CreateChatCompletionResponse originalResponse;
@@ -110,7 +114,12 @@ public OpenAiAssistantMessage getMessage() {
110114
*/
111115
@Nonnull
112116
public List<OpenAiToolMessage> executeTools() {
113-
final var tools = originalRequest.getToolsExecutable();
114-
return OpenAiTool.execute(tools != null ? tools : List.of(), getMessage());
117+
final var tools =
118+
Optional.ofNullable(originalRequest.getToolsExecutable()).orElseGet(Collections::emptyList);
119+
if (!tools.isEmpty()) {
120+
final var toolNames = tools.stream().map(OpenAiTool::getName).toList();
121+
log.debug("Executing {} tool call(s) - {}", toolNames.size(), toolNames);
122+
}
123+
return OpenAiTool.execute(tools, getMessage());
115124
}
116125
}

foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,19 @@
3030
import java.io.IOException;
3131
import java.util.ArrayList;
3232
import java.util.List;
33+
import java.util.UUID;
3334
import java.util.stream.Stream;
3435
import javax.annotation.Nonnull;
3536
import javax.annotation.Nullable;
3637
import lombok.AccessLevel;
3738
import lombok.RequiredArgsConstructor;
3839
import lombok.extern.slf4j.Slf4j;
40+
import lombok.val;
3941
import org.apache.hc.client5.http.classic.methods.HttpPost;
4042
import org.apache.hc.core5.http.ContentType;
4143
import org.apache.hc.core5.http.io.entity.StringEntity;
4244
import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
45+
import org.slf4j.MDC;
4346

4447
/** Client for interacting with OpenAI models. */
4548
@Slf4j
@@ -417,6 +420,7 @@ private <T> T execute(
417420
@Nonnull final Class<T> responseType) {
418421
final var request = new HttpPost(path);
419422
serializeAndSetHttpEntity(request, payload, this.customHeaders);
423+
MDC.put("endpoint", path);
420424
return executeRequest(request, responseType);
421425
}
422426

@@ -427,6 +431,7 @@ private <D extends StreamedDelta> Stream<D> executeStream(
427431
@Nonnull final Class<D> deltaType) {
428432
final var request = new HttpPost(path);
429433
serializeAndSetHttpEntity(request, payload, this.customHeaders);
434+
MDC.put("endpoint", path);
430435
return streamRequest(request, deltaType);
431436
}
432437

@@ -449,10 +454,15 @@ private <T> T executeRequest(
449454
final BasicClassicHttpRequest request, @Nonnull final Class<T> responseType) {
450455
try {
451456
final var client = ApacheHttpClient5Accessor.getHttpClient(destination);
457+
MDC.put("destination", ((HttpDestination) destination).getUri().toASCIIString());
458+
MDC.put("mode", "streaming");
459+
logRequestStart();
452460
return client.execute(
453461
request, new ClientResponseHandler<>(responseType, OpenAiError.class, FACTORY));
454462
} catch (final IOException e) {
455463
throw new OpenAiClientException("Request to OpenAI model failed", e).setHttpRequest(request);
464+
} finally {
465+
MDC.clear();
456466
}
457467
}
458468

@@ -461,11 +471,28 @@ private <D extends StreamedDelta> Stream<D> streamRequest(
461471
final BasicClassicHttpRequest request, @Nonnull final Class<D> deltaType) {
462472
try {
463473
final var client = ApacheHttpClient5Accessor.getHttpClient(destination);
474+
MDC.put("destination", ((HttpDestination) destination).getUri().toASCIIString());
475+
MDC.put("mode", "streaming");
476+
logRequestStart();
464477
return new ClientStreamingHandler<>(deltaType, OpenAiError.class, FACTORY)
465478
.objectMapper(JACKSON)
466479
.handleStreamingResponse(client.executeOpen(null, request, null));
467480
} catch (final IOException e) {
468481
throw new OpenAiClientException("Request to OpenAI model failed", e).setHttpRequest(request);
482+
} finally {
483+
MDC.clear();
469484
}
470485
}
486+
487+
private static void logRequestStart() {
488+
val reqId = UUID.randomUUID().toString().substring(0, 8);
489+
MDC.put("reqId", reqId);
490+
MDC.put("service", "Orchestration");
491+
log.debug(
492+
"[reqId={}] Starting OpenAI {} request to {}, destination={}",
493+
reqId,
494+
MDC.get("mode"),
495+
MDC.get("endpoint"),
496+
MDC.get("destination"));
497+
}
471498
}

foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/spring/OpenAiChatModel.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,12 @@ public ChatResponse call(@Nonnull final Prompt prompt) {
6969
val response = new ChatResponse(toGenerations(result));
7070

7171
if (options != null && isInternalToolExecutionEnabled(options) && response.hasToolCalls()) {
72+
val toolCalls =
73+
response.getResult().getOutput().getToolCalls().stream().map(ToolCall::name).toList();
74+
log.info("Executing {} tool call(s) - {}", toolCalls.size(), toolCalls);
7275
val toolExecutionResult = toolCallingManager.executeToolCalls(prompt, response);
7376
// Send the tool execution result back to the model.
74-
log.info("Re-invoking model with tool execution results.");
77+
log.debug("Re-invoking model with tool execution results.");
7578
return call(new Prompt(toolExecutionResult.conversationHistory(), options));
7679
}
7780
return response;

logback.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<configuration>
3-
<property name="LOG_PATTERN" value="%d{HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n"/>
3+
<property name="LOG_PATTERN" value="%d{HH:mm:ss.SSS} %-5level [%thread] %logger{36} : %msg%n"/>
44

55
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
66
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">

orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationHttpExecutor.java

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import com.sap.cloud.sdk.cloudplatform.connectivity.exception.HttpClientInstantiationException;
1717
import java.io.IOException;
1818
import java.util.List;
19+
import java.util.UUID;
1920
import java.util.function.Supplier;
2021
import java.util.stream.Stream;
2122
import javax.annotation.Nonnull;
@@ -25,6 +26,7 @@
2526
import org.apache.hc.client5.http.classic.methods.HttpPost;
2627
import org.apache.hc.core5.http.ContentType;
2728
import org.apache.hc.core5.http.io.entity.StringEntity;
29+
import org.slf4j.MDC;
2830

2931
@Slf4j
3032
class OrchestrationHttpExecutor {
@@ -45,7 +47,6 @@ <T> T execute(
4547
@Nonnull final List<Header> customHeaders) {
4648
try {
4749
val json = JACKSON.writeValueAsString(payload);
48-
log.debug("Successfully serialized request into JSON payload");
4950
val request = new HttpPost(path);
5051
request.setEntity(new StringEntity(json, ContentType.APPLICATION_JSON));
5152
customHeaders.forEach(h -> request.addHeader(h.getName(), h.getValue()));
@@ -55,6 +56,9 @@ <T> T execute(
5556
val handler =
5657
new ClientResponseHandler<>(responseType, OrchestrationError.Synchronous.class, FACTORY)
5758
.objectMapper(JACKSON);
59+
MDC.put("endpoint", path);
60+
MDC.put("mode", "synchronous");
61+
logRequestStart();
5862
return client.execute(request, handler);
5963

6064
} catch (JsonProcessingException e) {
@@ -66,6 +70,8 @@ <T> T execute(
6670
| IOException e) {
6771
throw new OrchestrationClientException(
6872
"Request to Orchestration service failed for " + path, e);
73+
} finally {
74+
MDC.clear();
6975
}
7076
}
7177

@@ -81,7 +87,9 @@ Stream<OrchestrationChatCompletionDelta> stream(
8187
customHeaders.forEach(h -> request.addHeader(h.getName(), h.getValue()));
8288

8389
val client = getHttpClient();
84-
90+
MDC.put("endpoint", path);
91+
MDC.put("method", "streaming");
92+
logRequestStart();
8593
return new ClientStreamingHandler<>(
8694
OrchestrationChatCompletionDelta.class, OrchestrationError.Streaming.class, FACTORY)
8795
.objectMapper(JACKSON)
@@ -93,13 +101,27 @@ Stream<OrchestrationChatCompletionDelta> stream(
93101
} catch (IOException e) {
94102
throw new OrchestrationClientException(
95103
"Streaming request to the Orchestration service failed", e);
104+
} finally {
105+
MDC.clear();
96106
}
97107
}
98108

99109
@Nonnull
100110
private HttpClient getHttpClient() {
101111
val destination = destinationSupplier.get();
102-
log.debug("Using destination {} to connect to orchestration service", destination);
112+
MDC.put("destination", destination.getUri().toASCIIString());
103113
return ApacheHttpClient5Accessor.getHttpClient(destination);
104114
}
115+
116+
private static void logRequestStart() {
117+
val reqId = UUID.randomUUID().toString().substring(0, 8);
118+
MDC.put("reqId", reqId);
119+
MDC.put("service", "Orchestration");
120+
log.debug(
121+
"[reqId={}] Starting Orchestration {} request to {}, destination={}",
122+
reqId,
123+
MDC.get("mode"),
124+
MDC.get("endpoint"),
125+
MDC.get("destination"));
126+
}
105127
}

orchestration/src/main/java/com/sap/ai/sdk/orchestration/spring/OrchestrationChatModel.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,13 @@ public ChatResponse call(@Nonnull final Prompt prompt) {
7272

7373
if (ToolCallingChatOptions.isInternalToolExecutionEnabled(prompt.getOptions())
7474
&& response.hasToolCalls()) {
75+
76+
val toolCalls =
77+
response.getResult().getOutput().getToolCalls().stream().map(ToolCall::name).toList();
78+
log.debug("Executing {} tool call(s) - {}", toolCalls.size(), toolCalls);
7579
val toolExecutionResult = toolCallingManager.executeToolCalls(prompt, response);
7680
// Send the tool execution result back to the model.
77-
log.info("Re-invoking model with tool execution results.");
81+
log.debug("Re-invoking LLM with tool execution results.");
7882
return call(new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()));
7983
}
8084
return response;

0 commit comments

Comments
 (0)