diff --git a/docs/advanced-guide/gofr-errors/page.md b/docs/advanced-guide/gofr-errors/page.md index f93f8594a..6ecbe504e 100644 --- a/docs/advanced-guide/gofr-errors/page.md +++ b/docs/advanced-guide/gofr-errors/page.md @@ -44,6 +44,8 @@ dbErr2 := datasource.ErrorDB{Message : "database connection timed out!"} GoFr's error structs implements an interface with `Error() string` and `StatusCode() int` methods, users can override the status code by implementing it for their custom error. +You can optionally define a log level for your error with the `LogLevel() logging.Level` methods + #### Usage: ```go type customError struct { @@ -57,4 +59,8 @@ func (c customError) Error() string { func (c customError) StatusCode() int { return http.StatusMethodNotAllowed } + +func (c customError) LogLevel() logging.Level { + return logging.WARN +} ``` diff --git a/pkg/gofr/handler.go b/pkg/gofr/handler.go index 70a6fcf15..fee0529ce 100644 --- a/pkg/gofr/handler.go +++ b/pkg/gofr/handler.go @@ -145,22 +145,30 @@ func panicRecoveryHandler(re any, log logging.Logger, panicked chan struct{}) { }) } -//nolint:nestif // Log the error(if any) with traceID and errorMessage. +// Log the error(if any) with traceID and errorMessage. func (h handler) logError(traceID string, err error) { if err != nil { errorLog := &ErrorLogEntry{TraceID: traceID, Error: err.Error()} - if errors.As(err, &gofrHTTP.ErrorEntityAlreadyExist{}) { - h.container.Logger.Info(errorLog) - } else if errors.As(err, &gofrHTTP.ErrorEntityNotFound{}) { - h.container.Logger.Info(errorLog) - } else if errors.As(err, &gofrHTTP.ErrorInvalidParam{}) { - h.container.Logger.Info(errorLog) - } else if errors.As(err, &gofrHTTP.ErrorMissingParam{}) { - h.container.Logger.Info(errorLog) - } else { - h.container.Logger.Error(errorLog) + // define the default log level for error + loggerHelper := h.container.Logger.Error + + switch logging.GetLogLevelForError(err) { + case logging.ERROR: + // we use the default log level for error + case logging.INFO: + loggerHelper = h.container.Logger.Info + case logging.NOTICE: + loggerHelper = h.container.Logger.Notice + case logging.DEBUG: + loggerHelper = h.container.Logger.Debug + case logging.WARN: + loggerHelper = h.container.Logger.Warn + case logging.FATAL: + loggerHelper = h.container.Logger.Fatal } + + loggerHelper(errorLog) } } diff --git a/pkg/gofr/http/errors.go b/pkg/gofr/http/errors.go index 13efe41bb..6f7838780 100644 --- a/pkg/gofr/http/errors.go +++ b/pkg/gofr/http/errors.go @@ -5,6 +5,8 @@ import ( "fmt" "net/http" "strings" + + "gofr.dev/pkg/gofr/logging" ) const alreadyExistsMessage = "entity already exists" @@ -24,10 +26,13 @@ func (ErrorEntityNotFound) StatusCode() int { return http.StatusNotFound } -// ErrorEntityAlreadyExist represents an error for when entity is already present in the storage and we are trying to make duplicate entry. -type ErrorEntityAlreadyExist struct { +func (ErrorEntityNotFound) LogLevel() logging.Level { + return logging.INFO } +// ErrorEntityAlreadyExist represents an error for when entity is already present in the storage and we are trying to make duplicate entry. +type ErrorEntityAlreadyExist struct{} + func (ErrorEntityAlreadyExist) Error() string { return alreadyExistsMessage } @@ -36,6 +41,10 @@ func (ErrorEntityAlreadyExist) StatusCode() int { return http.StatusConflict } +func (ErrorEntityAlreadyExist) LogLevel() logging.Level { + return logging.WARN +} + // ErrorInvalidParam represents an error for invalid parameter values. type ErrorInvalidParam struct { Params []string `json:"param,omitempty"` // Params contains the list of invalid parameter names. @@ -49,6 +58,10 @@ func (ErrorInvalidParam) StatusCode() int { return http.StatusBadRequest } +func (ErrorInvalidParam) LogLevel() logging.Level { + return logging.INFO +} + // ErrorMissingParam represents an error for missing parameters in a request. type ErrorMissingParam struct { Params []string `json:"param,omitempty"` @@ -58,6 +71,10 @@ func (e ErrorMissingParam) Error() string { return fmt.Sprintf("'%d' missing parameter(s): %s", len(e.Params), strings.Join(e.Params, ", ")) } +func (ErrorMissingParam) LogLevel() logging.Level { + return logging.INFO +} + func (ErrorMissingParam) StatusCode() int { return http.StatusBadRequest } @@ -69,6 +86,10 @@ func (ErrorInvalidRoute) Error() string { return "route not registered" } +func (ErrorInvalidRoute) LogLevel() logging.Level { + return logging.INFO +} + func (ErrorInvalidRoute) StatusCode() int { return http.StatusNotFound } @@ -84,6 +105,10 @@ func (ErrorRequestTimeout) StatusCode() int { return http.StatusRequestTimeout } +func (ErrorRequestTimeout) LogLevel() logging.Level { + return logging.INFO +} + // ErrorPanicRecovery represents an error for request which panicked. type ErrorPanicRecovery struct{} @@ -94,3 +119,26 @@ func (ErrorPanicRecovery) Error() string { func (ErrorPanicRecovery) StatusCode() int { return http.StatusInternalServerError } + +func (ErrorPanicRecovery) LogLevel() logging.Level { + return logging.ERROR +} + +// validate the errors satisfy the underlying interfaces they depend on. +var ( + _ statusCodeResponder = ErrorEntityNotFound{} + _ statusCodeResponder = ErrorEntityAlreadyExist{} + _ statusCodeResponder = ErrorInvalidParam{} + _ statusCodeResponder = ErrorMissingParam{} + _ statusCodeResponder = ErrorInvalidRoute{} + _ statusCodeResponder = ErrorRequestTimeout{} + _ statusCodeResponder = ErrorPanicRecovery{} + + _ logging.LogLevelResponder = ErrorEntityNotFound{} + _ logging.LogLevelResponder = ErrorEntityAlreadyExist{} + _ logging.LogLevelResponder = ErrorInvalidParam{} + _ logging.LogLevelResponder = ErrorMissingParam{} + _ logging.LogLevelResponder = ErrorInvalidRoute{} + _ logging.LogLevelResponder = ErrorRequestTimeout{} + _ logging.LogLevelResponder = ErrorPanicRecovery{} +) diff --git a/pkg/gofr/logging/logger.go b/pkg/gofr/logging/logger.go index f92b9d1a6..0a3245521 100644 --- a/pkg/gofr/logging/logger.go +++ b/pkg/gofr/logging/logger.go @@ -219,3 +219,21 @@ func checkIfTerminal(w io.Writer) bool { func (l *logger) ChangeLevel(level Level) { l.level = level } + +// LogLevelResponder is an interface that provides a method to get the log level. +type LogLevelResponder interface { + LogLevel() Level +} + +// GetLogLevelForError returns the log level for the given error. +// If the error implements [logLevelResponder], its log level is returned. +// Otherwise, the default log level "error" is returned. +func GetLogLevelForError(err error) Level { + level := ERROR + + if e, ok := err.(LogLevelResponder); ok { + level = e.LogLevel() + } + + return level +}