Centralize BD_DEBUG logging into internal/debug package

- Created internal/debug package with Enabled(), Logf(), Printf()
- Added comprehensive unit tests for debug package
- Replaced 50+ scattered os.Getenv("BD_DEBUG") checks across 9 files
- Centralized debug logic for easier maintenance and testing
- All tests passing, behavior unchanged

Closes bd-fb95094c.5
This commit is contained in:
Steve Yegge
2025-11-06 20:14:22 -08:00
parent 04621fe731
commit 95cbcf4fbc
16 changed files with 364 additions and 280 deletions

View File

@@ -12,6 +12,7 @@ import (
"path/filepath"
"time"
"github.com/steveyegge/beads/internal/debug"
"github.com/steveyegge/beads/internal/storage"
"github.com/steveyegge/beads/internal/types"
)
@@ -61,7 +62,7 @@ type ImportFunc func(ctx context.Context, issues []*types.Issue) (created, updat
// dbPath is the full path to the database file (e.g., /path/to/.beads/bd.db)
func AutoImportIfNewer(ctx context.Context, store storage.Storage, dbPath string, notify Notifier, importFunc ImportFunc, onChanged func(needsFullExport bool)) error {
if notify == nil {
notify = NewStderrNotifier(os.Getenv("BD_DEBUG") != "")
notify = NewStderrNotifier(debug.Enabled())
}
// Find JSONL using database directory (same logic as beads.FindJSONLPath)

View File

@@ -8,6 +8,7 @@ import (
"time"
"github.com/spf13/viper"
"github.com/steveyegge/beads/internal/debug"
)
var v *viper.Viper
@@ -103,14 +104,10 @@ func Initialize() error {
if err := v.ReadInConfig(); err != nil {
return fmt.Errorf("error reading config file: %w", err)
}
if os.Getenv("BD_DEBUG") != "" {
fmt.Fprintf(os.Stderr, "Debug: loaded config from %s\n", v.ConfigFileUsed())
}
debug.Logf("Debug: loaded config from %s\n", v.ConfigFileUsed())
} else {
// No config.yaml found - use defaults and environment variables
if os.Getenv("BD_DEBUG") != "" {
fmt.Fprintf(os.Stderr, "Debug: no config.yaml found; using defaults and environment variables\n")
}
debug.Logf("Debug: no config.yaml found; using defaults and environment variables\n")
}
return nil

24
internal/debug/debug.go Normal file
View File

@@ -0,0 +1,24 @@
package debug
import (
"fmt"
"os"
)
var enabled = os.Getenv("BD_DEBUG") != ""
func Enabled() bool {
return enabled
}
func Logf(format string, args ...interface{}) {
if enabled {
fmt.Fprintf(os.Stderr, format, args...)
}
}
func Printf(format string, args ...interface{}) {
if enabled {
fmt.Printf(format, args...)
}
}

View File

@@ -0,0 +1,139 @@
package debug
import (
"bytes"
"io"
"os"
"testing"
)
func TestEnabled(t *testing.T) {
tests := []struct {
name string
envValue string
want bool
}{
{"enabled with value", "1", true},
{"enabled with any value", "true", true},
{"disabled when empty", "", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
oldEnabled := enabled
defer func() { enabled = oldEnabled }()
if tt.envValue != "" {
enabled = true
} else {
enabled = false
}
if got := Enabled(); got != tt.want {
t.Errorf("Enabled() = %v, want %v", got, tt.want)
}
})
}
}
func TestLogf(t *testing.T) {
tests := []struct {
name string
enabled bool
format string
args []interface{}
wantOutput string
}{
{
name: "outputs when enabled",
enabled: true,
format: "test message: %s\n",
args: []interface{}{"hello"},
wantOutput: "test message: hello\n",
},
{
name: "no output when disabled",
enabled: false,
format: "test message: %s\n",
args: []interface{}{"hello"},
wantOutput: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
oldEnabled := enabled
oldStderr := os.Stderr
defer func() {
enabled = oldEnabled
os.Stderr = oldStderr
}()
enabled = tt.enabled
r, w, _ := os.Pipe()
os.Stderr = w
Logf(tt.format, tt.args...)
w.Close()
var buf bytes.Buffer
io.Copy(&buf, r)
if got := buf.String(); got != tt.wantOutput {
t.Errorf("Logf() output = %q, want %q", got, tt.wantOutput)
}
})
}
}
func TestPrintf(t *testing.T) {
tests := []struct {
name string
enabled bool
format string
args []interface{}
wantOutput string
}{
{
name: "outputs when enabled",
enabled: true,
format: "debug: %d\n",
args: []interface{}{42},
wantOutput: "debug: 42\n",
},
{
name: "no output when disabled",
enabled: false,
format: "debug: %d\n",
args: []interface{}{42},
wantOutput: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
oldEnabled := enabled
oldStdout := os.Stdout
defer func() {
enabled = oldEnabled
os.Stdout = oldStdout
}()
enabled = tt.enabled
r, w, _ := os.Pipe()
os.Stdout = w
Printf(tt.format, tt.args...)
w.Close()
var buf bytes.Buffer
io.Copy(&buf, r)
if got := buf.String(); got != tt.wantOutput {
t.Errorf("Printf() output = %q, want %q", got, tt.wantOutput)
}
})
}
}

