From fb5e9b56945363df6c562e82af264c24c94aa2db Mon Sep 17 00:00:00 2001 From: AnnatarHe Date: Thu, 25 Dec 2025 15:24:35 +0800 Subject: [PATCH] feat(ccotel): add debug config for raw JSON output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add debug option to CCOtel config that writes raw OTEL metrics and logs to files in /tmp/shelltime/ for troubleshooting purposes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- daemon/ccotel_processor.go | 43 ++++++++++++++++++++++++++++++++++++++ model/types.go | 1 + 2 files changed, 44 insertions(+) diff --git a/daemon/ccotel_processor.go b/daemon/ccotel_processor.go index a4d962e..90e5328 100644 --- a/daemon/ccotel_processor.go +++ b/daemon/ccotel_processor.go @@ -3,9 +3,12 @@ package daemon import ( "context" "encoding/json" + "fmt" "log/slog" "os" + "path/filepath" "strconv" + "time" "github.com/google/uuid" "github.com/malamtime/cli/model" @@ -22,6 +25,7 @@ type CCOtelProcessor struct { config model.ShellTimeConfig endpoint model.Endpoint hostname string + debug bool } // NewCCOtelProcessor creates a new CCOtel processor @@ -31,6 +35,8 @@ func NewCCOtelProcessor(config model.ShellTimeConfig) *CCOtelProcessor { hostname = "unknown" } + debug := config.CCOtel != nil && config.CCOtel.Debug != nil && *config.CCOtel.Debug + return &CCOtelProcessor{ config: config, endpoint: model.Endpoint{ @@ -38,6 +44,35 @@ func NewCCOtelProcessor(config model.ShellTimeConfig) *CCOtelProcessor { APIEndpoint: config.APIEndpoint, }, hostname: hostname, + debug: debug, + } +} + +// writeDebugFile appends JSON-formatted data to a debug file +func (p *CCOtelProcessor) writeDebugFile(filename string, data interface{}) { + debugDir := filepath.Join(os.TempDir(), "shelltime") + if err := os.MkdirAll(debugDir, 0755); err != nil { + slog.Error("CCOtel: Failed to create debug directory", "error", err) + return + } + + filePath := filepath.Join(debugDir, filename) + f, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + slog.Error("CCOtel: Failed to open debug file", "error", err, "path", filePath) + return + } + defer f.Close() + + jsonData, err := json.MarshalIndent(data, "", " ") + if err != nil { + slog.Error("CCOtel: Failed to marshal debug data", "error", err) + return + } + + timestamp := time.Now().Format(time.RFC3339) + if _, err := f.WriteString(fmt.Sprintf("\n--- %s ---\n%s\n", timestamp, jsonData)); err != nil { + slog.Error("CCOtel: Failed to write debug data", "error", err) } } @@ -45,6 +80,10 @@ func NewCCOtelProcessor(config model.ShellTimeConfig) *CCOtelProcessor { func (p *CCOtelProcessor) ProcessMetrics(ctx context.Context, req *collmetricsv1.ExportMetricsServiceRequest) (*collmetricsv1.ExportMetricsServiceResponse, error) { slog.Debug("CCOtel: Processing metrics request", "resourceMetricsCount", len(req.GetResourceMetrics())) + if p.debug { + p.writeDebugFile("ccotel-debug-metrics.txt", req) + } + for _, rm := range req.GetResourceMetrics() { resource := rm.GetResource() @@ -94,6 +133,10 @@ func (p *CCOtelProcessor) ProcessMetrics(ctx context.Context, req *collmetricsv1 func (p *CCOtelProcessor) ProcessLogs(ctx context.Context, req *collogsv1.ExportLogsServiceRequest) (*collogsv1.ExportLogsServiceResponse, error) { slog.Debug("CCOtel: Processing logs request", "resourceLogsCount", len(req.GetResourceLogs())) + if p.debug { + p.writeDebugFile("ccotel-debug-logs.txt", req) + } + for _, rl := range req.GetResourceLogs() { resource := rl.GetResource() diff --git a/model/types.go b/model/types.go index daf2e1a..af2f2ec 100644 --- a/model/types.go +++ b/model/types.go @@ -29,6 +29,7 @@ type CCUsage struct { type CCOtel struct { Enabled *bool `toml:"enabled"` GRPCPort int `toml:"grpcPort"` // default: 4317 + Debug *bool `toml:"debug"` // write raw JSON to debug files } // CodeTracking configuration for coding activity heartbeat tracking