Skip to content
Open
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
2 changes: 2 additions & 0 deletions docs/README-clients.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ URIs: clients accept `http`/`https` or `ws`/`wss` and convert to WebSocket (`htt

Each document lists every constructor option, method, type, and usage example for that client.

The Python client also ships the OpenTelemetry bridge (`rocketride otel`), which exports live pipeline traces and metrics over OTLP — see [README-otel-bridge.md](README-otel-bridge.md).

---

## Installation
Expand Down
369 changes: 369 additions & 0 deletions docs/README-otel-bridge.md

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions docs/README-python-client.md
Original file line number Diff line number Diff line change
Expand Up @@ -554,11 +554,29 @@ rocketride status --token <token> # Monitor task progress
rocketride stop --token <token> # Terminate a running task
rocketride list # List all active tasks
rocketride events ALL --token <token> # Stream task events
rocketride otel # Export pipeline traces + metrics over OpenTelemetry
rocketride rrext_store get_all_projects # List stored projects

@coderabbitai coderabbitai Bot Jul 16, 2026

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Replace the unsupported rrext_store command.

RocketRideCLI.setup_parser() only registers store; rocketride rrext_store get_all_projects will be rejected as an unknown command. Replace it with a supported rocketride store ... example or remove it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/README-python-client.md` at line 558, Update the get_all_projects CLI
example in the documentation to use the supported store command registered by
RocketRideCLI.setup_parser(), replacing rrext_store with store while preserving
the example’s project-listing intent.

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.

Verified and you're right that the example is wrong (main.py registers store, and get_all_projects doesn't exist) — but git diff shows that line is pre-existing content this PR never touched (our diff only added the adjacent otel line). Keeping this PR's scope tight; flagged it for a separate docs fix.

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.

@nihalnihalani, agreed—thanks for verifying. The documentation issue is valid but unrelated to this PR’s otel addition, so deferring it to a focused docs fix is appropriate.

Would you like me to open a follow-up GitHub issue for the invalid rrext_store get_all_projects example?