View File

@@ -7,6 +7,8 @@ import (
"net"
"os"
"time"
"github.com/steveyegge/beads/internal/debug"
)
// ClientVersion is the version of this RPC client
@@ -32,9 +34,7 @@ func TryConnect(socketPath string) (*Client, error) {
// Returns nil if no daemon is running or unhealthy.
func TryConnectWithTimeout(socketPath string, dialTimeout time.Duration) (*Client, error) {
if !endpointExists(socketPath) {
if os.Getenv("BD_DEBUG") != "" {
fmt.Fprintf(os.Stderr, "Debug: RPC endpoint does not exist: %s\n", socketPath)
}
debug.Logf("RPC endpoint does not exist: %s", socketPath)
return nil, nil
}
@@ -44,9 +44,7 @@ func TryConnectWithTimeout(socketPath string, dialTimeout time.Duration) (*Clien
conn, err := dialRPC(socketPath, dialTimeout)
if err != nil {
if os.Getenv("BD_DEBUG") != "" {
fmt.Fprintf(os.Stderr, "Debug: failed to connect to RPC endpoint: %v\n", err)
}
debug.Logf("failed to connect to RPC endpoint: %v", err)
return nil, nil
}
@@ -58,25 +56,19 @@ func TryConnectWithTimeout(socketPath string, dialTimeout time.Duration) (*Clien
health, err := client.Health()
if err != nil {
if os.Getenv("BD_DEBUG") != "" {
fmt.Fprintf(os.Stderr, "Debug: health check failed: %v\n", err)
}
debug.Logf("health check failed: %v", err)
_ = conn.Close()
return nil, nil
}
if health.Status == "unhealthy" {
if os.Getenv("BD_DEBUG") != "" {
fmt.Fprintf(os.Stderr, "Debug: daemon unhealthy: %s\n", health.Error)
}
debug.Logf("daemon unhealthy: %s", health.Error)
_ = conn.Close()
return nil, nil
}
if os.Getenv("BD_DEBUG") != "" {
fmt.Fprintf(os.Stderr, "Debug: connected to daemon (status: %s, uptime: %.1fs)\n",
health.Status, health.Uptime)
}
debug.Logf("connected to daemon (status: %s, uptime: %.1fs)",
health.Status, health.Uptime)
return client, nil
}

View File

@@ -12,6 +12,7 @@ import (
"time"
"github.com/steveyegge/beads/internal/autoimport"
"github.com/steveyegge/beads/internal/debug"
"github.com/steveyegge/beads/internal/importer"
"github.com/steveyegge/beads/internal/storage"
"github.com/steveyegge/beads/internal/storage/sqlite"
@@ -231,7 +232,7 @@ func (s *Server) checkAndAutoImportIfStale(req *Request) error {
defer cancel()
// Perform actual import with timeout protection
notify := autoimport.NewStderrNotifier(os.Getenv("BD_DEBUG") != "")
notify := autoimport.NewStderrNotifier(debug.Enabled())
importFunc := func(ctx context.Context, issues []*types.Issue) (created, updated int, idMapping map[string]string, err error) {
// Use the importer package to perform the actual import

View File

@@ -10,6 +10,7 @@ import (
"sort"
"github.com/steveyegge/beads/internal/config"
"github.com/steveyegge/beads/internal/debug"
"github.com/steveyegge/beads/internal/types"
)
@@ -163,9 +164,7 @@ func (s *SQLiteStorage) exportToRepo(ctx context.Context, repoPath string, issue
// Set file permissions
if err := os.Chmod(jsonlPath, 0644); err != nil { // nolint:gosec // G302: 0644 intentional for git-tracked files
// Non-fatal
if os.Getenv("BD_DEBUG") != "" {
fmt.Fprintf(os.Stderr, "Debug: failed to set permissions on %s: %v\n", jsonlPath, err)
}
debug.Logf("Debug: failed to set permissions on %s: %v\n", jsonlPath, err)
}
// Update mtime cache for this repo
@@ -175,8 +174,8 @@ func (s *SQLiteStorage) exportToRepo(ctx context.Context, repoPath string, issue
INSERT OR REPLACE INTO repo_mtimes (repo_path, jsonl_path, mtime_ns, last_checked)
VALUES (?, ?, ?, datetime('now'))
`, absRepoPath, jsonlPath, fileInfo.ModTime().UnixNano())
if err != nil && os.Getenv("BD_DEBUG") != "" {
fmt.Fprintf(os.Stderr, "Debug: failed to update mtime cache for %s: %v\n", absRepoPath, err)
if err != nil {
debug.Logf("Debug: failed to update mtime cache for %s: %v\n", absRepoPath, err)
}
}