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
20 changes: 20 additions & 0 deletions middleware/gapchi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# gaphttp

handler and client middleware, and other tools

### CLIENT ROUND TRIPPER

```go
httpClient := &http.Client{
Transport: NewMiddlewareRoundTrip{http.DefaultTransport},
}
```

### HTTP MIDDLEWARE

```go
router := chi.NewRouter()
router.Use(NewMiddleware())

router.Post("/do", func(writer http.ResponseWriter, request *http.Request) {})
```
19 changes: 19 additions & 0 deletions middleware/gapchi/chi_path_cleaner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package gaphttp

import (
"net/http"
"strings"

"github.com/go-chi/chi/v5"
)

func RemoveChiPathParam(request *http.Request) string {
path := request.URL.Path
if rctx := chi.RouteContext(request.Context()); rctx != nil {
for i, v := range rctx.URLParams.Values {
path = strings.Replace(path, v, rctx.URLParams.Keys[i], 1)
}
}

return path
}
20 changes: 20 additions & 0 deletions middleware/gapchi/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module github.com/tel-io/instrumentation/middleware/gapchi

go 1.19

require (
github.com/go-chi/chi/v5 v5.0.7
github.com/stretchr/testify v1.8.1
go.opentelemetry.io/otel v1.11.1
go.opentelemetry.io/otel/metric v0.33.0
go.opentelemetry.io/otel/trace v1.11.1
go.uber.org/zap v1.23.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
36 changes: 36 additions & 0 deletions middleware/gapchi/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8=
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
go.opentelemetry.io/otel v1.11.1 h1:4WLLAmcfkmDk2ukNXJyq3/kiz/3UzCaYq6PskJsaou4=
go.opentelemetry.io/otel v1.11.1/go.mod h1:1nNhXBbWSD0nsL38H6btgnFN2k4i0sNLHNNMZMSbUGE=
go.opentelemetry.io/otel/metric v0.33.0 h1:xQAyl7uGEYvrLAiV/09iTJlp1pZnQ9Wl793qbVvED1E=
go.opentelemetry.io/otel/metric v0.33.0/go.mod h1:QlTYc+EnYNq/M2mNk1qDDMRLpqCOj2f/r5c7Fd5FYaI=
go.opentelemetry.io/otel/trace v1.11.1 h1:ofxdnzsNrGBYXbP7t7zpUK281+go5rF7dvdIZXF8gdQ=
go.opentelemetry.io/otel/trace v1.11.1/go.mod h1:f/Q9G7vzk5u91PhbmKbg1Qn0rzH1LJ4vbPHFGkTPtOk=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY=
go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
56 changes: 56 additions & 0 deletions middleware/gapchi/handler_basic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package gaphttp

import (
"context"
"encoding/json"
"net/http"

"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
)

type HandlerBasic struct {
ServiceName string
Logger *zap.Logger
}

func NewHandlerBasic(svcName string, log *zap.Logger) *HandlerBasic {
return &HandlerBasic{
ServiceName: svcName,
Logger: log,
}
}

func (h *HandlerBasic) OK(writer http.ResponseWriter, request *http.Request) {
h.Write(request.Context(), writer, http.StatusOK, h.ServiceName)
}

func (h *HandlerBasic) NotFound(writer http.ResponseWriter, request *http.Request) {
h.Write(request.Context(), writer, http.StatusNotFound, "invalid path")
}

func (h *HandlerBasic) Write(ctx context.Context, writer http.ResponseWriter, statusCode int, response interface{}) {
writer.Header().Set("Content-Type", "application/json")
writer.WriteHeader(statusCode)

if out, ok := response.([]byte); ok {
if _, err := writer.Write(out); err != nil {
h.Log(ctx).Error("write response", zap.Error(err), zap.ByteString("response", out))
}

return
}

if err := json.NewEncoder(writer).Encode(response); err != nil {
h.Log(ctx).Error("json encoder, write response", zap.Error(err), zap.Any("response", response))
}
}

func (h *HandlerBasic) Log(ctx context.Context) *zap.Logger {
spCtx := trace.SpanContextFromContext(ctx)

return h.Logger.With(
zap.String("traceID", spCtx.TraceID().String()),
zap.String("spanID", spCtx.SpanID().String()),
)
}
154 changes: 154 additions & 0 deletions middleware/gapchi/middleware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package gaphttp

import (
"bytes"
"errors"
"net/http"

"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
)

type Middleware func(next http.Handler) http.Handler

type MiddlewareOptions struct {
ServiceName string
EnabledLogger bool
EnabledTracer bool
EnabledRecover bool
EnabledMetrics bool
}

const (
builderNameMiddlewareTrace = "trace.middleware"
builderNameMiddlewareLogger = "logger.middleware"
builderNameMiddlewareRecovery = "recovery.middleware"
builderNameMiddlewareMetrics = "metrics.middleware"
)

type MiddlewareBuilder struct {
opt *MiddlewareOptions
logger *zap.Logger
tracer trace.Tracer
metrics Metrics
middlewares map[string]func() func(next http.Handler) http.Handler
}

func NewMiddlewareBuilder(
opt *MiddlewareOptions,
logger *zap.Logger,
tracer trace.Tracer,
metrics Metrics,
) *MiddlewareBuilder {
return &MiddlewareBuilder{
opt: opt,
logger: logger,
tracer: tracer,
metrics: metrics,
middlewares: make(map[string]func() func(next http.Handler) http.Handler),
}

}

func (b *MiddlewareBuilder) AddTrace() *MiddlewareBuilder {
b.middlewares[builderNameMiddlewareTrace] = func() func(next http.Handler) http.Handler {
return NewMiddlewareTracer(b.tracer, b.opt)
}

return b
}

func (b *MiddlewareBuilder) AddLogger() *MiddlewareBuilder {
b.middlewares[builderNameMiddlewareLogger] = func() func(next http.Handler) http.Handler {
return NewMiddlewareLogger(b.logger, b.opt)
}

return b
}

func (b *MiddlewareBuilder) AddMiddlewareRecover() *MiddlewareBuilder {
b.middlewares[builderNameMiddlewareRecovery] = func() func(next http.Handler) http.Handler {
return NewMiddlewareRecovery(b.logger, b.opt)
}

return b
}

func (b *MiddlewareBuilder) AddMiddlewareMeter() *MiddlewareBuilder {
b.middlewares[builderNameMiddlewareMetrics] = func() func(next http.Handler) http.Handler {
return NewMiddlewareMetrics(b.metrics, b.opt)
}

return b
}

// Build - build middleware and create correct position in stack
func (b *MiddlewareBuilder) Build() ([]func(next http.Handler) http.Handler, error) {
if b.opt == nil {
return nil, errors.New("options cannot be blank")
}

out := make([]func(next http.Handler) http.Handler, 0, len(b.middlewares))

recMidForMid, ok := b.middlewares[builderNameMiddlewareRecovery]
if ok {
out = append(out, recMidForMid())
}

metrMid, ok := b.middlewares[builderNameMiddlewareMetrics]
if ok {
out = append(out, metrMid())
}

traceMid, ok := b.middlewares[builderNameMiddlewareTrace]
if ok {
out = append(out, traceMid())
}

logMid, ok := b.middlewares[builderNameMiddlewareLogger]
if ok {
out = append(out, logMid())
}

recMidSrv, ok := b.middlewares[builderNameMiddlewareRecovery]
if ok {
out = append(out, recMidSrv())
}

return out, nil
}

// WrapWriter supported butch loading
type WrapWriter struct {
status int
body *bytes.Buffer
inner http.ResponseWriter
}

func NewWrapWriter(inner http.ResponseWriter) *WrapWriter {
return &WrapWriter{body: bytes.NewBuffer([]byte{}), inner: inner}
}

func (w *WrapWriter) Header() http.Header {
return w.inner.Header()
}

func (w *WrapWriter) Write(i []byte) (int, error) {
w.body.Write(i)

return w.inner.Write(i)
}

func (w *WrapWriter) WriteHeader(statusCode int) {
w.status = statusCode

w.inner.WriteHeader(statusCode)
}

func (w *WrapWriter) Status() int {
return w.status
}

func (w *WrapWriter) Body() []byte {
return w.body.Bytes()
}
67 changes: 67 additions & 0 deletions middleware/gapchi/middleware_logger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package gaphttp

import (
"bytes"
"io"
"net/http"

"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
)

func NewMiddlewareLogger(logger *zap.Logger, option *MiddlewareOptions) Middleware {
// logger enabled
return func(next http.Handler) http.Handler {
return http.HandlerFunc(
func(writer http.ResponseWriter, request *http.Request) {
// set trace id and span id to logger
span := trace.SpanFromContext(request.Context())

log := logger.With(
zap.String("traceID", span.SpanContext().TraceID().String()),
zap.String("spanID", span.SpanContext().SpanID().String()),
)

log = log.With(
zap.String("http.request.method", request.Method),
zap.Any("http.request.header", request.Header),
zap.Any("http.request.ip", request.RemoteAddr),
zap.Any("http.request.path", request.URL.Path),
zap.Any("http.request.query", request.URL.Query()),
zap.Any("http.request.cookie", request.Cookies()),
)

// set request body to logger if exist
if request.Body != nil {
reqBody, err := io.ReadAll(request.Body)
if err != nil {
logger.Error("http.request.body.read", zap.Error(err))
}

log = log.With(zap.ByteString("http.request.body", reqBody))

request.Body = io.NopCloser(bytes.NewBuffer(reqBody)) // Reset
}

wrapWriter := NewWrapWriter(writer)

log.Debug("handle started!")

next.ServeHTTP(wrapWriter, request)

if wrapWriter.status == http.StatusOK {
log.Debug(
"handle stopped!",
zap.Int("http.response.status", wrapWriter.Status()),
zap.ByteString("http.response.body", wrapWriter.Body()),
)
} else {
log.Error("handle stopped!",
zap.Int("http.response.status", wrapWriter.Status()),
zap.ByteString("http.response.body", wrapWriter.Body()),
)
}
},
)
}
}
Loading