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 configs import dify_config
from dify_app import DifyApp 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_logged_in.connect
@user_loaded_from_request.connect @user_loaded_from_request.connect
@ -108,6 +112,8 @@ def init_app(app: DifyApp):
from opentelemetry.instrumentation.celery import CeleryInstrumentor from opentelemetry.instrumentation.celery import CeleryInstrumentor
from opentelemetry.instrumentation.flask import FlaskInstrumentor from opentelemetry.instrumentation.flask import FlaskInstrumentor
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor 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.metrics import get_meter, get_meter_provider, set_meter_provider
from opentelemetry.propagate import set_global_textmap from opentelemetry.propagate import set_global_textmap
from opentelemetry.propagators.b3 import B3Format from opentelemetry.propagators.b3 import B3Format
@ -124,7 +130,7 @@ def init_app(app: DifyApp):
from opentelemetry.semconv.resource import ResourceAttributes from opentelemetry.semconv.resource import ResourceAttributes
from opentelemetry.trace import Span, get_tracer_provider, set_tracer_provider from opentelemetry.trace import Span, get_tracer_provider, set_tracer_provider
from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
from opentelemetry.trace.status import StatusCode from opentelemetry.trace.status import StatusCode, Status
setup_context_propagation() setup_context_propagation()
# Initialize OpenTelemetry # Initialize OpenTelemetry
@ -178,11 +184,61 @@ def init_app(app: DifyApp):
export_timeout_millis=dify_config.OTEL_METRIC_EXPORT_TIMEOUT, export_timeout_millis=dify_config.OTEL_METRIC_EXPORT_TIMEOUT,
) )
set_meter_provider(MeterProvider(resource=resource, metric_readers=[reader])) 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(): if not is_celery_worker():
init_flask_instrumentor(app) init_flask_instrumentor(app)
CeleryInstrumentor(tracer_provider=get_tracer_provider(), meter_provider=get_meter_provider()).instrument() CeleryInstrumentor(tracer_provider=get_tracer_provider(), meter_provider=get_meter_provider()).instrument()
instrument_exception_logging() instrument_exception_logging()
init_sqlalchemy_instrumentor(app) init_sqlalchemy_instrumentor(app)
init_requests_instrumentor()
init_httpx_instrumentor()
atexit.register(shutdown_tracer) atexit.register(shutdown_tracer)

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

@ -1201,6 +1201,8 @@ dependencies = [
{ name = "opentelemetry-instrumentation" }, { name = "opentelemetry-instrumentation" },
{ name = "opentelemetry-instrumentation-celery" }, { name = "opentelemetry-instrumentation-celery" },
{ name = "opentelemetry-instrumentation-flask" }, { name = "opentelemetry-instrumentation-flask" },
{ name = "opentelemetry-instrumentation-httpx" },
{ name = "opentelemetry-instrumentation-requests" },
{ name = "opentelemetry-instrumentation-sqlalchemy" }, { name = "opentelemetry-instrumentation-sqlalchemy" },
{ name = "opentelemetry-propagator-b3" }, { name = "opentelemetry-propagator-b3" },
{ name = "opentelemetry-proto" }, { name = "opentelemetry-proto" },
@ -1371,6 +1373,8 @@ requires-dist = [
{ name = "opentelemetry-instrumentation", specifier = "==0.48b0" }, { name = "opentelemetry-instrumentation", specifier = "==0.48b0" },
{ name = "opentelemetry-instrumentation-celery", specifier = "==0.48b0" }, { name = "opentelemetry-instrumentation-celery", specifier = "==0.48b0" },
{ name = "opentelemetry-instrumentation-flask", 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-instrumentation-sqlalchemy", specifier = "==0.48b0" },
{ name = "opentelemetry-propagator-b3", specifier = "==1.27.0" }, { name = "opentelemetry-propagator-b3", specifier = "==1.27.0" },
{ name = "opentelemetry-proto", 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 }, { 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]] [[package]]
name = "opentelemetry-instrumentation-sqlalchemy" name = "opentelemetry-instrumentation-sqlalchemy"
version = "0.48b0" version = "0.48b0"

Loading…
Cancel
Save