Skip to content

Commit d3d694d

Browse files
committed
add opentelemetery
1 parent 3b4147b commit d3d694d

9 files changed

Lines changed: 347 additions & 147 deletions

File tree

README.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,12 @@ MAX_REPO_SIZE_MB=500
263263
QUERY_TIMEOUT=30
264264
QUERY_CACHE_ENABLED=true
265265
QUERY_CACHE_TTL=300
266+
267+
# Telemetry (OpenTelemetry)
268+
OTEL_ENABLED=false
269+
OTEL_SERVICE_NAME=codebadger
270+
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
271+
OTEL_EXPORTER_OTLP_PROTOCOL=grpc
266272
```
267273

268274
### Config File
@@ -275,6 +281,71 @@ cp config.example.yaml config.yaml
275281

276282
Then customize as needed.
277283

284+
## Telemetry (OpenTelemetry)
285+
286+
CodeBadger has built-in OpenTelemetry support for distributed tracing. When enabled, all MCP tool calls are automatically traced, plus custom spans for CPG generation, Joern server management, and query execution.
287+
288+
### Quick Start
289+
290+
1. Install the telemetry dependencies (included in `requirements.txt`):
291+
292+
```bash
293+
pip install opentelemetry-sdk opentelemetry-exporter-otlp
294+
```
295+
296+
2. Enable via environment variables:
297+
298+
```bash
299+
export OTEL_ENABLED=true
300+
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
301+
python main.py
302+
```
303+
304+
Or via `config.yaml`:
305+
306+
```yaml
307+
telemetry:
308+
enabled: true
309+
service_name: codebadger
310+
otlp_endpoint: http://localhost:4317
311+
otlp_protocol: grpc # or "http/protobuf"
312+
```
313+
314+
### Local Development with Jaeger
315+
316+
```bash
317+
# Start Jaeger (provides UI at http://localhost:16686)
318+
docker run -d --name jaeger \
319+
-p 16686:16686 \
320+
-p 4317:4317 \
321+
jaegertracing/all-in-one:latest
322+
323+
# Start CodeBadger with telemetry
324+
OTEL_ENABLED=true python main.py
325+
```
326+
327+
### What Gets Traced
328+
329+
| Span | Description |
330+
|------|-------------|
331+
| `tools/call {name}` | Every MCP tool invocation (automatic via FastMCP) |
332+
| `cpg.generate` | Full CPG generation pipeline |
333+
| `cpg.joern_cli_exec` | Joern CLI command execution inside Docker |
334+
| `cpg.spawn_server` | Joern server instance creation |
335+
| `cpg.load_cpg` | CPG file loading into Joern server |
336+
| `query.execute` | CPGQL query execution with timing and success attributes |
337+
338+
### Configuration Reference
339+
340+
| Setting | Env Variable | Default | Description |
341+
|---------|-------------|---------|-------------|
342+
| `enabled` | `OTEL_ENABLED` | `false` | Enable/disable telemetry |
343+
| `service_name` | `OTEL_SERVICE_NAME` | `codebadger` | Service name in traces |
344+
| `otlp_endpoint` | `OTEL_EXPORTER_OTLP_ENDPOINT` | `http://localhost:4317` | OTLP collector endpoint |
345+
| `otlp_protocol` | `OTEL_EXPORTER_OTLP_PROTOCOL` | `grpc` | Export protocol (`grpc` or `http/protobuf`) |
346+
347+
When telemetry is disabled (default), all tracing is no-op with zero overhead.
348+
278349

279350

280351

config.example.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -954,3 +954,8 @@ storage:
954954
workspace_root: ${WORKSPACE_ROOT:/tmp/codebadger}
955955
cleanup_on_shutdown: ${CLEANUP_ON_SHUTDOWN:true}
956956

957+
telemetry:
958+
enabled: ${OTEL_ENABLED:false}
959+
service_name: ${OTEL_SERVICE_NAME:codebadger}
960+
otlp_endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT:http://localhost:4317}
961+
otlp_protocol: ${OTEL_EXPORTER_OTLP_PROTOCOL:grpc}

main.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,42 @@
3636
logger = logging.getLogger(__name__)
3737

3838

