Skip to content

Commit

Permalink
[NA] Healthcheck improvements (#1414)
Browse files Browse the repository at this point in the history
* Healthcheck improvements

* Refactor config, provide better naming

* Rename method

* Refactor misconfiguration check log messages

* Fix lint errors

* Small output improvements

* Rename is_misconfiguration_detected to check_for_known_misconfigurations

* Fix grammar, is_config_file_exists -> config_file_exists

* Fix comment
  • Loading branch information
alexkuzmik authored Feb 28, 2025
1 parent f31828a commit 36ca491
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 90 deletions.
2 changes: 1 addition & 1 deletion sdks/python/src/opik/api_objects/opik_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def __init__(
api_key=api_key,
)

config_.is_misconfigured(
config_.check_for_known_misconfigurations(
show_misconfiguration_message=_show_misconfiguration_message,
)
self._config = config_
Expand Down
150 changes: 76 additions & 74 deletions sdks/python/src/opik/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,32 +191,25 @@ def config_file_fullpath(self) -> pathlib.Path:
config_file_path = os.getenv("OPIK_CONFIG_PATH", CONFIG_FILE_PATH_DEFAULT)
return pathlib.Path(config_file_path).expanduser()

def save_to_file(self) -> None:
@property
def config_file_exists(self) -> bool:
"""
Save configuration to a file
Raises:
OSError: If there is an issue writing to the file.
Determines whether the configuration file exists at the specified path.
"""
config_file_content = configparser.ConfigParser()

config_file_content["opik"] = {
"url_override": self.url_override,
"workspace": self.workspace,
}
return self.config_file_fullpath.exists()

if self.api_key is not None:
config_file_content["opik"]["api_key"] = self.api_key
@property
def is_cloud_installation(self) -> bool:
"""
Determine if the installation type is a cloud installation.
"""
return url_helpers.get_base_url(self.url_override) == url_helpers.get_base_url(
OPIK_URL_CLOUD
)

try:
with open(
self.config_file_fullpath, mode="w+", encoding="utf-8"
) as config_file:
config_file_content.write(config_file)
LOGGER.info(f"Configuration saved to file: {self.config_file_fullpath}")
except OSError as e:
LOGGER.error(f"Failed to save configuration: {e}")
raise
@property
def is_localhost_installation(self) -> bool:
return "localhost" in self.url_override

@pydantic.model_validator(mode="after")
def _set_url_override_from_api_key(self) -> "OpikConfig":
Expand All @@ -238,43 +231,57 @@ def _set_url_override_from_api_key(self) -> "OpikConfig":

return self

@property
def is_config_file_exists(self) -> bool:
"""
Determines whether the configuration file exists at the specified path.
def save_to_file(self) -> None:
"""
return self.config_file_fullpath.exists()
Save configuration to a file
@property
def is_cloud_installation(self) -> bool:
"""
Determine if the installation type is a cloud installation.
Raises:
OSError: If there is an issue writing to the file.
"""
return url_helpers.get_base_url(self.url_override) == url_helpers.get_base_url(
OPIK_URL_CLOUD
)
config_file_content = configparser.ConfigParser()

def get_current_config_with_api_key_hidden(self) -> Dict[str, Any]:
config_file_content["opik"] = {
"url_override": self.url_override,
"workspace": self.workspace,
}

if self.api_key is not None:
config_file_content["opik"]["api_key"] = self.api_key

try:
with open(
self.config_file_fullpath, mode="w+", encoding="utf-8"
) as config_file:
config_file_content.write(config_file)
LOGGER.info(f"Configuration saved to file: {self.config_file_fullpath}")
except OSError as e:
LOGGER.error(f"Failed to save configuration: {e}")
raise

def as_dict(self, mask_api_key: bool) -> Dict[str, Any]:
"""
Retrieves the current configuration with the API key value masked.
"""
current_values = self.model_dump()
if current_values.get("api_key") is not None:
if current_values.get("api_key") is not None and mask_api_key:
current_values["api_key"] = "*** HIDDEN ***"
return current_values

def is_misconfigured(self, show_misconfiguration_message: bool = False) -> bool:
def check_for_known_misconfigurations(
self, show_misconfiguration_message: bool = False
) -> bool:
"""
Determines if Opik configuration is misconfigured and optionally displays
Attempts to detects if Opik is misconfigured and optionally displays
a corresponding error message.
Works only for Opik cloud and OSS localhost installations.
Parameters:
show_misconfiguration_message : A flag indicating whether to display detailed error messages if the configuration
is determined to be misconfigured. Defaults to False.
"""

is_misconfigured_flag, error_message = (
self.get_misconfiguration_validation_results()
self.get_misconfiguration_detection_results()
)

if is_misconfigured_flag:
Expand All @@ -289,7 +296,33 @@ def is_misconfigured(self, show_misconfiguration_message: bool = False) -> bool:

return False

def is_misconfigured_for_cloud(self) -> Tuple[bool, Optional[str]]:
def get_misconfiguration_detection_results(self) -> Tuple[bool, Optional[str]]:
"""
Tries detecting misconfigurations for either cloud or localhost environments.
The detection will not work for any other kind of installation.
Returns:
Tuple[bool, Optional[str]]: A tuple where the first element indicates
whether the configuration is misconfigured (True for misconfigured, False for valid).
The second element is an optional string that contains
an error message if there is a configuration issue, or None if the
configuration is valid.
"""
is_misconfigured_for_cloud_flag, error_message = (
self._is_misconfigured_for_cloud()
)
if is_misconfigured_for_cloud_flag:
return True, error_message

is_misconfigured_for_localhost_flag, error_message = (
self._is_misconfigured_for_localhost()
)
if is_misconfigured_for_localhost_flag:
return True, error_message

return False, None

def _is_misconfigured_for_cloud(self) -> Tuple[bool, Optional[str]]:
"""
Determines if the current Opik configuration is misconfigured for cloud logging.
Expand All @@ -316,7 +349,7 @@ def is_misconfigured_for_cloud(self) -> Tuple[bool, Optional[str]]:

return False, None

def is_misconfigured_for_local(self) -> Tuple[bool, Optional[str]]:
def _is_misconfigured_for_localhost(self) -> Tuple[bool, Optional[str]]:
"""
Determines if the current setup is misconfigured for a local open-source installation.
Expand All @@ -325,14 +358,12 @@ def is_misconfigured_for_local(self) -> Tuple[bool, Optional[str]]:
the configuration is misconfigured for local logging, and the second element is either
an error message indicating the reason for misconfiguration or None.
"""
localhost_installation = (
"localhost" in self.url_override
) # does not detect all OSS installations

workspace_is_default = self.workspace == OPIK_WORKSPACE_DEFAULT_NAME
tracking_disabled = self.track_disable

if (
localhost_installation
self.is_localhost_installation
and not workspace_is_default
and not tracking_disabled
):
Expand All @@ -346,35 +377,6 @@ def is_misconfigured_for_local(self) -> Tuple[bool, Optional[str]]:

return False, None

def get_misconfiguration_validation_results(self) -> Tuple[bool, Optional[str]]:
"""
Validates the current configuration and identifies any misconfigurations
for either cloud or local environments. This method checks both cloud
and local configurations and determines the validity of each, returning
a boolean indicator of success or failure and an optional error message
if there is an issue.
Returns:
Tuple[bool, Optional[str]]: A tuple where the first element indicates
whether the configuration is misconfigured (True for misconfigured, False for valid).
The second element is an optional string that contains
an error message if there is a configuration issue, or None if the
configuration is valid.
"""
is_misconfigured_for_cloud_flag, error_message = (
self.is_misconfigured_for_cloud()
)
if is_misconfigured_for_cloud_flag:
return True, error_message

is_misconfigured_for_local_flag, error_message = (
self.is_misconfigured_for_local()
)
if is_misconfigured_for_local_flag:
return True, error_message

return False, None


def update_session_config(key: str, value: Any) -> None:
_SESSION_CACHE_DICT[key] = value
Expand Down
2 changes: 1 addition & 1 deletion sdks/python/src/opik/evaluation/metrics/base_metric.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def __init__(self, name: str, track: bool = True) -> None:

config = opik_config.OpikConfig()

if track and config.is_misconfigured() is False:
if track and config.check_for_known_misconfigurations() is False:
self.score = opik.track(name=self.name)(self.score) # type: ignore
self.ascore = opik.track(name=self.name)(self.ascore) # type: ignore

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def enabled_in_config() -> bool:
@functools.lru_cache
def opik_is_misconfigured() -> bool:
config_ = config.OpikConfig()
return config_.is_misconfigured()
return config_.check_for_known_misconfigurations()


def _add_span_metadata_to_params(params: Dict[str, Any]) -> Dict[str, Any]:
Expand Down
12 changes: 9 additions & 3 deletions sdks/python/src/opik/healthcheck/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,15 @@ def run(show_installed_packages: bool = True) -> None:
rich_representation.print_header("current configuration")
rich_representation.print_current_config(config_obj)

rich_representation.print_header("current configuration validation")
is_misconfigured, err_msg = config_obj.get_misconfiguration_validation_results()
rich_representation.print_config_validation(not is_misconfigured, err_msg)
if config_obj.is_cloud_installation or config_obj.is_localhost_installation:
# Misconfigurations can be detected ONLY for localhost and cloud, not for other installations
rich_representation.print_header("Configuration scan")
misconfiguration_detected, err_msg = (
config_obj.get_misconfiguration_detection_results()
)
rich_representation.print_config_scan_results(
misconfiguration_detected=misconfiguration_detected, error_message=err_msg
)

rich_representation.print_header("backend workspace availability")
is_available, err_msg = checks.get_backend_workspace_availability()
Expand Down
2 changes: 1 addition & 1 deletion sdks/python/src/opik/healthcheck/checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def get_backend_workspace_availability() -> Tuple[bool, Optional[str]]:
try:
opik_obj.auth_check()
is_available = True
except httpx.ConnectError as e:
except (httpx.ConnectError, httpx.TimeoutException) as e:
err_msg = (
f"Error while checking backend workspace availability: {e}\n\n"
"Can't connect to the backend service. "
Expand Down
21 changes: 12 additions & 9 deletions sdks/python/src/opik/healthcheck/rich_representation.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def print_config_file_details(config: OpikConfig) -> None:
file_path = make_value_text(str(config.config_file_fullpath))

is_exists_label = make_key_text("Config file exists:")
is_exists = make_value_text(str(config.is_config_file_exists))
is_exists = make_value_text("yes" if config.config_file_exists else "no")

console.print(file_path_label, file_path)
console.print(is_exists_label, is_exists)
Expand All @@ -66,23 +66,26 @@ def print_current_config(config: config.OpikConfig) -> None:
table.add_column("Setting", style=DEFAULT_KEY_COLOR)
table.add_column("Value", style=DEFAULT_VALUE_COLOR)

current_config_values = config.get_current_config_with_api_key_hidden()
current_config_values = config.as_dict(mask_api_key=True)
for key, value in sorted(current_config_values.items()):
if key != "sentry_dsn":
table.add_row(key, str(value))

console.print(table)


def print_config_validation(is_valid: bool, error_message: Optional[str]) -> None:
is_valid_text = Text(
str(is_valid), style=DEFAULT_VALUE_COLOR if is_valid else DEFAULT_ERROR_COLOR
def print_config_scan_results(
misconfiguration_detected: bool, error_message: Optional[str]
) -> None:
is_misconfigured_text = Text(
"found" if misconfiguration_detected else "not found",
style=DEFAULT_ERROR_COLOR if misconfiguration_detected else DEFAULT_VALUE_COLOR,
)
is_valid_label = make_key_text("Current configuration is valid:")
issues_found_label = make_key_text("Configuration issues:")

console.print(is_valid_label, is_valid_text)
console.print(issues_found_label, is_misconfigured_text)

if is_valid:
if not misconfiguration_detected:
return

err_msg = Text(error_message, style=DEFAULT_ERROR_COLOR)
Expand All @@ -94,7 +97,7 @@ def print_backend_workspace_availability(
err_msg: Optional[str],
) -> None:
is_available_text = Text(
str(is_available),
"yes" if is_available else "no",
style=DEFAULT_VALUE_COLOR if is_available else DEFAULT_ERROR_COLOR,
)
is_available_label = make_key_text("Backend workspace available:")
Expand Down

0 comments on commit 36ca491

Please sign in to comment.