Documentation
¶
Overview ¶
Package eal (Extended Access Logging) is used to simplify access and error logging of GO endpoints. It can also be used to help create a structured way of handling error codes to be sent to frontend.
A small example of how this package can be used:
package main
import (
"net/http"
"github.com/labstack/echo/v4"
"github.com/modfin/eal"
)
type FrontendMessage struct {
ErrorCode int `json:"error_code"`
ErrorMessage string `json:"error_message"`
}
// Show two different echo.HTTPError examples, the first where the message parameter is set to a string.
// That will send JSON: {"message":"Nope"} to caller, and the second where te message parameter is set to an object
// (ErrSomeMessage), that will send back JSON: {"error_code:42,"error_message":"common.error.some_message"} to the caller.
var (
ErrNope error = echo.NewHTTPError(http.StatusForbidden, "Nope") // Return 403 {"message":"Nope"}, to caller
ErrUserDisabled error = echo.NewHTTPError(http.StatusForbidden, "User disabled") // Return 403 {"message":"User disabled"}, to caller
ErrSomeMessage error = echo.NewHTTPError(http.StatusNotFound, &FrontendMessage{ErrorCode: 42, ErrorMessage: "common.error.some_message"})
)
func ErrUser(err error) error {
return eal.NewHTTPError(eal.Trace(err), http.StatusInternalServerError, "User error") // Return 500 User error, to caller
}
func getUser(c echo.Context) (User, error) {
usr, err := dao.GetUser()
if err != nil {
// Failed to get user, send back generic user error message to caller
return nil, ErrUser(err)
}
if usr.Disabled {
// User is disabled, send back user disabled message to caller
return ErrUserDisabled
}
return usr, nil
}
func main() {
// Initialize logrus JSON logger.
eal.Init(false)
// Initialize eal default error logging for echo.HTTPError and jwt.ValidationError error types.
eal.InitDefaultErrorLogging()
// Create echo instance and set up the access logging middleware.
e := echo.New()
e.Use(eal.CreateLoggerMiddleware())
e.GET("/ping", func(c echo.Context) error {
usr, err := getUser(c)
if err != nil {
// When several echo.HTTPError exist in the error-chain, only the first/earliest will be sent to the caller.
return ErrUser(err)
}
return c.String(200, "pong")
})
e.GET("/nope", func(c echo.Context) error {
return ErrNope
})
}
Index ¶
- Variables
- func AddContextFields(c echo.Context, fields Fields)
- func CreateLoggerMiddleware(logFunctions ...ContextLogFunc) echo.MiddlewareFunc
- func GetInnerHTTPError(err error) *echo.HTTPError
- func InhibitStacktraceForError(err ...error)
- func Init(devMode bool)
- func InitDefaultErrorLogging()
- func NewHTTPError(err error, code int, msg ...interface{}) error
- func RegisterErrorLogFunc(errFmtFunc ErrLogFunc, errList ...error)
- func Trace(err error) error
- func UnwrapError(err error, fields map[string]interface{})
- type ContextLogFunc
- type CustomTextFormatter
- type Entry
- type ErrLogFunc
- type ErrorStackTrace
- type Fields
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var DefaultContextLogFunc = func(c echo.Context, fields Fields) { req := c.Request() res := c.Response() host := req.Header.Get("X-Host") if host == "" { alt := req.Header.Get("X-Forwarded-Host") if alt != "" { host = strings.Split(alt, ":")[0] req.Header.Set("X-Host", host) } } id := req.Header.Get("X-Request-Id") if id == "" { id = uuid.New().String() req.Header.Set("X-Request-Id", id) res.Header().Set("X-Request-Id", id) } // Attempt to get remote address of the client var remoteAddr string for _, h := range []string{"X-Forwarded-For", "X-Real-Ip", "X-Remote-Addr"} { remoteAddr = req.Header.Get(h) if remoteAddr != "" { break } } if remoteAddr == "" { remoteAddr = req.RemoteAddr } fields["request_id"] = id fields["remote_addr"] = remoteAddr fields["host"] = host fields["method"] = req.Method fields["uri"] = req.RequestURI fields["router_path"] = c.Path() }
var LogCallStackDirectly bool
LogCallStackDirectly control if an error message should be logged immediately with the callstack when the Trace method is called. If there is a chance that the error that is returned by the Trace method is thrown away before it's logged, LogCallStackDirectly can be set to true to log the callstack immediately.
Functions ¶
func AddContextFields ¶
AddContextFields add the fields to the log context, fields added to the context is included in logging done by the CreateLoggerMiddleware. The fields added by this method can also be logged elsewhere by using Entry.WithCtx method.
Example ¶
e := echo.New()
// Initialize the logging middleware
e.Use(CreateLoggerMiddleware())
e.GET("/ping", func(c echo.Context) error {
userID := c.FormValue("user-id")
// Add "user-id" field to context, that will be included in the log entry generated by the middleware when
// handler have returned.
AddContextFields(c, Fields{"user-id": userID})
return c.String(200, "")
})
func CreateLoggerMiddleware ¶
func CreateLoggerMiddleware(logFunctions ...ContextLogFunc) echo.MiddlewareFunc
CreateLoggerMiddleware return an echo middleware method that handle access and error logging of the call.
If an error is returned from the handlerFunc, the middleware will look at the complete error-chain to find the earliest echo.HTTPError, and return the status code and message from that to the frontend. If the error-chain don't contain an echo.HTTPError, a new echo.HTTPError will be created that wrap the returned error.
Example ¶
e := echo.New() e.Use(CreateLoggerMiddleware())
func GetInnerHTTPError ¶
GetInnerHTTPError check if the provided error is, or have a wrapped echo.HTTPError, and if there is one, it's returned. If the error chain contains more than one, the inner/earliest is returned.
func InhibitStacktraceForError ¶
func InhibitStacktraceForError(err ...error)
InhibitStacktraceForError will add the error types/instances to a map that is checked when Trace is called. If Trace is called with an error type/instance that exist in the map, a callstack won't be generated and Trace will return the error unmodified.
Example (ErrorReference) ¶
// Don't generate a stacktrace when Trace is called with a sql.ErrNoRows error. InhibitStacktraceForError(sql.ErrNoRows)
func Init ¶
func Init(devMode bool)
Init initialize the logrus logger. If devMode is true, a text based logger will be used, otherwise a JSON logger is used to output the log information to STDOUT.
func InitDefaultErrorLogging ¶
func InitDefaultErrorLogging()
InitDefaultErrorLogging register a error logger that append more information to the log for echo.HTTPError.
func NewHTTPError ¶
NewHTTPError complements echo.NewHTTPError, this also takes an error as a parameter.
func RegisterErrorLogFunc ¶
func RegisterErrorLogFunc(errFmtFunc ErrLogFunc, errList ...error)
RegisterErrorLogFunc registers a function that is called when a specific error interface is seen by UnwrapError. If you have your own error types (structs) that you want to log, it is easier to implement a SetLogFields method to handle logging. RegisterErrorLogFunc should be used for other error types that you don't have any control over, that contains information that isn't exposed via the Error() method or if you want to use structured logging for data in the error type, for example:
eal.RegisterErrorLogFunc(func(err error, fields eal.Fields) {
oe, ok := err.(*net.OpError)
if !ok {
return
}
fields["net_oper"] = oe.Op
fields["net_addr"] = oe.Addr.String()
fields["temporary"] = oe.Temporary()
fields["timeout"] = oe.Timeout()
}, (*net.OpError)(nil))
Example (Multiple) ¶
errFmt := func(err error, fields Fields) {
var i interface{} = err
switch e := i.(type) {
case *net.OpError:
fields["net_oper"] = e.Op
fields["net_addr"] = e.Addr.String()
fields["temporary"] = e.Temporary()
fields["timeout"] = e.Timeout()
case *net.ParseError:
fields["net_type"] = e.Type
fields["net_text"] = e.Text
}
}
RegisterErrorLogFunc(errFmt, (*net.OpError)(nil), (*net.ParseError)(nil))
Example (Single) ¶
RegisterErrorLogFunc(func(err error, fields Fields) {
oe, ok := err.(*net.OpError)
if !ok {
return
}
fields["net_oper"] = oe.Op
fields["net_addr"] = oe.Addr.String()
fields["temporary"] = oe.Temporary()
fields["timeout"] = oe.Timeout()
}, (*net.OpError)(nil))
func Trace ¶
Trace can wrap the provided error in a ErrorStackTrace type that contain the callstack. If the provided error type/instance have been added to the inhibit-map by calling InhibitStacktraceForError, the error will be returned as-is and won't be wrapped in a ErrorStackTrace type. If the provided error already is, or contain a wrapped ErrorStackTrace error, the error is also returned as-is.
func UnwrapError ¶
UnwrapError walks the error-chain and add information to the provided log-fields. For each error in the error-chain, it will check if the error either implements the SetLogFields(map[string]interface{}) interface or if the type have a registered log function that is used to populate the log-fields. This is used by Entry.WithError to add error information to a log event.
Types ¶
type ContextLogFunc ¶
ContextLogFunc can be implemented to be able to add log fields from an echo context.
type CustomTextFormatter ¶
type CustomTextFormatter struct{}
type Entry ¶
Entry extend the logrus.Entry type, with additional convenience methods: WithCtx, WithError and WithFields, to simplify logging.
func NewEntry ¶
func NewEntry() *Entry
NewEntry return an Entry instance to be used for creating a log entry. For example:
eal.NewEntry().Info("App started")
func (*Entry) WithError ¶
WithError uses UnwrapError internally to extract more information from the error and add it to the log entry fields.
See UnwrapError and RegisterErrorLogFunc methods for more information about how to extend the log entry fields.
func (*Entry) WithFields ¶
WithFields adds custom fields (key/value) to the log entry. For example:
eal.NewEntry().WithFields(eal.Fields{"time": time.Since(start)}).Info("Work completed")
type ErrLogFunc ¶
ErrLogFunc type can be implemented to be able to add log fields for a specific error.
See RegisterErrorLogFunc and UnwrapError regarding the SetLogFields interface for more information.
type ErrorStackTrace ¶
type ErrorStackTrace struct {
// contains filtered or unexported fields
}
ErrorStackTrace is created by the Trace function and hold a stacktrace to where Trace where first called. The error message returned by Error isn't changed from the original error message. To retrieve the recorded callstack, the Stack function can be used, the callstack is also logged so the only way to retrieve the callstack, is to either walk the chain of errors
func GetErrorStackTrace ¶
func GetErrorStackTrace(err error) (st *ErrorStackTrace, ok bool)
GetErrorStackTrace check if the provided error is, or have a wrapped ErrorStackTrace, and if there is one, it's returned.
func (*ErrorStackTrace) Error ¶
func (st *ErrorStackTrace) Error() string
Error return the wrapped errors message, the ErrorStackTrace type don't add the stacktrace information to the error string. The stacktrace can be accessed by calling Stack, or through SetLogFields.
func (*ErrorStackTrace) SetLogFields ¶
func (st *ErrorStackTrace) SetLogFields(logFields map[string]interface{})
SetLogFields is used by Entry.WithError to populate log fields.
func (*ErrorStackTrace) Stack ¶
func (st *ErrorStackTrace) Stack() string
Stack return the stacktrace to where the ErrorStackTrace first were inserted in the error chain.
func (*ErrorStackTrace) TypeName ¶
func (st *ErrorStackTrace) TypeName() string
TypeName return the name of the wrapped error struct.
func (*ErrorStackTrace) Unwrap ¶
func (st *ErrorStackTrace) Unwrap() error
Unwrap return the wrapped error.