Skip to content

Commit

Permalink
Introduce usage metrics for all API endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
erickskrauch committed Mar 13, 2024
1 parent 4e9a145 commit 680effa
Show file tree
Hide file tree
Showing 16 changed files with 342 additions and 175 deletions.
15 changes: 7 additions & 8 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,17 @@ require (
github.com/gorilla/mux v1.8.1
github.com/jellydator/ttlcache/v3 v3.1.1
github.com/mediocregopher/radix/v4 v4.1.4
github.com/mono83/slf v0.0.0-20170919161409-79153e9636db
github.com/spf13/cobra v1.8.0
github.com/spf13/viper v1.18.1
github.com/valyala/fastjson v1.6.4
go.opentelemetry.io/contrib/exporters/autoexport v0.49.0
go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.48.0
go.opentelemetry.io/contrib/instrumentation/runtime v0.48.0
go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.49.0
go.opentelemetry.io/contrib/instrumentation/runtime v0.49.0
go.opentelemetry.io/otel v1.24.0
go.opentelemetry.io/otel/metric v1.24.0
go.opentelemetry.io/otel/sdk v1.24.0
go.opentelemetry.io/otel/sdk/metric v1.24.0
go.opentelemetry.io/otel/trace v1.24.0
go.uber.org/multierr v1.11.0
)

