Files
starry-sdk-observability-demo/starry-sdk/starry_client_sdk/client.py

127 lines
4.1 KiB
Python

from __future__ import annotations
import time
from dataclasses import dataclass
from typing import Any, Optional
import requests
from .telemetry import SDK_NAME, configure_telemetry
from .version import __version__
DEFAULT_BASE_URL = "https://blog.starryskymeow.top"
class StarrySdkError(Exception):
"""Base exception for this demo SDK."""
class StarryNotFoundError(StarrySdkError):
"""Raised when the upstream service returns HTTP 404."""
def __init__(self, *, path: str, url: str) -> None:
self.path = path
self.url = url
self.status_code = 404
super().__init__(f"Resource not found: path={path!r}, url={url!r}")
@dataclass(frozen=True)
class _CallContext:
path: str
url: str
sdk_interface: str = "get"
http_method: str = "GET"
def _normalize_path(path: str) -> str:
if path is None:
raise ValueError("path must be a string, got None")
return str(path).lstrip("/")
def _metric_path(path: str) -> str:
# Keep metrics low-cardinality: drop query strings and collapse an empty path to "/".
normalized = _normalize_path(path).split("?", 1)[0]
return f"/{normalized}" if normalized else "/"
def _build_url(base_url: str, path: str) -> str:
normalized = _normalize_path(path)
if normalized:
return f"{base_url.rstrip('/')}/{normalized}"
return f"{base_url.rstrip('/')}/"
class StarryClient:
"""Demo client SDK.
`get(path)` requests `https://blog.starryskymeow.top/{path}` and returns `str`.
HTTP 404 is converted to `StarryNotFoundError` and logged through SDK telemetry.
"""
def __init__(
self,
*,
base_url: str = DEFAULT_BASE_URL,
timeout_seconds: float = 10.0,
sdk_version: str = __version__,
service_name: Optional[str] = None,
) -> None:
self.base_url = base_url.rstrip("/")
self.timeout_seconds = timeout_seconds
self.sdk_version = sdk_version
self._telemetry = configure_telemetry(service_name=service_name, sdk_version=sdk_version)
def get(self, path: str = "") -> str:
normalized_path = _normalize_path(path)
url = _build_url(self.base_url, normalized_path)
context = _CallContext(path=normalized_path, url=url)
attrs: dict[str, Any] = {
"sdk_name": SDK_NAME,
"sdk_version": self.sdk_version,
"sdk_interface": context.sdk_interface,
"http_method": context.http_method,
"url_path": _metric_path(normalized_path),
"outcome": "unknown",
"http_status_code": 0,
"error_type": "none",
}
start = time.perf_counter()
try:
response = requests.get(url, timeout=self.timeout_seconds)
attrs["http_status_code"] = response.status_code
if response.status_code == 404:
attrs["outcome"] = "error"
attrs["error_type"] = "StarryNotFoundError"
raise StarryNotFoundError(path=normalized_path, url=url)
response.raise_for_status()
attrs["outcome"] = "success"
return response.text
except Exception as exc:
attrs["outcome"] = "error"
attrs["error_type"] = exc.__class__.__name__
self._telemetry.logger.exception(
"Starry SDK request error",
extra={
"sdk_name": SDK_NAME,
"sdk_version": self.sdk_version,
"sdk_interface": context.sdk_interface,
"http_method": context.http_method,
"http_status_code": attrs.get("http_status_code", 0),
"url_path": attrs["url_path"],
"error_type": attrs["error_type"],
},
)
raise
finally:
duration_seconds = time.perf_counter() - start
self._telemetry.request_counter.add(1, attributes=attrs)
self._telemetry.duration_histogram.record(duration_seconds, attributes=attrs)
if attrs.get("outcome") == "error":
self._telemetry.error_counter.add(1, attributes=attrs)