Add conditinal support for logging errors in observation Span
- Useful for not Web apps. - Introduce ErrorLoggingObservationHandler for tracing errors across various AI contexts - Add error logging configuration option to ChatObservationProperties - Include ErrorLoggingObservationHandler bean in ChatObservationAutoConfiguration - Update docs Resolves #1440
This commit is contained in:
committed by
Mark Pollack
parent
05292ac730
commit
d6dc2b26bf
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2024 - 2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.ai.model.observation;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import io.micrometer.observation.Observation;
|
||||
import io.micrometer.observation.Observation.Context;
|
||||
import io.micrometer.observation.ObservationHandler;
|
||||
import io.micrometer.tracing.Tracer;
|
||||
import io.micrometer.tracing.handler.TracingObservationHandler.TracingContext;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* @author Christian Tzolov
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@SuppressWarnings({ "rawtypes", "null" })
|
||||
public class ErrorLoggingObservationHandler implements ObservationHandler {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ErrorLoggingObservationHandler.class);
|
||||
|
||||
private final Tracer tracer;
|
||||
|
||||
private final List<Class<? extends Observation.Context>> supportedContextTypes;
|
||||
|
||||
private final Consumer<Context> errorConsumer;
|
||||
|
||||
public ErrorLoggingObservationHandler(Tracer tracer,
|
||||
List<Class<? extends Observation.Context>> supportedContextTypes) {
|
||||
this(tracer, supportedContextTypes, context -> logger.error("Traced Error: ", context.getError()));
|
||||
}
|
||||
|
||||
public ErrorLoggingObservationHandler(Tracer tracer,
|
||||
List<Class<? extends Observation.Context>> supportedContextTypes, Consumer<Context> errorConsumer) {
|
||||
|
||||
Assert.notNull(tracer, "Tracer must not be null");
|
||||
Assert.notNull(supportedContextTypes, "SupportedContextTypes must not be null");
|
||||
Assert.notNull(errorConsumer, "ErrorConsumer must not be null");
|
||||
|
||||
this.tracer = tracer;
|
||||
this.supportedContextTypes = supportedContextTypes;
|
||||
this.errorConsumer = errorConsumer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsContext(Context context) {
|
||||
return (context == null) ? false : this.supportedContextTypes.stream().anyMatch(clz -> clz.isInstance(context));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Context context) {
|
||||
if (context != null) {
|
||||
TracingContext tracingContext = context.get(TracingContext.class);
|
||||
if (tracingContext != null) {
|
||||
try (var val = this.tracer.withSpan(tracingContext.getSpan())) {
|
||||
this.errorConsumer.accept(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -27,7 +27,7 @@ import io.micrometer.observation.Observation;
|
||||
/**
|
||||
* Context used to store metadata for vector store operations.
|
||||
*
|
||||
* @author Christian Tzolo
|
||||
* @author Christian Tzolov
|
||||
* @author Thomas Vitale
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
package org.springframework.ai.observation.tracing;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import io.micrometer.tracing.Span;
|
||||
import io.micrometer.tracing.TraceContext;
|
||||
import io.micrometer.tracing.handler.TracingObservationHandler;
|
||||
import io.micrometer.tracing.otel.bridge.OtelCurrentTraceContext;
|
||||
import io.micrometer.tracing.otel.bridge.OtelTracer;
|
||||
import io.opentelemetry.api.OpenTelemetry;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.ai.chat.observation.ChatModelObservationContentProcessor;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link TracingHelper}.
|
||||
|
||||
@@ -106,13 +106,14 @@ The chat prompt and completion data are typically too big to be included in an o
|
||||
The preferred way to store large data it is as span events, which are supported by OpenTelemetry but not yet surfaced through the Micrometer APIs.
|
||||
Spring AI supports storing these fields as events in OpenTelemetry and will provide a more general event based solution once the issue https://github.com/micrometer-metrics/micrometer/issues/5238 is resolved.
|
||||
|
||||
|
||||
[cols="6,3,1"]
|
||||
|====
|
||||
| Property | Description | Default
|
||||
| `spring.ai.chat.observations.include-prompt` | `true` or `false` | `false`
|
||||
| `spring.ai.chat.observations.include-completion` | `true` or `false` | `false`
|
||||
| `spring.ai.chat.observations.include-prompt` | Include the prompt content in observations. `true` or `false` | `false`
|
||||
| `spring.ai.chat.observations.include-completion` | Include the completion content in observations. `true` or `false` | `false`
|
||||
| `spring.ai.chat.observations.include-error-logging` | Include error logging in observations. `true` or `false` | `false`
|
||||
|====
|
||||
|
||||
== EmbeddingModel
|
||||
|
||||
NOTE: Observability features are currently supported only for ChatModel and EmbeddingModel implementations from the following AI model providers: OpenAI, Ollama, Anthropic, and Mistral. Additional AI model providers will be supported in a future release.
|
||||
|
||||
@@ -15,16 +15,23 @@
|
||||
*/
|
||||
package org.springframework.ai.autoconfigure.chat.observation;
|
||||
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.micrometer.tracing.otel.bridge.OtelTracer;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ai.chat.client.advisor.observation.AdvisorObservationContext;
|
||||
import org.springframework.ai.chat.client.observation.ChatClientObservationContext;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.observation.ChatModelCompletionObservationFilter;
|
||||
import org.springframework.ai.chat.observation.ChatModelCompletionObservationHandler;
|
||||
import org.springframework.ai.chat.observation.ChatModelMeterObservationHandler;
|
||||
import org.springframework.ai.chat.observation.ChatModelObservationContext;
|
||||
import org.springframework.ai.chat.observation.ChatModelPromptContentObservationFilter;
|
||||
import org.springframework.ai.chat.observation.ChatModelPromptContentObservationHandler;
|
||||
import org.springframework.ai.embedding.observation.EmbeddingModelObservationContext;
|
||||
import org.springframework.ai.image.observation.ImageModelObservationContext;
|
||||
import org.springframework.ai.model.observation.ErrorLoggingObservationHandler;
|
||||
import org.springframework.ai.vectorstore.observation.VectorStoreObservationContext;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
@@ -36,6 +43,10 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.micrometer.tracing.Tracer;
|
||||
import io.micrometer.tracing.otel.bridge.OtelTracer;
|
||||
|
||||
/**
|
||||
* Auto-configuration for Spring AI chat model observations.
|
||||
*
|
||||
@@ -113,6 +124,17 @@ public class ChatObservationAutoConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(Tracer.class)
|
||||
@ConditionalOnProperty(prefix = ChatObservationProperties.CONFIG_PREFIX, name = "include-error-logging",
|
||||
havingValue = "true")
|
||||
public ErrorLoggingObservationHandler errorLoggingObservationHandler(Tracer tracer) {
|
||||
return new ErrorLoggingObservationHandler(tracer,
|
||||
List.of(EmbeddingModelObservationContext.class, ImageModelObservationContext.class,
|
||||
ChatModelObservationContext.class, ChatClientObservationContext.class,
|
||||
AdvisorObservationContext.class, VectorStoreObservationContext.class));
|
||||
}
|
||||
|
||||
private static void logPromptContentWarning() {
|
||||
logger.warn(
|
||||
"You have enabled the inclusion of the prompt content in the observations, with the risk of exposing sensitive or private information. Please, be careful!");
|
||||
|
||||
@@ -38,6 +38,11 @@ public class ChatObservationProperties {
|
||||
*/
|
||||
private boolean includePrompt = false;
|
||||
|
||||
/**
|
||||
* Whether to include error logging in the observations.
|
||||
*/
|
||||
private boolean includeErrorLogging = false;
|
||||
|
||||
public boolean isIncludeCompletion() {
|
||||
return includeCompletion;
|
||||
}
|
||||
@@ -54,4 +59,12 @@ public class ChatObservationProperties {
|
||||
this.includePrompt = includePrompt;
|
||||
}
|
||||
|
||||
public boolean isIncludeErrorLogging() {
|
||||
return this.includeErrorLogging;
|
||||
}
|
||||
|
||||
public void setIncludeErrorLogging(boolean includeErrorLogging) {
|
||||
this.includeErrorLogging = includeErrorLogging;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user