Repository: pipecat-ai/pipecat
Pull request #1768: Add DebugLogObserver
Author: login: markbackman
Created: 2025-05-07T19:06:56Z | Merged: 2025-05-07T21:30:50Z

#### Please describe the changes in your PR. If it is addressing an issue, please reference that as well.

The goal here is to add an observer class that makes it easy to filter on specific frames and optionally their source and destination. Basically, I got sick of writing a custom observer for debugging. This gives me more immediate access to seeing logs that I want by just specifying it directly in the observers arg in PipelineParams.


Diff:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5edb1b5a3d..dacfb5fd4c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Added
 
+- Added `DebugLogObserver` for detailed frame logging with configurable
+  filtering by frame type and endpoint. This observer automatically extracts
+  and formats all frame data fields for debug logging.
+
 - `UserImageRequestFrame.video_source` field has been added to request an image
   from the desired video source.
 
diff --git a/examples/foundational/30-observer.py b/examples/foundational/30-observer.py
index 46bd96e533..c9cd08aee8 100644
--- a/examples/foundational/30-observer.py
+++ b/examples/foundational/30-observer.py
@@ -14,18 +14,26 @@
 from pipecat.frames.frames import (
     BotStartedSpeakingFrame,
     BotStoppedSpeakingFrame,
+    EndFrame,
     StartInterruptionFrame,
+    TTSTextFrame,
+    UserStartedSpeakingFrame,
 )
 from pipecat.observers.base_observer import BaseObserver, FramePushed
+from pipecat.observers.loggers.debug_log_observer import DebugLogObserver, FrameEndpoint
 from pipecat.observers.loggers.llm_log_observer import LLMLogObserver
 from pipecat.pipeline.pipeline import Pipeline
 from pipecat.pipeline.runner import PipelineRunner
 from pipecat.pipeline.task import PipelineParams, PipelineTask
-from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
+from pipecat.processors.aggregators.openai_llm_context import (
+    OpenAILLMContext,
+)
 from pipecat.processors.frame_processor import FrameDirection
 from pipecat.services.cartesia.tts import CartesiaTTSService
 from pipecat.services.deepgram.stt import DeepgramSTTService
 from pipecat.services.openai.llm import OpenAILLMService
+from pipecat.transports.base_input import BaseInputTransport
+from pipecat.transports.base_output import BaseOutputTransport
 from pipecat.transports.base_transport import TransportParams
 from pipecat.transports.network.small_webrtc import SmallWebRTCTransport
 from pipecat.transports.network.webrtc_connection import SmallWebRTCConnection
@@ -33,7 +41,7 @@
 load_dotenv(override=True)
 
 
