diff --git a/go.mod b/go.mod index 720d03a..73860fa 100644 --- a/go.mod +++ b/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 ) diff --git a/go.sum b/go.sum index 683e2e2..25cbeac 100644 --- a/go.sum +++ b/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= diff --git a/internal/di/handlers.go b/internal/di/handlers.go index 39dc9ff..569afa8 100644 --- a/internal/di/handlers.go +++ b/internal/di/handlers.go @@ -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) { diff --git a/internal/di/logger.go b/internal/di/logger.go index fabd93a..910ece3 100644 --- a/internal/di/logger.go +++ b/internal/di/logger.go @@ -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 == "" { diff --git a/internal/http/http.go b/internal/http/http.go index 53d546f..f10608e 100644 --- a/internal/http/http.go +++ b/internal/http/http.go @@ -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) diff --git a/internal/http/profiles.go b/internal/http/profiles.go index bd4251b..7399030 100644 --- a/internal/http/profiles.go +++ b/internal/http/profiles.go @@ -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 +} diff --git a/internal/http/profiles_test.go b/internal/http/profiles_test.go index 34a4cea..d6e4207 100644 --- a/internal/http/profiles_test.go +++ b/internal/http/profiles_test.go @@ -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() { diff --git a/internal/http/signer.go b/internal/http/signer.go index 7ac7a88..ce238e1 100644 --- a/internal/http/signer.go +++ b/internal/http/signer.go @@ -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 +} diff --git a/internal/http/signer_test.go b/internal/http/signer_test.go index 4729e8c..9cdeb3c 100644 --- a/internal/http/signer_test.go +++ b/internal/http/signer_test.go @@ -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() { diff --git a/internal/http/skinsystem.go b/internal/http/skinsystem.go index dd88914..4a6be84 100644 --- a/internal/http/skinsystem.go +++ b/internal/http/skinsystem.go @@ -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 +} diff --git a/internal/http/skinsystem_test.go b/internal/http/skinsystem_test.go index 347a730..fb59e2a 100644 --- a/internal/http/skinsystem_test.go +++ b/internal/http/skinsystem_test.go @@ -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() { diff --git a/internal/mojang/batch_uuids_provider.go b/internal/mojang/batch_uuids_provider.go index 4e51ecc..b6b809e 100644 --- a/internal/mojang/batch_uuids_provider.go +++ b/internal/mojang/batch_uuids_provider.go @@ -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"), ) diff --git a/internal/mojang/provider.go b/internal/mojang/provider.go index 7945d78..c387fd9 100644 --- a/internal/mojang/provider.go +++ b/internal/mojang/provider.go @@ -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"), ) diff --git a/internal/mojang/textures_provider.go b/internal/mojang/textures_provider.go index b21c004..c6fb1de 100644 --- a/internal/mojang/textures_provider.go +++ b/internal/mojang/textures_provider.go @@ -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"), ) diff --git a/internal/mojang/uuids_provider.go b/internal/mojang/uuids_provider.go index 48ab9fe..016d9df 100644 --- a/internal/mojang/uuids_provider.go +++ b/internal/mojang/uuids_provider.go @@ -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"), ) diff --git a/internal/otel/otel.go b/internal/otel/otel.go new file mode 100644 index 0000000..99473f5 --- /dev/null +++ b/internal/otel/otel.go @@ -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...) +}