from __future__ import annotations import time from dataclasses import dataclass from typing import Any, Optional from opentelemetry.trace import SpanKind, Status, StatusCode 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", } with self._telemetry.tracer.start_as_current_span( "starry_client_sdk.get", kind=SpanKind.CLIENT, attributes={ "sdk.name": SDK_NAME, "sdk.version": self.sdk_version, "sdk.interface": context.sdk_interface, "http.request.method": context.http_method, "url.path": attrs["url_path"], }, record_exception=False, set_status_on_exception=False, ) as span: start = time.perf_counter() try: response = requests.get(url, timeout=self.timeout_seconds) attrs["http_status_code"] = response.status_code span.set_attribute("http.response.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" span.set_attribute("sdk.outcome", attrs["outcome"]) span.set_status(Status(StatusCode.OK)) return response.text except Exception as exc: attrs["outcome"] = "error" attrs["error_type"] = exc.__class__.__name__ span.set_attribute("sdk.outcome", attrs["outcome"]) span.set_attribute("error.type", attrs["error_type"]) span.record_exception(exc) span.set_status(Status(StatusCode.ERROR, attrs["error_type"])) 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 span.set_attribute("sdk.duration_seconds", duration_seconds) 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)