From f0bf74fbef3355e9853774232e2aaf2b0e25747f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 24 Dec 2025 06:33:12 +0000 Subject: [PATCH] Add debug logging to campaign and parser modules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enhanced 5 Go files with debug logging following project guidelines: - pkg/campaign/status.go: Added campaign:status logger for campaign workflow tracking, metrics, and cursor management - pkg/parser/github.go: Added parser:github logger for token retrieval - pkg/parser/import_directive.go: Added parser:import_directive logger for import parsing - pkg/parser/import_error.go: Added parser:import_error logger for error formatting - pkg/parser/yaml_error.go: Added parser:yaml_error logger for YAML error extraction All loggers follow the pkg:filename naming convention and include meaningful logging at function entry points and key decision points. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- pkg/campaign/status.go | 41 +++++++++++++++++++++------------- pkg/parser/github.go | 18 +++++++++------ pkg/parser/import_directive.go | 9 +++++++- pkg/parser/import_error.go | 5 +++++ pkg/parser/yaml_error.go | 7 ++++++ 5 files changed, 57 insertions(+), 23 deletions(-) diff --git a/pkg/campaign/status.go b/pkg/campaign/status.go index 155024f5b93..3eeda633afb 100644 --- a/pkg/campaign/status.go +++ b/pkg/campaign/status.go @@ -11,9 +11,12 @@ import ( "path/filepath" "strings" + "github.com/githubnext/gh-aw/pkg/logger" "github.com/githubnext/gh-aw/pkg/workflow" ) +var statusLog = logger.New("campaign:status") + // ComputeCompiledState inspects the compiled state of all // workflows referenced by a campaign. It returns: // @@ -22,6 +25,8 @@ import ( // "Missing workflow" - at least one referenced workflow markdown file does not exist // "N/A" - campaign does not reference any workflows func ComputeCompiledState(spec CampaignSpec, workflowsDir string) string { + statusLog.Printf("Computing compiled state for campaign '%s' with %d workflows", spec.ID, len(spec.Workflows)) + if len(spec.Workflows) == 0 { return "N/A" } @@ -35,7 +40,7 @@ func ComputeCompiledState(spec CampaignSpec, workflowsDir string) string { mdInfo, err := os.Stat(mdPath) if err != nil { - log.Printf("Workflow markdown not found for campaign '%s': %s", spec.ID, mdPath) + statusLog.Printf("Workflow markdown not found for campaign '%s': %s", spec.ID, mdPath) missingAny = true compiledAll = false continue @@ -43,13 +48,13 @@ func ComputeCompiledState(spec CampaignSpec, workflowsDir string) string { lockInfo, err := os.Stat(lockPath) if err != nil { - log.Printf("Lock file not found for workflow '%s' in campaign '%s': %s", wf, spec.ID, lockPath) + statusLog.Printf("Lock file not found for workflow '%s' in campaign '%s': %s", wf, spec.ID, lockPath) compiledAll = false continue } if mdInfo.ModTime().After(lockInfo.ModTime()) { - log.Printf("Lock file out of date for workflow '%s' in campaign '%s'", wf, spec.ID) + statusLog.Printf("Lock file out of date for workflow '%s' in campaign '%s'", wf, spec.ID) compiledAll = false } } @@ -75,6 +80,8 @@ type ghIssueOrPRState struct { // If trackerLabel is empty or any errors occur, it falls back to zeros and // logs at debug level instead of failing the command. func FetchItemCounts(trackerLabel string) (issuesOpen, issuesClosed, prsOpen, prsMerged int) { + statusLog.Printf("Fetching item counts for tracker label: %s", trackerLabel) + if strings.TrimSpace(trackerLabel) == "" { return 0, 0, 0, 0 } @@ -94,10 +101,10 @@ func FetchItemCounts(trackerLabel string) (issuesOpen, issuesClosed, prsOpen, pr } } } else if err != nil { - log.Printf("Failed to decode issue list for tracker label '%s': %v", trackerLabel, err) + statusLog.Printf("Failed to decode issue list for tracker label '%s': %v", trackerLabel, err) } } else if err != nil { - log.Printf("Failed to fetch issues for tracker label '%s': %v", trackerLabel, err) + statusLog.Printf("Failed to fetch issues for tracker label '%s': %v", trackerLabel, err) } // Pull requests @@ -116,10 +123,10 @@ func FetchItemCounts(trackerLabel string) (issuesOpen, issuesClosed, prsOpen, pr } } } else if err != nil { - log.Printf("Failed to decode PR list for tracker label '%s': %v", trackerLabel, err) + statusLog.Printf("Failed to decode PR list for tracker label '%s': %v", trackerLabel, err) } } else if err != nil { - log.Printf("Failed to fetch PRs for tracker label '%s': %v", trackerLabel, err) + statusLog.Printf("Failed to fetch PRs for tracker label '%s': %v", trackerLabel, err) } return issuesOpen, issuesClosed, prsOpen, prsMerged @@ -130,6 +137,8 @@ func FetchItemCounts(trackerLabel string) (issuesOpen, issuesClosed, prsOpen, pr // memory/campaigns branch. It is best-effort: errors are logged and // treated as "no metrics" rather than failing the command. func FetchMetricsFromRepoMemory(metricsGlob string) (*CampaignMetricsSnapshot, error) { + statusLog.Printf("Fetching metrics from repo memory with glob: %s", metricsGlob) + if strings.TrimSpace(metricsGlob) == "" { return nil, nil } @@ -138,7 +147,7 @@ func FetchMetricsFromRepoMemory(metricsGlob string) (*CampaignMetricsSnapshot, e cmd := exec.Command("git", "ls-tree", "-r", "--name-only", "memory/campaigns") output, err := cmd.Output() if err != nil { - log.Printf("Unable to list repo-memory branch for metrics (memory/campaigns): %v", err) + statusLog.Printf("Unable to list repo-memory branch for metrics (memory/campaigns): %v", err) return nil, nil } @@ -151,7 +160,7 @@ func FetchMetricsFromRepoMemory(metricsGlob string) (*CampaignMetricsSnapshot, e } matched, err := path.Match(metricsGlob, pathStr) if err != nil { - log.Printf("Invalid metrics_glob '%s': %v", metricsGlob, err) + statusLog.Printf("Invalid metrics_glob '%s': %v", metricsGlob, err) return nil, nil } if matched { @@ -175,13 +184,13 @@ func FetchMetricsFromRepoMemory(metricsGlob string) (*CampaignMetricsSnapshot, e showCmd := exec.Command("git", "show", showArg) fileData, err := showCmd.Output() if err != nil { - log.Printf("Failed to read metrics file '%s' from memory/campaigns: %v", latest, err) + statusLog.Printf("Failed to read metrics file '%s' from memory/campaigns: %v", latest, err) return nil, nil } var snapshot CampaignMetricsSnapshot if err := json.Unmarshal(fileData, &snapshot); err != nil { - log.Printf("Failed to decode metrics JSON from '%s': %v", latest, err) + statusLog.Printf("Failed to decode metrics JSON from '%s': %v", latest, err) return nil, nil } @@ -194,6 +203,8 @@ func FetchMetricsFromRepoMemory(metricsGlob string) (*CampaignMetricsSnapshot, e // // Errors are treated as "no cursor" rather than failing the command. func FetchCursorFreshnessFromRepoMemory(cursorGlob string) (cursorPath string, cursorUpdatedAt string) { + statusLog.Printf("Fetching cursor freshness from repo memory with glob: %s", cursorGlob) + if strings.TrimSpace(cursorGlob) == "" { return "", "" } @@ -201,7 +212,7 @@ func FetchCursorFreshnessFromRepoMemory(cursorGlob string) (cursorPath string, c cmd := exec.Command("git", "ls-tree", "-r", "--name-only", "memory/campaigns") output, err := cmd.Output() if err != nil { - log.Printf("Unable to list repo-memory branch for cursor (memory/campaigns): %v", err) + statusLog.Printf("Unable to list repo-memory branch for cursor (memory/campaigns): %v", err) return "", "" } @@ -214,7 +225,7 @@ func FetchCursorFreshnessFromRepoMemory(cursorGlob string) (cursorPath string, c } matched, err := path.Match(cursorGlob, pathStr) if err != nil { - log.Printf("Invalid cursor_glob '%s': %v", cursorGlob, err) + statusLog.Printf("Invalid cursor_glob '%s': %v", cursorGlob, err) return "", "" } if matched { @@ -238,7 +249,7 @@ func FetchCursorFreshnessFromRepoMemory(cursorGlob string) (cursorPath string, c logCmd := exec.Command("git", "log", "-1", "--format=%cI", "memory/campaigns", "--", latest) logOut, err := logCmd.Output() if err != nil { - log.Printf("Failed to read cursor freshness for '%s' from memory/campaigns: %v", latest, err) + statusLog.Printf("Failed to read cursor freshness for '%s' from memory/campaigns: %v", latest, err) return latest, "" } @@ -257,7 +268,7 @@ func BuildRuntimeStatus(spec CampaignSpec, workflowsDir string) CampaignRuntimeS var metricsETA string if strings.TrimSpace(spec.MetricsGlob) != "" { if snapshot, err := FetchMetricsFromRepoMemory(spec.MetricsGlob); err != nil { - log.Printf("Failed to fetch metrics for campaign '%s': %v", spec.ID, err) + statusLog.Printf("Failed to fetch metrics for campaign '%s': %v", spec.ID, err) } else if snapshot != nil { metricsTasksTotal = snapshot.TasksTotal metricsTasksCompleted = snapshot.TasksCompleted diff --git a/pkg/parser/github.go b/pkg/parser/github.go index 859af0a21e8..e611d4c0ad2 100644 --- a/pkg/parser/github.go +++ b/pkg/parser/github.go @@ -5,38 +5,42 @@ import ( "os" "os/exec" "strings" + + "github.com/githubnext/gh-aw/pkg/logger" ) +var githubLog = logger.New("parser:github") + // GetGitHubToken attempts to get GitHub token from environment or gh CLI func GetGitHubToken() (string, error) { - log.Print("Getting GitHub token") + githubLog.Print("Getting GitHub token") // First try environment variable if token := os.Getenv("GITHUB_TOKEN"); token != "" { - log.Print("Found GITHUB_TOKEN environment variable") + githubLog.Print("Found GITHUB_TOKEN environment variable") return token, nil } if token := os.Getenv("GH_TOKEN"); token != "" { - log.Print("Found GH_TOKEN environment variable") + githubLog.Print("Found GH_TOKEN environment variable") return token, nil } // Fall back to gh auth token command - log.Print("Attempting to get token from gh auth token command") + githubLog.Print("Attempting to get token from gh auth token command") cmd := exec.Command("gh", "auth", "token") // Note: gh auth token should respect GH_HOST environment variable for enterprise output, err := cmd.Output() if err != nil { - log.Printf("Failed to get token from gh auth token: %v", err) + githubLog.Printf("Failed to get token from gh auth token: %v", err) return "", fmt.Errorf("GITHUB_TOKEN environment variable not set and 'gh auth token' failed: %w", err) } token := strings.TrimSpace(string(output)) if token == "" { - log.Print("gh auth token returned empty token") + githubLog.Print("gh auth token returned empty token") return "", fmt.Errorf("GITHUB_TOKEN environment variable not set and 'gh auth token' returned empty token") } - log.Print("Successfully retrieved token from gh auth token") + githubLog.Print("Successfully retrieved token from gh auth token") return token, nil } diff --git a/pkg/parser/import_directive.go b/pkg/parser/import_directive.go index 24568babbc5..00b017da719 100644 --- a/pkg/parser/import_directive.go +++ b/pkg/parser/import_directive.go @@ -3,8 +3,12 @@ package parser import ( "regexp" "strings" + + "github.com/githubnext/gh-aw/pkg/logger" ) +var importDirectiveLog = logger.New("parser:import_directive") + // IncludeDirectivePattern matches @include, @import (deprecated), or {{#import (new) directives // The colon after #import is optional and ignored if present var IncludeDirectivePattern = regexp.MustCompile(`^(?:@(?:include|import)(\?)?\s+(.+)|{{#import(\?)?\s*:?\s*(.+?)\s*}})$`) @@ -32,6 +36,7 @@ func ParseImportDirective(line string) *ImportDirectiveMatch { // Check if it's legacy syntax isLegacy := LegacyIncludeDirectivePattern.MatchString(trimmedLine) + importDirectiveLog.Printf("Parsing import directive: legacy=%t, line=%s", isLegacy, trimmedLine) var isOptional bool var path string @@ -48,10 +53,12 @@ func ParseImportDirective(line string) *ImportDirectiveMatch { path = strings.TrimSpace(matches[4]) } - return &ImportDirectiveMatch{ + match := &ImportDirectiveMatch{ IsOptional: isOptional, Path: path, IsLegacy: isLegacy, Original: trimmedLine, } + importDirectiveLog.Printf("Parsed import directive: path=%s, optional=%t, legacy=%t", path, isOptional, isLegacy) + return match } diff --git a/pkg/parser/import_error.go b/pkg/parser/import_error.go index ddf132b71d7..27c957af37d 100644 --- a/pkg/parser/import_error.go +++ b/pkg/parser/import_error.go @@ -5,8 +5,11 @@ import ( "strings" "github.com/githubnext/gh-aw/pkg/console" + "github.com/githubnext/gh-aw/pkg/logger" ) +var importErrorLog = logger.New("parser:import_error") + // ImportError represents an error that occurred during import resolution type ImportError struct { ImportPath string // The import path that failed (e.g., "nonexistent.md") @@ -28,6 +31,8 @@ func (e *ImportError) Unwrap() error { // FormatImportError formats an import error as a compilation error with source location func FormatImportError(err *ImportError, yamlContent string) error { + importErrorLog.Printf("Formatting import error: path=%s, file=%s, line=%d", err.ImportPath, err.FilePath, err.Line) + lines := strings.Split(yamlContent, "\n") // Create context lines around the error diff --git a/pkg/parser/yaml_error.go b/pkg/parser/yaml_error.go index 6b318df3ee7..1cc841e30b2 100644 --- a/pkg/parser/yaml_error.go +++ b/pkg/parser/yaml_error.go @@ -3,21 +3,28 @@ package parser import ( "fmt" "strings" + + "github.com/githubnext/gh-aw/pkg/logger" ) +var yamlErrorLog = logger.New("parser:yaml_error") + // ExtractYAMLError extracts line and column information from YAML parsing errors // frontmatterLineOffset is the line number where the frontmatter content begins in the document (1-based) // This allows proper line number reporting when frontmatter is not at the beginning of the document func ExtractYAMLError(err error, frontmatterLineOffset int) (line int, column int, message string) { + yamlErrorLog.Printf("Extracting YAML error information: offset=%d", frontmatterLineOffset) errStr := err.Error() // First try to extract from goccy/go-yaml's [line:column] format line, column, message = extractFromGoccyFormat(errStr, frontmatterLineOffset) if line > 0 || column > 0 { + yamlErrorLog.Printf("Extracted error location from goccy format: line=%d, column=%d", line, column) return line, column, message } // Fallback to standard YAML error string parsing for other libraries + yamlErrorLog.Print("Falling back to string parsing for error location") return extractFromStringParsing(errStr, frontmatterLineOffset) }