refactor(ax): finish AX docs pass and test naming alignment
Some checks failed
CI / test (push) Failing after 2s
CI / auto-fix (push) Failing after 0s
CI / auto-merge (push) Failing after 0s

This commit is contained in:
Virgil 2026-03-30 00:36:24 +00:00
parent d7c46d9482
commit 6b6f025be7
4 changed files with 57 additions and 26 deletions

View file

@ -102,24 +102,33 @@ func NewCode(code, msg string) error {
// Is reports whether any error in err's tree matches target.
// Wrapper around errors.Is for convenience.
//
// if log.Is(err, context.DeadlineExceeded) { /* handle timeout */ }
func Is(err, target error) bool {
return errors.Is(err, target)
}
// As finds the first error in err's tree that matches target.
// Wrapper around errors.As for convenience.
//
// var e *log.Err
// if log.As(err, &e) { /* use e.Code */ }
func As(err error, target any) bool {
return errors.As(err, target)
}
// NewError creates a simple error with the given text.
// Wrapper around errors.New for convenience.
//
// return log.NewError("invalid state")
func NewError(text string) error {
return errors.New(text)
}
// Join combines multiple errors into one.
// Wrapper around errors.Join for convenience.
//
// return log.Join(validateErr, persistErr)
func Join(errs ...error) error {
return errors.Join(errs...)
}
@ -152,6 +161,8 @@ func ErrCode(err error) string {
// Message extracts the message from an error.
// Returns the error's Error() string if not an *Err.
//
// msg := log.Message(err)
func Message(err error) string {
if err == nil {
return ""

View file

@ -336,18 +336,18 @@ func TestStackTrace_Good(t *testing.T) {
assert.Equal(t, "op3 -> op2 -> op1", formatted)
}
func TestStackTrace_PlainError(t *testing.T) {
func TestStackTrace_Bad_PlainError(t *testing.T) {
err := errors.New("plain error")
assert.Empty(t, StackTrace(err))
assert.Empty(t, FormatStackTrace(err))
}
func TestStackTrace_Nil(t *testing.T) {
func TestStackTrace_Bad_Nil(t *testing.T) {
assert.Empty(t, StackTrace(nil))
assert.Empty(t, FormatStackTrace(nil))
}
func TestStackTrace_NoOp(t *testing.T) {
func TestStackTrace_Bad_NoOp(t *testing.T) {
err := &Err{Msg: "no op"}
assert.Empty(t, StackTrace(err))
assert.Empty(t, FormatStackTrace(err))

50
log.go
View file

@ -73,13 +73,18 @@ type Logger struct {
// RedactKeys is a list of keys whose values should be masked in logs.
redactKeys []string
// Style functions for formatting (can be overridden)
// StyleTimestamp formats the rendered timestamp prefix.
StyleTimestamp func(string) string
StyleDebug func(string) string
StyleInfo func(string) string
StyleWarn func(string) string
StyleError func(string) string
StyleSecurity func(string) string
// StyleDebug formats the debug level prefix.
StyleDebug func(string) string
// StyleInfo formats the info level prefix.
StyleInfo func(string) string
// StyleWarn formats the warning level prefix.
StyleWarn func(string) string
// StyleError formats the error level prefix.
StyleError func(string) string
// StyleSecurity formats the security event prefix.
StyleSecurity func(string) string
}
// RotationOptions defines the log rotation and retention policy.
@ -107,6 +112,7 @@ type RotationOptions struct {
// Options configures a Logger.
type Options struct {
// Level controls which messages are emitted.
Level Level
// Output is the destination for log messages. If Rotation is provided,
// Output is ignored and logs are written to the rotating file instead.
@ -266,11 +272,11 @@ func (l *Logger) log(level Level, prefix, msg string, keyvals ...any) {
if i > 0 {
kvStr += " "
}
key := normaliseLogText(fmt.Sprintf("%v", keyvals[i]))
var val any
if i+1 < len(keyvals) {
val = keyvals[i+1]
}
key := normaliseLogText(fmt.Sprintf("%v", keyvals[i]))
var val any
if i+1 < len(keyvals) {
val = keyvals[i+1]
}
// Redaction logic
if shouldRedact(key, redactKeys) {
@ -279,12 +285,12 @@ func (l *Logger) log(level Level, prefix, msg string, keyvals ...any) {
// Secure formatting to prevent log injection
if s, ok := val.(string); ok {
kvStr += fmt.Sprintf("%s=%q", key, s)
} else {
kvStr += fmt.Sprintf("%s=%v", key, normaliseLogText(fmt.Sprintf("%v", val)))
kvStr += fmt.Sprintf("%s=%q", key, s)
} else {
kvStr += fmt.Sprintf("%s=%v", key, normaliseLogText(fmt.Sprintf("%v", val)))
}
}
}
}
_, _ = fmt.Fprintf(output, "%s %s %s%s\n", timestamp, prefix, normaliseLogText(msg), kvStr)
}
@ -404,36 +410,50 @@ func SetDefault(l *Logger) {
}
// SetLevel sets the default logger's level.
//
// log.SetLevel(log.LevelDebug)
func SetLevel(level Level) {
Default().SetLevel(level)
}
// SetRedactKeys sets the default logger's redaction keys.
//
// log.SetRedactKeys("password", "token")
func SetRedactKeys(keys ...string) {
Default().SetRedactKeys(keys...)
}
// Debug logs to the default logger.
//
// log.Debug("query started", "sql", query)
func Debug(msg string, keyvals ...any) {
Default().Debug(msg, keyvals...)
}
// Info logs to the default logger.
//
// log.Info("server ready", "port", 8080)
func Info(msg string, keyvals ...any) {
Default().Info(msg, keyvals...)
}
// Warn logs to the default logger.
//
// log.Warn("retrying request", "attempt", 2)
func Warn(msg string, keyvals ...any) {
Default().Warn(msg, keyvals...)
}
// Error logs to the default logger.
//
// log.Error("request failed", "err", err)
func Error(msg string, keyvals ...any) {
Default().Error(msg, keyvals...)
}
// Security logs to the default logger.
//
// log.Security("suspicious login", "ip", remoteAddr)
func Security(msg string, keyvals ...any) {
Default().Security(msg, keyvals...)
}

View file

@ -12,7 +12,7 @@ type nopWriteCloser struct{ goio.Writer }
func (nopWriteCloser) Close() error { return nil }
func TestLogger_Levels(t *testing.T) {
func TestLogger_Levels_Good(t *testing.T) {
tests := []struct {
name string
level Level
@ -62,7 +62,7 @@ func TestLogger_Levels(t *testing.T) {
}
}
func TestLogger_KeyValues(t *testing.T) {
func TestLogger_KeyValues_Good(t *testing.T) {
var buf bytes.Buffer
l := New(Options{Level: LevelDebug, Output: &buf})
@ -80,7 +80,7 @@ func TestLogger_KeyValues(t *testing.T) {
}
}
func TestLogger_ErrorContext(t *testing.T) {
func TestLogger_ErrorContext_Good(t *testing.T) {
var buf bytes.Buffer
l := New(Options{Output: &buf, Level: LevelInfo})
@ -98,7 +98,7 @@ func TestLogger_ErrorContext(t *testing.T) {
}
}
func TestLogger_Redaction(t *testing.T) {
func TestLogger_Redaction_Good(t *testing.T) {
var buf bytes.Buffer
l := New(Options{
Level: LevelInfo,
@ -120,7 +120,7 @@ func TestLogger_Redaction(t *testing.T) {
}
}
func TestLogger_InjectionPrevention(t *testing.T) {
func TestLogger_InjectionPrevention_Good(t *testing.T) {
var buf bytes.Buffer
l := New(Options{Level: LevelInfo, Output: &buf})
@ -174,7 +174,7 @@ func TestLogger_MessageSanitization_Good(t *testing.T) {
}
}
func TestLogger_SetLevel(t *testing.T) {
func TestLogger_SetLevel_Good(t *testing.T) {
l := New(Options{Level: LevelInfo})
if l.Level() != LevelInfo {
@ -192,7 +192,7 @@ func TestLogger_SetLevel(t *testing.T) {
}
}
func TestLevel_String(t *testing.T) {
func TestLevel_String_Good(t *testing.T) {
tests := []struct {
level Level
expected string
@ -214,7 +214,7 @@ func TestLevel_String(t *testing.T) {
}
}
func TestLogger_Security(t *testing.T) {
func TestLogger_Security_Good(t *testing.T) {
var buf bytes.Buffer
l := New(Options{Level: LevelError, Output: &buf})