Skip to content

Commit

Permalink
fixup! feat: config logger and stats common packages
Browse files Browse the repository at this point in the history
  • Loading branch information
atzoum committed Mar 15, 2023
1 parent cb99d98 commit a7a3669
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 0 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/cenkalti/backoff/v4 v4.2.0
github.com/fsnotify/fsnotify v1.6.0
github.com/golang/mock v1.6.0
github.com/gorilla/mux v1.8.0
github.com/joho/godotenv v1.5.1
github.com/ory/dockertest/v3 v3.9.1
github.com/spf13/viper v1.15.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QGdLI4y34qQGjGWO0=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks=
Expand Down
81 changes: 81 additions & 0 deletions gorillaware/stats.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package gorillaware

import (
"context"
"fmt"
"net/http"
"strconv"
"sync/atomic"
"time"

"github.com/gorilla/mux"
"github.com/rudderlabs/rudder-go-kit/stats"
)

func StatMiddleware(ctx context.Context, router *mux.Router, s stats.Stats, component string) func(http.Handler) http.Handler {
var concurrentRequests int32
activeClientCount := s.NewStat(fmt.Sprintf("%s.concurrent_requests_count", component), stats.GaugeType)
go func() {
for {
select {
case <-ctx.Done():
return
case <-time.After(10 * time.Second):
activeClientCount.Gauge(atomic.LoadInt32(&concurrentRequests))
}
}
}()

// getPath retrieves the path from the request.
// The matched route's template is used if a match is found,
// otherwise the request's URL path is used instead.
getPath := func(r *http.Request) string {
var match mux.RouteMatch
if router.Match(r, &match) {
if path, err := match.Route.GetPathTemplate(); err == nil {
return path
}
}
return r.URL.Path
}
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
sw := newStatusCapturingWriter(w)
path := getPath(r)
start := time.Now()
atomic.AddInt32(&concurrentRequests, 1)
defer atomic.AddInt32(&concurrentRequests, -1)

next.ServeHTTP(sw, r)

s.NewSampledTaggedStat(
fmt.Sprintf("%s.response_time", component),
stats.TimerType,
map[string]string{
"reqType": path,
"method": r.Method,
"code": strconv.Itoa(sw.status),
}).Since(start)
})
}
}

// newStatusCapturingWriter returns a new, properly initialized statusCapturingWriter
func newStatusCapturingWriter(w http.ResponseWriter) *statusCapturingWriter {
return &statusCapturingWriter{
ResponseWriter: w,
status: http.StatusOK,
}
}

// statusCapturingWriter is a response writer decorator that captures the status code.
type statusCapturingWriter struct {
http.ResponseWriter
status int
}

// WriteHeader override the http.ResponseWriter's `WriteHeader` method
func (w *statusCapturingWriter) WriteHeader(status int) {
w.status = status
w.ResponseWriter.WriteHeader(status)
}
57 changes: 57 additions & 0 deletions gorillaware/stats_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package gorillaware_test

import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"strconv"
"testing"

"github.com/golang/mock/gomock"
"github.com/gorilla/mux"
"github.com/rudderlabs/rudder-go-kit/gorillaware"
"github.com/rudderlabs/rudder-go-kit/stats"
"github.com/rudderlabs/rudder-go-kit/stats/mock_stats"
"github.com/stretchr/testify/require"
)

func TestStatsMiddleware(t *testing.T) {
component := "test"
testCase := func(expectedStatusCode int, pathTemplate, requestPath, expectedReqType, expectedMethod string) func(t *testing.T) {
return func(t *testing.T) {
ctrl := gomock.NewController(t)
mockStats := mock_stats.NewMockStats(ctrl)
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(expectedStatusCode)
})

measurement := mock_stats.NewMockMeasurement(ctrl)
mockStats.EXPECT().NewStat(fmt.Sprintf("%s.concurrent_requests_count", component), stats.GaugeType).Return(measurement).Times(1)
mockStats.EXPECT().NewSampledTaggedStat(fmt.Sprintf("%s.response_time", component), stats.TimerType,
map[string]string{
"reqType": expectedReqType,
"method": expectedMethod,
"code": strconv.Itoa(expectedStatusCode),
}).Return(measurement).Times(1)
measurement.EXPECT().Since(gomock.Any()).Times(1)

ctx, cancel := context.WithCancel(context.Background())
defer cancel()
router := mux.NewRouter()
router.Use(
gorillaware.StatMiddleware(ctx, router, mockStats, component),
)
router.HandleFunc(pathTemplate, handler).Methods(expectedMethod)

response := httptest.NewRecorder()
request := httptest.NewRequest("GET", "http://example.com"+requestPath, http.NoBody)
router.ServeHTTP(response, request)
require.Equal(t, expectedStatusCode, response.Code)
}
}

t.Run("template with param in path", testCase(http.StatusNotFound, "/v1/{param}", "/v1/abc", "/v1/{param}", "GET"))

t.Run("template without param in path", testCase(http.StatusNotFound, "/v1/some-other/key", "/v1/some-other/key", "/v1/some-other/key", "GET"))
}

0 comments on commit a7a3669

Please sign in to comment.