Skip to content

Commit

Permalink
feat(log): allow to log target
Browse files Browse the repository at this point in the history
  • Loading branch information
jindrichskupa committed Apr 8, 2024
1 parent 5212e38 commit d3b2222
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 41 deletions.
38 changes: 27 additions & 11 deletions logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ type Config struct {
RequestIDHeaderName string `json:"RequestIDHeaderName,omitempty"` //nolint:tagliatelle // This is a configuration option
StatusCodes []int `json:"StatusCodes,omitempty"` //nolint:tagliatelle // This is a configuration option
ContentTypes []string `json:"ContentTypes,omitempty"` //nolint:tagliatelle // This is a configuration option
LogTarget string `json:"LogTarget,omitempty"` //nolint:tagliatelle // This is a configuration option
LogTargetURL string `json:"LogTargetUrl,omitempty"` //nolint:tagliatelle // This is a configuration option
Limits ConfigLimit `json:"Limits,omitempty"` //nolint:tagliatelle // This is a configuration option
}

Expand All @@ -42,6 +44,8 @@ type logRequest struct {
statusCodes []int
maxBodySize int
requestIDHeaderName string
logTarget string
logTargetURL string
}

// RequestLog holds the plugin configuration.
Expand Down Expand Up @@ -81,6 +85,8 @@ func New(_ context.Context, next http.Handler, config *Config, name string) (htt
contentTypes: config.ContentTypes,
statusCodes: config.StatusCodes,
maxBodySize: config.Limits.MaxBodySize,
logTarget: config.LogTarget,
logTargetURL: config.LogTargetURL,
}, nil
}

Expand Down Expand Up @@ -124,11 +130,7 @@ func (p *logRequest) ServeHTTP(w http.ResponseWriter, r *http.Request) {
Verb: r.Method,
}

if allowBodySize(len(requestBody), p.maxBodySize) && allowContentType(r.Header.Get("Content-Type"), p.contentTypes) {
reqData.Body = string(requestBody)
} else {
reqData.Body = fmt.Sprintf("Request body too large to log or wrong content type. Size: %d bytes, Content-type: %s", len(requestBody), r.Header.Get("Content-Type"))
}
reqData.Body = allowedBody(requestBody, r.Header.Get("Content-Type"), p.maxBodySize, p.contentTypes)

responseBody := io.NopCloser(bytes.NewBuffer(respBodyBytes))
responseBodyBytes, _ := io.ReadAll(responseBody)
Expand All @@ -144,11 +146,7 @@ func (p *logRequest) ServeHTTP(w http.ResponseWriter, r *http.Request) {
Time: time.Now().Format(time.RFC3339),
}

if allowBodySize(len(responseBodyBytes), p.maxBodySize) && allowContentType(resp.Header().Get("Content-Type"), p.contentTypes) {
resData.Body = string(responseBodyBytes)
} else {
resData.Body = fmt.Sprintf("Response body too large to log or wrong content type. Size: %d bytes, Content-type: %s", len(responseBodyBytes), resp.Header().Get("Content-Type"))
}
resData.Body = allowedBody(responseBodyBytes, resp.Header().Get("Content-Type"), p.maxBodySize, p.contentTypes)

log := requestLog{
RequestID: requestID,
Expand All @@ -163,12 +161,23 @@ func (p *logRequest) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Println(err)
}

if allowStatusCode(resp.code, p.statusCodes) {
if allowStatusCode(resp.code, p.statusCodes) && p.logTarget == "stdout" {
_, err = os.Stdout.WriteString(string(jsonData) + "\n")
if err != nil {
fmt.Println(err)
}
}

if allowStatusCode(resp.code, p.statusCodes) && p.logTarget == "stderr" {
_, err = os.Stderr.WriteString(string(jsonData) + "\n")
if err != nil {
fmt.Println(err)
}
}

if allowStatusCode(resp.code, p.statusCodes) && p.logTarget == "url" && p.logTargetURL != "" {
go http.Post(p.logTargetURL, "application/json", bytes.NewBuffer(jsonData)) //nolint:errcheck
}
}