```

All commands accept `--uri` and `--apikey` flags, or read from environment variables.

### OpenTelemetry export (`rocketride otel`)

The `otel` command bridges live pipeline traces and metrics to any OpenTelemetry
collector over OTLP (Jaeger, Grafana, Datadog, Langfuse, LangSmith, ...). It consumes the
engine's documented WebSocket monitor protocol — no engine changes — and requires the
optional extra:

```bash
pip install 'rocketride[otel]'
rocketride otel --endpoint http://localhost:4318
```

Payload content is excluded from spans by default (`--include-content` opts in,
size-capped). Per-component spans appear for runs started with
`client.use(..., pipelineTraceLevel='summary')`. Full guide:
[README-otel-bridge.md](https://github.com/rocketride-org/rocketride-server/blob/develop/docs/README-otel-bridge.md).

## Configuration

| Variable | Description |
Expand Down
5 changes: 5 additions & 0 deletions packages/client-python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ test = [
"python-dotenv>=1.0.0",
]

otel = [

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.

Dependency discipline is the make-or-break packaging decision here: everything OpenTelemetry lives behind the [otel] extra, all imports are lazy, and the base install gains zero dependencies. Verified in a bare venv audit: every other subcommand's --help works without the extra, the otel test modules skip cleanly via importorskip (so the existing CI matrix stays green), and rocketride otel exits 2 with the exact pip install 'rocketride[otel]' hint before any connection attempt. The gRPC exporter is deliberately not part of the extra — --protocol grpc names the additional package in its error (http/protobuf is the default and covers Jaeger/Langfuse/Datadog).

"opentelemetry-sdk>=1.27.0",
"opentelemetry-exporter-otlp-proto-http>=1.27.0",
]

[project.urls]
Homepage = "https://github.com/rocketride-ai/rocketride-server"
Documentation = "https://github.com/rocketride-ai/rocketride-server#readme"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
EventsCommand: Monitor real-time pipeline events
ListCommand: List all active tasks
StoreCommand: Project and template storage operations
OtelCommand: Export pipeline traces and metrics over OpenTelemetry
"""

from .start import StartCommand
Expand All @@ -43,6 +44,7 @@
from .events import EventsCommand
from .list import ListCommand
from .store import StoreCommand
from .otel import OtelCommand

__all__ = [
'StartCommand',
Expand All @@ -52,4 +54,5 @@
'EventsCommand',
'ListCommand',
'StoreCommand',
'OtelCommand',
]
233 changes: 233 additions & 0 deletions packages/client-python/src/rocketride/cli/commands/otel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
# MIT License
#
# Copyright (c) 2026 Aparavi Software AG
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

"""
RocketRide CLI OpenTelemetry Bridge Command Implementation.

This module provides the OtelCommand class for exporting live pipeline traces
and metrics to any OpenTelemetry collector over OTLP through the RocketRide
CLI. Use this command to observe pipeline executions in backends such as
Jaeger, Grafana Tempo, Datadog, Langfuse, or LangSmith without any engine or
server changes: the bridge is a pure consumer of the engine's documented
WebSocket monitor protocol.

The command subscribes to the TASK, SUMMARY, FLOW, and SSE monitor event
types with the wildcard token scope and maps them to OTel spans (pipeline
flow) and metrics (task status snapshots), exporting both over the same OTLP
endpoint.

Trace level: the bridge can only observe runs; it cannot change the trace
level of a run it did not start. Pipeline FLOW spans appear only for runs
started with the ``pipelineTraceLevel`` execute argument, e.g.
``client.use(pipelineTraceLevel='summary')``. Without it, the bridge still
exports task lifecycle spans and status metrics.

Key Features:
- OTLP export of pipeline flow spans and task status metrics
- Wildcard monitor subscription covering all tasks for the API key
- Standard OTEL_EXPORTER_OTLP_ENDPOINT/_HEADERS/OTEL_SERVICE_NAME support
(with no endpoint configured at all, the exporters' own env semantics
apply, including OTEL_EXPORTER_OTLP_TRACES/METRICS_ENDPOINT)
- Privacy by default: payload content excluded unless --include-content
- Automatic reconnection with capped exponential backoff
- Graceful shutdown on SIGINT/SIGTERM (spans closed, exporters flushed)

Usage:
rocketride otel --apikey <key>
rocketride otel --endpoint http://localhost:4318 --service-name my-engine
rocketride otel --headers 'Langsmith-Project=my-project' --no-metrics
rocketride otel --include-content --apikey <key>

Secrets (collector auth values) are best passed via OTEL_EXPORTER_OTLP_HEADERS
rather than --headers: command-line arguments land in shell history and are
visible in process listings.

Requires the optional OpenTelemetry dependencies:
pip install 'rocketride[otel]'

Components:
OtelCommand: Main command implementation for the OpenTelemetry bridge
"""

import importlib.util
import sys
from typing import TYPE_CHECKING

from .base import BaseCommand
from ...otelbridge.bridge import run_bridge
from ...otelbridge.config import OtelConfig

# Safe without the 'otel' extra: setup.py keeps all opentelemetry imports lazy.
from ...otelbridge.setup import OtelNotInstalledError

if TYPE_CHECKING:
from ..main import RocketRideClient

# Exact install hint for the missing-extra error path (contract: exit code 2)
OTEL_INSTALL_HINT = "pip install 'rocketride[otel]'"


def _otel_available() -> bool:
"""
Check whether the optional OpenTelemetry SDK is installed.

Probes for the ``opentelemetry`` and ``opentelemetry.sdk`` modules
without importing them, so the check itself never pays import cost and
never fails on a partially installed distribution.

Returns:
bool: True when the 'rocketride[otel]' extra appears importable.
"""
try:
return (
importlib.util.find_spec('opentelemetry') is not None
and importlib.util.find_spec('opentelemetry.sdk') is not None
)
except (ImportError, ValueError):
return False


class OtelCommand(BaseCommand):
"""
Command implementation for the OpenTelemetry bridge.

Exports live pipeline traces and metrics over OTLP by consuming the
engine's WebSocket monitor protocol. Runs until interrupted (Ctrl+C or
SIGTERM), closing open spans and flushing exporters on shutdown.

Example:
```python
# Initialize and execute the OTel bridge
command = OtelCommand(cli, args)
exit_code = await command.execute(client)
```

Key Features:
- Lazy dependency guard: clear exit code 2 with install hint when
the 'rocketride[otel]' extra is missing (checked before connecting)
- Configuration precedence: CLI args > OTEL_* env vars > defaults
- Traces and metrics over one OTLP endpoint (http/protobuf default)
- Content privacy gate via --include-content
"""

def __init__(self, cli, args):
"""
Initialize OtelCommand with CLI context and parsed arguments.

Args:
cli: CLI instance providing cancellation state and event handling
args: Parsed command line arguments containing exporter options
and connection configuration
"""
super().__init__(cli, args)

async def execute(self, client: 'RocketRideClient') -> int:
"""
Execute the OpenTelemetry bridge command.

Verifies the optional OpenTelemetry dependencies are installed,
resolves the exporter configuration, and runs the bridge loop until
interrupted.

Args:
client: RocketRideClient instance for server communication
(connected by the bridge if not already connected)

Returns:
Exit code: 0 for graceful shutdown, 1 for unexpected errors,
2 when dependencies are missing or the startup connection fails

Process Flow:
1. Guard: missing 'rocketride[otel]' extra -> exit 2 with hint
(before any connection attempt)
2. Resolve OtelConfig (CLI args > OTEL_* env vars > defaults)
3. Print the effective bridge configuration
4. Run the bridge loop (connect, subscribe, dispatch, reconnect)
5. Graceful shutdown on SIGINT/SIGTERM: spans closed, exporters
flushed, exit 0
"""
# Dependency guard MUST run before any connection attempt
if not _otel_available():
print(
f"Error: 'rocketride otel' requires the OpenTelemetry extra. Install it with: {OTEL_INSTALL_HINT}",
file=sys.stderr,
)
return 2

# Resolve configuration: CLI args > OTEL_* env vars > defaults
config = OtelConfig.from_args_env(self.args)

# --trace-level is documentation-surface only: the monitor protocol
# has no way to change the trace level of runs the bridge didn't start
trace_level = getattr(self.args, 'trace_level', None)
if trace_level == 'none':
# 'none' disables flow tracing, so "emit FLOW events at that
# level" would be nonsense for it.
print(
"Note: --trace-level=none is informational only. 'none' means flow tracing stays "
'disabled: runs started without a pipelineTraceLevel (or with '
"pipelineTraceLevel='none') emit no FLOW events, so only task lifecycle spans "
'and metrics are exported.'
)
elif trace_level:
print(
f'Note: --trace-level={trace_level} is informational only. The bridge cannot change the '
f'trace level of runs it did not start; start runs with '
f"pipelineTraceLevel='{trace_level}' (e.g. client.use(pipelineTraceLevel='{trace_level}')) "
f'to emit FLOW events at that level.'
)
Comment thread
coderabbitai[bot] marked this conversation as resolved.

# Show the effective configuration before entering the run loop.
# With no endpoint configured the OTLP exporters resolve their own:
# OTEL_EXPORTER_OTLP_TRACES/METRICS_ENDPOINT, else the SDK default.
if config.endpoint:
endpoint_display = config.endpoint
else:
sdk_default = 'localhost:4317' if config.protocol == 'grpc' else 'http://localhost:4318'
endpoint_display = f'exporter default (OTEL_EXPORTER_OTLP_*_ENDPOINT or {sdk_default})'
print(f'OpenTelemetry bridge starting (endpoint: {endpoint_display}, protocol: {config.protocol})')
print(f' service.name: {config.service_name}')
print(f' metrics: {"disabled" if config.no_metrics else "enabled"}')
print(f' payload content: {"included (size-capped)" if config.include_content else "excluded"}')
print(
" FLOW spans require runs started with pipelineTraceLevel (e.g. client.use(pipelineTraceLevel='summary'))"
)

try:
# Run the bridge until stopped (returns 0) or startup fails (2)
return await run_bridge(client, config)

except (ImportError, OtelNotInstalledError) as e:
# Missing optional dependency surfaced after the guard, e.g.
# --protocol grpc without the OTLP gRPC exporter package
# (build_providers raises OtelNotInstalledError for that path).
print(f'Error: {e}', file=sys.stderr)
return 2

except KeyboardInterrupt:
# Fallback when signal handlers could not be installed
print('\nStopping OpenTelemetry bridge...')
return 0

except Exception as e:
print(f'Error: OpenTelemetry bridge failed: {e}', file=sys.stderr)
return 1
Loading
Loading