Add error handling for OTLP/gRPC listener binding in trace command (#748)

This commit is contained in:
Musa 2026-02-14 15:28:45 -08:00 committed by GitHub
parent 1df43872a6
commit ef285f1213
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 68 additions and 2 deletions

View file

@ -28,6 +28,17 @@ MAX_TRACES = 50
MAX_SPANS_PER_TRACE = 500
class TraceListenerBindError(RuntimeError):
"""Raised when the OTLP/gRPC listener cannot bind to the requested address."""
def _trace_listener_bind_error_message(address: str) -> str:
return (
f"Failed to start OTLP listener on {address}: address is already in use.\n"
"Stop the process using that port or run `planoai trace listen --port <PORT>`."
)
@dataclass
class TraceSummary:
trace_id: str
@ -501,7 +512,15 @@ def _create_trace_server(host: str, grpc_port: int) -> grpc.Server:
trace_service_pb2_grpc.add_TraceServiceServicer_to_server(
_OTLPTraceServicer(), grpc_server
)
grpc_server.add_insecure_port(f"{host}:{grpc_port}")
address = f"{host}:{grpc_port}"
try:
bound_port = grpc_server.add_insecure_port(address)
except RuntimeError as exc:
raise TraceListenerBindError(
_trace_listener_bind_error_message(address)
) from exc
if bound_port == 0:
raise TraceListenerBindError(_trace_listener_bind_error_message(address))
grpc_server.start()
return grpc_server
@ -509,7 +528,10 @@ def _create_trace_server(host: str, grpc_port: int) -> grpc.Server:
def _start_trace_listener(host: str, grpc_port: int) -> None:
"""Start the OTLP/gRPC listener and block until interrupted."""
console = Console()
grpc_server = _create_trace_server(host, grpc_port)
try:
grpc_server = _create_trace_server(host, grpc_port)
except TraceListenerBindError as exc:
raise click.ClickException(str(exc)) from exc
console.print()
console.print(f"[bold {PLANO_COLOR}]Listening for traces...[/bold {PLANO_COLOR}]")

View file

@ -0,0 +1,44 @@
import pytest
import rich_click as click
from planoai import trace_cmd
class _FakeGrpcServer:
def add_insecure_port(self, _address: str) -> int:
raise RuntimeError("bind failed")
def start(self) -> None:
return None
def test_create_trace_server_raises_bind_error(monkeypatch):
monkeypatch.setattr(
trace_cmd.grpc, "server", lambda *_args, **_kwargs: _FakeGrpcServer()
)
monkeypatch.setattr(
trace_cmd.trace_service_pb2_grpc,
"add_TraceServiceServicer_to_server",
lambda *_args, **_kwargs: None,
)
with pytest.raises(trace_cmd.TraceListenerBindError) as excinfo:
trace_cmd._create_trace_server("0.0.0.0", 4317)
assert "already in use" in str(excinfo.value)
assert "planoai trace listen --port" in str(excinfo.value)
def test_start_trace_listener_converts_bind_error_to_click_exception(monkeypatch):
monkeypatch.setattr(
trace_cmd,
"_create_trace_server",
lambda *_args, **_kwargs: (_ for _ in ()).throw(
trace_cmd.TraceListenerBindError("port in use")
),
)
with pytest.raises(click.ClickException) as excinfo:
trace_cmd._start_trace_listener("0.0.0.0", 4317)
assert "port in use" in str(excinfo.value)