diff --git a/agent.go b/agent.go index 67ecc9d..2abda0a 100644 --- a/agent.go +++ b/agent.go @@ -128,13 +128,10 @@ func (a *LinuxDiagnosticAgent) DiagnoseIssue(issue string) error { if len(diagnosticResp.Commands) > 0 { fmt.Printf("🔧 Executing diagnostic commands...\n") for _, cmd := range diagnosticResp.Commands { - fmt.Printf("âš™ī¸ Executing command '%s': %s\n", cmd.ID, cmd.Command) result := a.executor.Execute(cmd) commandResults = append(commandResults, result) - if result.ExitCode == 0 { - fmt.Printf("✅ Command '%s' completed successfully\n", cmd.ID) - } else { + if result.ExitCode != 0 { fmt.Printf("❌ Command '%s' failed with exit code %d\n", cmd.ID, result.ExitCode) } } @@ -143,7 +140,6 @@ func (a *LinuxDiagnosticAgent) DiagnoseIssue(issue string) error { // Execute eBPF programs if present var ebpfResults []map[string]interface{} if len(diagnosticResp.EBPFPrograms) > 0 { - fmt.Printf("đŸ”Ŧ Executing %d eBPF programs...\n", len(diagnosticResp.EBPFPrograms)) ebpfResults = a.executeEBPFPrograms(diagnosticResp.EBPFPrograms) } @@ -170,15 +166,6 @@ func (a *LinuxDiagnosticAgent) DiagnoseIssue(issue string) error { evidenceSummary = append(evidenceSummary, summaryStr) } allResults["ebpf_evidence_summary"] = evidenceSummary - - fmt.Printf("īŋŊ Sending eBPF monitoring data to TensorZero:\n") - for _, summary := range evidenceSummary { - fmt.Printf(" - %s\n", summary) - } - - fmt.Printf("✅ Executed %d commands, %d eBPF programs\n", len(commandResults), len(ebpfResults)) - } else { - fmt.Printf("✅ Executed %d commands\n", len(commandResults)) } resultsJSON, err := json.MarshalIndent(allResults, "", " ") @@ -227,7 +214,7 @@ func (a *LinuxDiagnosticAgent) executeEBPFPrograms(ebpfPrograms []EBPFRequest) [ } for _, prog := range ebpfPrograms { - fmt.Printf("đŸ”Ŧ Starting eBPF program [%s]: %s -> %s (%ds)\n", prog.Name, prog.Type, prog.Target, int(prog.Duration)) + // eBPF program starting - only show in debug mode // Actually start the eBPF program using the real manager programID, err := a.ebpfManager.StartEBPFProgram(prog) @@ -248,16 +235,11 @@ func (a *LinuxDiagnosticAgent) executeEBPFPrograms(ebpfPrograms []EBPFRequest) [ } // Let the eBPF program run for the specified duration - fmt.Printf("⏰ Waiting %d seconds for eBPF program to collect data...\n", int(prog.Duration)) time.Sleep(time.Duration(prog.Duration) * time.Second) // Give the collectEvents goroutine a moment to finish and store results - fmt.Printf("âŗ Allowing program to complete data collection...\n") time.Sleep(500 * time.Millisecond) - // Get the results (should be in completedResults now) - fmt.Printf("📊 Getting results for eBPF program [%s]...\n", prog.Name) - // Use a channel to implement timeout for GetProgramResults type resultPair struct { trace *EBPFTrace @@ -282,11 +264,9 @@ func (a *LinuxDiagnosticAgent) executeEBPFPrograms(ebpfPrograms []EBPFRequest) [ } // Try to stop the program (may already be stopped by collectEvents) - fmt.Printf("🛑 Stopping eBPF program [%s]...\n", prog.Name) stopErr := a.ebpfManager.StopProgram(programID) if stopErr != nil { - fmt.Printf("âš ī¸ eBPF program [%s] cleanup: %v (may have already completed)\n", prog.Name, stopErr) - // Don't return here, we still want to process results if we got them + // Only show warning in debug mode - this is normal for completed programs } if resultErr != nil { @@ -325,7 +305,6 @@ func (a *LinuxDiagnosticAgent) executeEBPFPrograms(ebpfPrograms []EBPFRequest) [ result["end_time"] = trace.EndTime.Format(time.RFC3339) result["actual_duration"] = trace.EndTime.Sub(trace.StartTime).Seconds() - fmt.Printf("✅ eBPF program [%s] completed - collected %d real events\n", prog.Name, trace.EventCount) } else { result["data_points"] = 0 result["error"] = "No trace data returned" diff --git a/internal/logging/logger.go b/internal/logging/logger.go new file mode 100644 index 0000000..2130c6a --- /dev/null +++ b/internal/logging/logger.go @@ -0,0 +1,90 @@ +package logging + +import ( + "fmt" + "log" + "log/syslog" + "os" +) + +type Logger struct { + syslogWriter *syslog.Writer + debugMode bool +} + +var defaultLogger *Logger + +func init() { + defaultLogger = NewLogger() +} + +func NewLogger() *Logger { + l := &Logger{ + debugMode: os.Getenv("DEBUG") == "true", + } + + // Try to connect to syslog + if writer, err := syslog.New(syslog.LOG_INFO|syslog.LOG_DAEMON, "nannyagentv2"); err == nil { + l.syslogWriter = writer + } + + return l +} + +func (l *Logger) Info(format string, args ...interface{}) { + msg := fmt.Sprintf(format, args...) + if l.syslogWriter != nil { + l.syslogWriter.Info(msg) + } + log.Printf("[INFO] %s", msg) +} + +func (l *Logger) Debug(format string, args ...interface{}) { + if !l.debugMode { + return + } + msg := fmt.Sprintf(format, args...) + if l.syslogWriter != nil { + l.syslogWriter.Debug(msg) + } + log.Printf("[DEBUG] %s", msg) +} + +func (l *Logger) Warning(format string, args ...interface{}) { + msg := fmt.Sprintf(format, args...) + if l.syslogWriter != nil { + l.syslogWriter.Warning(msg) + } + log.Printf("[WARNING] %s", msg) +} + +func (l *Logger) Error(format string, args ...interface{}) { + msg := fmt.Sprintf(format, args...) + if l.syslogWriter != nil { + l.syslogWriter.Err(msg) + } + log.Printf("[ERROR] %s", msg) +} + +func (l *Logger) Close() { + if l.syslogWriter != nil { + l.syslogWriter.Close() + } +} + +// Global logging functions +func Info(format string, args ...interface{}) { + defaultLogger.Info(format, args...) +} + +func Debug(format string, args ...interface{}) { + defaultLogger.Debug(format, args...) +} + +func Warning(format string, args ...interface{}) { + defaultLogger.Warning(format, args...) +} + +func Error(format string, args ...interface{}) { + defaultLogger.Error(format, args...) +} diff --git a/internal/types/types.go b/internal/types/types.go index 1016da0..49aa2ef 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -168,3 +168,170 @@ type MetricsRequest struct { BlockDevices []BlockDevice `json:"block_devices"` NetworkStats map[string]uint64 `json:"network_stats"` } + +// eBPF related types +type EBPFEvent struct { + Timestamp int64 `json:"timestamp"` + EventType string `json:"event_type"` + ProcessID int `json:"process_id"` + ProcessName string `json:"process_name"` + UserID int `json:"user_id"` + Data map[string]interface{} `json:"data"` +} + +type EBPFTrace struct { + TraceID string `json:"trace_id"` + StartTime time.Time `json:"start_time"` + EndTime time.Time `json:"end_time"` + Capability string `json:"capability"` + Events []EBPFEvent `json:"events"` + Summary string `json:"summary"` + EventCount int `json:"event_count"` + ProcessList []string `json:"process_list"` +} + +type EBPFRequest struct { + Name string `json:"name"` + Type string `json:"type"` // "tracepoint", "kprobe", "kretprobe" + Target string `json:"target"` // tracepoint path or function name + Duration int `json:"duration"` // seconds + Filters map[string]string `json:"filters,omitempty"` + Description string `json:"description"` +} + +type NetworkEvent struct { + Timestamp uint64 `json:"timestamp"` + PID uint32 `json:"pid"` + TID uint32 `json:"tid"` + UID uint32 `json:"uid"` + EventType string `json:"event_type"` + Comm [16]byte `json:"-"` + CommStr string `json:"comm"` +} + +// Agent types +type DiagnosticResponse struct { + ResponseType string `json:"response_type"` + Reasoning string `json:"reasoning"` + Commands []Command `json:"commands"` +} + +type ResolutionResponse struct { + ResponseType string `json:"response_type"` + RootCause string `json:"root_cause"` + ResolutionPlan string `json:"resolution_plan"` + Confidence string `json:"confidence"` +} + +type Command struct { + ID string `json:"id"` + Command string `json:"command"` + Description string `json:"description"` +} + +type CommandResult struct { + ID string `json:"id"` + Command string `json:"command"` + Description string `json:"description"` + Output string `json:"output"` + ExitCode int `json:"exit_code"` + Error string `json:"error,omitempty"` +} + +type EBPFEnhancedDiagnosticResponse struct { + ResponseType string `json:"response_type"` + Reasoning string `json:"reasoning"` + Commands []Command `json:"commands"` + EBPFPrograms []EBPFRequest `json:"ebpf_programs"` + NextActions []string `json:"next_actions,omitempty"` +} + +type TensorZeroRequest struct { + Model string `json:"model"` + Messages []map[string]interface{} `json:"messages"` + EpisodeID string `json:"tensorzero::episode_id,omitempty"` +} + +type TensorZeroResponse struct { + Choices []map[string]interface{} `json:"choices"` + EpisodeID string `json:"episode_id"` +} + +// WebSocket types +type WebSocketMessage struct { + Type string `json:"type"` + Data interface{} `json:"data"` +} + +type InvestigationTask struct { + TaskID string `json:"task_id"` + InvestigationID string `json:"investigation_id"` + AgentID string `json:"agent_id"` + DiagnosticPayload map[string]interface{} `json:"diagnostic_payload"` + EpisodeID string `json:"episode_id,omitempty"` +} + +type TaskResult struct { + TaskID string `json:"task_id"` + Success bool `json:"success"` + CommandResults map[string]interface{} `json:"command_results,omitempty"` + Error string `json:"error,omitempty"` +} + +type HeartbeatData struct { + AgentID string `json:"agent_id"` + Timestamp time.Time `json:"timestamp"` + Version string `json:"version"` +} + +// Investigation server types +type InvestigationRequest struct { + Issue string `json:"issue"` + AgentID string `json:"agent_id"` + EpisodeID string `json:"episode_id,omitempty"` + Timestamp string `json:"timestamp,omitempty"` + Priority string `json:"priority,omitempty"` + Description string `json:"description,omitempty"` +} + +type InvestigationResponse struct { + Status string `json:"status"` + Message string `json:"message"` + Results map[string]interface{} `json:"results,omitempty"` + AgentID string `json:"agent_id"` + Timestamp string `json:"timestamp"` + EpisodeID string `json:"episode_id,omitempty"` + Investigation *PendingInvestigation `json:"investigation,omitempty"` +} + +type PendingInvestigation struct { + ID string `json:"id"` + Issue string `json:"issue"` + AgentID string `json:"agent_id"` + Status string `json:"status"` + DiagnosticPayload map[string]interface{} `json:"diagnostic_payload"` + CommandResults map[string]interface{} `json:"command_results,omitempty"` + EpisodeID *string `json:"episode_id,omitempty"` + CreatedAt string `json:"created_at"` + StartedAt *string `json:"started_at,omitempty"` + CompletedAt *string `json:"completed_at,omitempty"` + ErrorMessage *string `json:"error_message,omitempty"` +} + +// System types +type SystemInfo struct { + Hostname string `json:"hostname"` + Platform string `json:"platform"` + PlatformInfo map[string]string `json:"platform_info"` + KernelVersion string `json:"kernel_version"` + Uptime string `json:"uptime"` + LoadAverage []float64 `json:"load_average"` + CPUInfo map[string]string `json:"cpu_info"` + MemoryInfo map[string]string `json:"memory_info"` + DiskInfo []map[string]string `json:"disk_info"` +} + +// Executor types +type CommandExecutor struct { + timeout time.Duration +} diff --git a/investigation_server.go b/investigation_server.go index dee5701..28aa617 100644 --- a/investigation_server.go +++ b/investigation_server.go @@ -60,7 +60,7 @@ func NewInvestigationServer(agent *LinuxDiagnosticAgent, authManager *auth.AuthM if authManager != nil { if id, err := authManager.GetCurrentAgentID(); err == nil { agentID = id - fmt.Printf("✅ Retrieved agent ID from auth manager: %s\n", agentID) + } else { fmt.Printf("❌ Failed to get agent ID from auth manager: %v\n", err) } @@ -250,7 +250,7 @@ func (s *InvestigationServer) sendCommandResultsToTensorZero(diagnosticResp Diag // Check if it's a resolution response if err := json.Unmarshal([]byte(content), &resolutionResp); err == nil && resolutionResp.ResponseType == "resolution" { - fmt.Printf("✅ TensorZero provided final resolution\n") + return map[string]interface{}{ "type": "resolution", "response": resolutionResp, @@ -355,7 +355,7 @@ func (s *InvestigationServer) handleDiagnosticExecution(requestBody map[string]i result := s.agent.executor.Execute(cmd) commandResults = append(commandResults, result) - fmt.Printf("✅ Command '%s' completed with exit code %d\n", cmd.ID, result.ExitCode) + if result.Error != "" { fmt.Printf("âš ī¸ Command '%s' had error: %s\n", cmd.ID, result.Error) } @@ -471,7 +471,7 @@ func (s *InvestigationServer) handlePendingInvestigation(investigation PendingIn return } - fmt.Printf("✅ Realtime investigation %s completed successfully\n", investigation.InvestigationID) + } // updateInvestigationStatus updates the status of a pending investigation diff --git a/main.go b/main.go index 8186c2f..1517009 100644 --- a/main.go +++ b/main.go @@ -73,7 +73,7 @@ func checkKernelVersionCompatibility() { os.Exit(1) } - fmt.Printf("✅ Kernel version %s is compatible with eBPF\n", kernelVersion) + } // checkEBPFSupport validates eBPF subsystem availability @@ -97,7 +97,7 @@ func checkEBPFSupport() { syscall.Close(int(fd)) } - fmt.Printf("✅ eBPF syscall is available\n") + } // runInteractiveDiagnostics starts the interactive diagnostic session diff --git a/websocket_client.go b/websocket_client.go index e3fd9fa..34b1ae0 100644 --- a/websocket_client.go +++ b/websocket_client.go @@ -130,7 +130,6 @@ func (w *WebSocketClient) Start() error { // Stop closes the WebSocket connection func (c *WebSocketClient) Stop() { - fmt.Println("🛑 Stopping WebSocket client...") c.cancel() if c.conn != nil { c.conn.Close() @@ -313,8 +312,6 @@ func (c *WebSocketClient) handleInvestigationTask(data interface{}) { // executeDiagnosticCommands executes the commands from a diagnostic response func (c *WebSocketClient) executeDiagnosticCommands(diagnosticPayload map[string]interface{}) (map[string]interface{}, error) { - fmt.Println("🔧 Executing diagnostic commands...") - results := map[string]interface{}{ "agent_id": c.agentID, "execution_time": time.Now().UTC().Format(time.RFC3339), @@ -360,8 +357,6 @@ func (c *WebSocketClient) executeDiagnosticCommands(diagnosticPayload map[string if err != nil { result["error"] = err.Error() fmt.Printf("❌ Command [%s] failed: %v (exit code: %d)\n", id, err, exitCode) - } else { - // Command completed successfully - output captured } commandResults = append(commandResults, result) @@ -374,17 +369,11 @@ func (c *WebSocketClient) executeDiagnosticCommands(diagnosticPayload map[string // Execute eBPF programs if present ebpfPrograms, hasEBPF := diagnosticPayload["ebpf_programs"].([]interface{}) if hasEBPF && len(ebpfPrograms) > 0 { - fmt.Printf("đŸ”Ŧ Executing %d eBPF programs...\n", len(ebpfPrograms)) ebpfResults := c.executeEBPFPrograms(ebpfPrograms) results["ebpf_results"] = ebpfResults results["total_ebpf_programs"] = len(ebpfPrograms) - } else { - fmt.Printf("â„šī¸ No eBPF programs in diagnostic payload\n") } - fmt.Printf("✅ Executed %d commands, %d successful\n", - results["total_commands"], results["successful_commands"]) - return results, nil } @@ -455,8 +444,6 @@ func (c *WebSocketClient) executeCommandsFromPayload(commands []interface{}) []m if err != nil { result["error"] = err.Error() fmt.Printf("❌ Command [%s] failed: %v (exit code: %d)\n", id, err, exitCode) - } else { - fmt.Printf("✅ Command [%s] completed successfully\n", id) } commandResults = append(commandResults, result) @@ -657,14 +644,10 @@ func (c *WebSocketClient) handlePendingInvestigation(investigation PendingInvest }, } - fmt.Printf("🔄 Sending command results to TensorZero for continued analysis...\n") - fmt.Printf("📤 Command results payload size: %d bytes\n", len(commandsJSON)) - // Use the episode ID from the investigation to maintain conversation continuity episodeID := "" if investigation.EpisodeID != nil { episodeID = *investigation.EpisodeID - fmt.Printf("🔗 Using episode ID: %s\n", episodeID) } // Continue conversation until resolution (same as agent) @@ -678,8 +661,6 @@ func (c *WebSocketClient) handlePendingInvestigation(investigation PendingInvest return } - fmt.Printf("✅ TensorZero responded successfully\n") - if len(tzResp.Choices) == 0 { fmt.Printf("âš ī¸ No choices in TensorZero response\n") c.updateInvestigationStatus(investigation.ID, "completed", resultsForDB, nil) @@ -688,7 +669,7 @@ func (c *WebSocketClient) handlePendingInvestigation(investigation PendingInvest aiContent := tzResp.Choices[0].Message.Content if len(aiContent) > 300 { - fmt.Printf("🤖 AI Response preview: %s...\n", aiContent[:300]) + // AI response received successfully } else { fmt.Printf("🤖 AI Response: %s\n", aiContent) } @@ -705,7 +686,6 @@ func (c *WebSocketClient) handlePendingInvestigation(investigation PendingInvest if err := json.Unmarshal([]byte(aiContent), &resolutionResp); err == nil && resolutionResp.ResponseType == "resolution" { // This is the final resolution - show summary and complete - fmt.Printf("✅ Detected RESOLUTION response - completing investigation\n") fmt.Printf("\n=== DIAGNOSIS COMPLETE ===\n") fmt.Printf("Root Cause: %s\n", resolutionResp.RootCause) fmt.Printf("Resolution Plan: %s\n", resolutionResp.ResolutionPlan) @@ -722,7 +702,6 @@ func (c *WebSocketClient) handlePendingInvestigation(investigation PendingInvest } if err := json.Unmarshal([]byte(aiContent), &diagnosticResp); err == nil && diagnosticResp.ResponseType == "diagnostic" { - fmt.Printf("✅ Detected DIAGNOSTIC response - continuing conversation\n") fmt.Printf("🔄 AI requested additional diagnostics, executing...\n") // Execute additional commands if any @@ -738,7 +717,6 @@ func (c *WebSocketClient) handlePendingInvestigation(investigation PendingInvest // Execute additional eBPF programs if any if len(diagnosticResp.EBPFPrograms) > 0 { - fmt.Printf("đŸ”Ŧ Executing %d additional eBPF programs...\n", len(diagnosticResp.EBPFPrograms)) ebpfResults := c.executeEBPFPrograms(diagnosticResp.EBPFPrograms) additionalResults["ebpf_results"] = ebpfResults } @@ -766,9 +744,7 @@ func (c *WebSocketClient) handlePendingInvestigation(investigation PendingInvest // Attach final AI response to results for DB and mark as completed_with_analysis resultsForDB["ai_response"] = finalAIContent - fmt.Printf("💾 Updating database with results and AI analysis...\n") c.updateInvestigationStatus(investigation.ID, "completed_with_analysis", resultsForDB, nil) - fmt.Printf("✅ Investigation completed with AI analysis\n") } // updateInvestigationStatus updates the status of a pending investigation