Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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"))
}
36 changes: 15 additions & 21 deletions pkg/database/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,80 +31,74 @@ func (e InternalServerError) Error() string {
return fmt.Sprintf("internal server error: %s", e.Source.Error())
}

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

// Deprecated: database errors are classified here without logging. Return domain errors directly for new code.
func WrapDBError(err error, logger *zap.Logger, operation string) error {
Comment thread
yukicoder0509 marked this conversation as resolved.
if err == nil {
return nil
}

logger.WithOptions(zap.AddCallerSkip(1)).Error("Failed to "+operation, zap.Error(err))

var wrappedErr 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)
}
}
}

isUnknownError := false
if wrappedErr == nil {
wrappedErr = InternalServerError{Source: err}
isUnknownError = true
}

logger.WithOptions(zap.AddCallerSkip(1)).Warn("Wrapped database error", zap.Error(wrappedErr), zap.String("operation", operation), zap.Bool("unknown_error", isUnknownError))

return wrappedErr
}

// Deprecated: database errors are classified here without logging. Return domain errors directly for new code.
func WrapDBErrorWithKeyValue(err error, table, key, value string, logger *zap.Logger, operation string) error {
if err == nil {
return nil
}

logger.WithOptions(zap.AddCallerSkip(1)).Error("Failed to "+operation, zap.Error(err))

var wrappedErr error

switch {
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)
}
}
}

isUnknownError := false
if wrappedErr == nil {
wrappedErr = InternalServerError{Source: err}
isUnknownError = true
}

logger.WithOptions(zap.AddCallerSkip(1)).Warn("Wrapped database error with key value", zap.Error(wrappedErr), zap.String("table", table), zap.String("key", key), zap.String("value", value), zap.String("operation", operation), zap.Bool("unknown_error", isUnknownError))

return wrappedErr
}
32 changes: 11 additions & 21 deletions pkg/database/errors_mssql.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,80 +19,70 @@ const (
MSSQLErrDeadlockDetected = 1205 // Deadlock detected
)

// Deprecated: database errors are classified here without logging. Return domain errors directly for new code.
func WrapMSSQLError(err error, logger *zap.Logger, operation string) error {
if err == nil {
return nil
}

logger.Error("Failed to "+operation, zap.Error(err))

var wrappedErr 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)
}
}
}

isUnknownError := false
if wrappedErr == nil {
wrappedErr = InternalServerError{Source: err}
isUnknownError = true
}

logger.Warn("Wrapped database error", zap.Error(wrappedErr), zap.String("operation", operation), zap.Bool("unknown_error", isUnknownError))

return wrappedErr
}

// Deprecated: database errors are classified here without logging. Return domain errors directly for new code.
func WrapMSSQLErrorWithKeyValue(err error, table, key, value string, logger *zap.Logger, operation string) error {
if err == nil {
return nil
}

logger.Error("Failed to "+operation, zap.Error(err))

var wrappedErr error

switch {
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)
}
}
}

isUnknownError := false
if wrappedErr == nil {
wrappedErr = InternalServerError{Source: err}
isUnknownError = true
}

logger.Warn("Wrapped database error with key value", zap.Error(wrappedErr), zap.String("table", table), zap.String("key", key), zap.String("value", value), zap.String("operation", operation), zap.Bool("unknown_error", isUnknownError))

return wrappedErr
}
Loading