Skip to content

Commit 0b990d7

Browse files
authored
Rpatel/add telemetry (#1233)
# Motivation <!-- Why is this change necessary? --> # Content <!-- Please include a summary of the change --> # Testing <!-- How was the change tested? --> # Please check the following before marking your PR as ready for review - [ ] I have added tests for my changes - [ ] I have updated the documentation or added new documentation as needed
1 parent 736a31d commit 0b990d7

File tree

21 files changed

+2025
-35
lines changed

21 files changed

+2025
-35
lines changed

QUICK_START_LOGGING.md

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
# 🚀 Quick Start: Using OpenTelemetry Logging in Your CLI
2+
3+
## ⚡ TL;DR - 3 Step Process
4+
5+
1. **Import the logger**: `from codegen.shared.logging.get_logger import get_logger`
6+
2. **Add `extra={}` to your log calls**: `logger.info("message", extra={"key": "value"})`
7+
3. **Enable telemetry**: `codegen config telemetry enable`
8+
9+
**That's it!** Your logs automatically go to Grafana Cloud when telemetry is enabled.
10+
11+
## 🎯 Immediate Actions You Can Take
12+
13+
### 1. Quick Enhancement of Existing Commands
14+
15+
Pick **any existing CLI command** and add 2-3 lines:
16+
17+
```python
18+
# Add this import at the top
19+
from codegen.shared.logging.get_logger import get_logger
20+
21+
# Add this line after imports
22+
logger = get_logger(__name__)
23+
24+
# Find any existing console.print() or error handling and add:
25+
logger.info("Operation completed", extra={
26+
"operation": "command_name",
27+
"org_id": org_id, # if available
28+
"success": True
29+
})
30+
```
31+
32+
### 2. Test the Integration Right Now
33+
34+
```bash
35+
# 1. Enable telemetry
36+
codegen config telemetry enable
37+
38+
# 2. Run the demo
39+
python example_enhanced_agent_command.py
40+
41+
# 3. Run any CLI command
42+
codegen agents # or any other command
43+
44+
# 4. Check status
45+
codegen config telemetry status
46+
```
47+
48+
## 📝 Copy-Paste Patterns
49+
50+
### Pattern 1: Operation Start/End
51+
```python
52+
logger = get_logger(__name__)
53+
54+
# At start of function
55+
logger.info("Operation started", extra={
56+
"operation": "command.subcommand",
57+
"user_input": relevant_input
58+
})
59+
60+
# At end of function
61+
logger.info("Operation completed", extra={
62+
"operation": "command.subcommand",
63+
"success": True
64+
})
65+
```
66+
67+
### Pattern 2: Error Handling
68+
```python
69+
try:
70+
# your existing code
71+
pass
72+
except SomeSpecificError as e:
73+
logger.error("Specific error occurred", extra={
74+
"operation": "command.subcommand",
75+
"error_type": "specific_error",
76+
"error_details": str(e)
77+
}, exc_info=True)
78+
# your existing error handling
79+
```
80+
81+
### Pattern 3: API Calls
82+
```python
83+
# Before API call
84+
logger.info("Making API request", extra={
85+
"operation": "api.request",
86+
"endpoint": "agent/run",
87+
"org_id": org_id
88+
})
89+
90+
# After successful API call
91+
logger.info("API request successful", extra={
92+
"operation": "api.request",
93+
"endpoint": "agent/run",
94+
"response_id": response.get("id"),
95+
"status_code": response.status_code
96+
})
97+
```
98+
99+
## 🎯 What to Log (Priority Order)
100+
101+
### 🔥 High Priority (Add These First)
102+
- **Operation start/end**: When commands begin/complete
103+
- **API calls**: Requests to your backend
104+
- **Authentication events**: Login/logout/token issues
105+
- **Errors**: Any exception or failure
106+
- **User actions**: Commands run, options selected
107+
108+
### ⭐ Medium Priority
109+
- **Performance**: Duration of operations
110+
- **State changes**: Status updates, configuration changes
111+
- **External tools**: Claude CLI detection, git operations
112+
113+
### 💡 Low Priority (Nice to Have)
114+
- **Debug info**: Internal state, validation steps
115+
- **User behavior**: Which features are used most
116+
117+
## 🔧 Minimal Changes to Existing Commands
118+
119+
### Example: Enhance agent/main.py
120+
121+
```python
122+
# Just add these 3 lines to your existing create() function:
123+
124+
from codegen.shared.logging.get_logger import get_logger
125+
logger = get_logger(__name__)
126+
127+
def create(prompt: str, org_id: int | None = None, ...):
128+
"""Create a new agent run with the given prompt."""
129+
130+
# ADD: Log start
131+
logger.info("Agent creation started", extra={
132+
"operation": "agent.create",
133+
"org_id": org_id,
134+
"prompt_length": len(prompt)
135+
})
136+
137+
# Your existing code...
138+
try:
139+
response = requests.post(url, headers=headers, json=payload)
140+
agent_run_data = response.json()
141+
142+
# ADD: Log success
143+
logger.info("Agent created successfully", extra={
144+
"operation": "agent.create",
145+
"agent_run_id": agent_run_data.get("id"),
146+
"status": agent_run_data.get("status")
147+
})
148+
149+
except requests.RequestException as e:
150+
# ADD: Log error
151+
logger.error("Agent creation failed", extra={
152+
"operation": "agent.create",
153+
"error_type": "api_error",
154+
"error": str(e)
155+
})
156+
# Your existing error handling...
157+
```
158+
159+
### Example: Enhance claude/main.py
160+
161+
```python
162+
# Add to your _run_claude_interactive function:
163+
164+
logger = get_logger(__name__)
165+
166+
def _run_claude_interactive(resolved_org_id: int, no_mcp: bool | None) -> None:
167+
session_id = generate_session_id()
168+
169+
# ADD: Log session start
170+
logger.info("Claude session started", extra={
171+
"operation": "claude.session_start",
172+
"session_id": session_id[:8], # Short version for privacy
173+
"org_id": resolved_org_id
174+
})
175+
176+
# Your existing code...
177+
178+
try:
179+
process = subprocess.Popen([claude_path, "--session-id", session_id])
180+
returncode = process.wait()
181+
182+
# ADD: Log session end
183+
logger.info("Claude session completed", extra={
184+
"operation": "claude.session_complete",
185+
"session_id": session_id[:8],
186+
"exit_code": returncode,
187+
"status": "COMPLETE" if returncode == 0 else "ERROR"
188+
})
189+
190+
except Exception as e:
191+
# ADD: Log session error
192+
logger.error("Claude session failed", extra={
193+
"operation": "claude.session_error",
194+
"session_id": session_id[:8],
195+
"error": str(e)
196+
})
197+
```
198+
199+
## 🧪 Verification
200+
201+
After making changes:
202+
203+
1. **Run the command**: Execute your enhanced CLI command
204+
2. **Check telemetry status**: `codegen config telemetry status`
205+
3. **Look for logs in Grafana Cloud**: Search for your operation names
206+
4. **Test with telemetry disabled**: `codegen config telemetry disable` - should still work normally
207+
208+
## 🚀 Progressive Enhancement
209+
210+
**Week 1**: Add basic operation logging to 2-3 commands
211+
**Week 2**: Add error logging to all commands
212+
**Week 3**: Add performance metrics and detailed context
213+
**Week 4**: Create Grafana dashboards using the collected data
214+
215+
## 🎉 Benefits You'll See Immediately
216+
217+
- **Real usage data**: Which commands are used most?
218+
- **Error tracking**: What breaks and how often?
219+
- **Performance insights**: Which operations are slow?
220+
- **User behavior**: How do users actually use your CLI?
221+
- **Debugging**: Rich context when things go wrong
222+
223+
Start with just **one command** and **one log line** - you'll see the value immediately! 🎯

pyproject.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ dependencies = [
2525
"unidiff>=0.7.5",
2626
"datamodel-code-generator>=0.26.5",
2727
"fastmcp>=2.9.0",
28+
# OpenTelemetry logging dependencies
29+
"opentelemetry-api>=1.26.0",
30+
"opentelemetry-sdk>=1.26.0",
31+
"opentelemetry-exporter-otlp>=1.26.0",
2832
# Utility dependencies
2933
"colorlog>=6.9.0",
3034
"psutil>=5.8.0",

src/codegen/cli/cli.py

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
1+
import atexit
2+
13
import typer
24
from rich.traceback import install
35

46
from codegen import __version__
5-
6-
# Import config command (still a Typer app)
77
from codegen.cli.commands.agent.main import agent
88
from codegen.cli.commands.agents.main import agents_app
9-
10-
# Import the actual command functions
119
from codegen.cli.commands.claude.main import claude
1210
from codegen.cli.commands.config.main import config_command
1311
from codegen.cli.commands.init.main import init
@@ -21,35 +19,60 @@
2119
from codegen.cli.commands.tools.main import tools
2220
from codegen.cli.commands.tui.main import tui
2321
from codegen.cli.commands.update.main import update
22+
from codegen.shared.logging.get_logger import get_logger
23+
24+
# Initialize logger for CLI command tracking
25+
logger = get_logger(__name__)
26+
27+
# Set up global exception logging early
28+
try:
29+
from codegen.cli.telemetry.exception_logger import setup_global_exception_logging
30+
31+
setup_global_exception_logging()
32+
except ImportError:
33+
# Exception logging dependencies not available - continue without it
34+
pass
35+
2436

2537
install(show_locals=True)
2638

39+
# Register telemetry shutdown on exit
40+
try:
41+
from codegen.cli.telemetry.exception_logger import teardown_global_exception_logging
42+
from codegen.cli.telemetry.otel_setup import shutdown_otel_logging
43+
44+
atexit.register(shutdown_otel_logging)
45+
atexit.register(teardown_global_exception_logging)
46+
except ImportError:
47+
# OTel dependencies not available
48+
pass
49+
2750

2851
def version_callback(value: bool):
2952
"""Print version and exit."""
3053
if value:
54+
logger.info("Version command invoked", extra={"operation": "cli.version", "version": __version__})
3155
print(__version__)
3256
raise typer.Exit()
3357

3458

3559
# Create the main Typer app
3660
main = typer.Typer(name="codegen", help="Codegen - the Operating System for Code Agents.", rich_markup_mode="rich")
3761

38-
# Add individual commands to the main app
62+
# Add individual commands to the main app (logging now handled within each command)
3963
main.command("agent", help="Create a new agent run with a prompt.")(agent)
4064
main.command("claude", help="Run Claude Code with OpenTelemetry monitoring and logging.")(claude)
4165
main.command("init", help="Initialize or update the Codegen folder.")(init)
4266
main.command("login", help="Store authentication token.")(login)
4367
main.command("logout", help="Clear stored authentication token.")(logout)
4468
main.command("org", help="Manage and switch between organizations.")(org)
45-
# Profile is now a Typer app
4669
main.command("repo", help="Manage repository configuration and environment variables.")(repo)
4770
main.command("style-debug", help="Debug command to visualize CLI styling (spinners, etc).")(style_debug)
4871
main.command("tools", help="List available tools from the Codegen API.")(tools)
4972
main.command("tui", help="Launch the interactive TUI interface.")(tui)
5073
main.command("update", help="Update Codegen to the latest or specified version")(update)
5174

52-
# Add Typer apps as sub-applications
75+
# Add Typer apps as sub-applications (these will handle their own sub-command logging)
5376
main.add_typer(agents_app, name="agents")
5477
main.add_typer(config_command, name="config")
5578
main.add_typer(integrations_app, name="integrations")
@@ -61,9 +84,13 @@ def main_callback(ctx: typer.Context, version: bool = typer.Option(False, "--ver
6184
"""Codegen - the Operating System for Code Agents"""
6285
if ctx.invoked_subcommand is None:
6386
# No subcommand provided, launch TUI
87+
logger.info("CLI launched without subcommand - starting TUI", extra={"operation": "cli.main", "action": "default_tui_launch", "command": "codegen"})
6488
from codegen.cli.tui.app import run_tui
6589

6690
run_tui()
91+
else:
92+
# Log when a subcommand is being invoked
93+
logger.debug("CLI main callback with subcommand", extra={"operation": "cli.main", "subcommand": ctx.invoked_subcommand, "command": f"codegen {ctx.invoked_subcommand}"})
6794

6895

6996
if __name__ == "__main__":

src/codegen/cli/commands/agents/main.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
from codegen.cli.auth.token_manager import get_current_token
1010
from codegen.cli.rich.spinners import create_spinner
1111
from codegen.cli.utils.org import resolve_org_id
12+
from codegen.shared.logging.get_logger import get_logger
13+
14+
# Initialize logger
15+
logger = get_logger(__name__)
1216

1317
console = Console()
1418

@@ -19,9 +23,12 @@
1923
@agents_app.command("list")
2024
def list_agents(org_id: int | None = typer.Option(None, help="Organization ID (defaults to CODEGEN_ORG_ID/REPOSITORY_ORG_ID or auto-detect)")):
2125
"""List agent runs from the Codegen API."""
26+
logger.info("Agents list command invoked", extra={"operation": "agents.list", "org_id": org_id, "command": "codegen agents list"})
27+
2228
# Get the current token
2329
token = get_current_token()
2430
if not token:
31+
logger.error("Agents list failed - not authenticated", extra={"operation": "agents.list", "error_type": "not_authenticated"})
2532
console.print("[red]Error:[/red] Not authenticated. Please run 'codegen login' first.")
2633
raise typer.Exit(1)
2734

0 commit comments

Comments
 (0)