* feat(log): log all errors at handling point with context This change ensures all errors are logged at the point where they are handled, including contextual information such as operations and logical stack traces. Key changes: - Added `StackTrace` and `FormatStackTrace` to `pkg/log/errors.go`. - Enhanced `Logger.log` in `pkg/log/log.go` to automatically extract and log `op` and `stack` keys when an error is passed in keyvals. - Updated CLI logging and output helpers to support structured logging. - Updated CLI fatal error handlers to log errors before exiting. - Audited and updated error logging in MCP service (tool handlers and TCP transport), CLI background services (signal and health), and Agentic task handlers. * feat(log): log all errors at handling point with context This change ensures all errors are logged at the point where they are handled, including contextual information such as operations and logical stack traces. Key changes: - Added `StackTrace` and `FormatStackTrace` to `pkg/log/errors.go`. - Enhanced `Logger.log` in `pkg/log/log.go` to automatically extract and log `op` and `stack` keys when an error is passed in keyvals. - Updated CLI logging and output helpers to support structured logging. - Updated CLI fatal error handlers to log errors before exiting. - Audited and updated error logging in MCP service (tool handlers and TCP transport), CLI background services (signal and health), and Agentic task handlers. - Fixed formatting in `pkg/mcp/mcp.go` and `pkg/io/local/client.go`. - Removed unused `fmt` import in `pkg/cli/runtime.go`. * feat(log): log all errors at handling point with context This change ensures all errors are logged at the point where they are handled, including contextual information such as operations and logical stack traces. Key changes: - Added `StackTrace` and `FormatStackTrace` to `pkg/log/errors.go`. - Enhanced `Logger.log` in `pkg/log/log.go` to automatically extract and log `op` and `stack` keys when an error is passed in keyvals. - Updated CLI logging and output helpers to support structured logging. - Updated CLI fatal error handlers to log errors before exiting. - Audited and updated error logging in MCP service (tool handlers and TCP transport), CLI background services (signal and health), and Agentic task handlers. - Fixed formatting in `pkg/mcp/mcp.go` and `pkg/io/local/client.go`. - Removed unused `fmt` import in `pkg/cli/runtime.go`. - Fixed CI failure in `auto-merge` workflow by providing explicit repository context to the GitHub CLI. * feat(log): address PR feedback and improve error context extraction Addressed feedback from PR review: - Improved `Fatalf` and other fatal functions in `pkg/cli/errors.go` to use structured logging for the formatted message. - Added direct unit tests for `StackTrace` and `FormatStackTrace` in `pkg/log/errors_test.go`, covering edge cases like plain errors, nil errors, and mixed error chains. - Optimized the automatic context extraction loop in `pkg/log/log.go` by capturing the original length of keyvals. - Fixed a bug in `StackTrace` where operations were duplicated when the error chain included non-`*log.Err` errors. - Fixed formatting and unused imports from previous commits. * fix: address code review comments - Simplify Fatalf logging by removing redundant format parameter (the formatted message is already logged as "msg") - Tests for StackTrace/FormatStackTrace edge cases already exist - Loop optimization in pkg/log/log.go already implemented Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude <developers@lethean.io> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
195 lines
5.3 KiB
Go
195 lines
5.3 KiB
Go
package cli
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/host-uk/core/pkg/i18n"
|
|
)
|
|
|
|
// Blank prints an empty line.
|
|
func Blank() {
|
|
fmt.Println()
|
|
}
|
|
|
|
// Echo translates a key via i18n.T and prints with newline.
|
|
// No automatic styling - use Success/Error/Warn/Info for styled output.
|
|
func Echo(key string, args ...any) {
|
|
fmt.Println(i18n.T(key, args...))
|
|
}
|
|
|
|
// Print outputs formatted text (no newline).
|
|
// Glyph shortcodes like :check: are converted.
|
|
func Print(format string, args ...any) {
|
|
fmt.Print(compileGlyphs(fmt.Sprintf(format, args...)))
|
|
}
|
|
|
|
// Println outputs formatted text with newline.
|
|
// Glyph shortcodes like :check: are converted.
|
|
func Println(format string, args ...any) {
|
|
fmt.Println(compileGlyphs(fmt.Sprintf(format, args...)))
|
|
}
|
|
|
|
// Text prints arguments like fmt.Println, but handling glyphs.
|
|
func Text(args ...any) {
|
|
fmt.Println(compileGlyphs(fmt.Sprint(args...)))
|
|
}
|
|
|
|
// Success prints a success message with checkmark (green).
|
|
func Success(msg string) {
|
|
fmt.Println(SuccessStyle.Render(Glyph(":check:") + " " + msg))
|
|
}
|
|
|
|
// Successf prints a formatted success message.
|
|
func Successf(format string, args ...any) {
|
|
Success(fmt.Sprintf(format, args...))
|
|
}
|
|
|
|
// Error prints an error message with cross (red) to stderr and logs it.
|
|
func Error(msg string) {
|
|
LogError(msg)
|
|
fmt.Fprintln(os.Stderr, ErrorStyle.Render(Glyph(":cross:")+" "+msg))
|
|
}
|
|
|
|
// Errorf prints a formatted error message to stderr and logs it.
|
|
func Errorf(format string, args ...any) {
|
|
Error(fmt.Sprintf(format, args...))
|
|
}
|
|
|
|
// ErrorWrap prints a wrapped error message to stderr and logs it.
|
|
func ErrorWrap(err error, msg string) {
|
|
if err == nil {
|
|
return
|
|
}
|
|
Error(fmt.Sprintf("%s: %v", msg, err))
|
|
}
|
|
|
|
// ErrorWrapVerb prints a wrapped error using i18n grammar to stderr and logs it.
|
|
func ErrorWrapVerb(err error, verb, subject string) {
|
|
if err == nil {
|
|
return
|
|
}
|
|
msg := i18n.ActionFailed(verb, subject)
|
|
Error(fmt.Sprintf("%s: %v", msg, err))
|
|
}
|
|
|
|
// ErrorWrapAction prints a wrapped error using i18n grammar to stderr and logs it.
|
|
func ErrorWrapAction(err error, verb string) {
|
|
if err == nil {
|
|
return
|
|
}
|
|
msg := i18n.ActionFailed(verb, "")
|
|
Error(fmt.Sprintf("%s: %v", msg, err))
|
|
}
|
|
|
|
// Warn prints a warning message with warning symbol (amber) to stderr and logs it.
|
|
func Warn(msg string) {
|
|
LogWarn(msg)
|
|
fmt.Fprintln(os.Stderr, WarningStyle.Render(Glyph(":warn:")+" "+msg))
|
|
}
|
|
|
|
// Warnf prints a formatted warning message to stderr and logs it.
|
|
func Warnf(format string, args ...any) {
|
|
Warn(fmt.Sprintf(format, args...))
|
|
}
|
|
|
|
// Info prints an info message with info symbol (blue).
|
|
func Info(msg string) {
|
|
fmt.Println(InfoStyle.Render(Glyph(":info:") + " " + msg))
|
|
}
|
|
|
|
// Infof prints a formatted info message.
|
|
func Infof(format string, args ...any) {
|
|
Info(fmt.Sprintf(format, args...))
|
|
}
|
|
|
|
// Dim prints dimmed text.
|
|
func Dim(msg string) {
|
|
fmt.Println(DimStyle.Render(msg))
|
|
}
|
|
|
|
// Progress prints a progress indicator that overwrites the current line.
|
|
// Uses i18n.Progress for gerund form ("Checking...").
|
|
func Progress(verb string, current, total int, item ...string) {
|
|
msg := i18n.Progress(verb)
|
|
if len(item) > 0 && item[0] != "" {
|
|
fmt.Printf("\033[2K\r%s %d/%d %s", DimStyle.Render(msg), current, total, item[0])
|
|
} else {
|
|
fmt.Printf("\033[2K\r%s %d/%d", DimStyle.Render(msg), current, total)
|
|
}
|
|
}
|
|
|
|
// ProgressDone clears the progress line.
|
|
func ProgressDone() {
|
|
fmt.Print("\033[2K\r")
|
|
}
|
|
|
|
// Label prints a "Label: value" line.
|
|
func Label(word, value string) {
|
|
fmt.Printf("%s %s\n", KeyStyle.Render(i18n.Label(word)), value)
|
|
}
|
|
|
|
// Scanln reads from stdin.
|
|
func Scanln(a ...any) (int, error) {
|
|
return fmt.Scanln(a...)
|
|
}
|
|
|
|
// Task prints a task header: "[label] message"
|
|
//
|
|
// cli.Task("php", "Running tests...") // [php] Running tests...
|
|
// cli.Task("go", i18n.Progress("build")) // [go] Building...
|
|
func Task(label, message string) {
|
|
fmt.Printf("%s %s\n\n", DimStyle.Render("["+label+"]"), message)
|
|
}
|
|
|
|
// Section prints a section header: "── SECTION ──"
|
|
//
|
|
// cli.Section("audit") // ── AUDIT ──
|
|
func Section(name string) {
|
|
header := "── " + strings.ToUpper(name) + " ──"
|
|
fmt.Println(AccentStyle.Render(header))
|
|
}
|
|
|
|
// Hint prints a labelled hint: "label: message"
|
|
//
|
|
// cli.Hint("install", "composer require vimeo/psalm")
|
|
// cli.Hint("fix", "core php fmt --fix")
|
|
func Hint(label, message string) {
|
|
fmt.Printf(" %s %s\n", DimStyle.Render(label+":"), message)
|
|
}
|
|
|
|
// Severity prints a severity-styled message.
|
|
//
|
|
// cli.Severity("critical", "SQL injection") // red, bold
|
|
// cli.Severity("high", "XSS vulnerability") // orange
|
|
// cli.Severity("medium", "Missing CSRF") // amber
|
|
// cli.Severity("low", "Debug enabled") // gray
|
|
func Severity(level, message string) {
|
|
var style *AnsiStyle
|
|
switch strings.ToLower(level) {
|
|
case "critical":
|
|
style = NewStyle().Bold().Foreground(ColourRed500)
|
|
case "high":
|
|
style = NewStyle().Bold().Foreground(ColourOrange500)
|
|
case "medium":
|
|
style = NewStyle().Foreground(ColourAmber500)
|
|
case "low":
|
|
style = NewStyle().Foreground(ColourGray500)
|
|
default:
|
|
style = DimStyle
|
|
}
|
|
fmt.Printf(" %s %s\n", style.Render("["+level+"]"), message)
|
|
}
|
|
|
|
// Result prints a result line: "✓ message" or "✗ message"
|
|
//
|
|
// cli.Result(passed, "All tests passed")
|
|
// cli.Result(false, "3 tests failed")
|
|
func Result(passed bool, message string) {
|
|
if passed {
|
|
Success(message)
|
|
} else {
|
|
Error(message)
|
|
}
|
|
}
|