Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tidy up middlewares #1487

Merged
merged 1 commit into from
Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
127 changes: 124 additions & 3 deletions src/api/rest/server/middlewares/custom-middleware.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package middlewares

import (
"encoding/json"
"fmt"
"time"

"github.com/cloud-barista/cb-tumblebug/src/core/common"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"github.com/rs/zerolog/log"
Expand Down Expand Up @@ -39,13 +44,13 @@ func Zerologger() echo.MiddlewareFunc {
log.Error().
Err(v.Error).
Str("id", v.RequestID).
Str("remote_ip", v.RemoteIP).
Str("host", v.Host).
Str("client_ip", v.RemoteIP).
// Str("host", v.Host).
Str("method", v.Method).
Str("URI", v.URI).
//Str("user_agent", v.UserAgent).
Int("status", v.Status).
Int64("latency", v.Latency.Nanoseconds()).
// Int64("latency", v.Latency.Nanoseconds()).
Str("latency_human", v.Latency.String()).
Str("bytes_in", v.ContentLength).
Int64("bytes_out", v.ResponseSize).
Expand All @@ -55,3 +60,119 @@ func Zerologger() echo.MiddlewareFunc {
},
})
}

func RequestIdAndDetailsIssuer(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// log.Debug().Msg("Start - Request ID middleware")

// Make X-Request-Id visible to all handlers
c.Response().Header().Set("Access-Control-Expose-Headers", echo.HeaderXRequestID)

// Get or generate Request ID
reqID := c.Request().Header.Get(echo.HeaderXRequestID)
if reqID == "" {
reqID = fmt.Sprintf("%d", time.Now().UnixNano())
}

// Set Request on the context
c.Set("RequestID", reqID)

log.Trace().Msgf("(Request ID middleware) Request ID: %s", reqID)
if _, ok := common.RequestMap.Load(reqID); ok {
return fmt.Errorf("the X-Request-Id is already in use")
}

// Set "X-Request-Id" in response header
c.Response().Header().Set(echo.HeaderXRequestID, reqID)

details := common.RequestDetails{
StartTime: time.Now(),
Status: "Handling",
RequestInfo: common.ExtractRequestInfo(c.Request()),
}
common.RequestMap.Store(reqID, details)

// log.Debug().Msg("End - Request ID middleware")

return next(c)
}
}

func ResponseBodyDump() echo.MiddlewareFunc {
return middleware.BodyDumpWithConfig(middleware.BodyDumpConfig{
Skipper: func(c echo.Context) bool {
if c.Path() == "/tumblebug/api" {
return true
}
return false
},
Handler: func(c echo.Context, reqBody, resBody []byte) {
// log.Debug().Msg("Start - BodyDump() middleware")

// Get the request ID
reqID := c.Get("RequestID").(string)
log.Trace().Msgf("(BodyDump middleware) Request ID: %s", reqID)

// Get the content type
contentType := c.Response().Header().Get(echo.HeaderContentType)
log.Trace().Msgf("contentType: %s", contentType)

// Dump the response body if content type is "application/json" or "application/json; charset=UTF-8"
if contentType == echo.MIMEApplicationJSONCharsetUTF8 || contentType == echo.MIMEApplicationJSON {
// Load or check the request by ID
if v, ok := common.RequestMap.Load(reqID); ok {
log.Trace().Msg("OK, common.RequestMap.Load(reqID)")
details := v.(common.RequestDetails)
details.EndTime = time.Now()

// Set "X-Request-Id" in response header
c.Response().Header().Set(echo.HeaderXRequestID, reqID)

// Unmarshal the response body
var resData interface{} // Use interface{} to handle both objects and arrays
err := json.Unmarshal(resBody, &resData)
if err != nil {
log.Error().Err(err).Msgf("Error while unmarshaling response body: %s", err)
log.Debug().Msgf("Type of resBody: %T", resData)
log.Debug().Msgf("Request body: %s", string(reqBody))
log.Debug().Msgf("Response body: %s", string(resBody))
return
}

switch data := resData.(type) {
case map[string]interface{}:
// 1XX: Information responses
// 2XX: Successful responses (200 OK, 201 Created, 202 Accepted, 204 No Content)
// 3XX: Redirection messages
// 4XX: Client error responses (400 Bad Request, 401 Unauthorized, 404 Not Found, 408 Request Timeout)
// 5XX: Server error responses (500 Internal Server Error, 501 Not Implemented, 503 Service Unavailable)
if c.Response().Status >= 400 && c.Response().Status <= 599 {
log.Trace().Msgf("c.Response().Status (%d)", c.Response().Status)
details.Status = "Error"
details.ErrorResponse = data["message"].(string)
} else {
log.Trace().Msgf("c.Response().Status (%d)", c.Response().Status)
details.Status = "Success"
details.ResponseData = data
}
case []interface{}:
log.Trace().Msgf("c.Response().Status (%d)", c.Response().Status)
details.Status = "Success"
details.ResponseData = data
case string:
log.Trace().Msgf("c.Response().Status (%d)", c.Response().Status)
details.Status = "Success"
details.ResponseData = data
default:
log.Error().Msgf("Unknown response data type: %T", data)
}

// Store details of the request
common.RequestMap.Store(reqID, details)
}
}

// log.Debug().Msg("Start - BodyDump() middleware")
},
})
}
103 changes: 4 additions & 99 deletions src/api/rest/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ package server

