mirror of
https://github.com/elyby/chrly.git
synced 2025-01-11 06:12:09 +05:30
Introduce usage metrics for all API endpoints
This commit is contained in:
parent
4e9a145f74
commit
680effa47a
15
go.mod
15
go.mod
@ -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
|
||||
)
|
||||
|
||||
@ -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
|
||||
@ -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
go.sum
14
go.sum
@ -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=
|
||||
@ -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=
|
||||
@ -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=
|
||||
@ -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=
|
||||
@ -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=
|
||||
@ -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=
|
||||
|
@ -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) {
|
||||
|
@ -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 == "" {
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
@ -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"
|
||||
)
|
||||
|
||||
@ -17,19 +20,35 @@ type ProfilesManager interface {
|
||||
RemoveProfileByUuid(ctx context.Context, uuid string) error
|
||||
}
|
||||
|
||||
type ProfilesApi struct {
|
||||
ProfilesManager
|
||||
func NewProfilesApi(profilesManager ProfilesManager) (*ProfilesApi, error) {
|
||||
metrics, err := newProfilesApiMetrics(otel.GetMeter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ProfilesApi{
|
||||
ProfilesManager: profilesManager,
|
||||
metrics: metrics,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ctx *ProfilesApi) Handler() *mux.Router {
|
||||
type ProfilesApi struct {
|
||||
ProfilesManager
|
||||
|
||||
metrics *profilesApiMetrics
|
||||
}
|
||||
|
||||
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{
|
||||
@ -48,7 +67,7 @@ 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) {
|
||||
@ -56,20 +75,40 @@ func (ctx *ProfilesApi) postProfileHandler(resp http.ResponseWriter, req *http.R
|
||||
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
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -7,6 +7,10 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
"go.uber.org/multierr"
|
||||
|
||||
"ely.by/chrly/internal/otel"
|
||||
)
|
||||
|
||||
type Signer interface {
|
||||
@ -14,8 +18,22 @@ type Signer interface {
|
||||
GetPublicKey(format string) ([]byte, error)
|
||||
}
|
||||
|
||||
func NewSignerApi(signer Signer) (*SignerApi, error) {
|
||||
metrics, err := newSignerApiMetrics(otel.GetMeter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &SignerApi{
|
||||
Signer: signer,
|
||||
metrics: metrics,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type SignerApi struct {
|
||||
Signer
|
||||
|
||||
metrics *signerApiMetrics
|
||||
}
|
||||
|
||||
func (s *SignerApi) Handler() *mux.Router {
|
||||
@ -29,7 +47,7 @@ func (s *SignerApi) Handler() *mux.Router {
|
||||
func (s *SignerApi) signHandler(resp http.ResponseWriter, req *http.Request) {
|
||||
signature, err := s.Signer.Sign(req.Body)
|
||||
if err != nil {
|
||||
apiServerError(resp, fmt.Errorf("unable to sign the value: %w", err))
|
||||
apiServerError(resp, req, fmt.Errorf("unable to sign the value: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
@ -44,7 +62,7 @@ func (s *SignerApi) getPublicKeyHandler(resp http.ResponseWriter, req *http.Requ
|
||||
format := mux.Vars(req)["format"]
|
||||
publicKey, err := s.Signer.GetPublicKey(format)
|
||||
if err != nil {
|
||||
apiServerError(resp, fmt.Errorf("unable to retrieve public key: %w", err))
|
||||
apiServerError(resp, req, fmt.Errorf("unable to retrieve public key: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
@ -58,3 +76,21 @@ func (s *SignerApi) getPublicKeyHandler(resp http.ResponseWriter, req *http.Requ
|
||||
|
||||
_, _ = resp.Write(publicKey)
|
||||
}
|
||||
|
||||
func newSignerApiMetrics(meter metric.Meter) (*signerApiMetrics, error) {
|
||||
m := &signerApiMetrics{}
|
||||
var errors, err error
|
||||
|
||||
m.SignRequest, err = meter.Int64Counter("chrly.app.signer.sign.request", metric.WithUnit("{request}"))
|
||||
errors = multierr.Append(errors, err)
|
||||
|
||||
m.GetPublicKeyRequest, err = meter.Int64Counter("chrly.app.signer.get_public_key.request", metric.WithUnit("{request}"))
|
||||
errors = multierr.Append(errors, err)
|
||||
|
||||
return m, errors
|
||||
}
|
||||
|
||||
type signerApiMetrics struct {
|
||||
SignRequest metric.Int64Counter
|
||||
GetPublicKeyRequest metric.Int64Counter
|
||||
}
|
||||
|
@ -48,9 +48,7 @@ type SignerApiTestSuite struct {
|
||||
func (t *SignerApiTestSuite) SetupSubTest() {
|
||||
t.Signer = &SignerMock{}
|
||||
|
||||
t.App = &SignerApi{
|
||||
Signer: t.Signer,
|
||||
}
|
||||
t.App, _ = NewSignerApi(t.Signer)
|
||||
}
|
||||
|
||||
func (t *SignerApiTestSuite) TearDownSubTest() {
|
||||
|
@ -11,9 +11,12 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
"go.uber.org/multierr"
|
||||
|
||||
"ely.by/chrly/internal/db"
|
||||
"ely.by/chrly/internal/mojang"
|
||||
"ely.by/chrly/internal/otel"
|
||||
"ely.by/chrly/internal/utils"
|
||||
)
|
||||
|
||||
@ -29,11 +32,32 @@ type SignerService interface {
|
||||
GetPublicKey(ctx context.Context, format string) (string, error)
|
||||
}
|
||||
|
||||
func NewSkinsystemApi(
|
||||
profilesProvider ProfilesProvider,
|
||||
signerService SignerService,
|
||||
texturesExtraParamName string,
|
||||
texturesExtraParamValue string,
|
||||
) (*Skinsystem, error) {
|
||||
metrics, err := newSkinsystemMetrics(otel.GetMeter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Skinsystem{
|
||||
ProfilesProvider: profilesProvider,
|
||||
SignerService: signerService,
|
||||
TexturesExtraParamName: texturesExtraParamName,
|
||||
TexturesExtraParamValue: texturesExtraParamValue,
|
||||
metrics: metrics,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type Skinsystem struct {
|
||||
ProfilesProvider
|
||||
SignerService
|
||||
TexturesExtraParamName string
|
||||
TexturesExtraParamValue string
|
||||
metrics *skinsystemApiMetrics
|
||||
}
|
||||
|
||||
func (s *Skinsystem) Handler() *mux.Router {
|
||||
@ -46,8 +70,8 @@ func (s *Skinsystem) Handler() *mux.Router {
|
||||
router.HandleFunc("/textures/signed/{username}", s.signedTexturesHandler).Methods(http.MethodGet)
|
||||
router.HandleFunc("/profile/{username}", s.profileHandler).Methods(http.MethodGet)
|
||||
// Legacy
|
||||
router.HandleFunc("/skins", s.skinGetHandler).Methods(http.MethodGet)
|
||||
router.HandleFunc("/cloaks", s.capeGetHandler).Methods(http.MethodGet)
|
||||
router.HandleFunc("/skins", s.legacySkinHandler).Methods(http.MethodGet)
|
||||
router.HandleFunc("/cloaks", s.legacyCapeHandler).Methods(http.MethodGet)
|
||||
// Utils
|
||||
router.HandleFunc("/signature-verification-key.{format:(?:pem|der)}", s.signatureVerificationKeyHandler).Methods(http.MethodGet)
|
||||
|
||||
@ -55,99 +79,115 @@ func (s *Skinsystem) Handler() *mux.Router {
|
||||
}
|
||||
|
||||
func (s *Skinsystem) skinHandler(response http.ResponseWriter, request *http.Request) {
|
||||
profile, err := s.ProfilesProvider.FindProfileByUsername(request.Context(), parseUsername(mux.Vars(request)["username"]), true)
|
||||
s.metrics.SkinRequest.Add(request.Context(), 1)
|
||||
|
||||
s.skinHandlerWithUsername(response, request, mux.Vars(request)["username"])
|
||||
}
|
||||
|
||||
func (s *Skinsystem) legacySkinHandler(response http.ResponseWriter, request *http.Request) {
|
||||
s.metrics.LegacySkinRequest.Add(request.Context(), 1)
|
||||
|
||||
username := request.URL.Query().Get("name")
|
||||
if username == "" {
|
||||
response.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
s.skinHandlerWithUsername(response, request, username)
|
||||
}
|
||||
|
||||
func (s *Skinsystem) skinHandlerWithUsername(resp http.ResponseWriter, req *http.Request, username string) {
|
||||
profile, err := s.ProfilesProvider.FindProfileByUsername(req.Context(), parseUsername(username), true)
|
||||
if err != nil {
|
||||
apiServerError(response, fmt.Errorf("unable to retrieve a profile: %w", err))
|
||||
apiServerError(resp, req, fmt.Errorf("unable to retrieve a profile: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
if profile == nil || profile.SkinUrl == "" {
|
||||
response.WriteHeader(http.StatusNotFound)
|
||||
resp.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
|
||||
http.Redirect(response, request, profile.SkinUrl, http.StatusMovedPermanently)
|
||||
http.Redirect(resp, req, profile.SkinUrl, http.StatusMovedPermanently)
|
||||
}
|
||||
|
||||
func (s *Skinsystem) skinGetHandler(response http.ResponseWriter, request *http.Request) {
|
||||
func (s *Skinsystem) capeHandler(response http.ResponseWriter, request *http.Request) {
|
||||
s.metrics.CapeRequest.Add(request.Context(), 1)
|
||||
|
||||
s.capeHandlerWithUsername(response, request, mux.Vars(request)["username"])
|
||||
}
|
||||
|
||||
func (s *Skinsystem) legacyCapeHandler(response http.ResponseWriter, request *http.Request) {
|
||||
s.metrics.CapeRequest.Add(request.Context(), 1)
|
||||
|
||||
username := request.URL.Query().Get("name")
|
||||
if username == "" {
|
||||
response.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
mux.Vars(request)["username"] = username
|
||||
|
||||
s.skinHandler(response, request)
|
||||
s.capeHandlerWithUsername(response, request, username)
|
||||
}
|
||||
|
||||
func (s *Skinsystem) capeHandler(response http.ResponseWriter, request *http.Request) {
|
||||
profile, err := s.ProfilesProvider.FindProfileByUsername(request.Context(), parseUsername(mux.Vars(request)["username"]), true)
|
||||
func (s *Skinsystem) capeHandlerWithUsername(resp http.ResponseWriter, req *http.Request, username string) {
|
||||
profile, err := s.ProfilesProvider.FindProfileByUsername(req.Context(), parseUsername(username), true)
|
||||
if err != nil {
|
||||
apiServerError(response, fmt.Errorf("unable to retrieve a profile: %w", err))
|
||||
apiServerError(resp, req, fmt.Errorf("unable to retrieve a profile: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
if profile == nil || profile.CapeUrl == "" {
|
||||
response.WriteHeader(http.StatusNotFound)
|
||||
resp.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
|
||||
http.Redirect(response, request, profile.CapeUrl, http.StatusMovedPermanently)
|
||||
http.Redirect(resp, req, profile.CapeUrl, http.StatusMovedPermanently)
|
||||
}
|
||||
|
||||
func (s *Skinsystem) capeGetHandler(response http.ResponseWriter, request *http.Request) {
|
||||
username := request.URL.Query().Get("name")
|
||||
if username == "" {
|
||||
response.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
func (s *Skinsystem) texturesHandler(resp http.ResponseWriter, req *http.Request) {
|
||||
s.metrics.TexturesRequest.Add(req.Context(), 1)
|
||||
|
||||
mux.Vars(request)["username"] = username
|
||||
|
||||
s.capeHandler(response, request)
|
||||
}
|
||||
|
||||
func (s *Skinsystem) texturesHandler(response http.ResponseWriter, request *http.Request) {
|
||||
profile, err := s.ProfilesProvider.FindProfileByUsername(request.Context(), mux.Vars(request)["username"], true)
|
||||
profile, err := s.ProfilesProvider.FindProfileByUsername(req.Context(), mux.Vars(req)["username"], true)
|
||||
if err != nil {
|
||||
apiServerError(response, fmt.Errorf("unable to retrieve a profile: %w", err))
|
||||
apiServerError(resp, req, fmt.Errorf("unable to retrieve a profile: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
if profile == nil {
|
||||
response.WriteHeader(http.StatusNotFound)
|
||||
resp.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if profile.SkinUrl == "" && profile.CapeUrl == "" {
|
||||
response.WriteHeader(http.StatusNoContent)
|
||||
resp.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
|
||||
textures := texturesFromProfile(profile)
|
||||
|
||||
responseData, _ := json.Marshal(textures)
|
||||
response.Header().Set("Content-Type", "application/json")
|
||||
_, _ = response.Write(responseData)
|
||||
resp.Header().Set("Content-Type", "application/json")
|
||||
_, _ = resp.Write(responseData)
|
||||
}
|
||||
|
||||
func (s *Skinsystem) signedTexturesHandler(response http.ResponseWriter, request *http.Request) {
|
||||
func (s *Skinsystem) signedTexturesHandler(resp http.ResponseWriter, req *http.Request) {
|
||||
s.metrics.SignedTexturesRequest.Add(req.Context(), 1)
|
||||
|
||||
profile, err := s.ProfilesProvider.FindProfileByUsername(
|
||||
request.Context(),
|
||||
mux.Vars(request)["username"],
|
||||
getToBool(request.URL.Query().Get("proxy")),
|
||||
req.Context(),
|
||||
mux.Vars(req)["username"],
|
||||
getToBool(req.URL.Query().Get("proxy")),
|
||||
)
|
||||
if err != nil {
|
||||
apiServerError(response, fmt.Errorf("unable to retrieve a profile: %w", err))
|
||||
apiServerError(resp, req, fmt.Errorf("unable to retrieve a profile: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
if profile == nil {
|
||||
response.WriteHeader(http.StatusNotFound)
|
||||
resp.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if profile.MojangTextures == "" {
|
||||
response.WriteHeader(http.StatusNoContent)
|
||||
resp.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
|
||||
@ -168,19 +208,21 @@ func (s *Skinsystem) signedTexturesHandler(response http.ResponseWriter, request
|
||||
}
|
||||
|
||||
responseJson, _ := json.Marshal(profileResponse)
|
||||
response.Header().Set("Content-Type", "application/json")
|
||||
_, _ = response.Write(responseJson)
|
||||
resp.Header().Set("Content-Type", "application/json")
|
||||
_, _ = resp.Write(responseJson)
|
||||
}
|
||||
|
||||
func (s *Skinsystem) profileHandler(response http.ResponseWriter, request *http.Request) {
|
||||
profile, err := s.ProfilesProvider.FindProfileByUsername(request.Context(), mux.Vars(request)["username"], true)
|
||||
func (s *Skinsystem) profileHandler(resp http.ResponseWriter, req *http.Request) {
|
||||
s.metrics.ProfileRequest.Add(req.Context(), 1)
|
||||
|
||||
profile, err := s.ProfilesProvider.FindProfileByUsername(req.Context(), mux.Vars(req)["username"], true)
|
||||
if err != nil {
|
||||
apiServerError(response, fmt.Errorf("unable to retrieve a profile: %w", err))
|
||||
apiServerError(resp, req, fmt.Errorf("unable to retrieve a profile: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
if profile == nil {
|
||||
response.WriteHeader(http.StatusNotFound)
|
||||
resp.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
@ -199,10 +241,10 @@ func (s *Skinsystem) profileHandler(response http.ResponseWriter, request *http.
|
||||
Value: texturesPropEncodedValue,
|
||||
}
|
||||
|
||||
if request.URL.Query().Has("unsigned") && !getToBool(request.URL.Query().Get("unsigned")) {
|
||||
signature, err := s.SignerService.Sign(request.Context(), texturesProp.Value)
|
||||
if req.URL.Query().Has("unsigned") && !getToBool(req.URL.Query().Get("unsigned")) {
|
||||
signature, err := s.SignerService.Sign(req.Context(), texturesProp.Value)
|
||||
if err != nil {
|
||||
apiServerError(response, fmt.Errorf("unable to sign textures: %w", err))
|
||||
apiServerError(resp, req, fmt.Errorf("unable to sign textures: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
@ -222,27 +264,29 @@ func (s *Skinsystem) profileHandler(response http.ResponseWriter, request *http.
|
||||
}
|
||||
|
||||
responseJson, _ := json.Marshal(profileResponse)
|
||||
response.Header().Set("Content-Type", "application/json")
|
||||
_, _ = response.Write(responseJson)
|
||||
resp.Header().Set("Content-Type", "application/json")
|
||||
_, _ = resp.Write(responseJson)
|
||||
}
|
||||
|
||||
func (s *Skinsystem) signatureVerificationKeyHandler(response http.ResponseWriter, request *http.Request) {
|
||||
format := mux.Vars(request)["format"]
|
||||
publicKey, err := s.SignerService.GetPublicKey(request.Context(), format)
|
||||
func (s *Skinsystem) signatureVerificationKeyHandler(resp http.ResponseWriter, req *http.Request) {
|
||||
s.metrics.SigningKeyRequest.Add(req.Context(), 1)
|
||||
|
||||
format := mux.Vars(req)["format"]
|
||||
publicKey, err := s.SignerService.GetPublicKey(req.Context(), format)
|
||||
if err != nil {
|
||||
apiServerError(response, fmt.Errorf("unable to retrieve public key: %w", err))
|
||||
apiServerError(resp, req, fmt.Errorf("unable to retrieve public key: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
if format == "pem" {
|
||||
response.Header().Set("Content-Type", "application/x-pem-file")
|
||||
response.Header().Set("Content-Disposition", `attachment; filename="yggdrasil_session_pubkey.pem"`)
|
||||
resp.Header().Set("Content-Type", "application/x-pem-file")
|
||||
resp.Header().Set("Content-Disposition", `attachment; filename="yggdrasil_session_pubkey.pem"`)
|
||||
} else {
|
||||
response.Header().Set("Content-Type", "application/octet-stream")
|
||||
response.Header().Set("Content-Disposition", `attachment; filename="yggdrasil_session_pubkey.der"`)
|
||||
resp.Header().Set("Content-Type", "application/octet-stream")
|
||||
resp.Header().Set("Content-Disposition", `attachment; filename="yggdrasil_session_pubkey.der"`)
|
||||
}
|
||||
|
||||
_, _ = io.WriteString(response, publicKey)
|
||||
_, _ = io.WriteString(resp, publicKey)
|
||||
}
|
||||
|
||||
func parseUsername(username string) string {
|
||||
@ -250,7 +294,7 @@ func parseUsername(username string) string {
|
||||
}
|
||||
|
||||
func getToBool(v string) bool {
|
||||
return v == "true" || v == "1" || v == "yes"
|
||||
return v == "1" || v == "true" || v == "yes"
|
||||
}
|
||||
|
||||
func texturesFromProfile(profile *db.Profile) *mojang.TexturesResponse {
|
||||
@ -278,3 +322,45 @@ func texturesFromProfile(profile *db.Profile) *mojang.TexturesResponse {
|
||||
Cape: cape,
|
||||
}
|
||||
}
|
||||
|
||||
func newSkinsystemMetrics(meter metric.Meter) (*skinsystemApiMetrics, error) {
|
||||
m := &skinsystemApiMetrics{}
|
||||
var errors, err error
|
||||
|
||||
m.SkinRequest, err = meter.Int64Counter("chrly.app.skinsystem.skin.request", metric.WithUnit("{request}"))
|
||||
errors = multierr.Append(errors, err)
|
||||
|
||||
m.LegacySkinRequest, err = meter.Int64Counter("chrly.app.skinsystem.legacy_skin.request", metric.WithUnit("{request}"))
|
||||
errors = multierr.Append(errors, err)
|
||||
|
||||
m.CapeRequest, err = meter.Int64Counter("chrly.app.skinsystem.cape.request", metric.WithUnit("{request}"))
|
||||
errors = multierr.Append(errors, err)
|
||||
|
||||
m.LegacyCapeRequest, err = meter.Int64Counter("chrly.app.skinsystem.legacy_cape.request", metric.WithUnit("{request}"))
|
||||
errors = multierr.Append(errors, err)
|
||||
|
||||
m.TexturesRequest, err = meter.Int64Counter("chrly.app.skinsystem.textures.request", metric.WithUnit("{request}"))
|
||||
errors = multierr.Append(errors, err)
|
||||
|
||||
m.SignedTexturesRequest, err = meter.Int64Counter("chrly.app.skinsystem.signed_textures.request", metric.WithUnit("{request}"))
|
||||
errors = multierr.Append(errors, err)
|
||||
|
||||
m.ProfileRequest, err = meter.Int64Counter("chrly.app.skinsystem.profile.request", metric.WithUnit("{request}"))
|
||||
errors = multierr.Append(errors, err)
|
||||
|
||||
m.SigningKeyRequest, err = meter.Int64Counter("chrly.app.skinsystem.signing_key.request", metric.WithUnit("{request}"))
|
||||
errors = multierr.Append(errors, err)
|
||||
|
||||
return m, errors
|
||||
}
|
||||
|
||||
type skinsystemApiMetrics struct {
|
||||
SkinRequest metric.Int64Counter
|
||||
LegacySkinRequest metric.Int64Counter
|
||||
CapeRequest metric.Int64Counter
|
||||
LegacyCapeRequest metric.Int64Counter
|
||||
TexturesRequest metric.Int64Counter
|
||||
SignedTexturesRequest metric.Int64Counter
|
||||
ProfileRequest metric.Int64Counter
|
||||
SigningKeyRequest metric.Int64Counter
|
||||
}
|
||||
|
@ -66,12 +66,12 @@ func (t *SkinsystemTestSuite) SetupSubTest() {
|
||||
t.ProfilesProvider = &ProfilesProviderMock{}
|
||||
t.SignerService = &SignerServiceMock{}
|
||||
|
||||
t.App = &Skinsystem{
|
||||
ProfilesProvider: t.ProfilesProvider,
|
||||
SignerService: t.SignerService,
|
||||
TexturesExtraParamName: "texturesParamName",
|
||||
TexturesExtraParamValue: "texturesParamValue",
|
||||
}
|
||||
t.App, _ = NewSkinsystemApi(
|
||||
t.ProfilesProvider,
|
||||
t.SignerService,
|
||||
"texturesParamName",
|
||||
"texturesParamValue",
|
||||
)
|
||||
}
|
||||
|
||||
func (t *SkinsystemTestSuite) TearDownSubTest() {
|
||||
|
@ -6,10 +6,10 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
"go.uber.org/multierr"
|
||||
|
||||
"ely.by/chrly/internal/otel"
|
||||
"ely.by/chrly/internal/utils"
|
||||
)
|
||||
|
||||
@ -36,7 +36,7 @@ func NewBatchUuidsProvider(
|
||||
) (*BatchUuidsProvider, error) {
|
||||
queue := utils.NewQueue[*job]()
|
||||
|
||||
metrics, err := newBatchUuidsProviderMetrics(otel.GetMeterProvider().Meter(ScopeName), queue)
|
||||
metrics, err := newBatchUuidsProviderMetrics(otel.GetMeter(), queue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -167,21 +167,21 @@ func newBatchUuidsProviderMetrics(meter metric.Meter, queue *utils.Queue[*job])
|
||||
var errors, err error
|
||||
|
||||
m.Requests, err = meter.Int64Counter(
|
||||
"uuids.batch.request.sent",
|
||||
"chrly.mojang.uuids.batch.request.sent",
|
||||
metric.WithDescription("Number of UUIDs requests sent to Mojang API"),
|
||||
metric.WithUnit("1"),
|
||||
)
|
||||
errors = multierr.Append(errors, err)
|
||||
|
||||
m.BatchSize, err = meter.Int64Histogram(
|
||||
"uuids.batch.request.batch_size",
|
||||
"chrly.mojang.uuids.batch.request.batch_size",
|
||||
metric.WithDescription("The number of usernames in the query"),
|
||||
metric.WithUnit("1"),
|
||||
)
|
||||
errors = multierr.Append(errors, err)
|
||||
|
||||
m.QueueLength, err = meter.Int64ObservableGauge(
|
||||
"uuids.batch.queue.length",
|
||||
"chrly.mojang.uuids.batch.queue.length",
|
||||
metric.WithDescription("Number of tasks in the queue waiting for execution"),
|
||||
metric.WithInt64Callback(func(_ context.Context, o metric.Int64Observer) error {
|
||||
o.Observe(int64(queue.Len()))
|
||||
@ -191,7 +191,7 @@ func newBatchUuidsProviderMetrics(meter metric.Meter, queue *utils.Queue[*job])
|
||||
errors = multierr.Append(errors, err)
|
||||
|
||||
m.QueueTime, err = meter.Float64Histogram(
|
||||
"uuids.batch.queue.lag",
|
||||
"chrly.mojang.uuids.batch.queue.lag",
|
||||
metric.WithDescription("Lag between placing a job in the queue and starting its processing"),
|
||||
metric.WithUnit("ms"),
|
||||
)
|
||||
|
@ -7,9 +7,10 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/brunomvsouza/singleflight"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
"go.uber.org/multierr"
|
||||
|
||||
"ely.by/chrly/internal/otel"
|
||||
)
|
||||
|
||||
const ScopeName = "ely.by/chrly/internal/mojang"
|
||||
@ -31,7 +32,7 @@ func NewMojangTexturesProvider(
|
||||
uuidsProvider UuidsProvider,
|
||||
texturesProvider TexturesProvider,
|
||||
) (*MojangTexturesProvider, error) {
|
||||
meter, err := newProviderMetrics(otel.GetMeterProvider().Meter(ScopeName))
|
||||
meter, err := newProviderMetrics(otel.GetMeter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -119,42 +120,42 @@ func newProviderMetrics(meter metric.Meter) (*providerMetrics, error) {
|
||||
var errors, err error
|
||||
|
||||
m.UsernameFound, err = meter.Int64Counter(
|
||||
"provider.username_found",
|
||||
"mojang.provider.username_found",
|
||||
metric.WithDescription("Number of queries for which username was found"),
|
||||
metric.WithUnit("1"),
|
||||
)
|
||||
errors = multierr.Append(errors, err)
|
||||
|
||||
m.UsernameMissed, err = meter.Int64Counter(
|
||||
"provider.username_missed",
|
||||
"chrly.mojang.provider.username_missed",
|
||||
metric.WithDescription("Number of queries for which username was not found"),
|
||||
metric.WithUnit("1"),
|
||||
)
|
||||
errors = multierr.Append(errors, err)
|
||||
|
||||
m.TextureFound, err = meter.Int64Counter(
|
||||
"provider.textures_found",
|
||||
"chrly.mojang.provider.textures_found",
|
||||
metric.WithDescription("Number of queries for which textures were successfully found"),
|
||||
metric.WithUnit("1"),
|
||||
)
|
||||
errors = multierr.Append(errors, err)
|
||||
|
||||
m.TextureMissed, err = meter.Int64Counter(
|
||||
"provider.textures_missed",
|
||||
"chrly.mojang.provider.textures_missed",
|
||||
metric.WithDescription("Number of queries for which no textures were found"),
|
||||
metric.WithUnit("1"),
|
||||
)
|
||||
errors = multierr.Append(errors, err)
|
||||
|
||||
m.Failed, err = meter.Int64Counter(
|
||||
"provider.failed",
|
||||
"chrly.mojang.provider.failed",
|
||||
metric.WithDescription("Number of requests that ended in an error"),
|
||||
metric.WithUnit("1"),
|
||||
)
|
||||
errors = multierr.Append(errors, err)
|
||||
|
||||
m.Shared, err = meter.Int64Counter(
|
||||
"provider.singleflight.shared",
|
||||
"chrly.mojang.provider.singleflight.shared",
|
||||
metric.WithDescription("Number of requests that are already being processed in another thread"),
|
||||
metric.WithUnit("1"),
|
||||
)
|
||||
|
@ -6,15 +6,16 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/jellydator/ttlcache/v3"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
"go.uber.org/multierr"
|
||||
|
||||
"ely.by/chrly/internal/otel"
|
||||
)
|
||||
|
||||
type MojangApiTexturesProviderFunc func(ctx context.Context, uuid string, signed bool) (*ProfileResponse, error)
|
||||
|
||||
func NewMojangApiTexturesProvider(endpoint MojangApiTexturesProviderFunc) (*MojangApiTexturesProvider, error) {
|
||||
metrics, err := newMojangApiTexturesProviderMetrics(otel.GetMeterProvider().Meter(ScopeName))
|
||||
metrics, err := newMojangApiTexturesProviderMetrics(otel.GetMeter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -46,7 +47,7 @@ type TexturesProviderWithInMemoryCache struct {
|
||||
}
|
||||
|
||||
func NewTexturesProviderWithInMemoryCache(provider TexturesProvider) (*TexturesProviderWithInMemoryCache, error) {
|
||||
metrics, err := newTexturesProviderWithInMemoryCacheMetrics(otel.GetMeterProvider().Meter(ScopeName))
|
||||
metrics, err := newTexturesProviderWithInMemoryCacheMetrics(otel.GetMeter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -100,7 +101,7 @@ func newMojangApiTexturesProviderMetrics(meter metric.Meter) (*mojangApiTextures
|
||||
var errors, err error
|
||||
|
||||
m.Requests, err = meter.Int64Counter(
|
||||
"textures.request.sent",
|
||||
"chrly.mojang.textures.request.sent",
|
||||
metric.WithDescription("Number of textures requests sent to Mojang API"),
|
||||
metric.WithUnit("1"),
|
||||
)
|
||||
@ -118,14 +119,14 @@ func newTexturesProviderWithInMemoryCacheMetrics(meter metric.Meter) (*texturesP
|
||||
var errors, err error
|
||||
|
||||
m.Hits, err = meter.Int64Counter(
|
||||
"textures.cache.hit",
|
||||
"chrly.mojang.textures.cache.hit",
|
||||
metric.WithDescription("Number of Mojang textures found in the local cache"),
|
||||
metric.WithUnit("1"),
|
||||
)
|
||||
errors = multierr.Append(errors, err)
|
||||
|
||||
m.Misses, err = meter.Int64Counter(
|
||||
"textures.cache.miss",
|
||||
"chrly.mojang.textures.cache.miss",
|
||||
metric.WithDescription("Number of Mojang textures missing from local cache"),
|
||||
metric.WithUnit("1"),
|
||||
)
|
||||
|
@ -3,9 +3,10 @@ package mojang
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
"go.uber.org/multierr"
|
||||
|
||||
"ely.by/chrly/internal/otel"
|
||||
)
|
||||
|
||||
type MojangUuidsStorage interface {
|
||||
@ -17,7 +18,7 @@ type MojangUuidsStorage interface {
|
||||
}
|
||||
|
||||
func NewUuidsProviderWithCache(o UuidsProvider, s MojangUuidsStorage) (*UuidsProviderWithCache, error) {
|
||||
metrics, err := newUuidsProviderWithCacheMetrics(otel.GetMeterProvider().Meter(ScopeName))
|
||||
metrics, err := newUuidsProviderWithCacheMetrics(otel.GetMeter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -88,14 +89,14 @@ func newUuidsProviderWithCacheMetrics(meter metric.Meter) (*uuidsProviderWithCac
|
||||
var errors, err error
|
||||
|
||||
m.Hits, err = meter.Int64Counter(
|
||||
"uuids.cache.hit",
|
||||
"chrly.mojang.uuids.cache.hit",
|
||||
metric.WithDescription("Number of Mojang UUIDs found in the local cache"),
|
||||
metric.WithUnit("1"),
|
||||
)
|
||||
errors = multierr.Append(errors, err)
|
||||
|
||||
m.Misses, err = meter.Int64Counter(
|
||||
"uuids.cache.miss",
|
||||
"chrly.mojang.uuids.cache.miss",
|
||||
metric.WithDescription("Number of Mojang UUIDs missing from local cache"),
|
||||
metric.WithUnit("1"),
|
||||
)
|
||||
|
17
internal/otel/otel.go
Normal file
17
internal/otel/otel.go
Normal file
@ -0,0 +1,17 @@
|
||||
package otel
|
||||
|
||||
import (
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
const Scope = "ely.by/chrly"
|
||||
|
||||
func GetMeter(opts ...metric.MeterOption) metric.Meter {
|
||||
return otel.GetMeterProvider().Meter(Scope, opts...)
|
||||
}
|
||||
|
||||
func GetTracer(opts ...trace.TracerOption) trace.Tracer {
|
||||
return otel.GetTracerProvider().Tracer(Scope, opts...)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user