From 264e9667fe037351501cb0e029996a391e1012c7 Mon Sep 17 00:00:00 2001 From: chiao Date: Sat, 6 Jan 2024 20:31:44 +0800 Subject: [PATCH] first commit --- .gitignore | 1 + .golangci.yml | 140 ++++++++++++++++++++++++++++++++++++++++++++++++++ .traefik.yml | 6 +++ README.md | 41 +++++++++++++++ go.mod | 3 ++ go.sum | 0 main.go | 118 ++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 309 insertions(+) create mode 100644 .gitignore create mode 100644 .golangci.yml create mode 100644 .traefik.yml create mode 100644 README.md create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cad2309 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/tmp \ No newline at end of file diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..403fa1a --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,140 @@ +# from https://github.com/golangci/golangci-lint/blob/master/.golangci.yml +linters-settings: + depguard: + list-type: blacklist + packages: + # logging is allowed only by logutils.Log, logrus + # is allowed to use only in logutils package + - github.com/sirupsen/logrus + packages-with-error-message: + - github.com/sirupsen/logrus: "logging is allowed only by logutils.Log" + dupl: + threshold: 100 + exhaustive: + default-signifies-exhaustive: false + funlen: + lines: 100 + statements: 50 + gci: + local-prefixes: github.com/golangci/golangci-lint + goconst: + min-len: 2 + min-occurrences: 2 + gocritic: + enabled-tags: + - diagnostic + - experimental + - opinionated + - performance + - style + disabled-checks: + - dupImport # https://github.com/go-critic/go-critic/issues/845 + - ifElseChain + - octalLiteral + - whyNoLint + - wrapperFunc + gocyclo: + min-complexity: 15 + goimports: + local-prefixes: github.com/golangci/golangci-lint + golint: + min-confidence: 0 + gomnd: + settings: + mnd: + # don't include the "operation" and "assign" + checks: argument,case,condition,return + govet: + check-shadowing: true + settings: + printf: + funcs: + - (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof + - (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf + - (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf + - (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf + lll: + line-length: 140 + maligned: + suggest-new: true + misspell: + locale: US + nolintlint: + allow-leading-space: true # don't require machine-readable nolint directives (i.e. with no leading space) + allow-unused: false # report any unused nolint directives + require-explanation: false # don't require an explanation for nolint directives + require-specific: false # don't require nolint directives to be specific about which linter is being skipped + +linters: + # please, do not use `enable-all`: it's deprecated and will be removed soon. + # inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint + disable-all: true + enable: + - bodyclose + - deadcode + - depguard + - dogsled + - dupl + - errcheck + - exhaustive + - funlen + - gochecknoinits + - goconst + - gocritic + - gocyclo + - gofmt + - goimports + - golint + - gomnd + - goprintffuncname + - gosec + - gosimple + - govet + - ineffassign + - interfacer + - lll + - misspell + - nakedret + - noctx + - nolintlint + - rowserrcheck + - scopelint + - staticcheck + - structcheck + - stylecheck + - typecheck + - unconvert + - unparam + - unused + - varcheck + - whitespace + + # don't enable: + # - asciicheck + # - gochecknoglobals + # - gocognit + # - godot + # - godox + # - goerr113 + # - maligned + # - nestif + # - prealloc + # - testpackage + # - wsl + +issues: + # Excluding configuration per-path, per-linter, per-text and per-source + exclude-rules: + - path: _test\.go + linters: + - gomnd + + # https://github.com/go-critic/go-critic/issues/926 + - linters: + - gocritic + text: "unnecessaryDefer:" + +run: + skip-dirs: + - vendor + - hack diff --git a/.traefik.yml b/.traefik.yml new file mode 100644 index 0000000..02a350f --- /dev/null +++ b/.traefik.yml @@ -0,0 +1,6 @@ +displayName: Request Logger +summary: Send request info to stdout. +type: middleware +import: github.com/joy2fun/traefik-plugin-log-request +testData: + responseBody: true diff --git a/README.md b/README.md new file mode 100644 index 0000000..40c7757 --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +# Traefik plugin to log requests + +file provider example: + +```yml +http: + middlewares: + my-plugin: + plugin: + log-request: + responseBody: true # also including response body +``` + +crd example: + +```yml +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: log-request +spec: + plugin: + log-request: + responseBody: true +``` + +helm chart values example: + +```yaml +additionalArguments: + - >- + --experimental.localplugins.log-request.modulename=github.com/joy2fun/traefik-plugin-log-request +additionalVolumeMounts: + - mountPath: /plugins-local + name: plugins +deployment: + additionalVolumes: + - hostPath: + path: /data/plugins-local + name: plugins +``` \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..239a0f4 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/joy2fun/traefik-plugin-log-request + +go 1.14 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e69de29 diff --git a/main.go b/main.go new file mode 100644 index 0000000..a88dee4 --- /dev/null +++ b/main.go @@ -0,0 +1,118 @@ +package traefik_plugin_log_request + +import ( + "bufio" + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net" + "net/http" + "os" +) + +// Config holds the plugin configuration. +type Config struct { + ResponseBody bool `json:"responseBody,omitempty"` +} + +// CreateConfig creates and initializes the plugin configuration. +func CreateConfig() *Config { + return &Config{} +} + +type logRequest struct { + name string + next http.Handler + responseBody bool +} + +type RequestData struct { + URL string `json:"url"` + Host string `json:"host"` + Body string `json:"body"` + Headers string `json:"headers"` + ResponseBody string `json:"response_body"` +} + +func New(_ context.Context, next http.Handler, config *Config, name string) (http.Handler, error) { + return &logRequest{ + name: name, + next: next, + responseBody: config.ResponseBody, + }, nil +} + +func (p *logRequest) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + body, err := io.ReadAll(req.Body) + if err != nil { + } + + req.Body = io.NopCloser(bytes.NewBuffer(body)) + + wrappedWriter := &responseWriter{ + ResponseWriter: rw, + } + + p.next.ServeHTTP(wrappedWriter, req) + + bodyBytes := wrappedWriter.buffer.Bytes() + rw.Write(bodyBytes) + + headers := make(map[string]string) + for name, values := range req.Header { + headers[name] = values[0] // Take the first value of the header + } + + jsonHeader, err := json.Marshal(headers) + if err != nil { + } + + requestData := RequestData{ + URL: req.URL.String(), + Host: req.Host, + Body: string(body), + Headers: string(jsonHeader), + } + + if p.responseBody { + responseBody := io.NopCloser(bytes.NewBuffer(bodyBytes)) + responseBodyBytes, err := io.ReadAll(responseBody) + if err != nil { + } + + requestData.ResponseBody = string(responseBodyBytes) + } + + jsonData, err := json.Marshal(requestData) + if err != nil { + } + + os.Stdout.WriteString(string(jsonData) + "\n") +} + +type responseWriter struct { + buffer bytes.Buffer + + http.ResponseWriter +} + +func (r *responseWriter) Write(p []byte) (int, error) { + return r.buffer.Write(p) +} + +func (r *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + hijacker, ok := r.ResponseWriter.(http.Hijacker) + if !ok { + return nil, nil, fmt.Errorf("%T is not a http.Hijacker", r.ResponseWriter) + } + + return hijacker.Hijack() +} + +func (r *responseWriter) Flush() { + if flusher, ok := r.ResponseWriter.(http.Flusher); ok { + flusher.Flush() + } +}