diff --git a/cmd/pierre/main.go b/cmd/pierre/main.go index 332be03..45cdf33 100644 --- a/cmd/pierre/main.go +++ b/cmd/pierre/main.go @@ -3,8 +3,10 @@ package main import ( "context" "log" + "log/slog" "os" "path/filepath" + "strings" "git.schreifuchs.ch/schreifuchs/pierre-bot/internal/chatter" "git.schreifuchs.ch/schreifuchs/pierre-bot/internal/gitadapters/bitbucket" @@ -46,6 +48,7 @@ type ReviewConfig struct { } type Config struct { + LogLevel string `help:"Log verbosity: debug, info, warn, error"` // Deprecated flags (no prefix). Retained for backward compatibility. // These will be mapped to the embedded ReviewConfig if provided. MaxChunkSize int `help:"Deprecated: use --review-max-chunk-size"` @@ -65,6 +68,7 @@ type Config struct { } func main() { + cfg := &Config{} home, err := os.UserHomeDir() if err != nil { @@ -81,6 +85,23 @@ func main() { kong.Configuration(kongyaml.Loader, "config.yaml", defaultConfig), ) + // Configure global slog logger based on the --log-level flag + lvl := slog.LevelInfo + switch strings.ToLower(cfg.LogLevel) { + case "debug": + lvl = slog.LevelDebug + case "info": + lvl = slog.LevelInfo + case "warn": + lvl = slog.LevelWarn + case "error": + lvl = slog.LevelError + } + // Logs are sent to stderr so that stdout can be safely piped. + // The review output (fmt.Printf) stays on stdout unchanged. + logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: lvl})) + slog.SetDefault(logger) + // Backwards compatibility: map deprecated flag values (if any) to the embedded ReviewConfig. if cfg.MaxChunkSize != 0 { cfg.Review.MaxChunkSize = cfg.MaxChunkSize diff --git a/internal/gitadapters/bitbucket/controller.go b/internal/gitadapters/bitbucket/controller.go index 64c3fee..6cad802 100644 --- a/internal/gitadapters/bitbucket/controller.go +++ b/internal/gitadapters/bitbucket/controller.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "io" + "log/slog" "net/http" "strconv" "strings" @@ -13,6 +14,7 @@ import ( ) func (b *BitbucketAdapter) GetDiff(ctx context.Context, projectKey, repositorySlug string, pullRequestID int) (diff io.ReadCloser, err error) { + slog.Debug("Bitbucket GetDiff start", "project", projectKey, "repo", repositorySlug, "pr", pullRequestID) r, err := b.CreateRequest( ctx, http.MethodGet, @@ -39,6 +41,7 @@ func (b *BitbucketAdapter) GetDiff(ctx context.Context, projectKey, repositorySl } func (b *BitbucketAdapter) GetPR(ctx context.Context, projectKey, repositorySlug string, pullRequestID int) (pr PullRequest, err error) { + slog.Debug("Bitbucket GetPR start", "project", projectKey, "repo", repositorySlug, "pr", pullRequestID) r, err := b.CreateRequest( ctx, http.MethodGet, @@ -53,11 +56,17 @@ func (b *BitbucketAdapter) GetPR(ctx context.Context, projectKey, repositorySlug defer response.Body.Close() // Add this err = json.NewDecoder(response.Body).Decode(&pr) + if err != nil { + slog.Error("Bitbucket GetPR decode error", "err", err) + return + } + slog.Info("Bitbucket GetPR success", "id", pullRequestID) return } func (b *BitbucketAdapter) AddComment(ctx context.Context, owner, repo string, prID int, comment pierre.Comment) (err error) { + slog.Debug("Bitbucket AddComment start", "owner", owner, "repo", repo, "pr", prID, "file", comment.File, "line", comment.Line) // pr, err := b.GetPR(ctx, owner, repo, prID) // if err != nil { // return @@ -95,6 +104,9 @@ func (b *BitbucketAdapter) AddComment(ctx context.Context, owner, repo string, p sb := &strings.Builder{} io.Copy(sb, response.Body) err = fmt.Errorf("error while creating comment staus %d, body %s", response.StatusCode, sb.String()) + slog.Error("Bitbucket AddComment failed", "status", response.StatusCode, "err", err) + } else { + slog.Info("Bitbucket AddComment succeeded", "pr", prID) } return err diff --git a/internal/gitadapters/gitea/adapter.go b/internal/gitadapters/gitea/adapter.go index 5674de1..0c943f9 100644 --- a/internal/gitadapters/gitea/adapter.go +++ b/internal/gitadapters/gitea/adapter.go @@ -5,6 +5,7 @@ import ( "context" "fmt" "io" + "log/slog" "code.gitea.io/sdk/gitea" "git.schreifuchs.ch/schreifuchs/pierre-bot/internal/pierre" @@ -25,15 +26,19 @@ func New(baseURL, token string) (*Adapter, error) { } func (g *Adapter) GetDiff(ctx context.Context, owner, repo string, prID int) (io.ReadCloser, error) { + slog.Debug("Gitea GetDiff start", "owner", owner, "repo", repo, "pr", prID) g.client.SetContext(ctx) diff, _, err := g.client.GetPullRequestDiff(owner, repo, int64(prID), gitea.PullRequestDiffOptions{}) if err != nil { return nil, err } - return io.NopCloser(bytes.NewReader(diff)), nil + rc := io.NopCloser(bytes.NewReader(diff)) + slog.Info("Gitea GetDiff success", "owner", owner, "repo", repo, "pr", prID, "bytes", len(diff)) + return rc, nil } func (g *Adapter) AddComment(ctx context.Context, owner, repo string, prID int, comment pierre.Comment) error { + slog.Debug("Gitea AddComment start", "owner", owner, "repo", repo, "pr", prID, "file", comment.File, "line", comment.Line) g.client.SetContext(ctx) opts := gitea.CreatePullReviewOptions{ State: gitea.ReviewStateComment, @@ -46,22 +51,30 @@ func (g *Adapter) AddComment(ctx context.Context, owner, repo string, prID int, }, } _, _, err := g.client.CreatePullReview(owner, repo, int64(prID), opts) - return err + if err != nil { + slog.Error("Gitea AddComment failed", "err", err) + return err + } + slog.Info("Gitea AddComment succeeded", "pr", prID) + return nil } // GetFileContent returns the file content at a given path and ref (commit SHA). func (g *Adapter) GetFileContent(ctx context.Context, owner, repo, path, ref string) (string, error) { + slog.Debug("Gitea GetFileContent start", "owner", owner, "repo", repo, "path", path, "ref", ref) g.client.SetContext(ctx) // The SDK's GetFile returns the raw bytes of the file. data, _, err := g.client.GetFile(owner, repo, ref, path) if err != nil { return "", err } + slog.Info("Gitea GetFileContent success", "bytes", len(data)) return string(data), nil } // GetPRHeadSHA fetches the pull request and returns the head commit SHA. func (g *Adapter) GetPRHeadSHA(ctx context.Context, owner, repo string, prID int) (string, error) { + slog.Debug("Gitea GetPRHeadSHA start", "owner", owner, "repo", repo, "pr", prID) g.client.SetContext(ctx) // GetPullRequest returns the detailed PR information. pr, _, err := g.client.GetPullRequest(owner, repo, int64(prID)) @@ -71,5 +84,6 @@ func (g *Adapter) GetPRHeadSHA(ctx context.Context, owner, repo string, prID int if pr == nil || pr.Head == nil { return "", fmt.Errorf("pull request %d has no head information", prID) } + slog.Info("Gitea GetPRHeadSHA success", "sha", pr.Head.Sha) return pr.Head.Sha, nil } diff --git a/internal/pierre/judge.go b/internal/pierre/judge.go index 7ae4f77..c768100 100644 --- a/internal/pierre/judge.go +++ b/internal/pierre/judge.go @@ -1,6 +1,8 @@ package pierre import ( + "log/slog" + "context" "fmt" "io" @@ -19,6 +21,7 @@ type Comment struct { } func (s *Service) judgePR(ctx context.Context, diff io.Reader) (comments []Comment, err error) { + slog.Info("judgePR started") diffBytes, err := io.ReadAll(diff) if err != nil { return nil, fmt.Errorf("failed to read diff: %w", err) @@ -81,6 +84,7 @@ If project guidelines are provided, treat them as hard rules that must be respec for _, v := range unique { comments = append(comments, v) } + slog.Info("judgePR finished", "comments", len(comments)) return } diff --git a/internal/pierre/review.go b/internal/pierre/review.go index e243e31..6711464 100644 --- a/internal/pierre/review.go +++ b/internal/pierre/review.go @@ -69,13 +69,11 @@ func (s *Service) MakeReview(ctx context.Context, organisation string, repo stri for _, c := range comments { c.Message = fmt.Sprintf("%s (Generated by: %s)", c.Message, model) + // Normal mode: print to stdout and post the comment to the VCS. + fmt.Printf("File: %s\nLine: %d\nMessage: %s\n%s\n", + c.File, c.Line, c.Message, "---") + if s.disableComments { - // Dry‑run: just log what would have been posted. - log.Printf("dry‑run: %s:%d => %s", c.File, c.Line, c.Message) - } else { - // Normal mode: print to stdout and post the comment to the VCS. - fmt.Printf("File: %s\nLine: %d\nMessage: %s\n%s\n", - c.File, c.Line, c.Message, "---") if err := s.git.AddComment(ctx, organisation, repo, prID, c); err != nil { log.Printf("Failed to add comment: %v", err) }