type wrappedResponseWriter struct {
Expand Down Expand Up @@ -244,3 +253,10 @@ func allowStatusCode(statusCode int, statusCodes []int) bool {
func allowBodySize(bodySize, maxBodySize int) bool {
return bodySize <= maxBodySize
}

func allowedBody(body []byte, contentType string, maxBodySize int, contentTypes []string) string {
if allowBodySize(len(body), maxBodySize) && allowContentType(contentType, contentTypes) {
return string(body)
}
return fmt.Sprintf("Request body too large to log or wrong content type. Size: %d bytes, Content-type: %s", len(body), contentType)
}
49 changes: 19 additions & 30 deletions logger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,7 @@ import (
)

func TestGetPlaintext(t *testing.T) {
cfg := traefikmiddlewarerequestlogger.CreateConfig()
cfg.ContentTypes = []string{"application/json"}
cfg.Limits = traefikmiddlewarerequestlogger.ConfigLimit{MaxBodySize: 5}
cfg.RequestIDHeaderName = "X-Request-ID"
cfg := prepareConfig([]string{"text/plain"}, 5, []int{})

ctx := context.Background()
next := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
Expand Down Expand Up @@ -45,10 +42,7 @@ func TestGetPlaintext(t *testing.T) {
}

func TestGetJson(t *testing.T) {
cfg := traefikmiddlewarerequestlogger.CreateConfig()
cfg.ContentTypes = []string{"application/json"}
cfg.Limits = traefikmiddlewarerequestlogger.ConfigLimit{MaxBodySize: 1024}
cfg.RequestIDHeaderName = "X-Request-ID"
cfg := prepareConfig([]string{"application/json"}, 1024, []int{})

ctx := context.Background()
next := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
Expand Down Expand Up @@ -78,10 +72,7 @@ func TestGetJson(t *testing.T) {
}

func TestPostJson(t *testing.T) {
cfg := traefikmiddlewarerequestlogger.CreateConfig()
cfg.ContentTypes = []string{"application/json"}
cfg.Limits = traefikmiddlewarerequestlogger.ConfigLimit{MaxBodySize: 1024}
cfg.RequestIDHeaderName = "X-Request-ID"
cfg := prepareConfig([]string{"application/json"}, 1024, []int{})

ctx := context.Background()
next := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
Expand Down Expand Up @@ -111,10 +102,7 @@ func TestPostJson(t *testing.T) {
}

func TestPostJsonWithBodyLimit(t *testing.T) {
cfg := traefikmiddlewarerequestlogger.CreateConfig()
cfg.ContentTypes = []string{"application/json"}
cfg.Limits = traefikmiddlewarerequestlogger.ConfigLimit{MaxBodySize: 5}
cfg.RequestIDHeaderName = "X-Request-ID"
cfg := prepareConfig([]string{"application/json"}, 5, []int{})

ctx := context.Background()
next := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
Expand Down Expand Up @@ -144,10 +132,7 @@ func TestPostJsonWithBodyLimit(t *testing.T) {
}

func TestPostJsonWithContentType(t *testing.T) {
cfg := traefikmiddlewarerequestlogger.CreateConfig()
cfg.ContentTypes = []string{"application/json"}
cfg.Limits = traefikmiddlewarerequestlogger.ConfigLimit{MaxBodySize: 1024}
cfg.RequestIDHeaderName = "X-Request-ID"
cfg := prepareConfig([]string{"application/json"}, 1024, []int{})

ctx := context.Background()
next := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
Expand Down Expand Up @@ -178,11 +163,7 @@ func TestPostJsonWithContentType(t *testing.T) {
}

func TestPostJsonWithStatusCode(t *testing.T) {
cfg := traefikmiddlewarerequestlogger.CreateConfig()
cfg.ContentTypes = []string{"application/json"}
cfg.Limits = traefikmiddlewarerequestlogger.ConfigLimit{MaxBodySize: 1024}
cfg.RequestIDHeaderName = "X-Request-ID"
cfg.StatusCodes = []int{http.StatusInternalServerError}
cfg := prepareConfig([]string{"application/json"}, 1024, []int{http.StatusInternalServerError})

ctx := context.Background()
next := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
Expand Down Expand Up @@ -213,11 +194,7 @@ func TestPostJsonWithStatusCode(t *testing.T) {
}

func TestPostJsonWithStatusCodeFalse(t *testing.T) {
cfg := traefikmiddlewarerequestlogger.CreateConfig()
cfg.ContentTypes = []string{"application/json"}
cfg.Limits = traefikmiddlewarerequestlogger.ConfigLimit{MaxBodySize: 1024}
cfg.RequestIDHeaderName = "X-Request-ID"
cfg.StatusCodes = []int{http.StatusInternalServerError}
cfg := prepareConfig([]string{"application/json"}, 1024, []int{http.StatusInternalServerError})

ctx := context.Background()
next := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
Expand Down Expand Up @@ -263,3 +240,15 @@ func assertHeaderExists(t *testing.T, req *http.Request) {
t.Errorf("missing expected header: %s", "X-Request-ID")
}
}

func prepareConfig(contentTypes []string, maxBodySize int, statusCodes []int) *traefikmiddlewarerequestlogger.Config {
cfg := traefikmiddlewarerequestlogger.CreateConfig()
cfg.ContentTypes = contentTypes
cfg.LogTarget = "stdout"
cfg.LogTargetURL = ""
cfg.Limits = traefikmiddlewarerequestlogger.ConfigLimit{MaxBodySize: maxBodySize}
cfg.RequestIDHeaderName = "X-Request-ID"
cfg.StatusCodes = statusCodes

return cfg
}
2 changes: 2 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ Limits:
RequestIDHeaderName: X-Request-ID # save uniq request id into this header
StatusCodes: # log only these status codes
- 200
LogTarget: stdout # or "stderr" or "url"
LogTargetUrl: https://consumer.logs.example.com/input
```
Conditions use "AND" (all conditions must be true). When request or response size exeed limit, the info string is present.

0 comments on commit d3b2222

Please sign in to comment.