-class DebugObserver(BaseObserver):
+class CustomObserver(BaseObserver):
     """Observer to log interruptions and bot speaking events to the console.
 
     Logs all frame instances of:
@@ -58,7 +66,7 @@ async def on_push_frame(self, data: FramePushed):
         # Create direction arrow
         arrow = "→" if direction == FrameDirection.DOWNSTREAM else "←"
 
-        if isinstance(frame, StartInterruptionFrame):
+        if isinstance(frame, StartInterruptionFrame) and isinstance(src, BaseOutputTransport):
             logger.info(f"⚡ INTERRUPTION START: {src} {arrow} {dst} at {time_sec:.2f}s")
         elif isinstance(frame, BotStartedSpeakingFrame):
             logger.info(f"🤖 BOT START SPEAKING: {src} {arrow} {dst} at {time_sec:.2f}s")
@@ -117,7 +125,17 @@ async def run_bot(webrtc_connection: SmallWebRTCConnection, _: argparse.Namespac
             enable_usage_metrics=True,
             report_only_initial_ttfb=True,
         ),
-        observers=[DebugObserver(), LLMLogObserver()],
+        observers=[
+            CustomObserver(),
+            LLMLogObserver(),
+            DebugLogObserver(
+                frame_types={
+                    TTSTextFrame: (BaseOutputTransport, FrameEndpoint.DESTINATION),
+                    UserStartedSpeakingFrame: (BaseInputTransport, FrameEndpoint.SOURCE),
+                    EndFrame: None,
+                }
+            ),
+        ],
     )
 
     @transport.event_handler("on_client_connected")
diff --git a/src/pipecat/observers/loggers/debug_log_observer.py b/src/pipecat/observers/loggers/debug_log_observer.py
new file mode 100644
index 0000000000..575a31683d
--- /dev/null
+++ b/src/pipecat/observers/loggers/debug_log_observer.py
@@ -0,0 +1,218 @@
+#
+# Copyright (c) 2024–2025, Daily
+#
+# SPDX-License-Identifier: BSD 2-Clause License
+#
+
+from dataclasses import fields, is_dataclass
+from enum import Enum, auto
+from typing import Dict, Optional, Set, Tuple, Type, Union
+
+from loguru import logger
+
+from pipecat.frames.frames import Frame
+from pipecat.observers.base_observer import BaseObserver, FramePushed
+from pipecat.processors.frame_processor import FrameDirection
+
+
+class FrameEndpoint(Enum):
+    """Specifies which endpoint (source or destination) to filter on."""
+
+    SOURCE = auto()
+    DESTINATION = auto()
+
+
+class DebugLogObserver(BaseObserver):
+    """Observer that logs frame activity with detailed content to the console.
+
+    Automatically extracts and formats data from any frame type, making it useful
+    for debugging pipeline behavior without needing frame-specific observers.
+
+    Args:
+        frame_types: Optional tuple of frame types to log, or a dict with frame type
+            filters. If None, logs all frame types.
+        exclude_fields: Optional set of field names to exclude from logging.
+
+    Examples:
+        Log all frames from all services:
+        ```python
+        observers = DebugLogObserver()
+        ```
+
+        Log specific frame types from any source/destination:
+        ```python
+        from pipecat.frames.frames import TranscriptionFrame, InterimTranscriptionFrame
+        observers = DebugLogObserver(frame_types=(TranscriptionFrame, InterimTranscriptionFrame))
+        ```
+
+        Log frames with specific source/destination filters:
+        ```python
+        from pipecat.frames.frames import StartInterruptionFrame, UserStartedSpeakingFrame, LLMTextFrame
+        from pipecat.transports.base_output_transport import BaseOutputTransport
+        from pipecat.services.stt_service import STTService
+
+        observers = DebugLogObserver(frame_types={
+            # Only log StartInterruptionFrame when source is BaseOutputTransport
+            StartInterruptionFrame: (BaseOutputTransport, FrameEndpoint.SOURCE),
+
+            # Only log UserStartedSpeakingFrame when destination is STTService
+            UserStartedSpeakingFrame: (STTService, FrameEndpoint.DESTINATION),
+
+            # Log LLMTextFrame regardless of source or destination type
+            LLMTextFrame: None
+        })
+        ```
+    """
+
+    def __init__(
+        self,
+        frame_types: Optional[
+            Union[Tuple[Type[Frame], ...], Dict[Type[Frame], Optional[Tuple[Type, FrameEndpoint]]]]
+        ] = None,
+        exclude_fields: Optional[Set[str]] = None,
+    ):
+        """Initialize the debug log observer.
+
+        Args:
+            frame_types: Tuple of frame types to log, or a dict mapping frame types to
+                filter configurations. Filter configs can be:
+                - None to log all instances of the frame type
+                - A tuple of (service_type, endpoint) to filter on a specific service
+                  and endpoint (SOURCE or DESTINATION)
+                If None is provided instead of a tuple/dict, log all frames.
+            exclude_fields: Set of field names to exclude from logging. If None, only binary
+                data fields are excluded.
+        """
+        # Process frame filters
+        self.frame_filters = {}
+
+        if frame_types is not None:
+            if isinstance(frame_types, tuple):
+                # Tuple of frame types - log all instances
+                self.frame_filters = {frame_type: None for frame_type in frame_types}
+            else:
+                # Dict of frame types with filters
+                self.frame_filters = frame_types
+
+        # By default, exclude binary data fields that would clutter logs
+        self.exclude_fields = (
+            exclude_fields
+            if exclude_fields is not None
+            else {
+                "audio",  # Skip binary audio data
+                "image",  # Skip binary image data
+                "images",  # Skip lists of images
+            }
+        )
+
+    def _format_value(self, value):
+        """Format a value for logging.
+
+        Args:
+            value: The value to format.
+
+        Returns:
+            str: A string representation of the value suitable for logging.
+        """
+        if value is None:
+            return "None"
+        elif isinstance(value, str):
+            return f"{value!r}"
+        elif isinstance(value, (list, tuple)):
+            if len(value) == 0:
+                return "[]"
+            if isinstance(value[0], dict) and len(value) > 3:
+                # For message lists, just show count
+                return f"{len(value)} items"
+            return str(value)
+        elif isinstance(value, (bytes, bytearray)):
+            return f"{len(value)} bytes"
+        elif hasattr(value, "get_messages_for_logging") and callable(
+            getattr(value, "get_messages_for_logging")
+        ):
+            # Special case for OpenAI context
+            return f"{value.__class__.__name__} with messages: {value.get_messages_for_logging()}"
+        else:
+            return str(value)
+
+    def _should_log_frame(self, frame, src, dst):
+        """Determine if a frame should be logged based on filters.
+
+        Args:
+            frame: The frame being processed
+            src: The source component
+            dst: The destination component
+
+        Returns:
+            bool: True if the frame should be logged, False otherwise
+        """
+        # If no filters, log all frames
+        if not self.frame_filters:
+            return True
+
+        # Check if this frame type is in our filters
+        for frame_type, filter_config in self.frame_filters.items():
+            if isinstance(frame, frame_type):
+                # If filter is None, log all instances of this frame type
+                if filter_config is None:
+                    return True
+
+                # Otherwise, check the specific filter
+                service_type, endpoint = filter_config
+
+                if endpoint == FrameEndpoint.SOURCE:
+                    return isinstance(src, service_type)
+                elif endpoint == FrameEndpoint.DESTINATION:
+                    return isinstance(dst, service_type)
+
+        return False
+
+    async def on_push_frame(self, data: FramePushed):
+        """Process a frame being pushed into the pipeline.
+
+        Logs frame details to the console with all relevant fields and values.
+
+        Args:
+            data: Event data containing the frame, source, destination, direction, and timestamp.
+        """
+        src = data.source
+        dst = data.destination
+        frame = data.frame
+        direction = data.direction
+        timestamp = data.timestamp
+
+        # Check if we should log this frame
+        if not self._should_log_frame(frame, src, dst):
+            return
+
+        # Format direction arrow
+        arrow = "→" if direction == FrameDirection.DOWNSTREAM else "←"
+
+        time_sec = timestamp / 1_000_000_000
+        class_name = frame.__class__.__name__
+
+        # Build frame representation
+        frame_details = []
+
+        # If dataclass, extract fields
+        if is_dataclass(frame):
+            for field in fields(frame):
+                if field.name in self.exclude_fields:
+                    continue
+
+                value = getattr(frame, field.name)
+                if value is None:
+                    continue
+
+                formatted_value = self._format_value(value)
+                frame_details.append(f"{field.name}: {formatted_value}")
+
+        # Format the message
+        if frame_details:
+            details = ", ".join(frame_details)
+            message = f"{class_name} {details} at {time_sec:.2f}s"
+        else:
+            message = f"{class_name} at {time_sec:.2f}s"
+
+        # Log the message
+        logger.debug(f"{src} {arrow} {dst}: {message}")
diff --git a/src/pipecat/observers/loggers/llm_log_observer.py b/src/pipecat/observers/loggers/llm_log_observer.py
index 9e4d53b28f..a6675b5c0b 100644
--- a/src/pipecat/observers/loggers/llm_log_observer.py
+++ b/src/pipecat/observers/loggers/llm_log_observer.py
@@ -7,7 +7,6 @@
 from loguru import logger
 
 from pipecat.frames.frames import (
-    Frame,
     FunctionCallInProgressFrame,
     FunctionCallResultFrame,
     LLMFullResponseEndFrame,
@@ -17,7 +16,7 @@
 )
 from pipecat.observers.base_observer import BaseObserver, FramePushed
 from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContextFrame
-from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
+from pipecat.processors.frame_processor import FrameDirection
 from pipecat.services.llm_service import LLMService
 
 
diff --git a/src/pipecat/observers/loggers/transcription_log_observer.py b/src/pipecat/observers/loggers/transcription_log_observer.py
index 57e38c952e..8ca1d9c9b4 100644
--- a/src/pipecat/observers/loggers/transcription_log_observer.py
+++ b/src/pipecat/observers/loggers/transcription_log_observer.py
@@ -7,12 +7,10 @@
 from loguru import logger
 
 from pipecat.frames.frames import (
-    Frame,
     InterimTranscriptionFrame,
     TranscriptionFrame,
 )
 from pipecat.observers.base_observer import BaseObserver, FramePushed
-from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
 from pipecat.services.stt_service import STTService
 
 


Reviews:
user: login: filipi87
body: It will make things a lot easier to debug. 🚀
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T19:45:19Z
user: login: aconchillo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T21:08:38Z
user: login: aconchillo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T21:10:39Z
user: login: aconchillo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T21:13:49Z
user: login: aconchillo
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T21:14:41Z
user: login: markbackman
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T21:15:06Z
user: login: markbackman
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T21:15:13Z

Review comments:
diff_hunk: @@ -0,0 +1,218 @@
+#
+# Copyright (c) 2024–2025, Daily
+#
+# SPDX-License-Identifier: BSD 2-Clause License
+#
+
+from dataclasses import fields, is_dataclass
+from enum import Enum, auto
+from typing import Dict, List, Optional, Set, Tuple, Type, Union
+
+from loguru import logger
+
+from pipecat.frames.frames import Frame
+from pipecat.observers.base_observer import BaseObserver, FramePushed
+from pipecat.processors.frame_processor import FrameDirection
+
+
+class FrameEndpoint(Enum):
+    """Specifies which endpoint (source or destination) to filter on."""
+
+    SOURCE = auto()
+    DESTINATION = auto()
+
+
+class DebugLogObserver(BaseObserver):
+    """Observer that logs frame activity with detailed content to the console.
+
+    Automatically extracts and formats data from any frame type, making it useful
+    for debugging pipeline behavior without needing frame-specific observers.
+
+    Args:
+        frame_types: Optional list of frame types to log, or a dict with frame type
+            filters. If None, logs all frame types.
+        exclude_fields: Optional set of field names to exclude from logging.
+
+    Examples:
+        Log all frames from all services:
+        ```python
+        observer = DebugLogObserver()
+        ```
+
+        Log specific frame types from any source/destination:
+        ```python
+        from pipecat.frames.frames import TranscriptionFrame, InterimTranscriptionFrame
+        observer = DebugLogObserver(frame_types=[TranscriptionFrame, InterimTranscriptionFrame])
path: src/pipecat/observers/loggers/debug_log_observer.py
user: login: aconchillo
body: Instead of a List, I've been using a Tuple instead, emulating the `isinstance()` approach:

```
        observer = DebugLogObserver(frame_types=(TranscriptionFrame, InterimTranscriptionFrame))