39+
def _setup_telemetry(config) -> None:
40+
"""Configure OpenTelemetry SDK if telemetry is enabled.
41+
42+
Must be called before FastMCP tools are invoked so the tracer provider
43+
is in place when FastMCP's built-in instrumentation fires.
44+
"""
45+
telemetry = config.telemetry
46+
if not telemetry.enabled:
47+
logger.debug("Telemetry disabled, skipping OpenTelemetry setup")
48+
return
49+
50+
try:
51+
from opentelemetry import trace
52+
from opentelemetry.sdk.trace import TracerProvider
53+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
54+
from opentelemetry.sdk.resources import Resource
55+
56+
resource = Resource.create({"service.name": telemetry.service_name})
57+
provider = TracerProvider(resource=resource)
58+
59+
if telemetry.otlp_protocol == "grpc":
60+
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
61+
else:
62+
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
63+
64+
exporter = OTLPSpanExporter(endpoint=telemetry.otlp_endpoint)
65+
provider.add_span_processor(BatchSpanProcessor(exporter))
66+
trace.set_tracer_provider(provider)
67+
68+
logger.info(f"OpenTelemetry enabled: exporting to {telemetry.otlp_endpoint} via {telemetry.otlp_protocol}")
69+
except ImportError:
70+
logger.warning("OpenTelemetry packages not installed. Install with: pip install opentelemetry-sdk opentelemetry-exporter-otlp")
71+
except Exception as e:
72+
logger.warning(f"Failed to initialize OpenTelemetry: {e}")
73+
74+
3975
async def _graceful_shutdown():
4076
"""Gracefully shutdown all services"""
4177
logger.info("Performing graceful shutdown...")
@@ -76,6 +112,9 @@ async def app_lifespan(server: FastMCP):
76112
setup_logging(config.server.log_level)
77113
logger.info("Starting CodeBadger Server")
78114

115+
# Setup OpenTelemetry (must happen before tool invocations)
116+
_setup_telemetry(config)
117+
79118
# Ensure required directories exist
80119
os.makedirs(config.storage.workspace_root, exist_ok=True)
81120

requirements.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ docker==7.1.0
1010
gitpython==3.1.46
1111
PyYAML==6.0.3
1212

13+
# Telemetry (optional - OpenTelemetry)
14+
opentelemetry-api>=1.33.0
15+
opentelemetry-sdk>=1.33.0
16+
opentelemetry-exporter-otlp>=1.33.0
17+
1318
# Development dependencies
1419
pytest==8.4.2
1520
pytest-asyncio==1.2.0

src/config.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
QueryConfig,
1414
ServerConfig,
1515
StorageConfig,
16+
TelemetryConfig,
1617
)
1718

1819

@@ -72,6 +73,12 @@ def load_config(config_path: Optional[str] = None) -> Config:
7273
cleanup_on_shutdown=os.getenv("CLEANUP_ON_SHUTDOWN", str(defaults.CLEANUP_ON_SHUTDOWN)).lower()
7374
== "true",
7475
),
76+
telemetry=TelemetryConfig(
77+
enabled=os.getenv("OTEL_ENABLED", "false").lower() == "true",
78+
service_name=os.getenv("OTEL_SERVICE_NAME", "codebadger"),
79+
otlp_endpoint=os.getenv("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4317"),
80+
otlp_protocol=os.getenv("OTEL_EXPORTER_OTLP_PROTOCOL", "grpc"),
81+
),
7582
)
7683

7784

@@ -158,4 +165,5 @@ def convert_config_section(config_class, values):
158165
cpg=convert_config_section(CPGConfig, cpg_data),
159166
query=convert_config_section(QueryConfig, data.get("query", {})),
160167
storage=convert_config_section(StorageConfig, data.get("storage", {})),
168+
telemetry=convert_config_section(TelemetryConfig, data.get("telemetry", {})),
161169
)

src/models.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,16 @@ class StorageConfig:
163163
cleanup_on_shutdown: bool = True
164164

165165

166+
@dataclass
167+
class TelemetryConfig:
168+
"""OpenTelemetry configuration"""
169+
170+
enabled: bool = False
171+
service_name: str = "codebadger"
172+
otlp_endpoint: str = "http://localhost:4317"
173+
otlp_protocol: str = "grpc" # "grpc" or "http/protobuf"
174+
175+
166176
@dataclass
167177
class Config:
168178
"""Main configuration"""
@@ -172,6 +182,7 @@ class Config:
172182
cpg: CPGConfig = field(default_factory=CPGConfig)
173183
query: QueryConfig = field(default_factory=QueryConfig)
174184
storage: StorageConfig = field(default_factory=StorageConfig)
185+
telemetry: TelemetryConfig = field(default_factory=TelemetryConfig)
175186

176187

177188
@dataclass

0 commit comments

Comments
 (0)