Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes.
38 changes: 38 additions & 0 deletions examples/simple-log/log.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package main

import (
"context"
"errors"

errutil "github.com/NYCU-SDC/summer/pkg/error"
logutil "github.com/NYCU-SDC/summer/pkg/log"
"go.uber.org/zap"
)

func main() {
logger := zap.NewExample()

ctx, eventLogger := logutil.SetupFlow(
context.Background(),
logger,
"user.create",
zap.String("request.id", "req-7"),
zap.String("enduser.id", "user-42"),
zap.String("service.name", "account-api"),
)
eventLogger = logutil.WithEventOutcome(logutil.EventOutcomeFailure, eventLogger)

ctx = logutil.WithErrorType(ctx, errutil.ALREADY_EXISTS)
ctx = logutil.WithReason(ctx, "duplicate_email")

baseErr := errors.New("email already exists")

// Wrap the error when it needs to carry detail to the logging layer.
err := errutil.WrapInfoError(baseErr, map[errutil.ErrorInfoKey]any{
errutil.ErrorInfoOperation: "create_user",
errutil.ErrorInfoField: "email",
errutil.ErrorInfoRetryable: false,
})

logutil.Error(ctx, eventLogger, "create user rejected", err, zap.String("email.domain", "example.com"))
}
27 changes: 18 additions & 9 deletions pkg/database/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ func (e InternalServerError) Error() string {
return fmt.Sprintf("internal server error: %s", e.Source.Error())
}

func (e InternalServerError) Unwrap() error {
return e.Source
}