```
created_at: 2025-05-07T21:08:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,218 @@
+#
+# Copyright (c) 2024–2025, Daily
+#
+# SPDX-License-Identifier: BSD 2-Clause License
+#
+
+from dataclasses import fields, is_dataclass
+from enum import Enum, auto
+from typing import Dict, List, Optional, Set, Tuple, Type, Union
+
+from loguru import logger
+
+from pipecat.frames.frames import Frame
+from pipecat.observers.base_observer import BaseObserver, FramePushed
+from pipecat.processors.frame_processor import FrameDirection
+
+
+class FrameEndpoint(Enum):
+    """Specifies which endpoint (source or destination) to filter on."""
+
+    SOURCE = auto()
+    DESTINATION = auto()
+
+
+class DebugLogObserver(BaseObserver):
+    """Observer that logs frame activity with detailed content to the console.
+
+    Automatically extracts and formats data from any frame type, making it useful
+    for debugging pipeline behavior without needing frame-specific observers.
+
+    Args:
+        frame_types: Optional list of frame types to log, or a dict with frame type
+            filters. If None, logs all frame types.
+        exclude_fields: Optional set of field names to exclude from logging.
+
+    Examples:
+        Log all frames from all services:
+        ```python
+        observer = DebugLogObserver()
+        ```
+
+        Log specific frame types from any source/destination:
+        ```python
+        from pipecat.frames.frames import TranscriptionFrame, InterimTranscriptionFrame
+        observer = DebugLogObserver(frame_types=[TranscriptionFrame, InterimTranscriptionFrame])
+        ```
+
+        Log frames with specific source/destination filters:
+        ```python
+        from pipecat.frames.frames import StartInterruptionFrame, UserStartedSpeakingFrame, LLMTextFrame
+        from pipecat.transports.base_output_transport import BaseOutputTransport
+        from pipecat.services.stt_service import STTService
+
+        observer = DebugLogObserver(frame_types={
+            # Only log StartInterruptionFrame when source is BaseOutputTransport
+            StartInterruptionFrame: (BaseOutputTransport, FrameEndpoint.SOURCE),
path: src/pipecat/observers/loggers/debug_log_observer.py
user: login: aconchillo
body: Can you do:

```
            StartInterruptionFrame: BaseOutputTransport,
```

or would you need two entries one for SOURCE and one for DESTINATION?
created_at: 2025-05-07T21:10:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,218 @@
+#
+# Copyright (c) 2024–2025, Daily
+#
+# SPDX-License-Identifier: BSD 2-Clause License
+#
+
+from dataclasses import fields, is_dataclass
+from enum import Enum, auto
+from typing import Dict, List, Optional, Set, Tuple, Type, Union
+
+from loguru import logger
+
+from pipecat.frames.frames import Frame
+from pipecat.observers.base_observer import BaseObserver, FramePushed
+from pipecat.processors.frame_processor import FrameDirection
+
+
+class FrameEndpoint(Enum):
+    """Specifies which endpoint (source or destination) to filter on."""
+
+    SOURCE = auto()
+    DESTINATION = auto()
+
+
+class DebugLogObserver(BaseObserver):
+    """Observer that logs frame activity with detailed content to the console.
+
+    Automatically extracts and formats data from any frame type, making it useful
+    for debugging pipeline behavior without needing frame-specific observers.
+
+    Args:
+        frame_types: Optional list of frame types to log, or a dict with frame type
+            filters. If None, logs all frame types.
+        exclude_fields: Optional set of field names to exclude from logging.
+
+    Examples:
+        Log all frames from all services:
+        ```python
+        observer = DebugLogObserver()
+        ```
+
+        Log specific frame types from any source/destination:
+        ```python
+        from pipecat.frames.frames import TranscriptionFrame, InterimTranscriptionFrame
+        observer = DebugLogObserver(frame_types=[TranscriptionFrame, InterimTranscriptionFrame])
+        ```
+
+        Log frames with specific source/destination filters:
+        ```python
+        from pipecat.frames.frames import StartInterruptionFrame, UserStartedSpeakingFrame, LLMTextFrame
+        from pipecat.transports.base_output_transport import BaseOutputTransport
+        from pipecat.services.stt_service import STTService
+
+        observer = DebugLogObserver(frame_types={
+            # Only log StartInterruptionFrame when source is BaseOutputTransport
+            StartInterruptionFrame: (BaseOutputTransport, FrameEndpoint.SOURCE),
path: src/pipecat/observers/loggers/debug_log_observer.py
user: login: aconchillo
body: Pretty sure we need two entries. I think that's fine.
created_at: 2025-05-07T21:13:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,218 @@
+#
+# Copyright (c) 2024–2025, Daily
+#
+# SPDX-License-Identifier: BSD 2-Clause License
+#
+
+from dataclasses import fields, is_dataclass
+from enum import Enum, auto
+from typing import Dict, List, Optional, Set, Tuple, Type, Union
+
+from loguru import logger
+
+from pipecat.frames.frames import Frame
+from pipecat.observers.base_observer import BaseObserver, FramePushed
+from pipecat.processors.frame_processor import FrameDirection
+
+
+class FrameEndpoint(Enum):
+    """Specifies which endpoint (source or destination) to filter on."""
+
+    SOURCE = auto()
+    DESTINATION = auto()
+
+
+class DebugLogObserver(BaseObserver):
+    """Observer that logs frame activity with detailed content to the console.
+
+    Automatically extracts and formats data from any frame type, making it useful
+    for debugging pipeline behavior without needing frame-specific observers.
+
+    Args:
+        frame_types: Optional list of frame types to log, or a dict with frame type
+            filters. If None, logs all frame types.
+        exclude_fields: Optional set of field names to exclude from logging.
+
+    Examples:
+        Log all frames from all services:
+        ```python
+        observer = DebugLogObserver()
+        ```
+
+        Log specific frame types from any source/destination:
+        ```python
+        from pipecat.frames.frames import TranscriptionFrame, InterimTranscriptionFrame
+        observer = DebugLogObserver(frame_types=[TranscriptionFrame, InterimTranscriptionFrame])
+        ```
+
+        Log frames with specific source/destination filters:
+        ```python
+        from pipecat.frames.frames import StartInterruptionFrame, UserStartedSpeakingFrame, LLMTextFrame
+        from pipecat.transports.base_output_transport import BaseOutputTransport
+        from pipecat.services.stt_service import STTService
+
+        observer = DebugLogObserver(frame_types={
+            # Only log StartInterruptionFrame when source is BaseOutputTransport
+            StartInterruptionFrame: (BaseOutputTransport, FrameEndpoint.SOURCE),
path: src/pipecat/observers/loggers/debug_log_observer.py
user: login: markbackman
body: I intentionally kept it to one entry per item to keep it simple. I think that makes sense. You really need to know what you're doing with the source and destination. This is pretty much a tool for us to use.
created_at: 2025-05-07T21:15:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,218 @@
+#
+# Copyright (c) 2024–2025, Daily
+#
+# SPDX-License-Identifier: BSD 2-Clause License
+#
+
+from dataclasses import fields, is_dataclass
+from enum import Enum, auto
+from typing import Dict, List, Optional, Set, Tuple, Type, Union
+
+from loguru import logger
+
+from pipecat.frames.frames import Frame
+from pipecat.observers.base_observer import BaseObserver, FramePushed
+from pipecat.processors.frame_processor import FrameDirection
+
+
+class FrameEndpoint(Enum):
+    """Specifies which endpoint (source or destination) to filter on."""
+
+    SOURCE = auto()
+    DESTINATION = auto()
+
+
+class DebugLogObserver(BaseObserver):
+    """Observer that logs frame activity with detailed content to the console.
+
+    Automatically extracts and formats data from any frame type, making it useful
+    for debugging pipeline behavior without needing frame-specific observers.
+
+    Args:
+        frame_types: Optional list of frame types to log, or a dict with frame type
+            filters. If None, logs all frame types.
+        exclude_fields: Optional set of field names to exclude from logging.
+
+    Examples:
+        Log all frames from all services:
+        ```python
+        observer = DebugLogObserver()
+        ```
+
+        Log specific frame types from any source/destination:
+        ```python
+        from pipecat.frames.frames import TranscriptionFrame, InterimTranscriptionFrame
+        observer = DebugLogObserver(frame_types=[TranscriptionFrame, InterimTranscriptionFrame])
path: src/pipecat/observers/loggers/debug_log_observer.py
user: login: markbackman
body: Good idea!
created_at: 2025-05-07T21:15:13Z
author_association: CONTRIBUTOR

PR comments:
user: login: aconchillo
created_at: 2025-05-07T21:15:02Z
body: LGTM! Just added a comment to change a List for a Tuple.
author_association: CONTRIBUTOR
user: login: codecov[bot]
created_at: 2025-05-07T21:23:07Z
body: ## [Codecov](https://app.codecov.io/gh/pipecat-ai/pipecat/pull/1768?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pipecat-ai) Report
:x: Patch coverage is `0%` with `74 lines` in your changes missing coverage. Please review.

| [Files with missing lines](https://app.codecov.io/gh/pipecat-ai/pipecat/pull/1768?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pipecat-ai) | Patch % | Lines |
|---|---|---|
| [...rc/pipecat/observers/loggers/debug\_log\_observer.py](https://app.codecov.io/gh/pipecat-ai/pipecat/pull/1768?src=pr&el=tree&filepath=src%2Fpipecat%2Fobservers%2Floggers%2Fdebug_log_observer.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pipecat-ai#diff-c3JjL3BpcGVjYXQvb2JzZXJ2ZXJzL2xvZ2dlcnMvZGVidWdfbG9nX29ic2VydmVyLnB5) | 0.00% | [73 Missing :warning: ](https://app.codecov.io/gh/pipecat-ai/pipecat/pull/1768?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pipecat-ai) |
| [src/pipecat/observers/loggers/llm\_log\_observer.py](https://app.codecov.io/gh/pipecat-ai/pipecat/pull/1768?src=pr&el=tree&filepath=src%2Fpipecat%2Fobservers%2Floggers%2Fllm_log_observer.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pipecat-ai#diff-c3JjL3BpcGVjYXQvb2JzZXJ2ZXJzL2xvZ2dlcnMvbGxtX2xvZ19vYnNlcnZlci5weQ==) | 0.00% | [1 Missing :warning: ](https://app.codecov.io/gh/pipecat-ai/pipecat/pull/1768?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pipecat-ai) |

| [Files with missing lines](https://app.codecov.io/gh/pipecat-ai/pipecat/pull/1768?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pipecat-ai) | Coverage Δ | |
|---|---|---|
| [...at/observers/loggers/transcription\_log\_observer.py](https://app.codecov.io/gh/pipecat-ai/pipecat/pull/1768?src=pr&el=tree&filepath=src%2Fpipecat%2Fobservers%2Floggers%2Ftranscription_log_observer.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pipecat-ai#diff-c3JjL3BpcGVjYXQvb2JzZXJ2ZXJzL2xvZ2dlcnMvdHJhbnNjcmlwdGlvbl9sb2dfb2JzZXJ2ZXIucHk=) | `0.00% <ø> (ø)` | |
| [src/pipecat/observers/loggers/llm\_log\_observer.py](https://app.codecov.io/gh/pipecat-ai/pipecat/pull/1768?src=pr&el=tree&filepath=src%2Fpipecat%2Fobservers%2Floggers%2Fllm_log_observer.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pipecat-ai#diff-c3JjL3BpcGVjYXQvb2JzZXJ2ZXJzL2xvZ2dlcnMvbGxtX2xvZ19vYnNlcnZlci5weQ==) | `0.00% <0.00%> (ø)` | |
| [...rc/pipecat/observers/loggers/debug\_log\_observer.py](https://app.codecov.io/gh/pipecat-ai/pipecat/pull/1768?src=pr&el=tree&filepath=src%2Fpipecat%2Fobservers%2Floggers%2Fdebug_log_observer.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pipecat-ai#diff-c3JjL3BpcGVjYXQvb2JzZXJ2ZXJzL2xvZ2dlcnMvZGVidWdfbG9nX29ic2VydmVyLnB5) | `0.00% <0.00%> (ø)` | |
<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE