Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for OTLP exported metrics and tracing #1489

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions app/backend/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,8 +413,7 @@ def create_app():
app = Quart(__name__)
app.register_blueprint(bp)

if os.getenv("APPLICATIONINSIGHTS_CONNECTION_STRING"):
configure_azure_monitor()
def instrument_app():
# This tracks HTTP requests made by aiohttp:
AioHttpClientInstrumentor().instrument()
# This tracks HTTP requests made by httpx:
Expand All @@ -424,6 +423,19 @@ def create_app():
# This middleware tracks app route requests:
app.asgi_app = OpenTelemetryMiddleware(app.asgi_app) # type: ignore[method-assign]

if os.getenv("APPLICATIONINSIGHTS_CONNECTION_STRING"):
configure_azure_monitor()
instrument_app()
elif os.getenv("OTLP_GRPC_ENDPOINT"):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this for non-Azure Monitor endpoint?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, it'll work with all the other vendors

from app.backend.otlp_tracing import configure_oltp_grpc_tracing
configure_oltp_grpc_tracing(endpoint=os.getenv("OTLP_GRPC_ENDPOINT"))
instrument_app()
elif os.getenv("OTLP_HTTP_ENDPOINT"):
from app.backend.otlp_tracing import configure_oltp_http_tracing
configure_oltp_http_tracing(endpoint=os.getenv("OTLP_HTTP_ENDPOINT"))
instrument_app()


# Level should be one of https://docs.python.org/3/library/logging.html#logging-levels
default_level = "INFO" # In development, log more verbosely
if os.getenv("WEBSITE_HOSTNAME"): # In production, don't log as heavily
Expand Down
9 changes: 8 additions & 1 deletion app/backend/approaches/chatreadretrieveread.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
from core.authentication import AuthenticationHelper
from core.modelhelper import get_token_limit

from opentelemetry import metrics


class ChatReadRetrieveReadApproach(ChatApproach):
"""
Expand Down Expand Up @@ -49,6 +51,8 @@ def __init__(
self.query_language = query_language
self.query_speller = query_speller
self.chatgpt_token_limit = get_token_limit(chatgpt_model)
self.meter_ratelimit_remaining_tokens = metrics.get_meter(__name__).create_gauge("openai.ratelimit-remaining-tokens", description="OpenAI tokens remaining in limit")
self.meter_ratelimit_remaining_requests = metrics.get_meter(__name__).create_gauge("openai.ratelimit-remaining-requests", description="OpenAI remaining requests")

@property
def system_message_chat_conversation(self):
Expand Down Expand Up @@ -128,7 +132,7 @@ async def run_until_final_call(
few_shots=self.query_prompt_few_shots,
)

chat_completion: ChatCompletion = await self.openai_client.chat.completions.create(
chat_completion_response = await self.openai_client.chat.completions.with_raw_response.create(
messages=query_messages, # type: ignore
# Azure OpenAI takes the deployment name as the model name
model=self.chatgpt_deployment if self.chatgpt_deployment else self.chatgpt_model,
Expand All @@ -138,6 +142,9 @@ async def run_until_final_call(
tools=tools,
tool_choice="auto",
)
self.meter_ratelimit_remaining_tokens.set(int(chat_completion_response.headers.get("x-ratelimit-remaining-tokens", 0)))
self.meter_ratelimit_remaining_requests.set(int(chat_completion_response.headers.get("x-ratelimit-remaining-requests", 0)))
chat_completion = chat_completion_response.parse()

query_text = self.get_search_query(chat_completion, original_user_query)

Expand Down
51 changes: 51 additions & 0 deletions app/backend/otlp_tracing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from opentelemetry.sdk.resources import SERVICE_NAME, Resource

from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter as OTLPHTTPSpanExporter
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter as OTLPGRPCSpanExporter
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor

from opentelemetry import metrics
from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter as OTLPHTTPMetricExporter
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter as OTLPGRPCMetricExporter
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader


def configure_oltp_http_tracing(service_name: str = "azure-search-openai-demo", endpoint: str = "http://localhost:4318"):
# Service name is required for most backends
resource = Resource(attributes={
SERVICE_NAME: service_name
})

traceProvider = TracerProvider(resource=resource)
# Trick OLTP into thinking the httpx client is a requests session so that HTTP2 works
processor = BatchSpanProcessor(OTLPHTTPSpanExporter(endpoint=f"{endpoint}/v1/traces",))
traceProvider.add_span_processor(processor)
trace.set_tracer_provider(traceProvider)

reader = PeriodicExportingMetricReader(
OTLPHTTPMetricExporter(endpoint=f"{endpoint}/v1/metrics")
)
meterProvider = MeterProvider(resource=resource, metric_readers=[reader])
metrics.set_meter_provider(meterProvider)


def configure_oltp_grpc_tracing(service_name: str = "azure-search-openai-demo", endpoint: str = "http://localhost:4317"):
# Service name is required for most backends
resource = Resource(attributes={
SERVICE_NAME: service_name
})

traceProvider = TracerProvider(resource=resource)
# Trick OLTP into thinking the httpx client is a requests session so that HTTP2 works
processor = BatchSpanProcessor(OTLPGRPCSpanExporter(endpoint=endpoint, insecure=True))
traceProvider.add_span_processor(processor)
trace.set_tracer_provider(traceProvider)

reader = PeriodicExportingMetricReader(
OTLPGRPCMetricExporter(endpoint=endpoint, insecure=True)
)
meterProvider = MeterProvider(resource=resource, metric_readers=[reader])
metrics.set_meter_provider(meterProvider)
3 changes: 3 additions & 0 deletions app/backend/requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ opentelemetry-instrumentation-httpx
opentelemetry-instrumentation-requests
opentelemetry-instrumentation-aiohttp-client
opentelemetry-instrumentation-openai
opentelemetry-exporter-otlp-proto-grpc
opentelemetry-exporter-otlp-proto-http

msal
azure-keyvault-secrets
cryptography
Expand Down
Loading
Loading