Skip to content
Merged
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
223 changes: 223 additions & 0 deletions QUICK_START_LOGGING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
# 🚀 Quick Start: Using OpenTelemetry Logging in Your CLI

## ⚡ TL;DR - 3 Step Process

1. **Import the logger**: `from codegen.shared.logging.get_logger import get_logger`
2. **Add `extra={}` to your log calls**: `logger.info("message", extra={"key": "value"})`
3. **Enable telemetry**: `codegen config telemetry enable`

**That's it!** Your logs automatically go to Grafana Cloud when telemetry is enabled.

## 🎯 Immediate Actions You Can Take

### 1. Quick Enhancement of Existing Commands

Pick **any existing CLI command** and add 2-3 lines:

```python
# Add this import at the top
from codegen.shared.logging.get_logger import get_logger

# Add this line after imports
logger = get_logger(__name__)

# Find any existing console.print() or error handling and add:
logger.info("Operation completed", extra={
"operation": "command_name",
"org_id": org_id, # if available
"success": True
})
```

### 2. Test the Integration Right Now

```bash
# 1. Enable telemetry
codegen config telemetry enable

# 2. Run the demo
python example_enhanced_agent_command.py

# 3. Run any CLI command
codegen agents # or any other command

# 4. Check status
codegen config telemetry status
```

## 📝 Copy-Paste Patterns

### Pattern 1: Operation Start/End
```python
logger = get_logger(__name__)

# At start of function
logger.info("Operation started", extra={
"operation": "command.subcommand",
"user_input": relevant_input
})

# At end of function
logger.info("Operation completed", extra={
"operation": "command.subcommand",
"success": True
})
```

### Pattern 2: Error Handling
```python
try:
# your existing code
pass
except SomeSpecificError as e:
logger.error("Specific error occurred", extra={
"operation": "command.subcommand",
"error_type": "specific_error",
"error_details": str(e)
}, exc_info=True)
# your existing error handling
```

### Pattern 3: API Calls
```python
# Before API call
logger.info("Making API request", extra={
"operation": "api.request",
"endpoint": "agent/run",
"org_id": org_id
})

# After successful API call
logger.info("API request successful", extra={
"operation": "api.request",
"endpoint": "agent/run",
"response_id": response.get("id"),
"status_code": response.status_code
})
```

## 🎯 What to Log (Priority Order)

### 🔥 High Priority (Add These First)
- **Operation start/end**: When commands begin/complete
- **API calls**: Requests to your backend
- **Authentication events**: Login/logout/token issues
- **Errors**: Any exception or failure
- **User actions**: Commands run, options selected

### ⭐ Medium Priority
- **Performance**: Duration of operations
- **State changes**: Status updates, configuration changes
- **External tools**: Claude CLI detection, git operations

### 💡 Low Priority (Nice to Have)
- **Debug info**: Internal state, validation steps
- **User behavior**: Which features are used most

## 🔧 Minimal Changes to Existing Commands

### Example: Enhance agent/main.py

```python
# Just add these 3 lines to your existing create() function:

from codegen.shared.logging.get_logger import get_logger
logger = get_logger(__name__)

def create(prompt: str, org_id: int | None = None, ...):
"""Create a new agent run with the given prompt."""

# ADD: Log start
logger.info("Agent creation started", extra={
"operation": "agent.create",
"org_id": org_id,
"prompt_length": len(prompt)
})

# Your existing code...
try:
response = requests.post(url, headers=headers, json=payload)
agent_run_data = response.json()

# ADD: Log success
logger.info("Agent created successfully", extra={
"operation": "agent.create",
"agent_run_id": agent_run_data.get("id"),
"status": agent_run_data.get("status")
})

except requests.RequestException as e:
# ADD: Log error
logger.error("Agent creation failed", extra={
"operation": "agent.create",
"error_type": "api_error",
"error": str(e)
})
# Your existing error handling...
```

### Example: Enhance claude/main.py

```python
# Add to your _run_claude_interactive function:

logger = get_logger(__name__)

def _run_claude_interactive(resolved_org_id: int, no_mcp: bool | None) -> None:
session_id = generate_session_id()

# ADD: Log session start
logger.info("Claude session started", extra={
"operation": "claude.session_start",
"session_id": session_id[:8], # Short version for privacy
"org_id": resolved_org_id
})

# Your existing code...

try:
process = subprocess.Popen([claude_path, "--session-id", session_id])
returncode = process.wait()

# ADD: Log session end
logger.info("Claude session completed", extra={
"operation": "claude.session_complete",
"session_id": session_id[:8],
"exit_code": returncode,
"status": "COMPLETE" if returncode == 0 else "ERROR"
})

except Exception as e:
# ADD: Log session error
logger.error("Claude session failed", extra={
"operation": "claude.session_error",
"session_id": session_id[:8],
"error": str(e)
})
```

