feat(api): add OpenTelemetry instrumentation for requests and httpx

pull/19152/head
ZeroZ_JQ 1 year ago
parent 69b43a955f
commit c9c4c2db4c

@ -12,6 +12,10 @@ from flask_login import user_loaded_from_request, user_logged_in # type: ignore
from configs import dify_config
from dify_app import DifyApp
import requests # To get requests.Response type hint
import httpx # To get httpx.Response type hint
from opentelemetry.semconv.trace import SpanAttributes
@user_logged_in.connect
@user_loaded_from_request.connect
@ -108,6 +112,8 @@ def init_app(app: DifyApp):
from opentelemetry.instrumentation.celery import CeleryInstrumentor
from opentelemetry.instrumentation.flask import FlaskInstrumentor
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
from opentelemetry.instrumentation.requests import RequestsInstrumentor
from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
from opentelemetry.metrics import get_meter, get_meter_provider, set_meter_provider
from opentelemetry.propagate import set_global_textmap
from opentelemetry.propagators.b3 import B3Format
@ -124,7 +130,7 @@ def init_app(app: DifyApp):
from opentelemetry.semconv.resource import ResourceAttributes
from opentelemetry.trace import Span, get_tracer_provider, set_tracer_provider
from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
from opentelemetry.trace.status import StatusCode
from opentelemetry.trace.status import StatusCode, Status
setup_context_propagation()
# Initialize OpenTelemetry
@ -178,11 +184,61 @@ def init_app(app: DifyApp):
export_timeout_millis=dify_config.OTEL_METRIC_EXPORT_TIMEOUT,
)
set_meter_provider(MeterProvider(resource=resource, metric_readers=[reader]))
# Helper function to initialize requests instrumentor
def init_requests_instrumentor():
def outgoing_requests_response_hook(span: Span, request: requests.PreparedRequest, response: requests.Response):
if span and span.is_recording() and response:
status_code = response.status_code
status_class = f"{status_code // 100}xx"
# Set span status
if 200 <= status_code < 400:
span.set_status(Status(StatusCode.OK))
else:
span.set_status(Status(StatusCode.ERROR, f"HTTP status code {status_code}"))
# Record metric (references counter from outer scope)
_http_client_response_counter.add(1, {"status_code": status_code, "status_class": status_class})
# Only record the URL path attribute
if hasattr(request, 'path_url'): # For requests.PreparedRequest
path = request.path_url.split('?', 1)[0] # Get path before query string
span.set_attribute(SpanAttributes.URL_PATH, path)
RequestsInstrumentor().instrument(response_hook=outgoing_requests_response_hook)
# Helper function to initialize httpx instrumentor
def init_httpx_instrumentor():
def outgoing_httpx_response_hook(span: Span, request: httpx.Request, response: httpx.Response):
if span and span.is_recording() and response:
status_code = response.status_code
status_class = f"{status_code // 100}xx"
# Set span status
if 200 <= status_code < 400:
span.set_status(Status(StatusCode.OK))
else:
span.set_status(Status(StatusCode.ERROR, f"HTTP status code {status_code}"))
# Record metric (references counter from outer scope)
_http_client_response_counter.add(1, {"status_code": status_code, "status_class": status_class})
# Only record the URL path attribute
if hasattr(request, 'url') and hasattr(request.url, 'path'): # For httpx.Request
span.set_attribute(SpanAttributes.URL_PATH, request.url.path)
HTTPXClientInstrumentor().instrument(response_hook=outgoing_httpx_response_hook)
# Initialize instrumentors
if not is_celery_worker():
init_flask_instrumentor(app)
CeleryInstrumentor(tracer_provider=get_tracer_provider(), meter_provider=get_meter_provider()).instrument()
instrument_exception_logging()
init_sqlalchemy_instrumentor(app)
init_requests_instrumentor()
init_httpx_instrumentor()
atexit.register(shutdown_tracer)