Expand All @@ -49,7 +49,7 @@ require (
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
Expand Down Expand Up @@ -81,18 +81,17 @@ require (
go.opentelemetry.io/otel/exporters/prometheus v0.46.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.24.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect
go.opentelemetry.io/proto/otlp v1.1.0 // indirect
golang.org/x/crypto v0.19.0 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sync v0.5.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240205150955-31a09d347014 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240205150955-31a09d347014 // indirect
google.golang.org/grpc v1.61.1 // indirect
google.golang.org/protobuf v1.32.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
14 changes: 12 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVI
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
Expand Down Expand Up @@ -80,8 +82,6 @@ github.com/mediocregopher/radix/v4 v4.1.4 h1:Uze6DEbEAvL+VHXUEu/EDBTkUk5CLct5h3n
github.com/mediocregopher/radix/v4 v4.1.4/go.mod h1:ajchozX/6ELmydxWeWM6xCFHVpZ4+67LXHOTOVR0nCE=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mono83/slf v0.0.0-20170919161409-79153e9636db h1:tlz4fTklh5mttoq5M+0yEc5Lap8W/02A2HCXCJn5iz0=
github.com/mono83/slf v0.0.0-20170919161409-79153e9636db/go.mod h1:MfF+zNMZz+5IGY9h8jpFaGLyGoJ2ZPri2FmUVftBoUU=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
Expand Down Expand Up @@ -136,8 +136,12 @@ go.opentelemetry.io/contrib/exporters/autoexport v0.49.0 h1:SPuRs5SgCd9loXBBY5Hu
go.opentelemetry.io/contrib/exporters/autoexport v0.49.0/go.mod h1:BDsrww+PTgwfvBjsZQMstsE1n5dS3hDCtAfYG1t3wag=
go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.48.0 h1:7rkdNoXgScpSUIqBch/VOB24fk9g0wl3Tr5WPtshi9o=
go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.48.0/go.mod h1:U3t9uswWhDzieXHMNWP6zk87J4HNondiibKMdNLpnMk=
go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.49.0 h1:h+c4WbSjBBc3j+IsxwB2mWvkm2nDh0SyGLa5Y5+V9cw=
go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.49.0/go.mod h1:FObmJ0epY1FcwMR7aq7sRkrCfwwV3d0GBGFfyV5JUBg=
go.opentelemetry.io/contrib/instrumentation/runtime v0.48.0 h1:dJlCKeq+zmO5Og4kgxqPvvJrzuD/mygs1g/NYM9dAsU=
go.opentelemetry.io/contrib/instrumentation/runtime v0.48.0/go.mod h1:p+hpBCpLHpuUrR0lHgnHbUnbCBll1IhrcMIlycC+xYs=
go.opentelemetry.io/contrib/instrumentation/runtime v0.49.0 h1:dg9y+7ArpumB6zwImJv47RHfdgOGQ1EMkzP5vLkEnTU=
go.opentelemetry.io/contrib/instrumentation/runtime v0.49.0/go.mod h1:Ul4MtXqu/hJBM+v7a6dCF0nHwckPMLpIpLeCi4+zfdw=
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.24.0 h1:f2jriWfOdldanBwS9jNBdeOKAQN7b4ugAMaNu1/1k9g=
Expand Down Expand Up @@ -172,6 +176,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8=
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
Expand All @@ -180,6 +186,8 @@ golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
Expand All @@ -195,6 +203,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
Expand Down
41 changes: 26 additions & 15 deletions internal/di/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,28 +108,39 @@ func newSkinsystemHandler(
config *viper.Viper,
profilesProvider ProfilesProvider,
texturesSigner SignerService,
) *mux.Router {
) (*mux.Router, error) {
config.SetDefault("textures.extra_param_name", "chrly")
config.SetDefault("textures.extra_param_value", "how do you tame a horse in Minecraft?")

return (&Skinsystem{
ProfilesProvider: profilesProvider,
SignerService: texturesSigner,
TexturesExtraParamName: config.GetString("textures.extra_param_name"),
TexturesExtraParamValue: config.GetString("textures.extra_param_value"),
}).Handler()
skinsystem, err := NewSkinsystemApi(
profilesProvider,
texturesSigner,
config.GetString("textures.extra_param_name"),
config.GetString("textures.extra_param_value"),
)
if err != nil {
return nil, err
}

return skinsystem.Handler(), nil
}

func newProfilesApiHandler(profilesManager ProfilesManager) *mux.Router {
return (&ProfilesApi{
ProfilesManager: profilesManager,
}).Handler()
func newProfilesApiHandler(profilesManager ProfilesManager) (*mux.Router, error) {
profilesApi, err := NewProfilesApi(profilesManager)
if err != nil {
return nil, err
}

return profilesApi.Handler(), nil
}

func newSignerApiHandler(signer Signer) *mux.Router {
return (&SignerApi{
Signer: signer,
}).Handler()
func newSignerApiHandler(signer Signer) (*mux.Router, error) {
signerApi, err := NewSignerApi(signer)
if err != nil {
return nil, err
}

return signerApi.Handler(), nil
}

func mount(router *mux.Router, path string, handler http.Handler) {
Expand Down
35 changes: 0 additions & 35 deletions internal/di/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,50 +3,15 @@ package di
import (
"github.com/defval/di"
"github.com/getsentry/raven-go"
"github.com/mono83/slf"
"github.com/mono83/slf/rays"
"github.com/mono83/slf/recievers/sentry"
"github.com/mono83/slf/recievers/writer"
"github.com/mono83/slf/wd"
"github.com/spf13/viper"

"ely.by/chrly/internal/version"
)

var loggerDiOptions = di.Options(
di.Provide(newLogger),
di.Provide(newSentry),
)

type loggerParams struct {
di.Inject

SentryRaven *raven.Client `di:"" optional:"true"`
}

func newLogger(params loggerParams) slf.Logger {
dispatcher := &slf.Dispatcher{}
dispatcher.AddReceiver(writer.New(writer.Options{
Marker: false,
TimeFormat: "15:04:05.000",
}))

if params.SentryRaven != nil {
sentryReceiver, _ := sentry.NewReceiverWithCustomRaven(
params.SentryRaven,
&sentry.Config{
MinLevel: "warn",
},
)
dispatcher.AddReceiver(sentryReceiver)
}

logger := wd.Custom("", "", dispatcher)
logger.WithParams(rays.Host)

return logger
}

func newSentry(config *viper.Viper) (*raven.Client, error) {
sentryAddr := config.GetString("sentry.dsn")
if sentryAddr == "" {
Expand Down
21 changes: 13 additions & 8 deletions internal/http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,37 @@ package http
import (
"context"
"encoding/json"
"log/slog"
"net/http"
"time"

"github.com/gorilla/mux"
"github.com/mono83/slf"
"github.com/mono83/slf/wd"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"

"ely.by/chrly/internal/security"
)

func StartServer(ctx context.Context, server *http.Server, logger slf.Logger) {
func StartServer(ctx context.Context, server *http.Server) {
srvErr := make(chan error, 1)
go func() {
logger.Info("Starting the server, HTTP on: :addr", wd.StringParam("addr", server.Addr))
slog.Info("Starting the server", slog.String("addr", server.Addr))
srvErr <- server.ListenAndServe()
close(srvErr)
}()

select {
case err := <-srvErr:
logger.Emergency("Error in main(): :err", wd.ErrParam(err))
slog.Error("Error in the server", slog.Any("error", err))
case <-ctx.Done():
logger.Info("Got stop signal, starting graceful shutdown: :ctx")
slog.Info("Got stop signal, starting graceful shutdown")

stopCtx, cancelFunc := context.WithTimeout(context.Background(), 3*time.Second)
defer cancelFunc()

_ = server.Shutdown(stopCtx)

logger.Info("Graceful shutdown succeed, exiting")
slog.Info("Graceful shutdown succeed, exiting")
}
}

Expand Down Expand Up @@ -88,7 +89,11 @@ func apiBadRequest(resp http.ResponseWriter, errorsPerField map[string][]string)

var internalServerError = []byte("Internal server error")

func apiServerError(resp http.ResponseWriter, err error) {
func apiServerError(resp http.ResponseWriter, req *http.Request, err error) {
span := trace.SpanFromContext(req.Context())
span.SetStatus(codes.Error, "")
span.RecordError(err)

resp.WriteHeader(http.StatusInternalServerError)
resp.Header().Set("Content-Type", "text/plain")
_, _ = resp.Write(internalServerError)
Expand Down
57 changes: 48 additions & 9 deletions internal/http/profiles.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ import (
"net/http"

"github.com/gorilla/mux"
"go.opentelemetry.io/otel/metric"
"go.uber.org/multierr"

"ely.by/chrly/internal/db"
"ely.by/chrly/internal/otel"
"ely.by/chrly/internal/profiles"
)

Expand All @@ -17,19 +20,35 @@ type ProfilesManager interface {
RemoveProfileByUuid(ctx context.Context, uuid string) error
}

func NewProfilesApi(profilesManager ProfilesManager) (*ProfilesApi, error) {
metrics, err := newProfilesApiMetrics(otel.GetMeter())
if err != nil {
return nil, err
}

return &ProfilesApi{
ProfilesManager: profilesManager,
metrics: metrics,
}, nil
}

type ProfilesApi struct {
ProfilesManager

metrics *profilesApiMetrics
}

func (ctx *ProfilesApi) Handler() *mux.Router {
func (p *ProfilesApi) Handler() *mux.Router {
router := mux.NewRouter().StrictSlash(true)
router.HandleFunc("/", ctx.postProfileHandler).Methods(http.MethodPost)
router.HandleFunc("/{uuid}", ctx.deleteProfileByUuidHandler).Methods(http.MethodDelete)
router.HandleFunc("/", p.postProfileHandler).Methods(http.MethodPost)
router.HandleFunc("/{uuid}", p.deleteProfileByUuidHandler).Methods(http.MethodDelete)

return router
}

func (ctx *ProfilesApi) postProfileHandler(resp http.ResponseWriter, req *http.Request) {
func (p *ProfilesApi) postProfileHandler(resp http.ResponseWriter, req *http.Request) {
p.metrics.UploadProfileRequest.Add(req.Context(), 1)

err := req.ParseForm()
if err != nil {
apiBadRequest(resp, map[string][]string{
Expand All @@ -48,28 +67,48 @@ func (ctx *ProfilesApi) postProfileHandler(resp http.ResponseWriter, req *http.R
MojangSignature: req.Form.Get("mojangSignature"),
}

err = ctx.PersistProfile(req.Context(), profile)
err = p.PersistProfile(req.Context(), profile)
if err != nil {
var v *profiles.ValidationError
if errors.As(err, &v) {
apiBadRequest(resp, v.Errors)
return
}

apiServerError(resp, fmt.Errorf("unable to save profile to db: %w", err))
apiServerError(resp, req, fmt.Errorf("unable to save profile to db: %w", err))
return
}

resp.WriteHeader(http.StatusCreated)
}

func (ctx *ProfilesApi) deleteProfileByUuidHandler(resp http.ResponseWriter, req *http.Request) {
func (p *ProfilesApi) deleteProfileByUuidHandler(resp http.ResponseWriter, req *http.Request) {
p.metrics.DeleteProfileRequest.Add(req.Context(), 1)

uuid := mux.Vars(req)["uuid"]
err := ctx.ProfilesManager.RemoveProfileByUuid(req.Context(), uuid)
err := p.ProfilesManager.RemoveProfileByUuid(req.Context(), uuid)
if err != nil {
apiServerError(resp, fmt.Errorf("unable to delete profile from db: %w", err))
apiServerError(resp, req, fmt.Errorf("unable to delete profile from db: %w", err))
return
}

resp.WriteHeader(http.StatusNoContent)
}

func newProfilesApiMetrics(meter metric.Meter) (*profilesApiMetrics, error) {
m := &profilesApiMetrics{}
var errors, err error

m.UploadProfileRequest, err = meter.Int64Counter("chrly.app.profiles.upload.request", metric.WithUnit("{request}"))
errors = multierr.Append(errors, err)

m.DeleteProfileRequest, err = meter.Int64Counter("chrly.app.profiles.delete.request", metric.WithUnit("{request}"))
errors = multierr.Append(errors, err)

return m, errors
}

type profilesApiMetrics struct {
UploadProfileRequest metric.Int64Counter
DeleteProfileRequest metric.Int64Counter
}
4 changes: 1 addition & 3 deletions internal/http/profiles_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@ type ProfilesTestSuite struct {

func (t *ProfilesTestSuite) SetupSubTest() {
t.ProfilesManager = &ProfilesManagerMock{}
t.App = &ProfilesApi{
ProfilesManager: t.ProfilesManager,
}
t.App, _ = NewProfilesApi(t.ProfilesManager)
}

func (t *ProfilesTestSuite) TearDownSubTest() {
Expand Down
Loading

0 comments on commit 680effa

Please sign in to comment.