import (
"context"
"encoding/json"

// "log"
"os/signal"
Expand Down Expand Up @@ -95,105 +94,11 @@ func RunServer(port string) {
// limit the application to 20 requests/sec using the default in-memory store
e.Use(middleware.RateLimiter(middleware.NewRateLimiterMemoryStore(20)))

// Customized middleware for request logging
e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// log.Debug().Msg("Start - Request ID middleware")
// Custom middleware for RequestID and RequestDetails
e.Use(middlewares.RequestIdAndDetailsIssuer)

// Make X-Request-Id visible to all handlers
c.Response().Header().Set("Access-Control-Expose-Headers", echo.HeaderXRequestID)

// Get or generate Request ID
reqID := c.Request().Header.Get(echo.HeaderXRequestID)
if reqID == "" {
reqID = fmt.Sprintf("%d", time.Now().UnixNano())
}

// Set Request on the context
c.Set("RequestID", reqID)

log.Debug().Msgf("(Request ID middleware) Request ID: %s", reqID)
if _, ok := common.RequestMap.Load(reqID); ok {
return fmt.Errorf("the X-Request-Id is already in use")
}

// Set "X-Request-Id" in response header
c.Response().Header().Set(echo.HeaderXRequestID, reqID)

details := common.RequestDetails{
StartTime: time.Now(),
Status: "Handling",
RequestInfo: common.ExtractRequestInfo(c.Request()),
}
common.RequestMap.Store(reqID, details)

// log.Debug().Msg("End - Request ID middleware")

return next(c)
}
})

e.Use(middleware.BodyDumpWithConfig(middleware.BodyDumpConfig{
Skipper: func(c echo.Context) bool {
if c.Path() == "/tumblebug/api" {
return true
}
return false
},
Handler: func(c echo.Context, reqBody, resBody []byte) {
// log.Debug().Msg("Start - BodyDump() middleware")

// Get the request ID
reqID := c.Get("RequestID").(string)
log.Debug().Msgf("(BodyDump middleware) Request ID: %s", reqID)

// Get the content type
contentType := c.Response().Header().Get(echo.HeaderContentType)
log.Trace().Msgf("contentType: %s", contentType)

// Dump the response body if content type is "application/json" or "application/json; charset=UTF-8"
if contentType == echo.MIMEApplicationJSONCharsetUTF8 || contentType == echo.MIMEApplicationJSON {
// Load or check the request by ID
if v, ok := common.RequestMap.Load(reqID); ok {
log.Trace().Msg("OK, common.RequestMap.Load(reqID)")
details := v.(common.RequestDetails)
details.EndTime = time.Now()

// Set "X-Request-Id" in response header
c.Response().Header().Set(echo.HeaderXRequestID, reqID)

// Unmarshal the response body
var resMap map[string]interface{}
err := json.Unmarshal(resBody, &resMap)
if err != nil {
log.Error().Err(err).Msgf("Error while unmarshaling response body: %s", err)
log.Debug().Msgf("Response body: %s", string(reqBody))
log.Debug().Msgf("Response body: %s", string(resBody))
}

// 1XX: Information responses
// 2XX: Successful responses (200 OK, 201 Created, 202 Accepted, 204 No Content)
// 3XX: Redirection messages
// 4XX: Client error responses (400 Bad Request, 401 Unauthorized, 404 Not Found, 408 Request Timeout)
// 5XX: Server error responses (500 Internal Server Error, 501 Not Implemented, 503 Service Unavailable)
if c.Response().Status >= 400 && c.Response().Status <= 599 {
log.Trace().Msg("Error, c.Response().Status")
details.Status = "Error"
details.ErrorResponse = resMap["message"].(string)
} else {
log.Trace().Msg("Not error, c.Response().Status")
details.Status = "Success"
details.ResponseData = resMap
}

// Store details of the request
common.RequestMap.Store(reqID, details)
}
}

// log.Debug().Msg("Start - BodyDump() middleware")
},
}))
// Custom middleware for ResponseBodyDump
e.Use(middlewares.ResponseBodyDump())

e.HideBanner = true
//e.colorer.Printf(banner, e.colorer.Red("v"+Version), e.colorer.Blue(website))
Expand Down