From 23150c2319f7c843ff4df9d59d284574857685be Mon Sep 17 00:00:00 2001 From: mesrop2008 <104313591+mesrop2008@users.noreply.github.com> Date: Fri, 21 Aug 2026 11:20:42 +0000 Subject: [PATCH] fix(api): attach exc_info=True to logger.warning sites that drop the traceback (#41068) --- .../remote_settings_sources/apollo/client.py | 8 +++--- api/core/app/apps/workflow/app_generator.py | 4 +-- .../annotation_reply/annotation_reply.py | 4 +-- api/core/entities/provider_configuration.py | 4 +-- api/core/model_manager.py | 4 +-- api/core/rag/extractor/word_extractor.py | 4 +-- .../processor/paragraph_index_processor.py | 12 ++++----- .../data_exporter/traceclient.py | 2 +- api/services/summary_index_service.py | 8 +++--- api/tasks/add_document_to_index_task.py | 4 +-- api/tasks/disable_segment_from_index_task.py | 4 +-- api/tasks/disable_segments_from_index_task.py | 4 +-- api/tasks/enable_segment_to_index_task.py | 4 +-- api/tasks/enable_segments_to_index_task.py | 4 +-- api/tasks/remove_document_from_index_task.py | 4 +-- .../test_paragraph_index_processor.py | 26 +++++++++++++++++++ 16 files changed, 63 insertions(+), 37 deletions(-) diff --git a/api/configs/remote_settings_sources/apollo/client.py b/api/configs/remote_settings_sources/apollo/client.py index e30e6218a11..feae2595d44 100644 --- a/api/configs/remote_settings_sources/apollo/client.py +++ b/api/configs/remote_settings_sources/apollo/client.py @@ -167,8 +167,8 @@ class ApolloClient: old_value = old_kv.get(key) if old_value is None: self._change_listener("add", namespace, key, new_value) - except BaseException as e: - logger.warning(str(e)) + except BaseException: + logger.warning("Apollo config change listener failed", exc_info=True) def _path_checker(self) -> None: if not os.path.isdir(self._cache_file_path): @@ -235,8 +235,8 @@ class ApolloClient: return else: logger.warning("Sleep...") - except Exception as e: - logger.warning(str(e)) + except Exception: + logger.warning("Apollo long polling failed", exc_info=True) def _get_net_and_set_local(self, namespace: str, n_id: int, call_change: bool = False) -> None: namespace_data = self.get_json_from_net(namespace) diff --git a/api/core/app/apps/workflow/app_generator.py b/api/core/app/apps/workflow/app_generator.py index 2d6c1512c48..8b5164a8528 100644 --- a/api/core/app/apps/workflow/app_generator.py +++ b/api/core/app/apps/workflow/app_generator.py @@ -686,8 +686,8 @@ class WorkflowAppGenerator(BaseAppGenerator): try: with active_workflow_task(application_generate_entity.task_id): runner.run() - except GenerateTaskStoppedError as e: - logger.warning("Task stopped: %s", str(e)) + except GenerateTaskStoppedError: + logger.warning("Task stopped", exc_info=True) pass except InvokeAuthorizationError: queue_manager.publish_error( diff --git a/api/core/app/features/annotation_reply/annotation_reply.py b/api/core/app/features/annotation_reply/annotation_reply.py index ca95b267015..049f7ac2d5a 100644 --- a/api/core/app/features/annotation_reply/annotation_reply.py +++ b/api/core/app/features/annotation_reply/annotation_reply.py @@ -90,8 +90,8 @@ class AnnotationReplyFeature: ) return annotation - except Exception as e: - logger.warning("Query annotation failed, exception: %s.", str(e)) + except Exception: + logger.warning("Query annotation failed.", exc_info=True) return None return None diff --git a/api/core/entities/provider_configuration.py b/api/core/entities/provider_configuration.py index a73160ed1de..3e72829a09b 100644 --- a/api/core/entities/provider_configuration.py +++ b/api/core/entities/provider_configuration.py @@ -486,8 +486,8 @@ class ProviderConfiguration(BaseModel): next_number = max(numbers, default=0) + 1 return f"API KEY {next_number}" - except Exception as e: - logger.warning("Error generating next credential name: %s", str(e)) + except Exception: + logger.warning("Error generating next credential name", exc_info=True) return "API KEY 1" def _get_provider_names(self): diff --git a/api/core/model_manager.py b/api/core/model_manager.py index 2e91e4fe507..81ef887d6dc 100644 --- a/api/core/model_manager.py +++ b/api/core/model_manager.py @@ -923,8 +923,8 @@ class LBModelManager: provider=self._provider, credential_type=PluginCredentialType.MODEL, ) - except Exception as e: - logger.warning("Load balancing config %s failed policy compliance check: %s", config.id, str(e)) + except Exception: + logger.warning("Load balancing config %s failed policy compliance check", config.id, exc_info=True) cooldown_load_balancing_configs.append(config) if len(cooldown_load_balancing_configs) >= len(self._load_balancing_configs): # all configs are in cooldown or failed policy compliance diff --git a/api/core/rag/extractor/word_extractor.py b/api/core/rag/extractor/word_extractor.py index e57e52f8975..44a1c5bba7d 100644 --- a/api/core/rag/extractor/word_extractor.py +++ b/api/core/rag/extractor/word_extractor.py @@ -131,8 +131,8 @@ class WordExtractor(BaseExtractor): continue try: response = remote_fetcher.make_request("GET", url) - except Exception as e: - logger.warning("Failed to download image from URL: %s: %s", url, str(e)) + except Exception: + logger.warning("Failed to download image from URL: %s", url, exc_info=True) continue if response.status_code == 200: image_ext = mimetypes.guess_extension(response.headers.get("Content-Type", "")) diff --git a/api/core/rag/index_processor/processor/paragraph_index_processor.py b/api/core/rag/index_processor/processor/paragraph_index_processor.py index 518d70eb284..d6323039c38 100644 --- a/api/core/rag/index_processor/processor/paragraph_index_processor.py +++ b/api/core/rag/index_processor/processor/paragraph_index_processor.py @@ -468,8 +468,8 @@ class ParagraphIndexProcessor(BaseIndexProcessor): file, image_detail_config=ImagePromptMessageContent.DETAIL.LOW ) prompt_message_contents.append(file_content) - except Exception as e: - logger.warning("Failed to convert image file to prompt message content: %s", str(e)) + except Exception: + logger.warning("Failed to convert image file to prompt message content", exc_info=True) continue # Add text content @@ -572,8 +572,8 @@ class ParagraphIndexProcessor(BaseIndexProcessor): access_controller=_file_access_controller, ) file_objects.append(file_obj) - except Exception as e: - logger.warning("Failed to create File object from UploadFile %s: %s", upload_file.id, str(e)) + except Exception: + logger.warning("Failed to create File object from UploadFile %s", upload_file.id, exc_info=True) continue return file_objects @@ -629,8 +629,8 @@ class ParagraphIndexProcessor(BaseIndexProcessor): storage_key=upload_file.key, ) file_objects.append(file_obj) - except Exception as e: - logger.warning("Failed to create File object from UploadFile %s: %s", upload_file.id, str(e)) + except Exception: + logger.warning("Failed to create File object from UploadFile %s", upload_file.id, exc_info=True) continue return file_objects diff --git a/api/providers/trace/trace-aliyun/src/dify_trace_aliyun/data_exporter/traceclient.py b/api/providers/trace/trace-aliyun/src/dify_trace_aliyun/data_exporter/traceclient.py index 374d20ade9d..7350cc6b825 100644 --- a/api/providers/trace/trace-aliyun/src/dify_trace_aliyun/data_exporter/traceclient.py +++ b/api/providers/trace/trace-aliyun/src/dify_trace_aliyun/data_exporter/traceclient.py @@ -86,7 +86,7 @@ class TraceClient: logger.warning("AliyunTrace API check failed: Unexpected status code: %s", response.status_code) return False except httpx.RequestError as e: - logger.warning("AliyunTrace API check failed: %s", str(e)) + logger.warning("AliyunTrace API check failed", exc_info=True) raise ValueError(f"AliyunTrace API check failed: {str(e)}") def get_project_url(self) -> str: diff --git a/api/services/summary_index_service.py b/api/services/summary_index_service.py index d82fca196a1..a8506f95cbc 100644 --- a/api/services/summary_index_service.py +++ b/api/services/summary_index_service.py @@ -243,8 +243,8 @@ class SummaryIndexService: tokens_list = embedding_model.get_text_embedding_num_tokens([summary_content]) raw_embedding_tokens = tokens_list[0] if tokens_list else 0 embedding_tokens = raw_embedding_tokens if isinstance(raw_embedding_tokens, int) else 0 - except Exception as e: - logger.warning("Failed to calculate embedding tokens for summary: %s", str(e)) + except Exception: + logger.warning("Failed to calculate embedding tokens for summary", exc_info=True) # Create document with summary content and metadata summary_document = Document( @@ -914,8 +914,8 @@ class SummaryIndexService: try: vector = Vector(dataset, session=session) vector.delete_by_ids(summary_node_ids) - except Exception as e: - logger.warning("Failed to remove summary vectors: %s", str(e)) + except Exception: + logger.warning("Failed to remove summary vectors", exc_info=True) # Disable summary records (don't delete) now = naive_utc_now() diff --git a/api/tasks/add_document_to_index_task.py b/api/tasks/add_document_to_index_task.py index 336fc1000f4..9bd08b7daa8 100644 --- a/api/tasks/add_document_to_index_task.py +++ b/api/tasks/add_document_to_index_task.py @@ -128,8 +128,8 @@ def add_document_to_index_task(dataset_document_id: str): dataset=dataset, segment_ids=segment_ids_list, ) - except Exception as e: - logger.warning("Failed to enable summaries for document %s: %s", dataset_document.id, str(e)) + except Exception: + logger.warning("Failed to enable summaries for document %s", dataset_document.id, exc_info=True) end_at = time.perf_counter() logger.info( diff --git a/api/tasks/disable_segment_from_index_task.py b/api/tasks/disable_segment_from_index_task.py index 02141bf7680..6db3837e0cd 100644 --- a/api/tasks/disable_segment_from_index_task.py +++ b/api/tasks/disable_segment_from_index_task.py @@ -73,8 +73,8 @@ def disable_segment_from_index_task(segment_id: str): segment_ids=[segment.id], disabled_by=segment.disabled_by, ) - except Exception as e: - logger.warning("Failed to disable summary for segment %s: %s", segment.id, str(e)) + except Exception: + logger.warning("Failed to disable summary for segment %s", segment.id, exc_info=True) end_at = time.perf_counter() logger.info( diff --git a/api/tasks/disable_segments_from_index_task.py b/api/tasks/disable_segments_from_index_task.py index aeefa78dfe9..da536e210e2 100644 --- a/api/tasks/disable_segments_from_index_task.py +++ b/api/tasks/disable_segments_from_index_task.py @@ -81,8 +81,8 @@ def disable_segments_from_index_task(segment_ids: list, dataset_id: str, documen segment_ids=segment_ids_list, disabled_by=disabled_by, ) - except Exception as e: - logger.warning("Failed to disable summaries for segments: %s", str(e)) + except Exception: + logger.warning("Failed to disable summaries for segments", exc_info=True) end_at = time.perf_counter() logger.info(click.style(f"Segments removed from index latency: {end_at - start_at}", fg="green")) diff --git a/api/tasks/enable_segment_to_index_task.py b/api/tasks/enable_segment_to_index_task.py index c9653eb285f..782dc82dde3 100644 --- a/api/tasks/enable_segment_to_index_task.py +++ b/api/tasks/enable_segment_to_index_task.py @@ -117,8 +117,8 @@ def enable_segment_to_index_task(segment_id: str): dataset=dataset, segment_ids=[segment.id], ) - except Exception as e: - logger.warning("Failed to enable summary for segment %s: %s", segment.id, str(e)) + except Exception: + logger.warning("Failed to enable summary for segment %s", segment.id, exc_info=True) end_at = time.perf_counter() logger.info(click.style(f"Segment enabled to index: {segment.id} latency: {end_at - start_at}", fg="green")) diff --git a/api/tasks/enable_segments_to_index_task.py b/api/tasks/enable_segments_to_index_task.py index b33de4ebc1e..d4ab9a333df 100644 --- a/api/tasks/enable_segments_to_index_task.py +++ b/api/tasks/enable_segments_to_index_task.py @@ -117,8 +117,8 @@ def enable_segments_to_index_task(segment_ids: list, dataset_id: str, document_i dataset=dataset, segment_ids=segment_ids_list, ) - except Exception as e: - logger.warning("Failed to enable summaries for segments: %s", str(e)) + except Exception: + logger.warning("Failed to enable summaries for segments", exc_info=True) end_at = time.perf_counter() logger.info(click.style(f"Segments enabled to index latency: {end_at - start_at}", fg="green")) diff --git a/api/tasks/remove_document_from_index_task.py b/api/tasks/remove_document_from_index_task.py index 2d3f8ea2a7a..ebffe41db0c 100644 --- a/api/tasks/remove_document_from_index_task.py +++ b/api/tasks/remove_document_from_index_task.py @@ -58,8 +58,8 @@ def remove_document_from_index_task(document_id: str): segment_ids=segment_ids_list, disabled_by=document.disabled_by, ) - except Exception as e: - logger.warning("Failed to disable summaries for document %s: %s", document.id, str(e)) + except Exception: + logger.warning("Failed to disable summaries for document %s", document.id, exc_info=True) index_node_ids = [segment.index_node_id for segment in segments if segment.index_node_id] if index_node_ids: diff --git a/api/tests/unit_tests/core/rag/indexing/processor/test_paragraph_index_processor.py b/api/tests/unit_tests/core/rag/indexing/processor/test_paragraph_index_processor.py index 692a9b80972..f9f8740cbf1 100644 --- a/api/tests/unit_tests/core/rag/indexing/processor/test_paragraph_index_processor.py +++ b/api/tests/unit_tests/core/rag/indexing/processor/test_paragraph_index_processor.py @@ -712,6 +712,32 @@ class TestParagraphIndexProcessor: assert files == [] assert sum(1 for r in caplog.records if r.levelno == logging.WARNING) == 1 + def test_extract_images_from_text_warning_carries_traceback(self, caplog: pytest.LogCaptureFixture) -> None: + """The build failure is logged at WARNING with the traceback attached, not just str(exception).""" + text = "![img](/files/11111111-1111-1111-1111-111111111111/image-preview)" + session = self.session + session.add(self._upload_file(file_id="11111111-1111-1111-1111-111111111111")) + session.flush() + + with ( + patch( + "core.rag.index_processor.processor.paragraph_index_processor.build_from_mapping", + side_effect=RuntimeError("build failed"), + ), + caplog.at_level(logging.WARNING, logger="core.rag.index_processor.processor.paragraph_index_processor"), + ): + ParagraphIndexProcessor._extract_images_from_text("tenant-1", text, session) + + record = next(r for r in caplog.records if r.levelno == logging.WARNING) + assert record.exc_info is not None + _, exc_value, exc_traceback = record.exc_info + assert isinstance(exc_value, RuntimeError) + assert exc_traceback is not None + # The formatted output carries the call chain, and the message itself no longer inlines the exception. + assert "Traceback (most recent call last)" in caplog.text + assert "RuntimeError: build failed" in caplog.text + assert "build failed" not in record.getMessage() + def test_extract_images_from_segment_attachments(self, caplog: pytest.LogCaptureFixture) -> None: session = self.session uploads = [