refactor(process): replace fmt.Errorf and errors.New with coreerr.E()

Replace all 27 instances of fmt.Errorf/errors.New in production code
with coreerr.E() from forge.lthn.ai/core/go-log for structured error
context (op, message, cause). Promote go-log from indirect to direct
dependency in go.mod.

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Snider 2026-03-16 20:34:23 +00:00
parent 10adcbe289
commit d73dfa3d73
8 changed files with 40 additions and 30 deletions

View file

@ -3,10 +3,11 @@ package process
import (
"context"
"errors"
"fmt"
"os"
"sync"
"time"
coreerr "forge.lthn.ai/core/go-log"
)
// DaemonOptions configures daemon mode execution.
@ -72,7 +73,7 @@ func (d *Daemon) Start() error {
defer d.mu.Unlock()
if d.running {
return errors.New("daemon already running")
return coreerr.E("Daemon.Start", "daemon already running", nil)
}
if d.pid != nil {
@ -100,7 +101,7 @@ func (d *Daemon) Start() error {
entry.Health = d.health.Addr()
}
if err := d.opts.Registry.Register(entry); err != nil {
return fmt.Errorf("registry: %w", err)
return coreerr.E("Daemon.Start", "registry", err)
}
}
@ -112,7 +113,7 @@ func (d *Daemon) Run(ctx context.Context) error {
d.mu.Lock()
if !d.running {
d.mu.Unlock()
return errors.New("daemon not started - call Start() first")
return coreerr.E("Daemon.Run", "daemon not started - call Start() first", nil)
}
d.mu.Unlock()
@ -138,13 +139,13 @@ func (d *Daemon) Stop() error {
if d.health != nil {
d.health.SetReady(false)
if err := d.health.Stop(shutdownCtx); err != nil {
errs = append(errs, fmt.Errorf("health server: %w", err))
errs = append(errs, coreerr.E("Daemon.Stop", "health server", err))
}
}
if d.pid != nil {
if err := d.pid.Release(); err != nil && !os.IsNotExist(err) {
errs = append(errs, fmt.Errorf("pid file: %w", err))
errs = append(errs, coreerr.E("Daemon.Stop", "pid file", err))
}
}

View file

@ -8,6 +8,8 @@ import (
"os"
"os/exec"
"strings"
coreerr "forge.lthn.ai/core/go-log"
)
// Options configuration for command execution
@ -147,7 +149,7 @@ func RunQuiet(ctx context.Context, name string, args ...string) error {
cmd := Command(ctx, name, args...).WithStderr(&stderr)
if err := cmd.Run(); err != nil {
// Include stderr in error message
return fmt.Errorf("%w: %s", err, strings.TrimSpace(stderr.String()))
return coreerr.E("RunQuiet", strings.TrimSpace(stderr.String()), err)
}
return nil
}
@ -155,9 +157,9 @@ func RunQuiet(ctx context.Context, name string, args ...string) error {
func wrapError(err error, name string, args []string) error {
cmdStr := name + " " + strings.Join(args, " ")
if exitErr, ok := err.(*exec.ExitError); ok {
return fmt.Errorf("command %q failed with exit code %d: %w", cmdStr, exitErr.ExitCode(), err)
return coreerr.E("wrapError", fmt.Sprintf("command %q failed with exit code %d", cmdStr, exitErr.ExitCode()), err)
}
return fmt.Errorf("failed to execute %q: %w", cmdStr, err)
return coreerr.E("wrapError", fmt.Sprintf("failed to execute %q", cmdStr), err)
}
func (c *Cmd) getLogger() Logger {

2
go.mod
View file

@ -6,13 +6,13 @@ require (
forge.lthn.ai/core/api v0.1.2
forge.lthn.ai/core/go v0.3.1
forge.lthn.ai/core/go-io v0.1.2
forge.lthn.ai/core/go-log v0.0.2
forge.lthn.ai/core/go-ws v0.2.0
github.com/gin-gonic/gin v1.12.0
github.com/stretchr/testify v1.11.1
)
require (
forge.lthn.ai/core/go-log v0.0.2 // indirect
github.com/99designs/gqlgen v0.17.88 // indirect
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/agnivade/levenshtein v1.2.1 // indirect

View file

@ -7,6 +7,8 @@ import (
"net/http"
"sync"
"time"
coreerr "forge.lthn.ai/core/go-log"
)
// HealthCheck is a function that returns nil if healthy.
@ -82,7 +84,7 @@ func (h *HealthServer) Start() error {
listener, err := net.Listen("tcp", h.addr)
if err != nil {
return fmt.Errorf("failed to listen on %s: %w", h.addr, err)
return coreerr.E("HealthServer.Start", fmt.Sprintf("failed to listen on %s", h.addr), err)
}
h.listener = listener

View file

@ -10,6 +10,7 @@ import (
"syscall"
coreio "forge.lthn.ai/core/go-io"
coreerr "forge.lthn.ai/core/go-log"
)
// PIDFile manages a process ID file for single-instance enforcement.
@ -34,7 +35,7 @@ func (p *PIDFile) Acquire() error {
if err == nil && pid > 0 {
if proc, err := os.FindProcess(pid); err == nil {
if err := proc.Signal(syscall.Signal(0)); err == nil {
return fmt.Errorf("another instance is running (PID %d)", pid)
return coreerr.E("PIDFile.Acquire", fmt.Sprintf("another instance is running (PID %d)", pid), nil)
}
}
}
@ -43,13 +44,13 @@ func (p *PIDFile) Acquire() error {
if dir := filepath.Dir(p.path); dir != "." {
if err := coreio.Local.EnsureDir(dir); err != nil {
return fmt.Errorf("failed to create PID directory: %w", err)
return coreerr.E("PIDFile.Acquire", "failed to create PID directory", err)
}
}
pid := os.Getpid()
if err := coreio.Local.Write(p.path, strconv.Itoa(pid)); err != nil {
return fmt.Errorf("failed to write PID file: %w", err)
return coreerr.E("PIDFile.Acquire", "failed to write PID file", err)
}
return nil

View file

@ -8,6 +8,8 @@ import (
"os/exec"
"sync"
"time"
coreerr "forge.lthn.ai/core/go-log"
)
// Process represents a managed external process.
@ -87,13 +89,13 @@ func (p *Process) Wait() error {
p.mu.RLock()
defer p.mu.RUnlock()
if p.Status == StatusFailed {
return fmt.Errorf("process failed to start: %s", p.ID)
return coreerr.E("Process.Wait", fmt.Sprintf("process failed to start: %s", p.ID), nil)
}
if p.Status == StatusKilled {
return fmt.Errorf("process was killed: %s", p.ID)
return coreerr.E("Process.Wait", fmt.Sprintf("process was killed: %s", p.ID), nil)
}
if p.ExitCode != 0 {
return fmt.Errorf("process exited with code %d", p.ExitCode)
return coreerr.E("Process.Wait", fmt.Sprintf("process exited with code %d", p.ExitCode), nil)
}
return nil
}

View file

@ -2,9 +2,10 @@ package process
import (
"context"
"errors"
"sync"
"time"
coreerr "forge.lthn.ai/core/go-log"
)
// Runner orchestrates multiple processes with dependencies.
@ -104,7 +105,7 @@ func (r *Runner) RunAll(ctx context.Context, specs []RunSpec) (*RunAllResult, er
Name: name,
Spec: remaining[name],
Skipped: true,
Error: errors.New("circular dependency or missing dependency"),
Error: coreerr.E("Runner.RunAll", "circular dependency or missing dependency", nil),
})
}
break
@ -136,7 +137,7 @@ func (r *Runner) RunAll(ctx context.Context, specs []RunSpec) (*RunAllResult, er
Name: spec.Name,
Spec: spec,
Skipped: true,
Error: errors.New("skipped due to dependency failure"),
Error: coreerr.E("Runner.RunAll", "skipped due to dependency failure", nil),
}
} else {
result = r.runSpec(ctx, spec)

View file

@ -13,6 +13,7 @@ import (
"time"
"forge.lthn.ai/core/go/pkg/core"
coreerr "forge.lthn.ai/core/go-log"
)
// Default buffer size for process output (1MB).
@ -20,9 +21,9 @@ const DefaultBufferSize = 1024 * 1024
// Errors
var (
ErrProcessNotFound = errors.New("process not found")
ErrProcessNotRunning = errors.New("process is not running")
ErrStdinNotAvailable = errors.New("stdin not available")
ErrProcessNotFound = coreerr.E("", "process not found", nil)
ErrProcessNotRunning = coreerr.E("", "process is not running", nil)
ErrStdinNotAvailable = coreerr.E("", "stdin not available", nil)
)
// Service manages process execution with Core IPC integration.
@ -121,19 +122,19 @@ func (s *Service) StartWithOptions(ctx context.Context, opts RunOptions) (*Proce
stdout, err := cmd.StdoutPipe()
if err != nil {
cancel()
return nil, fmt.Errorf("failed to create stdout pipe: %w", err)
return nil, coreerr.E("Service.StartWithOptions", "failed to create stdout pipe", err)
}
stderr, err := cmd.StderrPipe()
if err != nil {
cancel()
return nil, fmt.Errorf("failed to create stderr pipe: %w", err)
return nil, coreerr.E("Service.StartWithOptions", "failed to create stderr pipe", err)
}
stdin, err := cmd.StdinPipe()
if err != nil {
cancel()
return nil, fmt.Errorf("failed to create stdin pipe: %w", err)
return nil, coreerr.E("Service.StartWithOptions", "failed to create stdin pipe", err)
}
// Create output buffer (enabled by default)
@ -161,7 +162,7 @@ func (s *Service) StartWithOptions(ctx context.Context, opts RunOptions) (*Proce
// Start the process
if err := cmd.Start(); err != nil {
cancel()
return nil, fmt.Errorf("failed to start process: %w", err)
return nil, coreerr.E("Service.StartWithOptions", "failed to start process", err)
}
// Store process
@ -327,7 +328,7 @@ func (s *Service) Remove(id string) error {
}
if proc.IsRunning() {
return errors.New("cannot remove running process")
return coreerr.E("Service.Remove", "cannot remove running process", nil)
}
delete(s.processes, id)
@ -367,7 +368,7 @@ func (s *Service) Run(ctx context.Context, command string, args ...string) (stri
output := proc.Output()
if proc.ExitCode != 0 {
return output, fmt.Errorf("process exited with code %d", proc.ExitCode)
return output, coreerr.E("Service.Run", fmt.Sprintf("process exited with code %d", proc.ExitCode), nil)
}
return output, nil
}
@ -383,7 +384,7 @@ func (s *Service) RunWithOptions(ctx context.Context, opts RunOptions) (string,
output := proc.Output()
if proc.ExitCode != 0 {
return output, fmt.Errorf("process exited with code %d", proc.ExitCode)
return output, coreerr.E("Service.RunWithOptions", fmt.Sprintf("process exited with code %d", proc.ExitCode), nil)
}
return output, nil
}