// Package log provides structured logging and error handling for Core applications. // // This file implements structured error types and combined log-and-return helpers // that simplify common error handling patterns. package log import ( "errors" "fmt" ) // Err represents a structured error with operational context. // It implements the error interface and supports unwrapping. type Err struct { Op string // Operation being performed (e.g., "user.Save") Msg string // Human-readable message Err error // Underlying error (optional) Code string // Error code (optional, e.g., "VALIDATION_FAILED") } // Error implements the error interface. func (e *Err) Error() string { if e.Err != nil { if e.Code != "" { return fmt.Sprintf("%s: %s [%s]: %v", e.Op, e.Msg, e.Code, e.Err) } return fmt.Sprintf("%s: %s: %v", e.Op, e.Msg, e.Err) } if e.Code != "" { return fmt.Sprintf("%s: %s [%s]", e.Op, e.Msg, e.Code) } return fmt.Sprintf("%s: %s", e.Op, e.Msg) } // Unwrap returns the underlying error for use with errors.Is and errors.As. func (e *Err) Unwrap() error { return e.Err } // --- Error Creation Functions --- // E creates a new Err with operation context. // If err is nil, returns nil to support conditional wrapping. // // Example: // // return log.E("user.Save", "failed to save user", err) func E(op, msg string, err error) error { if err == nil { return nil } return &Err{Op: op, Msg: msg, Err: err} } // Wrap wraps an error with operation context. // Alias for E() for semantic clarity when wrapping existing errors. // // Example: // // return log.Wrap(err, "db.Query", "database query failed") func Wrap(err error, op, msg string) error { return E(op, msg, err) } // WrapCode wraps an error with operation context and error code. // Useful for API errors that need machine-readable codes. // // Example: // // return log.WrapCode(err, "VALIDATION_ERROR", "user.Validate", "invalid email") func WrapCode(err error, code, op, msg string) error { if err == nil { return nil } return &Err{Op: op, Msg: msg, Err: err, Code: code} } // NewCode creates an error with just code and message (no underlying error). // Useful for creating sentinel errors with codes. // // Example: // // var ErrNotFound = log.NewCode("NOT_FOUND", "resource not found") func NewCode(code, msg string) error { return &Err{Msg: msg, Code: code} } // --- Standard Library Wrappers --- // Is reports whether any error in err's tree matches target. // Wrapper around errors.Is for convenience. 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. 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. func NewError(text string) error { return errors.New(text) } // Join combines multiple errors into one. // Wrapper around errors.Join for convenience. func Join(errs ...error) error { return errors.Join(errs...) } // --- Error Introspection Helpers --- // Op extracts the operation name from an error. // Returns empty string if the error is not an *Err. func Op(err error) string { var e *Err if As(err, &e) { return e.Op } return "" } // ErrCode extracts the error code from an error. // Returns empty string if the error is not an *Err or has no code. func ErrCode(err error) string { var e *Err if As(err, &e) { return e.Code } return "" } // Message extracts the message from an error. // Returns the error's Error() string if not an *Err. func Message(err error) string { if err == nil { return "" } var e *Err if As(err, &e) { return e.Msg } return err.Error() } // Root returns the root cause of an error chain. // Unwraps until no more wrapped errors are found. func Root(err error) error { if err == nil { return nil } for { unwrapped := errors.Unwrap(err) if unwrapped == nil { return err } err = unwrapped } } // --- Combined Log-and-Return Helpers --- // LogError logs an error at Error level and returns a wrapped error. // Reduces boilerplate in error handling paths. // // Example: // // // Before // if err != nil { // log.Error("failed to save", "err", err) // return errors.Wrap(err, "user.Save", "failed to save") // } // // // After // if err != nil { // return log.LogError(err, "user.Save", "failed to save") // } func LogError(err error, op, msg string) error { if err == nil { return nil } wrapped := Wrap(err, op, msg) defaultLogger.Error(msg, "op", op, "err", err) return wrapped } // LogWarn logs at Warn level and returns a wrapped error. // Use for recoverable errors that should be logged but not treated as critical. // // Example: // // return log.LogWarn(err, "cache.Get", "cache miss, falling back to db") func LogWarn(err error, op, msg string) error { if err == nil { return nil } wrapped := Wrap(err, op, msg) defaultLogger.Warn(msg, "op", op, "err", err) return wrapped } // Must panics if err is not nil, logging first. // Use for errors that should never happen and indicate programmer error. // // Example: // // log.Must(Initialize(), "app", "startup failed") func Must(err error, op, msg string) { if err != nil { defaultLogger.Error(msg, "op", op, "err", err) panic(Wrap(err, op, msg)) } }