Fixes #39. Merge branch 'otel' into v5

This commit is contained in:
ErickSkrauch 2024-03-05 15:29:58 +01:00
commit 4e9a145f74
No known key found for this signature in database
GPG Key ID: 669339FCBB30EE0E
26 changed files with 775 additions and 157 deletions

45
go.mod
View File

@ -4,6 +4,8 @@ go 1.21
// Main dependencies
require (
github.com/agoda-com/opentelemetry-go/otelslog v0.1.1
github.com/agoda-com/opentelemetry-logs-go v0.4.3
github.com/brunomvsouza/singleflight v0.4.0
github.com/defval/di v1.12.0
github.com/etherlabsio/healthcheck/v2 v2.0.0
@ -14,10 +16,17 @@ require (
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/SentimensRG/ctx v0.0.0-20180729130232-0bfd988c655d
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/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.uber.org/multierr v1.11.0
)
// Dev dependencies
@ -28,20 +37,33 @@ require (
// Indirect dependencies
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/go-logr/logr v1.4.1 // indirect
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/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
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.1.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.18.0 // indirect
github.com/prometheus/client_model v0.6.0 // indirect
github.com/prometheus/common v0.45.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
@ -51,13 +73,26 @@ require (
github.com/stretchr/objx v0.5.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/tilinna/clock v1.0.2 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.16.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.24.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.24.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 // indirect
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/exp v0.0.0-20231206192017-f3f8817b8deb // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sync v0.5.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/sys v0.17.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
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

110
go.sum
View File

@ -1,9 +1,17 @@
github.com/SentimensRG/ctx v0.0.0-20180729130232-0bfd988c655d h1:CbB/Ef3TyBvSSJx2HDSUiw49ONTpaX6BGiI0jJEX6b8=
github.com/SentimensRG/ctx v0.0.0-20180729130232-0bfd988c655d/go.mod h1:cfn0Ycx1ASzCkl8+04zI4hrclf9YQ1QfncxzFiNtQLo=
github.com/agoda-com/opentelemetry-go/otelslog v0.1.1 h1:6nV8PZCzySHuh9kP/HZ2OJqGucwQiM+yZRugKDvtzj4=
github.com/agoda-com/opentelemetry-go/otelslog v0.1.1/go.mod h1:CSc0veIcY/HsIfH7l5PGtIpRvBttk09QUQlweVkD2PI=
github.com/agoda-com/opentelemetry-logs-go v0.4.3 h1:dYAx/q9di+/Pv6HuGq59DFIOjqKT0LTy3PYTIz8ccq8=
github.com/agoda-com/opentelemetry-logs-go v0.4.3/go.mod h1:gPQ0fHqroxNP2DlQFZt29/pfqGiP2m6Q5CCxEgLo6yQ=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/brunomvsouza/singleflight v0.4.0 h1:9dNcTeYoXSus3xbZEM0EEZ11EcCRjUZOvVW8rnDMG5Y=
github.com/brunomvsouza/singleflight v0.4.0/go.mod h1:8RYo9j5WQRupmsnUz5DlUWZxDLNi+t9Zhj3EZFmns7I=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d h1:S2NE3iHSwP0XV47EEXL8mWmRdEfGscSJ+7EgePNgt0s=
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -13,6 +21,8 @@ github.com/defval/di v1.12.0 h1:xXm7BMX2+Nr0Yyu55DeJl/rmfCA7CQX89f4AGE0zA6U=
github.com/defval/di v1.12.0/go.mod h1:PhVbOxQOvU7oawTOJXXTvqOJp1Dvsjs5PuzMw9gGl0I=
github.com/etherlabsio/healthcheck/v2 v2.0.0 h1:oKq8cbpwM/yNGPXf2Sff6MIjVUjx/pGYFydWzeK2MpA=
github.com/etherlabsio/healthcheck/v2 v2.0.0/go.mod h1:huNVOjKzu6FI1eaO1CGD3ZjhrmPWf5Obu/pzpI6/wog=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
@ -21,6 +31,11 @@ github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/getsentry/raven-go v0.2.1-0.20190419175539-919484f041ea h1:t6e33/eet/VyiHHHKs0cBytUISUWQ/hmQwOlqtFoGEo=
github.com/getsentry/raven-go v0.2.1-0.20190419175539-919484f041ea/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
@ -31,10 +46,16 @@ github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ
github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
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/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=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM=
github.com/h2non/gock v1.2.0 h1:K6ol8rfrRkUOefooBC8elXoaNGYkpp7y2qcxGG6BzUE=
github.com/h2non/gock v1.2.0/go.mod h1:tNhoxHYW2W42cYkYb1WqzdbYIieALC99kpYr7rH/BQk=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
@ -53,6 +74,8 @@ github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
github.com/mediocregopher/radix/v4 v4.1.4 h1:Uze6DEbEAvL+VHXUEu/EDBTkUk5CLct5h3nVSGpc6Ts=
github.com/mediocregopher/radix/v4 v4.1.4/go.mod h1:ajchozX/6ELmydxWeWM6xCFHVpZ4+67LXHOTOVR0nCE=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
@ -66,8 +89,16 @@ github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdU
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos=
github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8=
github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=
github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
@ -101,25 +132,72 @@ github.com/tilinna/clock v1.0.2 h1:6BO2tyAC9JbPExKH/z9zl44FLu1lImh3nDNKA0kgrkI=
github.com/tilinna/clock v1.0.2/go.mod h1:ZsP7BcY7sEEz7ktc0IVy8Us6boDrK8VradlKRUGfOao=
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
go.opentelemetry.io/contrib/exporters/autoexport v0.49.0 h1:SPuRs5SgCd9loXBBY5HuZsyuweowIs6ADg9UtStEv+k=
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/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/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=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.24.0/go.mod h1:B+bcQI1yTY+N0vqMpoZbEN7+XU4tNM0DmUiOwebFJWI=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.24.0 h1:mM8nKi6/iFQ0iqst80wDHU2ge198Ye/TfN0WBS5U24Y=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.24.0/go.mod h1:0PrIIzDteLSmNyxqcGYRL4mDIo8OTuBAOI/Bn1URxac=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 h1:Mw5xcxMwlqoJd97vwPxA8isEaIoxsta9/Q51+TTJLGE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0/go.mod h1:CQNu9bj7o7mC6U7+CA/schKEYakYXWr79ucDHTMGhCM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 h1:Xw8U6u2f8DK2XAkGRFV7BBLENgnTGX9i4rQRxJf+/vs=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0/go.mod h1:6KW1Fm6R/s6Z3PGXwSJN2K4eT6wQB3vXX6CVnYX9NmM=
go.opentelemetry.io/otel/exporters/prometheus v0.46.0 h1:I8WIFXR351FoLJYuloU4EgXbtNX2URfU/85pUPheIEQ=
go.opentelemetry.io/otel/exporters/prometheus v0.46.0/go.mod h1:ztwVUHe5DTR/1v7PeuGRnU5Bbd4QKYwApWmuutKsJSs=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.24.0 h1:JYE2HM7pZbOt5Jhk8ndWZTUWYOVift2cHjXVMkPdmdc=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.24.0/go.mod h1:yMb/8c6hVsnma0RpsBMNo0fEiQKeclawtgaIaOp2MLY=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 h1:s0PHtIkN+3xrbDOpt2M8OTG92cWqUESvzh2MxiR5xY8=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0/go.mod h1:hZlFbDbRt++MMPCCfSJfmhkGIWnX1h3XjkfxZUjLrIA=
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
go.opentelemetry.io/otel/sdk/metric v1.24.0 h1:yyMQrPzF+k88/DbH7o4FMAs80puqd+9osbiBrJrz/w8=
go.opentelemetry.io/otel/sdk/metric v1.24.0/go.mod h1:I6Y5FjH6rvEnTTAYQz3Mmv2kl6Ek5IIrmwTLqMrrOE0=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI=
go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
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.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
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/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.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
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.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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/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=
google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe h1:USL2DhxfgRchafRvt/wYyyQNzwgL7ZiURcozOE/Pkvo=
google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro=
google.golang.org/genproto/googleapis/api v0.0.0-20240205150955-31a09d347014 h1:x9PwdEgd11LgK+orcck69WVRo7DezSO4VUMPI4xpc8A=
google.golang.org/genproto/googleapis/api v0.0.0-20240205150955-31a09d347014/go.mod h1:rbHMSEDyoYX62nRVLOCc4Qt1HbsdytAYoVwgjiOhF3I=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240205150955-31a09d347014 h1:FSL3lRCkhaPFxqi0s9o+V4UI2WTzAVOvkgbd4kVV4Wg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240205150955-31a09d347014/go.mod h1:SaPjaZGWb0lPqs6Ittu0spdfrOArqji4ZdeP5IC/9N4=
google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY=
google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
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=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/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=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -8,7 +8,6 @@ import (
"github.com/spf13/viper"
"ely.by/chrly/internal/di"
"ely.by/chrly/internal/http"
"ely.by/chrly/internal/version"
)
@ -27,25 +26,6 @@ func shouldGetContainer() *Container {
return container
}
func startServer(modules ...string) error {
container := shouldGetContainer()
var config *viper.Viper
err := container.Resolve(&config)
if err != nil {
return err
}
config.Set("modules", modules)
err = container.Invoke(http.StartServer)
if err != nil {
return err
}
return nil
}
func init() {
cobra.OnInitialize(initConfig)
}

View File

@ -1,9 +1,15 @@
package cmd
import (
"context"
"log/slog"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"ely.by/chrly/internal/di"
"ely.by/chrly/internal/http"
"ely.by/chrly/internal/otel"
)
var serveCmd = &cobra.Command{
@ -17,3 +23,41 @@ var serveCmd = &cobra.Command{
func init() {
RootCmd.AddCommand(serveCmd)
}
func startServer(modules ...string) error {
container := shouldGetContainer()
var globalCtx context.Context
err := container.Resolve(&globalCtx)
if err != nil {
return err
}
var config *viper.Viper
err = container.Resolve(&config)
if err != nil {
return err
}
if !config.GetBool("otel.sdk.disabled") {
shutdownOtel, err := otel.SetupOTelSDK(globalCtx)
defer func() {
err := shutdownOtel(context.Background())
if err != nil {
slog.Error("Unable to shutdown OpenTelemetry", slog.Any("error", err))
}
}()
if err != nil {
return err
}
}
config.Set("modules", modules)
err = container.Invoke(http.StartServer)
if err != nil {
return err
}
return nil
}

21
internal/di/context.go Normal file
View File

@ -0,0 +1,21 @@
package di
import (
"context"
"os"
"os/signal"
"syscall"
"github.com/defval/di"
)
var contextDiOptions = di.Options(
di.Provide(newBaseContext),
)
func newBaseContext() context.Context {
ctx := context.Background()
ctx, _ = signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM, os.Kill)
return ctx
}

View File

@ -16,7 +16,7 @@ import (
// Since there are no options for selecting target backends,
// all constants in this case point to static specific implementations.
var dbDeOptions = di.Options(
var dbDiOptions = di.Options(
di.Provide(newRedis,
di.As(new(profiles.ProfilesRepository)),
di.As(new(profiles.ProfilesFinder)),
@ -24,13 +24,13 @@ var dbDeOptions = di.Options(
),
)
func newRedis(container *di.Container, config *viper.Viper) (*redis.Redis, error) {
func newRedis(container *di.Container, ctx context.Context, config *viper.Viper) (*redis.Redis, error) {
config.SetDefault("storage.redis.host", "localhost")
config.SetDefault("storage.redis.port", 6379)
config.SetDefault("storage.redis.poolSize", 10)
conn, err := redis.New(
context.Background(),
ctx,
db.NewZlibEncoder(&db.JsonSerializer{}),
fmt.Sprintf("%s:%d", config.GetString("storage.redis.host"), config.GetInt("storage.redis.port")),
config.GetInt("storage.redis.poolSize"),

View File

@ -5,12 +5,14 @@ import "github.com/defval/di"
func New() (*di.Container, error) {
return di.New(
configDiOptions,
loggerDiOptions,
dbDeOptions,
mojangDiOptions,
contextDiOptions,
dbDiOptions,
handlersDiOptions,
httpClientDiOptions,
loggerDiOptions,
mojangDiOptions,
profilesDiOptions,
serverDiOptions,
securityDiOptions,
serverDiOptions,
)
}

View File

@ -9,6 +9,7 @@ import (
"github.com/etherlabsio/healthcheck/v2"
"github.com/gorilla/mux"
"github.com/spf13/viper"
"go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux"
. "ely.by/chrly/internal/http"
"ely.by/chrly/internal/security"
@ -45,6 +46,7 @@ func newHandlerFactory(
}
router.StrictSlash(true)
router.Use(otelmux.Middleware("chrly"))
router.NotFoundHandler = http.HandlerFunc(NotFoundHandler)
if slices.Contains(enabledModules, ModuleProfiles) {

15
internal/di/httpClient.go Normal file
View File

@ -0,0 +1,15 @@
package di
import (
"net/http"
"github.com/defval/di"
)
var httpClientDiOptions = di.Options(
di.Provide(newHttpClient),
)
func newHttpClient() *http.Client {
return &http.Client{}
}

View File

@ -21,7 +21,7 @@ var mojangDiOptions = di.Options(
di.Provide(newMojangSignedTexturesProvider),
)
func newMojangApi(config *viper.Viper) (*mojang.MojangApi, error) {
func newMojangApi(config *viper.Viper, httpClient *http.Client) (*mojang.MojangApi, error) {
batchUuidsUrl := config.GetString("mojang.batch_uuids_url")
if batchUuidsUrl != "" {
if _, err := url.ParseRequestURI(batchUuidsUrl); err != nil {
@ -36,8 +36,6 @@ func newMojangApi(config *viper.Viper) (*mojang.MojangApi, error) {
}
}
httpClient := &http.Client{} // TODO: extract to the singleton dependency
return mojang.NewMojangApi(httpClient, batchUuidsUrl, profileUrl), nil
}
@ -62,21 +60,18 @@ func newMojangTexturesProviderFactory(
func newMojangTexturesProvider(
uuidsProvider mojang.UuidsProvider,
texturesProvider mojang.TexturesProvider,
) *mojang.MojangTexturesProvider {
return &mojang.MojangTexturesProvider{
UuidsProvider: uuidsProvider,
TexturesProvider: texturesProvider,
}
) (*mojang.MojangTexturesProvider, error) {
return mojang.NewMojangTexturesProvider(
uuidsProvider,
texturesProvider,
)
}
func newMojangTexturesUuidsProviderFactory(
batchProvider *mojang.BatchUuidsProvider,
uuidsStorage mojang.MojangUuidsStorage,
) mojang.UuidsProvider {
return &mojang.UuidsProviderWithCache{
Provider: batchProvider,
Storage: uuidsStorage,
}
) (mojang.UuidsProvider, error) {
return mojang.NewUuidsProviderWithCache(batchProvider, uuidsStorage)
}
func newMojangTexturesBatchUUIDsProvider(
@ -87,22 +82,19 @@ func newMojangTexturesBatchUUIDsProvider(
config.SetDefault("queue.batch_size", 10)
config.SetDefault("queue.strategy", "periodic")
// TODO: healthcheck is broken
uuidsProvider := mojang.NewBatchUuidsProvider(
return mojang.NewBatchUuidsProvider(
mojangApi.UsernamesToUuids,
config.GetInt("queue.batch_size"),
config.GetDuration("queue.loop_delay"),
config.GetString("queue.strategy") == "full-bus",
)
return uuidsProvider, nil
}
func newMojangSignedTexturesProvider(mojangApi *mojang.MojangApi) mojang.TexturesProvider {
return mojang.NewTexturesProviderWithInMemoryCache(
&mojang.MojangApiTexturesProvider{
MojangApiTexturesEndpoint: mojangApi.UuidToTextures,
},
)
func newMojangSignedTexturesProvider(mojangApi *mojang.MojangApi) (mojang.TexturesProvider, error) {
provider, err := mojang.NewMojangApiTexturesProvider(mojangApi.UuidToTextures)
if err != nil {
return nil, err
}
return mojang.NewTexturesProviderWithInMemoryCache(provider)
}

View File

@ -19,9 +19,9 @@ func newProfilesManager(r profiles.ProfilesRepository) *profiles.Manager {
func newProfilesProvider(
finder profiles.ProfilesFinder,
mojangProfilesProvider profiles.MojangProfilesProvider,
) *profiles.Provider {
return &profiles.Provider{
ProfilesFinder: finder,
MojangProfilesProvider: mojangProfilesProvider,
}
) (*profiles.Provider, error) {
return profiles.NewProvider(
finder,
mojangProfilesProvider,
)
}

View File

@ -8,7 +8,6 @@ import (
"time"
"github.com/defval/di"
"github.com/getsentry/raven-go"
"github.com/spf13/viper"
. "ely.by/chrly/internal/http"
@ -29,41 +28,22 @@ func newAuthenticator(config *viper.Viper) (*security.Jwt, error) {
return security.NewJwt([]byte(key)), nil
}
type serverParams struct {
di.Inject
func newServer(Config *viper.Viper, Handler http.Handler) *http.Server {
Config.SetDefault("server.host", "")
Config.SetDefault("server.port", 80)
Config *viper.Viper `di:""`
Handler http.Handler `di:""`
Sentry *raven.Client `di:"" optional:"true"`
}
var handler http.Handler = http.HandlerFunc(func(request http.ResponseWriter, response *http.Request) {
defer func() {
if recovered := recover(); recovered != nil {
debug.PrintStack()
request.WriteHeader(http.StatusInternalServerError)
}
}()
func newServer(params serverParams) *http.Server {
params.Config.SetDefault("server.host", "")
params.Config.SetDefault("server.port", 80)
Handler.ServeHTTP(request, response)
})
var handler http.Handler
if params.Sentry != nil {
// raven.Recoverer uses DefaultClient and nothing can be done about it
// To avoid code duplication, if the Sentry service is successfully initiated,
// it will also replace DefaultClient, so raven.Recoverer will work with the instance
// created in the application constructor
handler = raven.Recoverer(params.Handler)
} else {
// Raven's Recoverer is prints the stacktrace and sets the corresponding status itself.
// But there is no magic and if you don't define a panic handler, Mux will just reset the connection
handler = http.HandlerFunc(func(request http.ResponseWriter, response *http.Request) {
defer func() {
if recovered := recover(); recovered != nil {
debug.PrintStack() // TODO: colorize output
request.WriteHeader(http.StatusInternalServerError)
}
}()
params.Handler.ServeHTTP(request, response)
})
}
address := fmt.Sprintf("%s:%d", params.Config.GetString("server.host"), params.Config.GetInt("server.port"))
address := fmt.Sprintf("%s:%d", Config.GetString("server.host"), Config.GetInt("server.port"))
server := &http.Server{
Addr: address,
ReadTimeout: 5 * time.Second,

View File

@ -4,9 +4,6 @@ import (
"context"
"encoding/json"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/gorilla/mux"
@ -16,10 +13,7 @@ import (
"ely.by/chrly/internal/security"
)
func StartServer(server *http.Server, logger slf.Logger) {
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM, os.Kill)
defer cancel()
func StartServer(ctx context.Context, server *http.Server, logger slf.Logger) {
srvErr := make(chan error, 1)
go func() {
logger.Info("Starting the server, HTTP on: :addr", wd.StringParam("addr", server.Addr))

View File

@ -6,7 +6,9 @@ import (
"sync"
"time"
"github.com/SentimensRG/ctx/mergectx"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/metric"
"go.uber.org/multierr"
"ely.by/chrly/internal/utils"
)
@ -23,6 +25,7 @@ type BatchUuidsProvider struct {
fireChan chan any
stopChan chan any
onFirstCall sync.Once
metrics *batchUuidsProviderMetrics
}
func NewBatchUuidsProvider(
@ -30,22 +33,31 @@ func NewBatchUuidsProvider(
batchSize int,
awaitDelay time.Duration,
fireOnFull bool,
) *BatchUuidsProvider {
) (*BatchUuidsProvider, error) {
queue := utils.NewQueue[*job]()
metrics, err := newBatchUuidsProviderMetrics(otel.GetMeterProvider().Meter(ScopeName), queue)
if err != nil {
return nil, err
}
return &BatchUuidsProvider{
UsernamesToUuidsEndpoint: endpoint,
stopChan: make(chan any),
batch: batchSize,
delay: awaitDelay,
fireOnFull: fireOnFull,
queue: utils.NewQueue[*job](),
queue: queue,
fireChan: make(chan any),
}
metrics: metrics,
}, nil
}
type job struct {
Username string
Ctx context.Context
ResultChan chan<- *jobResult
Username string
Ctx context.Context
QueuingTime time.Time
ResultChan chan<- *jobResult
}
type jobResult struct {
@ -55,7 +67,7 @@ type jobResult struct {
func (p *BatchUuidsProvider) GetUuid(ctx context.Context, username string) (*ProfileInfo, error) {
resultChan := make(chan *jobResult)
n := p.queue.Enqueue(&job{username, ctx, resultChan})
n := p.queue.Enqueue(&job{username, ctx, time.Now(), resultChan})
if p.fireOnFull && n%p.batch == 0 {
p.fireChan <- struct{}{}
}
@ -92,11 +104,14 @@ func (p *BatchUuidsProvider) startQueue() {
}
func (p *BatchUuidsProvider) fireRequest() {
// Since this method is an aggregator, it uses its own context to manage its lifetime
reqCtx := context.Background()
jobs := make([]*job, 0, p.batch)
n := p.batch
for {
foundJobs, left := p.queue.Dequeue(n)
for i := range foundJobs {
p.metrics.QueueTime.Record(reqCtx, float64(time.Since(foundJobs[i].QueuingTime).Milliseconds()))
if foundJobs[i].Ctx.Err() != nil {
// If the job context has already ended, its result will be returned in the GetUuid method
close(foundJobs[i].ResultChan)
@ -119,14 +134,15 @@ func (p *BatchUuidsProvider) fireRequest() {
return
}
ctx := context.Background()
usernames := make([]string, len(jobs))
for i, job := range jobs {
usernames[i] = job.Username
ctx = mergectx.Join(ctx, job.Ctx)
}
profiles, err := p.UsernamesToUuidsEndpoint(ctx, usernames)
p.metrics.Requests.Add(reqCtx, 1)
p.metrics.BatchSize.Record(reqCtx, int64(len(usernames)))
profiles, err := p.UsernamesToUuidsEndpoint(reqCtx, usernames)
for _, job := range jobs {
response := &jobResult{}
if err == nil {
@ -145,3 +161,48 @@ func (p *BatchUuidsProvider) fireRequest() {
close(job.ResultChan)
}
}
func newBatchUuidsProviderMetrics(meter metric.Meter, queue *utils.Queue[*job]) (*batchUuidsProviderMetrics, error) {
m := &batchUuidsProviderMetrics{}
var errors, err error
m.Requests, err = meter.Int64Counter(
"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",
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",
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()))
return nil
}),
)
errors = multierr.Append(errors, err)
m.QueueTime, err = meter.Float64Histogram(
"uuids.batch.queue.lag",
metric.WithDescription("Lag between placing a job in the queue and starting its processing"),
metric.WithUnit("ms"),
)
errors = multierr.Append(errors, err)
return m, errors
}
type batchUuidsProviderMetrics struct {
Requests metric.Int64Counter
BatchSize metric.Int64Histogram
QueueLength metric.Int64ObservableGauge
QueueTime metric.Float64Histogram
}

View File

@ -41,7 +41,7 @@ type batchUuidsProviderTestSuite struct {
func (s *batchUuidsProviderTestSuite) SetupTest() {
s.MojangApi = &mojangUsernamesToUuidsRequestMock{}
s.Provider = NewBatchUuidsProvider(
s.Provider, _ = NewBatchUuidsProvider(
s.MojangApi.UsernamesToUuids,
3,
awaitDelay,

View File

@ -7,8 +7,13 @@ import (
"strings"
"github.com/brunomvsouza/singleflight"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/metric"
"go.uber.org/multierr"
)
const ScopeName = "ely.by/chrly/internal/mojang"
var InvalidUsername = errors.New("the username passed doesn't meet Mojang's requirements")
// https://help.minecraft.net/hc/en-us/articles/4408950195341#h_01GE5JX1Z0CZ833A7S54Y195KV
@ -22,11 +27,28 @@ type TexturesProvider interface {
GetTextures(ctx context.Context, uuid string) (*ProfileResponse, error)
}
func NewMojangTexturesProvider(
uuidsProvider UuidsProvider,
texturesProvider TexturesProvider,
) (*MojangTexturesProvider, error) {
meter, err := newProviderMetrics(otel.GetMeterProvider().Meter(ScopeName))
if err != nil {
return nil, err
}
return &MojangTexturesProvider{
UuidsProvider: uuidsProvider,
TexturesProvider: texturesProvider,
metrics: meter,
}, nil
}
type MojangTexturesProvider struct {
UuidsProvider
TexturesProvider
group singleflight.Group[string, *ProfileResponse]
metrics *providerMetrics
group singleflight.Group[string, *ProfileResponse]
}
func (p *MojangTexturesProvider) GetForUsername(ctx context.Context, username string) (*ProfileResponse, error) {
@ -36,8 +58,14 @@ func (p *MojangTexturesProvider) GetForUsername(ctx context.Context, username st
username = strings.ToLower(username)
result, err, _ := p.group.Do(username, func() (*ProfileResponse, error) {
profile, err := p.UuidsProvider.GetUuid(ctx, username)
result, err, shared := p.group.Do(username, func() (*ProfileResponse, error) {
var profile *ProfileInfo
var textures *ProfileResponse
var err error
defer p.recordMetrics(ctx, profile, textures, err)
profile, err = p.UuidsProvider.GetUuid(ctx, username)
if err != nil {
return nil, err
}
@ -46,15 +74,100 @@ func (p *MojangTexturesProvider) GetForUsername(ctx context.Context, username st
return nil, nil
}
return p.TexturesProvider.GetTextures(ctx, profile.Id)
textures, err = p.TexturesProvider.GetTextures(ctx, profile.Id)
return textures, err
})
if shared {
p.metrics.Shared.Add(ctx, 1)
}
return result, err
}
func (p *MojangTexturesProvider) recordMetrics(ctx context.Context, profile *ProfileInfo, textures *ProfileResponse, err error) {
if err != nil {
p.metrics.Failed.Add(ctx, 1)
return
}
if profile == nil {
p.metrics.UsernameMissed.Add(ctx, 1)
p.metrics.TextureMissed.Add(ctx, 1)
return
}
p.metrics.UsernameFound.Add(ctx, 1)
if textures != nil {
p.metrics.TextureFound.Add(ctx, 1)
} else {
p.metrics.TextureMissed.Add(ctx, 1)
}
}
type NilProvider struct {
}
func (*NilProvider) GetForUsername(ctx context.Context, username string) (*ProfileResponse, error) {
return nil, nil
}
func newProviderMetrics(meter metric.Meter) (*providerMetrics, error) {
m := &providerMetrics{}
var errors, err error
m.UsernameFound, err = meter.Int64Counter(
"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",
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",
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",
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",
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",
metric.WithDescription("Number of requests that are already being processed in another thread"),
metric.WithUnit("1"),
)
errors = multierr.Append(errors, err)
return m, errors
}
type providerMetrics struct {
UsernameFound metric.Int64Counter
UsernameMissed metric.Int64Counter
TextureFound metric.Int64Counter
TextureMissed metric.Int64Counter
Failed metric.Int64Counter
Shared metric.Int64Counter
}

View File

@ -51,10 +51,10 @@ func (s *providerTestSuite) SetupTest() {
s.UuidsProvider = &mockUuidsProvider{}
s.TexturesProvider = &TexturesProviderMock{}
s.Provider = &MojangTexturesProvider{
UuidsProvider: s.UuidsProvider,
TexturesProvider: s.TexturesProvider,
}
s.Provider, _ = NewMojangTexturesProvider(
s.UuidsProvider,
s.TexturesProvider,
)
}
func (s *providerTestSuite) TearDownTest() {

View File

@ -6,13 +6,33 @@ import (
"time"
"github.com/jellydator/ttlcache/v3"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/metric"
"go.uber.org/multierr"
)
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))
if err != nil {
return nil, err
}
return &MojangApiTexturesProvider{
MojangApiTexturesEndpoint: endpoint,
metrics: metrics,
}, nil
}
type MojangApiTexturesProvider struct {
MojangApiTexturesEndpoint func(ctx context.Context, uuid string, signed bool) (*ProfileResponse, error)
MojangApiTexturesEndpoint MojangApiTexturesProviderFunc
metrics *mojangApiTexturesProviderMetrics
}
func (p *MojangApiTexturesProvider) GetTextures(ctx context.Context, uuid string) (*ProfileResponse, error) {
p.metrics.Requests.Add(ctx, 1)
return p.MojangApiTexturesEndpoint(ctx, uuid, true)
}
@ -22,27 +42,35 @@ type TexturesProviderWithInMemoryCache struct {
provider TexturesProvider
once sync.Once
cache *ttlcache.Cache[string, *ProfileResponse]
metrics *texturesProviderWithInMemoryCacheMetrics
}
func NewTexturesProviderWithInMemoryCache(provider TexturesProvider) *TexturesProviderWithInMemoryCache {
storage := &TexturesProviderWithInMemoryCache{
func NewTexturesProviderWithInMemoryCache(provider TexturesProvider) (*TexturesProviderWithInMemoryCache, error) {
metrics, err := newTexturesProviderWithInMemoryCacheMetrics(otel.GetMeterProvider().Meter(ScopeName))
if err != nil {
return nil, err
}
return &TexturesProviderWithInMemoryCache{
provider: provider,
cache: ttlcache.New[string, *ProfileResponse](
ttlcache.WithDisableTouchOnHit[string, *ProfileResponse](),
// I'm aware of ttlcache.WithLoader(), but it doesn't allow to return an error
),
}
return storage
metrics: metrics,
}, nil
}
func (s *TexturesProviderWithInMemoryCache) GetTextures(ctx context.Context, uuid string) (*ProfileResponse, error) {
item := s.cache.Get(uuid)
// Don't check item.IsExpired() since Get function is already did this check
if item != nil {
s.metrics.Hits.Add(ctx, 1)
return item.Value(), nil
}
s.metrics.Misses.Add(ctx, 1)
result, err := s.provider.GetTextures(ctx, uuid)
if err != nil {
return nil, err
@ -66,3 +94,47 @@ func (s *TexturesProviderWithInMemoryCache) startGcOnce() {
go s.cache.Start()
})
}
func newMojangApiTexturesProviderMetrics(meter metric.Meter) (*mojangApiTexturesProviderMetrics, error) {
m := &mojangApiTexturesProviderMetrics{}
var errors, err error
m.Requests, err = meter.Int64Counter(
"textures.request.sent",
metric.WithDescription("Number of textures requests sent to Mojang API"),
metric.WithUnit("1"),
)
errors = multierr.Append(errors, err)
return m, errors
}
type mojangApiTexturesProviderMetrics struct {
Requests metric.Int64Counter
}
func newTexturesProviderWithInMemoryCacheMetrics(meter metric.Meter) (*texturesProviderWithInMemoryCacheMetrics, error) {
m := &texturesProviderWithInMemoryCacheMetrics{}
var errors, err error
m.Hits, err = meter.Int64Counter(
"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",
metric.WithDescription("Number of Mojang textures missing from local cache"),
metric.WithUnit("1"),
)
errors = multierr.Append(errors, err)
return m, errors
}
type texturesProviderWithInMemoryCacheMetrics struct {
Hits metric.Int64Counter
Misses metric.Int64Counter
}

View File

@ -53,9 +53,7 @@ type MojangApiTexturesProviderSuite struct {
func (s *MojangApiTexturesProviderSuite) SetupTest() {
s.MojangApi = &MojangUuidToTexturesRequestMock{}
s.Provider = &MojangApiTexturesProvider{
MojangApiTexturesEndpoint: s.MojangApi.UuidToTextures,
}
s.Provider, _ = NewMojangApiTexturesProvider(s.MojangApi.UuidToTextures)
}
func (s *MojangApiTexturesProviderSuite) TearDownTest() {
@ -95,7 +93,7 @@ type TexturesProviderWithInMemoryCacheSuite struct {
func (s *TexturesProviderWithInMemoryCacheSuite) SetupTest() {
s.Original = &TexturesProviderMock{}
s.Provider = NewTexturesProviderWithInMemoryCache(s.Original)
s.Provider, _ = NewTexturesProviderWithInMemoryCache(s.Original)
}
func (s *TexturesProviderWithInMemoryCacheSuite) TearDownTest() {

View File

@ -1,6 +1,12 @@
package mojang
import "context"
import (
"context"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/metric"
"go.uber.org/multierr"
)
type MojangUuidsStorage interface {
// The second argument must be returned as a incoming username in case,
@ -10,13 +16,32 @@ type MojangUuidsStorage interface {
StoreMojangUuid(ctx context.Context, username string, uuid string) error
}
func NewUuidsProviderWithCache(o UuidsProvider, s MojangUuidsStorage) (*UuidsProviderWithCache, error) {
metrics, err := newUuidsProviderWithCacheMetrics(otel.GetMeterProvider().Meter(ScopeName))
if err != nil {
return nil, err
}
return &UuidsProviderWithCache{
Provider: o,
Storage: s,
metrics: metrics,
}, nil
}
type UuidsProviderWithCache struct {
Provider UuidsProvider
Storage MojangUuidsStorage
metrics *uuidsProviderWithCacheMetrics
}
func (p *UuidsProviderWithCache) GetUuid(ctx context.Context, username string) (*ProfileInfo, error) {
uuid, foundUsername, err := p.Storage.GetUuidForMojangUsername(ctx, username)
var uuid, foundUsername string
var err error
defer p.recordMetrics(ctx, uuid, foundUsername, err)
uuid, foundUsername, err = p.Storage.GetUuidForMojangUsername(ctx, username)
if err != nil {
return nil, err
}
@ -45,3 +70,41 @@ func (p *UuidsProviderWithCache) GetUuid(ctx context.Context, username string) (
return profile, nil
}
func (p *UuidsProviderWithCache) recordMetrics(ctx context.Context, uuid string, username string, err error) {
if err != nil {
return
}
if username != "" {
p.metrics.Hits.Add(ctx, 1)
} else {
p.metrics.Misses.Add(ctx, 1)
}
}
func newUuidsProviderWithCacheMetrics(meter metric.Meter) (*uuidsProviderWithCacheMetrics, error) {
m := &uuidsProviderWithCacheMetrics{}
var errors, err error
m.Hits, err = meter.Int64Counter(
"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",
metric.WithDescription("Number of Mojang UUIDs missing from local cache"),
metric.WithUnit("1"),
)
errors = multierr.Append(errors, err)
return m, errors
}
type uuidsProviderWithCacheMetrics struct {
Hits metric.Int64Counter
Misses metric.Int64Counter
}

View File

@ -50,10 +50,7 @@ type UuidsProviderWithCacheSuite struct {
func (s *UuidsProviderWithCacheSuite) SetupTest() {
s.Original = &UuidsProviderMock{}
s.Storage = &MojangUuidsStorageMock{}
s.Provider = &UuidsProviderWithCache{
Provider: s.Original,
Storage: s.Storage,
}
s.Provider, _ = NewUuidsProviderWithCache(s.Original, s.Storage)
}
func (s *UuidsProviderWithCacheSuite) TearDownTest() {

148
internal/otel/setup.go Normal file
View File

@ -0,0 +1,148 @@
package otel
import (
"context"
"errors"
"log/slog"
"time"
"github.com/agoda-com/opentelemetry-go/otelslog"
logsOtel "github.com/agoda-com/opentelemetry-logs-go"
logsAutoconfig "github.com/agoda-com/opentelemetry-logs-go/autoconfigure/sdk/logs"
"github.com/agoda-com/opentelemetry-logs-go/sdk/logs"
"go.opentelemetry.io/contrib/exporters/autoexport"
runtimeMetrics "go.opentelemetry.io/contrib/instrumentation/runtime"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/resource"
"go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/semconv/v1.4.0"
"ely.by/chrly/internal/version"
)
func SetupOTelSDK(ctx context.Context) (shutdown func(context.Context) error, err error) {
var shutdownFuncs []func(context.Context) error
// shutdown calls cleanup functions registered via shutdownFuncs.
// The errors from the calls are joined.
// Each registered cleanup will be invoked once
shutdown = func(ctx context.Context) error {
var err error
for _, fn := range shutdownFuncs {
err = errors.Join(err, fn(ctx))
}
shutdownFuncs = nil
return err
}
// handleErr calls shutdown for cleanup and makes sure that all errors are returned
handleErr := func(inErr error) {
err = errors.Join(inErr, shutdown(ctx))
}
// Set up propagator
prop := newPropagator()
otel.SetTextMapPropagator(prop)
// Set up resource
res, err := newResource(ctx)
if err != nil {
handleErr(err)
return
}
// Set up logs provider
logsProvider, err := newLoggerProvider(ctx, res)
if err != nil {
handleErr(err)
return
}
shutdownFuncs = append(shutdownFuncs, logsProvider.Shutdown)
logsOtel.SetLoggerProvider(logsProvider)
otelSlog := slog.New(otelslog.NewOtelHandler(logsProvider, &otelslog.HandlerOptions{Level: slog.LevelDebug}))
slog.SetDefault(otelSlog)
// Set up trace provider
tracerProvider, err := newTraceProvider(ctx, res)
if err != nil {
handleErr(err)
return
}
shutdownFuncs = append(shutdownFuncs, tracerProvider.Shutdown)
otel.SetTracerProvider(tracerProvider)
// Set up meter provider
meterProvider, err := newMeterProvider(ctx, res)
if err != nil {
handleErr(err)
return
}
shutdownFuncs = append(shutdownFuncs, meterProvider.Shutdown)
otel.SetMeterProvider(meterProvider)
err = runtimeMetrics.Start(runtimeMetrics.WithMinimumReadMemStatsInterval(time.Second))
if err != nil {
handleErr(err)
return
}
return
}
func newPropagator() propagation.TextMapPropagator {
return propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{},
propagation.Baggage{},
)
}
func newResource(ctx context.Context) (*resource.Resource, error) {
return resource.New(
ctx,
resource.WithFromEnv(),
resource.WithTelemetrySDK(),
resource.WithOS(),
resource.WithContainer(),
resource.WithHost(),
resource.WithAttributes(
semconv.ServiceNameKey.String("chrly"),
semconv.ServiceVersionKey.String(version.Version()),
),
)
}
func newLoggerProvider(ctx context.Context, res *resource.Resource) (*logs.LoggerProvider, error) {
return logsAutoconfig.NewLoggerProvider(ctx, logsAutoconfig.WithResource(res)), nil
}
func newTraceProvider(ctx context.Context, res *resource.Resource) (*trace.TracerProvider, error) {
exporter, err := autoexport.NewSpanExporter(ctx)
if err != nil {
return nil, err
}
return trace.NewTracerProvider(
trace.WithBatcher(exporter),
trace.WithResource(res),
), nil
}
func newMeterProvider(ctx context.Context, res *resource.Resource) (*metric.MeterProvider, error) {
reader, err := autoexport.NewMetricReader(ctx)
if err != nil {
return nil, err
}
return metric.NewMeterProvider(
metric.WithReader(reader),
metric.WithResource(res),
), nil
}

View File

@ -16,6 +16,13 @@ type MojangProfilesProvider interface {
GetForUsername(ctx context.Context, username string) (*mojang.ProfileResponse, error)
}
func NewProvider(pf ProfilesFinder, mpf MojangProfilesProvider) (*Provider, error) {
return &Provider{
ProfilesFinder: pf,
MojangProfilesProvider: mpf,
}, nil
}
type Provider struct {
ProfilesFinder
MojangProfilesProvider

View File

@ -54,10 +54,10 @@ type CombinedProfilesProviderSuite struct {
func (t *CombinedProfilesProviderSuite) SetupSubTest() {
t.ProfilesFinder = &ProfilesFinderMock{}
t.MojangProfilesProvider = &MojangProfilesProviderMock{}
t.Provider = &Provider{
ProfilesFinder: t.ProfilesFinder,
MojangProfilesProvider: t.MojangProfilesProvider,
}
t.Provider, _ = NewProvider(
t.ProfilesFinder,
t.MojangProfilesProvider,
)
}
func (t *CombinedProfilesProviderSuite) TearDownSubTest() {

View File

@ -38,3 +38,10 @@ func (s *Queue[T]) Dequeue(n int) ([]T, int) {
return items, l - n
}
func (s *Queue[T]) Len() int {
s.lock.Lock()
defer s.lock.Unlock()
return len(s.items)
}

View File

@ -35,4 +35,13 @@ func TestQueue(t *testing.T) {
require.Equal(t, "username4", items[1])
require.Equal(t, "username5", items[2])
})
t.Run("Len", func(t *testing.T) {
s := NewQueue[string]()
s.Enqueue("username1")
s.Enqueue("username2")
s.Enqueue("username3")
require.Equal(t, 3, s.Len())
})
}