Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 31 additions & 6 deletions src/galileo/utils/catch_log.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,31 @@
import asyncio
import functools
import logging
import warnings
from logging import Logger
from typing import Any, Callable


def warn_catch_exception(logger: Logger = logging.getLogger(__name__), exception: type[Exception] = Exception) -> Any:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing support for the standard logger interface is not the correct approach.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, I added this back in addition to the warnings.warn() but sounds like we'll want to think about this a bit more deeply

"""
A function wrapper that catches exceptions and logs them to the logger.
A function wrapper that catches exceptions, logs them, and emits visible warnings.

This decorator ensures that observability code doesn't crash the user's application
while still providing visible feedback when errors occur.

Errors are:
1. Logged via standard Python logging (integrates with logging/tracing platforms)
2. Emitted as warnings (visible by default without any logging configuration)

Parameters
----------
exception
logger
exception : type[Exception]
The base exception type to catch. Defaults to Exception.

Returns
-------
Callable
The decorated function.
"""

def wrapper(f: Callable) -> Callable:
Expand All @@ -25,7 +34,10 @@ def inner(*args: Any, **kwargs: Any) -> Any:
try:
result = f(*args, **kwargs)
except exception as err:
# Standard logging for integration with logging/tracing platforms
logger.warning(f"Error occurred during execution: {f.__name__}: {err}")
# Warning for immediate visibility without config
warnings.warn(f"Galileo: {f.__name__} failed: {err}", RuntimeWarning, stacklevel=2)
else:
return result

Expand All @@ -38,16 +50,26 @@ def async_warn_catch_exception(
logger: Logger = logging.getLogger(__name__), exception: type[Exception] = Exception
) -> Any:
"""
A function wrapper that catches exceptions and logs them to the logger.
An async function wrapper that catches exceptions, logs them, and emits visible warnings.

This decorator ensures that observability code doesn't crash the user's application
while still providing visible feedback when errors occur.

Errors are:
1. Logged via standard Python logging (integrates with logging/tracing platforms)
2. Emitted as warnings (visible by default without any logging configuration)

Parameters
----------
exception
logger
logger : Logger
The logger to use for logging errors. Defaults to the module logger.
exception : type[Exception]
The base exception type to catch. Defaults to Exception.

Returns
-------
Callable
The decorated function.
"""

def wrapper(f: Callable) -> Callable:
Expand All @@ -56,7 +78,10 @@ async def inner(*args: Any, **kwargs: Any) -> Any:
try:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ Failed check: pre-commit / pre-commit
I’ve attached the relevant part of the log for your convenience:
pre-commit hook made changes to file: warnings.warn() call was reformatted from multi-line to single-line format in the async_warn_catch_exception function


Finding type: Log Error

result = await f(*args, **kwargs)
except exception as err:
# Standard logging for integration with logging/tracing platforms
logger.warning(f"Error occurred during execution: {f.__name__}: {err}")
# Warning for immediate visibility without config
warnings.warn(f"Galileo: {f.__name__} failed: {err}", RuntimeWarning, stacklevel=2)
else:
return result

Expand Down
5 changes: 3 additions & 2 deletions tests/test_logger_batch.py
Original file line number Diff line number Diff line change
Expand Up @@ -1046,6 +1046,7 @@ def test_get_last_output() -> None:
input="input",
output="llm output",
name="test-span",
model="test-model",
created_at=datetime.datetime.now(),
duration_ns=1_000_000,
status_code=200,
Expand Down Expand Up @@ -1280,7 +1281,7 @@ def test_set_session_id(mock_traces_client: Mock, mock_projects_client: Mock, mo

# Log a trace
logger.start_trace(input="input", name="test-trace", created_at=datetime.datetime.now())
logger.add_llm_span(input="input", output="output")
logger.add_llm_span(input="input", output="output", model="test-model")
logger.conclude("output", status_code=200)
logger.flush()

Expand Down Expand Up @@ -1352,7 +1353,7 @@ def test_start_session_with_external_id(

# Log a trace
logger.start_trace(input="input", name="test-trace", created_at=datetime.datetime.now())
logger.add_llm_span(input="input", output="output")
logger.add_llm_span(input="input", output="output", model="test-model")
logger.conclude("output", status_code=200)
logger.flush()

Expand Down
Loading