refactor(ax): finish AX docs pass and test naming alignment
This commit is contained in:
parent
d7c46d9482
commit
6b6f025be7
4 changed files with 57 additions and 26 deletions
11
errors.go
11
errors.go
|
|
@ -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 ""
|
||||
|
|
|
|||
|
|
@ -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
50
log.go
|
|
@ -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...)
|
||||
}
|
||||
|
|
|
|||
16
log_test.go
16
log_test.go
|
|
@ -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})
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue