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:
@@ -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)
|
||||
|
||||
@@ -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
24
internal/debug/debug.go
Normal 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...)
|
||||
}
|
||||
}
|
||||
139
internal/debug/debug_test.go
Normal file
139
internal/debug/debug_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user