// WrapDBError is the legacy helper: it logs the error and classifies it into a domain error.
// New code should return domain errors directly and log through pkg/log.
func WrapDBError(err error, logger *zap.Logger, operation string) error {
Comment thread
yukicoder0509 marked this conversation as resolved.
if err == nil {
return nil
Expand All @@ -42,19 +48,19 @@ func WrapDBError(err error, logger *zap.Logger, operation string) error {

switch {
case errors.Is(err, pgx.ErrNoRows):
wrappedErr = fmt.Errorf("%w: %v", errorPkg.ErrNotFound, err)
wrappedErr = fmt.Errorf("%w: %w", errorPkg.ErrNotFound, err)
case errors.Is(err, context.DeadlineExceeded):
wrappedErr = fmt.Errorf("%w: %v", ErrQueryTimeout, err)
wrappedErr = fmt.Errorf("%w: %w", ErrQueryTimeout, err)
default:
var pgErr *pgconn.PgError
if errors.As(err, &pgErr) {
switch pgErr.Code {
case PGErrUniqueViolation:
wrappedErr = fmt.Errorf("%w: %v", ErrUniqueViolation, err)
wrappedErr = fmt.Errorf("%w: %w", ErrUniqueViolation, err)
case PGErrForeignKeyViolation:
wrappedErr = fmt.Errorf("%w: %v", ErrForeignKeyViolation, err)
wrappedErr = fmt.Errorf("%w: %w", ErrForeignKeyViolation, err)
case PGErrDeadlockDetected:
wrappedErr = fmt.Errorf("%w: %v", ErrDeadlockDetected, err)
wrappedErr = fmt.Errorf("%w: %w", ErrDeadlockDetected, err)
}
}
}
Expand All @@ -70,6 +76,9 @@ func WrapDBError(err error, logger *zap.Logger, operation string) error {
return wrappedErr
}

// WrapDBErrorWithKeyValue is the legacy helper: it logs the error and classifies it into a domain error,
// using table/key/value to build the not-found error. New code should return domain errors directly
// and log through pkg/log.
func WrapDBErrorWithKeyValue(err error, table, key, value string, logger *zap.Logger, operation string) error {
if err == nil {
return nil
Expand All @@ -83,17 +92,17 @@ func WrapDBErrorWithKeyValue(err error, table, key, value string, logger *zap.Lo
case errors.Is(err, pgx.ErrNoRows):
wrappedErr = errorPkg.NewNotFoundError(table, key, value, "")
case errors.Is(err, context.DeadlineExceeded):
wrappedErr = fmt.Errorf("%w: %v", ErrQueryTimeout, err)
wrappedErr = fmt.Errorf("%w: %w", ErrQueryTimeout, err)
default:
var pgErr *pgconn.PgError
if errors.As(err, &pgErr) {
switch pgErr.Code {
case PGErrUniqueViolation:
wrappedErr = fmt.Errorf("%w: %v", ErrUniqueViolation, err)
wrappedErr = fmt.Errorf("%w: %w", ErrUniqueViolation, err)
case PGErrForeignKeyViolation:
wrappedErr = fmt.Errorf("%w: %v", ErrForeignKeyViolation, err)
wrappedErr = fmt.Errorf("%w: %w", ErrForeignKeyViolation, err)
case PGErrDeadlockDetected:
wrappedErr = fmt.Errorf("%w: %v", ErrDeadlockDetected, err)
wrappedErr = fmt.Errorf("%w: %w", ErrDeadlockDetected, err)
}
}
}
Expand Down
23 changes: 14 additions & 9 deletions pkg/database/errors_mssql.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ const (
MSSQLErrDeadlockDetected = 1205 // Deadlock detected
)

// WrapMSSQLError is the legacy helper: it logs the error and classifies it into a domain error.
// New code should return domain errors directly and log through pkg/log.
func WrapMSSQLError(err error, logger *zap.Logger, operation string) error {
if err == nil {
return nil
Expand All @@ -30,19 +32,19 @@ func WrapMSSQLError(err error, logger *zap.Logger, operation string) error {

switch {
case errors.Is(err, sql.ErrNoRows):
wrappedErr = fmt.Errorf("%w: %v", errorPkg.ErrNotFound, err)
wrappedErr = fmt.Errorf("%w: %w", errorPkg.ErrNotFound, err)
case errors.Is(err, context.DeadlineExceeded):
wrappedErr = fmt.Errorf("%w: %v", ErrQueryTimeout, err)
wrappedErr = fmt.Errorf("%w: %w", ErrQueryTimeout, err)
default:
var mssqlErr mssql.Error
if errors.As(err, &mssqlErr) {
switch mssqlErr.Number {
case MSSQLErrUniqueViolation, MSSQLErrUniqueIndex:
wrappedErr = fmt.Errorf("%w: %v", ErrUniqueViolation, err)
wrappedErr = fmt.Errorf("%w: %w", ErrUniqueViolation, err)
case MSSQLErrForeignKeyViolation:
wrappedErr = fmt.Errorf("%w: %v", ErrForeignKeyViolation, err)
wrappedErr = fmt.Errorf("%w: %w", ErrForeignKeyViolation, err)
case MSSQLErrDeadlockDetected:
wrappedErr = fmt.Errorf("%w: %v", ErrDeadlockDetected, err)
wrappedErr = fmt.Errorf("%w: %w", ErrDeadlockDetected, err)
}
}
}
Expand All @@ -58,6 +60,9 @@ func WrapMSSQLError(err error, logger *zap.Logger, operation string) error {
return wrappedErr
}

// WrapMSSQLErrorWithKeyValue is the legacy helper: it logs the error and classifies it into a domain error,
// using table/key/value to build the not-found error. New code should return domain errors directly
// and log through pkg/log.
func WrapMSSQLErrorWithKeyValue(err error, table, key, value string, logger *zap.Logger, operation string) error {
if err == nil {
return nil
Expand All @@ -71,17 +76,17 @@ func WrapMSSQLErrorWithKeyValue(err error, table, key, value string, logger *zap
case errors.Is(err, sql.ErrNoRows):
wrappedErr = errorPkg.NewNotFoundError(table, key, value, "")
case errors.Is(err, context.DeadlineExceeded):
wrappedErr = fmt.Errorf("%w: %v", ErrQueryTimeout, err)
wrappedErr = fmt.Errorf("%w: %w", ErrQueryTimeout, err)
default:
var mssqlErr mssql.Error
if errors.As(err, &mssqlErr) {
switch mssqlErr.Number {
case MSSQLErrUniqueViolation, MSSQLErrUniqueIndex:
wrappedErr = fmt.Errorf("%w: %v", ErrUniqueViolation, err)
wrappedErr = fmt.Errorf("%w: %w", ErrUniqueViolation, err)
case MSSQLErrForeignKeyViolation:
wrappedErr = fmt.Errorf("%w: %v", ErrForeignKeyViolation, err)
wrappedErr = fmt.Errorf("%w: %w", ErrForeignKeyViolation, err)
case MSSQLErrDeadlockDetected:
wrappedErr = fmt.Errorf("%w: %v", ErrDeadlockDetected, err)
wrappedErr = fmt.Errorf("%w: %w", ErrDeadlockDetected, err)
}
}
}
Expand Down
Loading