@ -50,6 +50,8 @@ dependencies = [
"opentelemetry-instrumentation-celery==0.48b0",
"opentelemetry-instrumentation-flask==0.48b0",
"opentelemetry-instrumentation-sqlalchemy==0.48b0",
"opentelemetry-instrumentation-requests==0.48b0",
"opentelemetry-instrumentation-httpx==0.48b0",
"opentelemetry-propagator-b3==1.27.0",
# opentelemetry-proto1.28.0 depends on protobuf (>=5.0,<6.0),
# which is conflict with googleapis-common-protos (1.63.0)

@ -1201,6 +1201,8 @@ dependencies = [
{ name = "opentelemetry-instrumentation" },
{ name = "opentelemetry-instrumentation-celery" },
{ name = "opentelemetry-instrumentation-flask" },
{ name = "opentelemetry-instrumentation-httpx" },
{ name = "opentelemetry-instrumentation-requests" },
{ name = "opentelemetry-instrumentation-sqlalchemy" },
{ name = "opentelemetry-propagator-b3" },
{ name = "opentelemetry-proto" },
@ -1371,6 +1373,8 @@ requires-dist = [
{ name = "opentelemetry-instrumentation", specifier = "==0.48b0" },
{ name = "opentelemetry-instrumentation-celery", specifier = "==0.48b0" },
{ name = "opentelemetry-instrumentation-flask", specifier = "==0.48b0" },
{ name = "opentelemetry-instrumentation-httpx", specifier = ">=0.48b0" },
{ name = "opentelemetry-instrumentation-requests", specifier = "==0.48b0" },
{ name = "opentelemetry-instrumentation-sqlalchemy", specifier = "==0.48b0" },
{ name = "opentelemetry-propagator-b3", specifier = "==1.27.0" },
{ name = "opentelemetry-proto", specifier = "==1.27.0" },
@ -3569,6 +3573,36 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/78/3d/fcde4f8f0bf9fa1ee73a12304fa538076fb83fe0a2ae966ab0f0b7da5109/opentelemetry_instrumentation_flask-0.48b0-py3-none-any.whl", hash = "sha256:26b045420b9d76e85493b1c23fcf27517972423480dc6cf78fd6924248ba5808", size = 14588 },
]
[[package]]
name = "opentelemetry-instrumentation-httpx"
version = "0.48b0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
{ name = "opentelemetry-instrumentation" },
{ name = "opentelemetry-semantic-conventions" },
{ name = "opentelemetry-util-http" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d3/d9/c65d818607c16d1b7ea8d2de6111c6cecadf8d2fd38c1885a72733a7c6d3/opentelemetry_instrumentation_httpx-0.48b0.tar.gz", hash = "sha256:ee977479e10398931921fb995ac27ccdeea2e14e392cb27ef012fc549089b60a", size = 16931 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c2/fe/f2daa9d6d988c093b8c7b1d35df675761a8ece0b600b035dc04982746c9d/opentelemetry_instrumentation_httpx-0.48b0-py3-none-any.whl", hash = "sha256:d94f9d612c82d09fe22944d1904a30a464c19bea2ba76be656c99a28ad8be8e5", size = 13900 },
]
[[package]]
name = "opentelemetry-instrumentation-requests"
version = "0.48b0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
{ name = "opentelemetry-instrumentation" },
{ name = "opentelemetry-semantic-conventions" },
{ name = "opentelemetry-util-http" },
]
sdist = { url = "https://files.pythonhosted.org/packages/52/ac/5eb78efde21ff21d0ad5dc8c6cc6a0f8ae482ce8a46293c2f45a628b6166/opentelemetry_instrumentation_requests-0.48b0.tar.gz", hash = "sha256:67ab9bd877a0352ee0db4616c8b4ae59736ddd700c598ed907482d44f4c9a2b3", size = 14120 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/43/df/0df9226d1b14f29d23c07e6194b9fd5ad50e7d987b7fd13df7dcf718aeb1/opentelemetry_instrumentation_requests-0.48b0-py3-none-any.whl", hash = "sha256:d4f01852121d0bd4c22f14f429654a735611d4f7bf3cf93f244bdf1489b2233d", size = 12366 },
]
[[package]]
name = "opentelemetry-instrumentation-sqlalchemy"
version = "0.48b0"

Loading…
Cancel
Save