Skip to content
Open
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
16 changes: 15 additions & 1 deletion gateway/mw_go_plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,20 @@ func (m *GoPluginMiddleware) handlePluginResponse(
return ErrResponseSucceed, middleware.StatusRespond
}

// levelForPluginStatus maps the status code sent by a Go-plugin to the level
// used for the "Failed to process request" log line. Codes conventionally used
// for intentional throttling and backpressure are logged at Warn so that
// plugin rejects do not flood severity-based alerting; everything else,
// including auth failures, stays at Error.
func levelForPluginStatus(code int) logrus.Level {
switch code {
case http.StatusRequestTimeout, http.StatusTeapot, http.StatusTooManyRequests, http.StatusServiceUnavailable:
return logrus.WarnLevel
default:
return logrus.ErrorLevel
}
}

func (m *GoPluginMiddleware) handleErrorResponse(
r *http.Request,
rw *customResponseWriter,
Expand All @@ -339,7 +353,7 @@ func (m *GoPluginMiddleware) handleErrorResponse(

// base middleware will report this error to analytics if needed
err := fmt.Errorf("plugin function sent error response code: %d", rw.statusCodeSent)
logger.WithError(err).Error("Failed to process request with Go-plugin middleware func")
logger.WithError(err).Log(levelForPluginStatus(rw.statusCodeSent), "Failed to process request with Go-plugin middleware func")

if rw.responseSent {
err = fmt.Errorf("%w: %w", ErrResponseErrorSent, err)
Expand Down
53 changes: 53 additions & 0 deletions gateway/mw_go_plugin_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package gateway

import (
"net/http"
"net/http/httptest"
"testing"

"github.com/sirupsen/logrus"
"github.com/sirupsen/logrus/hooks/test"
"github.com/stretchr/testify/assert"

"github.com/TykTechnologies/tyk/apidef"
Expand Down Expand Up @@ -53,3 +57,52 @@ func TestGoPluginMiddleware_EnabledForSpec(t *testing.T) {
})
})
}

func TestGoPluginMiddleware_handleErrorResponseLogLevel(t *testing.T) {
logger, hook := test.NewNullLogger()
entry := logrus.NewEntry(logger)

m := &GoPluginMiddleware{BaseMiddleware: &BaseMiddleware{}}

tests := []struct {
name string
statusCode int
expectedLevel logrus.Level
}{
{"request timeout logs at warn", http.StatusRequestTimeout, logrus.WarnLevel},
{"teapot logs at warn", http.StatusTeapot, logrus.WarnLevel},
{"rate limit logs at warn", http.StatusTooManyRequests, logrus.WarnLevel},
{"service unavailable logs at warn", http.StatusServiceUnavailable, logrus.WarnLevel},
{"bad request logs at error", http.StatusBadRequest, logrus.ErrorLevel},
{"unauthorized logs at error", http.StatusUnauthorized, logrus.ErrorLevel},
{"internal server error logs at error", http.StatusInternalServerError, logrus.ErrorLevel},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
hook.Reset()

r := httptest.NewRequest(http.MethodGet, "/test", nil)
rw := &customResponseWriter{
ResponseWriter: httptest.NewRecorder(),
responseSent: true,
statusCodeSent: tc.statusCode,
}

err, code := m.handleErrorResponse(r, rw, entry)

assert.ErrorIs(t, err, ErrResponseErrorSent)
assert.Equal(t, tc.statusCode, code)

if assert.Len(t, hook.Entries, 1) {
assert.Equal(t, tc.expectedLevel, hook.LastEntry().Level)
assert.Equal(t, "Failed to process request with Go-plugin middleware func", hook.LastEntry().Message)
}
})
}

// 403 is not in the table because handleErrorResponse fires
// EventAuthFailure for it, which needs the full test framework; assert
// its mapping directly on the pure function instead.
assert.Equal(t, logrus.ErrorLevel, levelForPluginStatus(http.StatusForbidden))
}