## 🧪 Verification

After making changes:

1. **Run the command**: Execute your enhanced CLI command
2. **Check telemetry status**: `codegen config telemetry status`
3. **Look for logs in Grafana Cloud**: Search for your operation names
4. **Test with telemetry disabled**: `codegen config telemetry disable` - should still work normally

## 🚀 Progressive Enhancement

**Week 1**: Add basic operation logging to 2-3 commands
**Week 2**: Add error logging to all commands
**Week 3**: Add performance metrics and detailed context
**Week 4**: Create Grafana dashboards using the collected data

## 🎉 Benefits You'll See Immediately

- **Real usage data**: Which commands are used most?
- **Error tracking**: What breaks and how often?
- **Performance insights**: Which operations are slow?
- **User behavior**: How do users actually use your CLI?
- **Debugging**: Rich context when things go wrong

Start with just **one command** and **one log line** - you'll see the value immediately! 🎯
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ dependencies = [
"unidiff>=0.7.5",
"datamodel-code-generator>=0.26.5",
"fastmcp>=2.9.0",
# OpenTelemetry logging dependencies
"opentelemetry-api>=1.26.0",
"opentelemetry-sdk>=1.26.0",
"opentelemetry-exporter-otlp>=1.26.0",
# Utility dependencies
"colorlog>=6.9.0",
"psutil>=5.8.0",
Expand Down
41 changes: 34 additions & 7 deletions src/codegen/cli/cli.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import atexit

import typer
from rich.traceback import install

from codegen import __version__

# Import config command (still a Typer app)
from codegen.cli.commands.agent.main import agent
from codegen.cli.commands.agents.main import agents_app

# Import the actual command functions
from codegen.cli.commands.claude.main import claude
from codegen.cli.commands.config.main import config_command
from codegen.cli.commands.init.main import init
Expand All @@ -21,35 +19,60 @@
from codegen.cli.commands.tools.main import tools
from codegen.cli.commands.tui.main import tui
from codegen.cli.commands.update.main import update
from codegen.shared.logging.get_logger import get_logger

# Initialize logger for CLI command tracking
logger = get_logger(__name__)

# Set up global exception logging early
try:
from codegen.cli.telemetry.exception_logger import setup_global_exception_logging

setup_global_exception_logging()
except ImportError:
# Exception logging dependencies not available - continue without it
pass


install(show_locals=True)

# Register telemetry shutdown on exit
try:
from codegen.cli.telemetry.exception_logger import teardown_global_exception_logging
from codegen.cli.telemetry.otel_setup import shutdown_otel_logging

atexit.register(shutdown_otel_logging)
atexit.register(teardown_global_exception_logging)
except ImportError:
# OTel dependencies not available
pass


def version_callback(value: bool):
"""Print version and exit."""
if value:
logger.info("Version command invoked", extra={"operation": "cli.version", "version": __version__})
print(__version__)
raise typer.Exit()


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

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

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

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


if __name__ == "__main__":
Expand Down
7 changes: 7 additions & 0 deletions src/codegen/cli/commands/agents/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
from codegen.cli.auth.token_manager import get_current_token
from codegen.cli.rich.spinners import create_spinner
from codegen.cli.utils.org import resolve_org_id
from codegen.shared.logging.get_logger import get_logger

# Initialize logger
logger = get_logger(__name__)

console = Console()

Expand All @@ -19,9 +23,12 @@
@agents_app.command("list")
def list_agents(org_id: int | None = typer.Option(None, help="Organization ID (defaults to CODEGEN_ORG_ID/REPOSITORY_ORG_ID or auto-detect)")):
"""List agent runs from the Codegen API."""
logger.info("Agents list command invoked", extra={"operation": "agents.list", "org_id": org_id, "command": "codegen agents list"})

# Get the current token
token = get_current_token()
if not token:
logger.error("Agents list failed - not authenticated", extra={"operation": "agents.list", "error_type": "not_authenticated"})
console.print("[red]Error:[/red] Not authenticated. Please run 'codegen login' first.")
raise typer.Exit(1)

Expand Down
Loading
Loading