Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>tags/1.8.0
| import logging | import logging | ||||
| import flask_login | import flask_login | ||||
| from flask import request | |||||
| from flask_restful import Resource, reqparse | from flask_restful import Resource, reqparse | ||||
| from werkzeug.exceptions import InternalServerError, NotFound | from werkzeug.exceptions import InternalServerError, NotFound | ||||
| ProviderTokenNotInitError, | ProviderTokenNotInitError, | ||||
| QuotaExceededError, | QuotaExceededError, | ||||
| ) | ) | ||||
| from core.helper.trace_id_helper import get_external_trace_id | |||||
| from core.model_runtime.errors.invoke import InvokeError | from core.model_runtime.errors.invoke import InvokeError | ||||
| from libs import helper | from libs import helper | ||||
| from libs.helper import uuid_value | from libs.helper import uuid_value | ||||
| streaming = args["response_mode"] != "blocking" | streaming = args["response_mode"] != "blocking" | ||||
| args["auto_generate_name"] = False | args["auto_generate_name"] = False | ||||
| external_trace_id = get_external_trace_id(request) | |||||
| if external_trace_id: | |||||
| args["external_trace_id"] = external_trace_id | |||||
| account = flask_login.current_user | account = flask_login.current_user | ||||
| try: | try: |
| from core.app.apps.base_app_queue_manager import AppQueueManager | from core.app.apps.base_app_queue_manager import AppQueueManager | ||||
| from core.app.entities.app_invoke_entities import InvokeFrom | from core.app.entities.app_invoke_entities import InvokeFrom | ||||
| from core.file.models import File | from core.file.models import File | ||||
| from core.helper.trace_id_helper import get_external_trace_id | |||||
| from extensions.ext_database import db | from extensions.ext_database import db | ||||
| from factories import file_factory, variable_factory | from factories import file_factory, variable_factory | ||||
| from fields.workflow_fields import workflow_fields, workflow_pagination_fields | from fields.workflow_fields import workflow_fields, workflow_pagination_fields | ||||
| args = parser.parse_args() | args = parser.parse_args() | ||||
| external_trace_id = get_external_trace_id(request) | |||||
| if external_trace_id: | |||||
| args["external_trace_id"] = external_trace_id | |||||
| try: | try: | ||||
| response = AppGenerateService.generate( | response = AppGenerateService.generate( | ||||
| app_model=app_model, user=current_user, args=args, invoke_from=InvokeFrom.DEBUGGER, streaming=True | app_model=app_model, user=current_user, args=args, invoke_from=InvokeFrom.DEBUGGER, streaming=True | ||||
| parser.add_argument("files", type=list, required=False, location="json") | parser.add_argument("files", type=list, required=False, location="json") | ||||
| args = parser.parse_args() | args = parser.parse_args() | ||||
| external_trace_id = get_external_trace_id(request) | |||||
| if external_trace_id: | |||||
| args["external_trace_id"] = external_trace_id | |||||
| try: | try: | ||||
| response = AppGenerateService.generate( | response = AppGenerateService.generate( | ||||
| app_model=app_model, | app_model=app_model, |
| """ | """ | ||||
| Retrieve the trace_id from the request. | Retrieve the trace_id from the request. | ||||
| Priority: header ('X-Trace-Id'), then parameters, then JSON body. Returns None if not provided or invalid. | |||||
| Priority: | |||||
| 1. header ('X-Trace-Id') | |||||
| 2. parameters | |||||
| 3. JSON body | |||||
| 4. Current OpenTelemetry context (if enabled) | |||||
| 5. OpenTelemetry traceparent header (if present and valid) | |||||
| Returns None if no valid trace_id is provided. | |||||
| """ | """ | ||||
| trace_id = request.headers.get("X-Trace-Id") | trace_id = request.headers.get("X-Trace-Id") | ||||
| if not trace_id: | if not trace_id: | ||||
| trace_id = request.args.get("trace_id") | trace_id = request.args.get("trace_id") | ||||
| if not trace_id and getattr(request, "is_json", False): | if not trace_id and getattr(request, "is_json", False): | ||||
| json_data = getattr(request, "json", None) | json_data = getattr(request, "json", None) | ||||
| if json_data: | if json_data: | ||||
| trace_id = json_data.get("trace_id") | trace_id = json_data.get("trace_id") | ||||
| if not trace_id: | |||||
| trace_id = get_trace_id_from_otel_context() | |||||
| if not trace_id: | |||||
| traceparent = request.headers.get("traceparent") | |||||
| if traceparent: | |||||
| trace_id = parse_traceparent_header(traceparent) | |||||
| if isinstance(trace_id, str) and is_valid_trace_id(trace_id): | if isinstance(trace_id, str) and is_valid_trace_id(trace_id): | ||||
| return trace_id | return trace_id | ||||
| return None | return None | ||||
| if trace_id: | if trace_id: | ||||
| return {"external_trace_id": trace_id} | return {"external_trace_id": trace_id} | ||||
| return {} | return {} | ||||
| def get_trace_id_from_otel_context() -> Optional[str]: | |||||
| """ | |||||
| Retrieve the current trace ID from the active OpenTelemetry trace context. | |||||
| Returns None if: | |||||
| 1. OpenTelemetry SDK is not installed or enabled. | |||||
| 2. There is no active span or trace context. | |||||
| """ | |||||
| try: | |||||
| from opentelemetry.trace import SpanContext, get_current_span | |||||
| from opentelemetry.trace.span import INVALID_TRACE_ID | |||||
| span = get_current_span() | |||||
| if not span: | |||||
| return None | |||||
| span_context: SpanContext = span.get_span_context() | |||||
| if not span_context or span_context.trace_id == INVALID_TRACE_ID: | |||||
| return None | |||||
| trace_id_hex = f"{span_context.trace_id:032x}" | |||||
| return trace_id_hex | |||||
| except Exception: | |||||
| return None | |||||
| def parse_traceparent_header(traceparent: str) -> Optional[str]: | |||||
| """ | |||||
| Parse the `traceparent` header to extract the trace_id. | |||||
| Expected format: | |||||
| 'version-trace_id-span_id-flags' | |||||
| Reference: | |||||
| W3C Trace Context Specification: https://www.w3.org/TR/trace-context/ | |||||
| """ | |||||
| try: | |||||
| parts = traceparent.split("-") | |||||
| if len(parts) == 4 and len(parts[1]) == 32: | |||||
| return parts[1] | |||||
| except Exception: | |||||
| pass | |||||
| return None |
| from typing import Optional | from typing import Optional | ||||
| from urllib.parse import urljoin | from urllib.parse import urljoin | ||||
| from opentelemetry.trace import Status, StatusCode | |||||
| from opentelemetry.trace import Link, Status, StatusCode | |||||
| from sqlalchemy.orm import Session, sessionmaker | from sqlalchemy.orm import Session, sessionmaker | ||||
| from core.ops.aliyun_trace.data_exporter.traceclient import ( | from core.ops.aliyun_trace.data_exporter.traceclient import ( | ||||
| TraceClient, | TraceClient, | ||||
| convert_datetime_to_nanoseconds, | convert_datetime_to_nanoseconds, | ||||
| convert_string_to_id, | |||||
| convert_to_span_id, | convert_to_span_id, | ||||
| convert_to_trace_id, | convert_to_trace_id, | ||||
| create_link, | |||||
| generate_span_id, | generate_span_id, | ||||
| ) | ) | ||||
| from core.ops.aliyun_trace.entities.aliyun_trace_entity import SpanData | from core.ops.aliyun_trace.entities.aliyun_trace_entity import SpanData | ||||
| def workflow_trace(self, trace_info: WorkflowTraceInfo): | def workflow_trace(self, trace_info: WorkflowTraceInfo): | ||||
| trace_id = convert_to_trace_id(trace_info.workflow_run_id) | trace_id = convert_to_trace_id(trace_info.workflow_run_id) | ||||
| links = [] | |||||
| if trace_info.trace_id: | if trace_info.trace_id: | ||||
| trace_id = convert_string_to_id(trace_info.trace_id) | |||||
| links.append(create_link(trace_id_str=trace_info.trace_id)) | |||||
| workflow_span_id = convert_to_span_id(trace_info.workflow_run_id, "workflow") | workflow_span_id = convert_to_span_id(trace_info.workflow_run_id, "workflow") | ||||
| self.add_workflow_span(trace_id, workflow_span_id, trace_info) | |||||
| self.add_workflow_span(trace_id, workflow_span_id, trace_info, links) | |||||
| workflow_node_executions = self.get_workflow_node_executions(trace_info) | workflow_node_executions = self.get_workflow_node_executions(trace_info) | ||||
| for node_execution in workflow_node_executions: | for node_execution in workflow_node_executions: | ||||
| status = Status(StatusCode.ERROR, trace_info.error) | status = Status(StatusCode.ERROR, trace_info.error) | ||||
| trace_id = convert_to_trace_id(message_id) | trace_id = convert_to_trace_id(message_id) | ||||
| links = [] | |||||
| if trace_info.trace_id: | if trace_info.trace_id: | ||||
| trace_id = convert_string_to_id(trace_info.trace_id) | |||||
| links.append(create_link(trace_id_str=trace_info.trace_id)) | |||||
| message_span_id = convert_to_span_id(message_id, "message") | message_span_id = convert_to_span_id(message_id, "message") | ||||
| message_span = SpanData( | message_span = SpanData( | ||||
| OUTPUT_VALUE: str(trace_info.outputs), | OUTPUT_VALUE: str(trace_info.outputs), | ||||
| }, | }, | ||||
| status=status, | status=status, | ||||
| links=links, | |||||
| ) | ) | ||||
| self.trace_client.add_span(message_span) | self.trace_client.add_span(message_span) | ||||
| message_id = trace_info.message_id | message_id = trace_info.message_id | ||||
| trace_id = convert_to_trace_id(message_id) | trace_id = convert_to_trace_id(message_id) | ||||
| links = [] | |||||
| if trace_info.trace_id: | if trace_info.trace_id: | ||||
| trace_id = convert_string_to_id(trace_info.trace_id) | |||||
| links.append(create_link(trace_id_str=trace_info.trace_id)) | |||||
| documents_data = extract_retrieval_documents(trace_info.documents) | documents_data = extract_retrieval_documents(trace_info.documents) | ||||
| dataset_retrieval_span = SpanData( | dataset_retrieval_span = SpanData( | ||||
| INPUT_VALUE: str(trace_info.inputs), | INPUT_VALUE: str(trace_info.inputs), | ||||
| OUTPUT_VALUE: json.dumps(documents_data, ensure_ascii=False), | OUTPUT_VALUE: json.dumps(documents_data, ensure_ascii=False), | ||||
| }, | }, | ||||
| links=links, | |||||
| ) | ) | ||||
| self.trace_client.add_span(dataset_retrieval_span) | self.trace_client.add_span(dataset_retrieval_span) | ||||
| status = Status(StatusCode.ERROR, trace_info.error) | status = Status(StatusCode.ERROR, trace_info.error) | ||||
| trace_id = convert_to_trace_id(message_id) | trace_id = convert_to_trace_id(message_id) | ||||
| links = [] | |||||
| if trace_info.trace_id: | if trace_info.trace_id: | ||||
| trace_id = convert_string_to_id(trace_info.trace_id) | |||||
| links.append(create_link(trace_id_str=trace_info.trace_id)) | |||||
| tool_span = SpanData( | tool_span = SpanData( | ||||
| trace_id=trace_id, | trace_id=trace_id, | ||||
| OUTPUT_VALUE: str(trace_info.tool_outputs), | OUTPUT_VALUE: str(trace_info.tool_outputs), | ||||
| }, | }, | ||||
| status=status, | status=status, | ||||
| links=links, | |||||
| ) | ) | ||||
| self.trace_client.add_span(tool_span) | self.trace_client.add_span(tool_span) | ||||
| status=self.get_workflow_node_status(node_execution), | status=self.get_workflow_node_status(node_execution), | ||||
| ) | ) | ||||
| def add_workflow_span(self, trace_id: int, workflow_span_id: int, trace_info: WorkflowTraceInfo): | |||||
| def add_workflow_span( | |||||
| self, trace_id: int, workflow_span_id: int, trace_info: WorkflowTraceInfo, links: Sequence[Link] | |||||
| ): | |||||
| message_span_id = None | message_span_id = None | ||||
| if trace_info.message_id: | if trace_info.message_id: | ||||
| message_span_id = convert_to_span_id(trace_info.message_id, "message") | message_span_id = convert_to_span_id(trace_info.message_id, "message") | ||||
| OUTPUT_VALUE: json.dumps(trace_info.workflow_run_outputs, ensure_ascii=False), | OUTPUT_VALUE: json.dumps(trace_info.workflow_run_outputs, ensure_ascii=False), | ||||
| }, | }, | ||||
| status=status, | status=status, | ||||
| links=links, | |||||
| ) | ) | ||||
| self.trace_client.add_span(message_span) | self.trace_client.add_span(message_span) | ||||
| OUTPUT_VALUE: json.dumps(trace_info.workflow_run_outputs, ensure_ascii=False), | OUTPUT_VALUE: json.dumps(trace_info.workflow_run_outputs, ensure_ascii=False), | ||||
| }, | }, | ||||
| status=status, | status=status, | ||||
| links=links, | |||||
| ) | ) | ||||
| self.trace_client.add_span(workflow_span) | self.trace_client.add_span(workflow_span) | ||||
| status = Status(StatusCode.ERROR, trace_info.error) | status = Status(StatusCode.ERROR, trace_info.error) | ||||
| trace_id = convert_to_trace_id(message_id) | trace_id = convert_to_trace_id(message_id) | ||||
| links = [] | |||||
| if trace_info.trace_id: | if trace_info.trace_id: | ||||
| trace_id = convert_string_to_id(trace_info.trace_id) | |||||
| links.append(create_link(trace_id_str=trace_info.trace_id)) | |||||
| suggested_question_span = SpanData( | suggested_question_span = SpanData( | ||||
| trace_id=trace_id, | trace_id=trace_id, | ||||
| OUTPUT_VALUE: json.dumps(trace_info.suggested_question, ensure_ascii=False), | OUTPUT_VALUE: json.dumps(trace_info.suggested_question, ensure_ascii=False), | ||||
| }, | }, | ||||
| status=status, | status=status, | ||||
| links=links, | |||||
| ) | ) | ||||
| self.trace_client.add_span(suggested_question_span) | self.trace_client.add_span(suggested_question_span) | ||||
| from opentelemetry.sdk.trace import ReadableSpan | from opentelemetry.sdk.trace import ReadableSpan | ||||
| from opentelemetry.sdk.util.instrumentation import InstrumentationScope | from opentelemetry.sdk.util.instrumentation import InstrumentationScope | ||||
| from opentelemetry.semconv.resource import ResourceAttributes | from opentelemetry.semconv.resource import ResourceAttributes | ||||
| from opentelemetry.trace import Link, SpanContext, TraceFlags | |||||
| from configs import dify_config | from configs import dify_config | ||||
| from core.ops.aliyun_trace.entities.aliyun_trace_entity import SpanData | from core.ops.aliyun_trace.entities.aliyun_trace_entity import SpanData | ||||
| return span | return span | ||||
| def create_link(trace_id_str: str) -> Link: | |||||
| placeholder_span_id = 0x0000000000000000 | |||||
| trace_id = int(trace_id_str, 16) | |||||
| span_context = SpanContext( | |||||
| trace_id=trace_id, span_id=placeholder_span_id, is_remote=False, trace_flags=TraceFlags(TraceFlags.SAMPLED) | |||||
| ) | |||||
| return Link(span_context) | |||||
| def generate_span_id() -> int: | def generate_span_id() -> int: | ||||
| span_id = random.getrandbits(64) | span_id = random.getrandbits(64) | ||||
| while span_id == INVALID_SPAN_ID: | while span_id == INVALID_SPAN_ID: |