package leader import ( "context" "encoding/json" "fmt" "log" "os" "strings" "sync" "time" ) // LogLevel represents different logging levels type LogLevel int const ( LogLevelDebug LogLevel = iota LogLevelInfo LogLevelWarn LogLevelError LogLevelCritical ) // String returns string representation of log level func (ll LogLevel) String() string { switch ll { case LogLevelDebug: return "DEBUG" case LogLevelInfo: return "INFO" case LogLevelWarn: return "WARN" case LogLevelError: return "ERROR" case LogLevelCritical: return "CRITICAL" default: return "UNKNOWN" } } // ContextLogger provides structured logging for context operations type ContextLogger struct { mu sync.RWMutex level LogLevel outputs []LogOutput fields map[string]interface{} nodeID string component string } // LogOutput represents a logging output destination type LogOutput interface { Write(entry *LogEntry) error Close() error } // LogEntry represents a single log entry type LogEntry struct { Timestamp time.Time `json:"timestamp"` Level LogLevel `json:"level"` Message string `json:"message"` Component string `json:"component"` NodeID string `json:"node_id"` Fields map[string]interface{} `json:"fields"` Context map[string]string `json:"context,omitempty"` RequestID string `json:"request_id,omitempty"` JobID string `json:"job_id,omitempty"` ElectionTerm int64 `json:"election_term,omitempty"` StackTrace string `json:"stack_trace,omitempty"` } // NewContextLogger creates a new context logger func NewContextLogger(nodeID, component string, level LogLevel) *ContextLogger { logger := &ContextLogger{ level: level, fields: make(map[string]interface{}), nodeID: nodeID, component: component, outputs: make([]LogOutput, 0), } // Add default console output logger.AddOutput(NewConsoleOutput()) return logger } // SetLevel sets the logging level func (cl *ContextLogger) SetLevel(level LogLevel) { cl.mu.Lock() defer cl.mu.Unlock() cl.level = level } // AddOutput adds a log output destination func (cl *ContextLogger) AddOutput(output LogOutput) { cl.mu.Lock() defer cl.mu.Unlock() cl.outputs = append(cl.outputs, output) } // WithField adds a field to all subsequent log entries func (cl *ContextLogger) WithField(key string, value interface{}) *ContextLogger { cl.mu.Lock() defer cl.mu.Unlock() newLogger := &ContextLogger{ level: cl.level, fields: make(map[string]interface{}), nodeID: cl.nodeID, component: cl.component, outputs: cl.outputs, } // Copy existing fields for k, v := range cl.fields { newLogger.fields[k] = v } // Add new field newLogger.fields[key] = value return newLogger } // WithFields adds multiple fields to all subsequent log entries func (cl *ContextLogger) WithFields(fields map[string]interface{}) *ContextLogger { cl.mu.Lock() defer cl.mu.Unlock() newLogger := &ContextLogger{ level: cl.level, fields: make(map[string]interface{}), nodeID: cl.nodeID, component: cl.component, outputs: cl.outputs, } // Copy existing fields for k, v := range cl.fields { newLogger.fields[k] = v } // Add new fields for k, v := range fields { newLogger.fields[k] = v } return newLogger } // WithContext creates a logger with context information func (cl *ContextLogger) WithContext(ctx context.Context) *ContextLogger { // Extract context values if present fields := make(map[string]interface{}) if requestID := ctx.Value("request_id"); requestID != nil { fields["request_id"] = requestID } if jobID := ctx.Value("job_id"); jobID != nil { fields["job_id"] = jobID } if term := ctx.Value("election_term"); term != nil { fields["election_term"] = term } return cl.WithFields(fields) } // Debug logs a debug message func (cl *ContextLogger) Debug(message string, args ...interface{}) { cl.log(LogLevelDebug, message, args...) } // Info logs an info message func (cl *ContextLogger) Info(message string, args ...interface{}) { cl.log(LogLevelInfo, message, args...) } // Warn logs a warning message func (cl *ContextLogger) Warn(message string, args ...interface{}) { cl.log(LogLevelWarn, message, args...) } // Error logs an error message func (cl *ContextLogger) Error(message string, args ...interface{}) { cl.log(LogLevelError, message, args...) } // Critical logs a critical message func (cl *ContextLogger) Critical(message string, args ...interface{}) { cl.log(LogLevelCritical, message, args...) } // LogContextGeneration logs context generation events func (cl *ContextLogger) LogContextGeneration(event string, req *ContextGenerationRequest, job *ContextGenerationJob, err error) { fields := map[string]interface{}{ "event": event, } if req != nil { fields["request_id"] = req.ID fields["ucxl_address"] = req.UCXLAddress.String() fields["file_path"] = req.FilePath fields["role"] = req.Role fields["priority"] = req.Priority.String() fields["requested_by"] = req.RequestedBy } if job != nil { fields["job_id"] = job.ID fields["job_status"] = job.Status fields["started_at"] = job.StartedAt if job.CompletedAt != nil { fields["completed_at"] = *job.CompletedAt fields["duration"] = job.CompletedAt.Sub(job.StartedAt) } fields["progress"] = job.Progress fields["node_id"] = job.NodeID } logger := cl.WithFields(fields) if err != nil { logger.Error("Context generation event: %s - Error: %v", event, err) } else { logger.Info("Context generation event: %s", event) } } // LogLeadershipChange logs leadership change events func (cl *ContextLogger) LogLeadershipChange(event, oldLeader, newLeader string, term int64, metadata map[string]interface{}) { fields := map[string]interface{}{ "event": event, "old_leader": oldLeader, "new_leader": newLeader, "term": term, } // Add metadata for k, v := range metadata { fields[k] = v } logger := cl.WithFields(fields) logger.Info("Leadership change: %s", event) } // LogElectionEvent logs election-related events func (cl *ContextLogger) LogElectionEvent(event string, term int64, candidates []string, winner string, metadata map[string]interface{}) { fields := map[string]interface{}{ "event": event, "term": term, "candidates": candidates, "winner": winner, } // Add metadata for k, v := range metadata { fields[k] = v } logger := cl.WithFields(fields) logger.Info("Election event: %s", event) } // LogFailoverEvent logs failover events func (cl *ContextLogger) LogFailoverEvent(event, oldLeader, newLeader string, duration time.Duration, success bool, issues []string) { fields := map[string]interface{}{ "event": event, "old_leader": oldLeader, "new_leader": newLeader, "duration": duration, "success": success, "issues": issues, } logger := cl.WithFields(fields) if success { logger.Info("Failover event: %s", event) } else { logger.Error("Failover event: %s - Failed with issues: %v", event, issues) } } // LogHealthEvent logs health monitoring events func (cl *ContextLogger) LogHealthEvent(event string, nodeID string, healthScore float64, status HealthStatus, issues []string) { fields := map[string]interface{}{ "event": event, "node_id": nodeID, "health_score": healthScore, "status": status, "issues": issues, } logger := cl.WithFields(fields) switch status { case HealthStatusHealthy: logger.Debug("Health event: %s", event) case HealthStatusDegraded: logger.Warn("Health event: %s - Node degraded", event) case HealthStatusUnhealthy: logger.Error("Health event: %s - Node unhealthy: %v", event, issues) case HealthStatusCritical: logger.Critical("Health event: %s - Node critical: %v", event, issues) } } // LogMetrics logs metrics information func (cl *ContextLogger) LogMetrics(metrics *ContextMetrics) { fields := map[string]interface{}{ "uptime": metrics.Uptime, "total_requests": metrics.TotalRequests, "success_rate": metrics.SuccessRate, "throughput": metrics.Throughput, "average_latency": metrics.AverageLatency, "queue_length": metrics.MaxQueueLength, "leadership_changes": metrics.LeadershipChanges, } logger := cl.WithFields(fields) logger.Debug("Context generation metrics") } // log is the internal logging method func (cl *ContextLogger) log(level LogLevel, message string, args ...interface{}) { cl.mu.RLock() defer cl.mu.RUnlock() // Check if level is enabled if level < cl.level { return } // Format message formattedMessage := message if len(args) > 0 { formattedMessage = fmt.Sprintf(message, args...) } // Create log entry entry := &LogEntry{ Timestamp: time.Now(), Level: level, Message: formattedMessage, Component: cl.component, NodeID: cl.nodeID, Fields: make(map[string]interface{}), } // Copy fields for k, v := range cl.fields { entry.Fields[k] = v } // Write to all outputs for _, output := range cl.outputs { if err := output.Write(entry); err != nil { // Fallback to standard log if output fails log.Printf("Failed to write log entry: %v", err) } } } // Close closes all log outputs func (cl *ContextLogger) Close() error { cl.mu.Lock() defer cl.mu.Unlock() var errors []string for _, output := range cl.outputs { if err := output.Close(); err != nil { errors = append(errors, err.Error()) } } if len(errors) > 0 { return fmt.Errorf("errors closing log outputs: %s", strings.Join(errors, ", ")) } return nil } // ConsoleOutput writes logs to console type ConsoleOutput struct { colorize bool } // NewConsoleOutput creates a new console output func NewConsoleOutput() *ConsoleOutput { return &ConsoleOutput{ colorize: true, // TODO: Detect if terminal supports colors } } // Write writes a log entry to console func (co *ConsoleOutput) Write(entry *LogEntry) error { var levelPrefix string if co.colorize { switch entry.Level { case LogLevelDebug: levelPrefix = "\033[36mDEBUG\033[0m" // Cyan case LogLevelInfo: levelPrefix = "\033[32mINFO\033[0m" // Green case LogLevelWarn: levelPrefix = "\033[33mWARN\033[0m" // Yellow case LogLevelError: levelPrefix = "\033[31mERROR\033[0m" // Red case LogLevelCritical: levelPrefix = "\033[35mCRIT\033[0m" // Magenta } } else { levelPrefix = entry.Level.String() } timestamp := entry.Timestamp.Format("2006-01-02 15:04:05.000") // Format basic log line logLine := fmt.Sprintf("%s [%s] [%s:%s] %s", timestamp, levelPrefix, entry.Component, entry.NodeID, entry.Message, ) // Add fields if any if len(entry.Fields) > 0 { if fieldsJSON, err := json.Marshal(entry.Fields); err == nil { logLine += fmt.Sprintf(" | %s", string(fieldsJSON)) } } fmt.Println(logLine) return nil } // Close closes the console output (no-op) func (co *ConsoleOutput) Close() error { return nil } // FileOutput writes logs to a file type FileOutput struct { mu sync.Mutex file *os.File filename string } // NewFileOutput creates a new file output func NewFileOutput(filename string) (*FileOutput, error) { file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) if err != nil { return nil, err } return &FileOutput{ file: file, filename: filename, }, nil } // Write writes a log entry to file func (fo *FileOutput) Write(entry *LogEntry) error { fo.mu.Lock() defer fo.mu.Unlock() // Convert to JSON entryJSON, err := json.Marshal(entry) if err != nil { return err } // Write to file with newline _, err = fo.file.Write(append(entryJSON, '\n')) return err } // Close closes the file output func (fo *FileOutput) Close() error { fo.mu.Lock() defer fo.mu.Unlock() if fo.file != nil { err := fo.file.Close() fo.file = nil return err } return nil } // Priority extension for logging func (p Priority) String() string { switch p { case PriorityLow: return "low" case PriorityNormal: return "normal" case PriorityHigh: return "high" case PriorityCritical: return "critical" case PriorityUrgent: return "urgent" default: return "unknown" } }