From 2f57a4edaadff606c9da5c25df0189e65f1f2c8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?To=CF=80?= Date: Wed, 21 Jun 2023 18:07:20 +0200 Subject: [PATCH] add context support to handler & add otel middlware + example --- _examples/handler/example.go | 4 +- _examples/otel/go.mod | 42 ++++++++ _examples/otel/go.sum | 72 +++++++++++++ _examples/otel/main.go | 99 ++++++++++++++++++ _examples/otel/otel.go | 45 ++++++++ disgo.go | 2 + go.mod | 9 +- go.sum | 21 +++- handler/autocomplete.go | 4 +- handler/command.go | 4 +- handler/component.go | 4 +- handler/handler.go | 18 ++-- handler/middleware.go | 4 +- handler/middleware/defer.go | 6 +- handler/middleware/go.go | 5 +- handler/middleware/logger.go | 5 +- handler/middleware/otelhandler/config.go | 50 +++++++++ handler/middleware/otelhandler/otel.go | 126 +++++++++++++++++++++++ handler/middleware/print.go | 6 +- handler/modal.go | 4 +- handler/mux.go | 14 +-- 21 files changed, 502 insertions(+), 42 deletions(-) create mode 100644 _examples/otel/go.mod create mode 100644 _examples/otel/go.sum create mode 100644 _examples/otel/main.go create mode 100644 _examples/otel/otel.go create mode 100644 handler/middleware/otelhandler/config.go create mode 100644 handler/middleware/otelhandler/otel.go diff --git a/_examples/handler/example.go b/_examples/handler/example.go index 8ccf4fe4b..e44653ca8 100644 --- a/_examples/handler/example.go +++ b/_examples/handler/example.go @@ -118,7 +118,7 @@ func handleContent(content string) handler.CommandHandler { } func handleVariableContent(event *handler.CommandEvent) error { - group := event.Variables["group"] + group := event.Vars["group"] return event.CreateMessage(discord.MessageCreate{Content: "group: " + group}) } @@ -134,7 +134,7 @@ func handlePing(event *handler.CommandEvent) error { } func handleComponent(event *handler.ComponentEvent) error { - data := event.Variables["data"] + data := event.Vars["data"] return event.CreateMessage(discord.MessageCreate{Content: "component: " + data}) } diff --git a/_examples/otel/go.mod b/_examples/otel/go.mod new file mode 100644 index 000000000..438d2694d --- /dev/null +++ b/_examples/otel/go.mod @@ -0,0 +1,42 @@ +module github.com/disgoorg/disgo/_examples/otel + +go 1.21 + +toolchain go1.21.0 + +replace github.com/disgoorg/disgo => ../../ + +require ( + github.com/disgoorg/disgo v0.17.1 + github.com/disgoorg/log v1.2.1 + github.com/disgoorg/snowflake/v2 v2.0.1 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0 + go.opentelemetry.io/otel v1.23.1 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.23.1 + go.opentelemetry.io/otel/sdk v1.23.1 + go.opentelemetry.io/otel/trace v1.23.1 +) + +require ( + github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/disgoorg/json v1.1.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/gorilla/websocket v1.5.1 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect + github.com/sasha-s/go-csync v0.0.0-20240107134140-fcbab37b09ad // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.23.1 // indirect + go.opentelemetry.io/otel/metric v1.23.1 // indirect + go.opentelemetry.io/proto/otlp v1.1.0 // indirect + golang.org/x/crypto v0.19.0 // indirect + golang.org/x/net v0.20.0 // indirect + golang.org/x/sys v0.17.0 // indirect + golang.org/x/text v0.14.0 // indirect + google.golang.org/genproto v0.0.0-20240205150955-31a09d347014 // 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.0 // indirect + google.golang.org/protobuf v1.32.0 // indirect +) diff --git a/_examples/otel/go.sum b/_examples/otel/go.sum new file mode 100644 index 000000000..2d6c9b44e --- /dev/null +++ b/_examples/otel/go.sum @@ -0,0 +1,72 @@ +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/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/disgoorg/json v1.1.0 h1:7xigHvomlVA9PQw9bMGO02PHGJJPqvX5AnwlYg/Tnys= +github.com/disgoorg/json v1.1.0/go.mod h1:BHDwdde0rpQFDVsRLKhma6Y7fTbQKub/zdGO5O9NqqA= +github.com/disgoorg/log v1.2.1 h1:kZYAWkUBcGy4LbZcgYtgYu49xNVLy+xG5Uq3yz5VVQs= +github.com/disgoorg/log v1.2.1/go.mod h1:hhQWYTFTnIGzAuFPZyXJEi11IBm9wq+/TVZt/FEwX0o= +github.com/disgoorg/snowflake/v2 v2.0.1 h1:CuUxGLwggUxEswZOmZ+mZ5i0xSumQdXW9tXW7uGqe+0= +github.com/disgoorg/snowflake/v2 v2.0.1/go.mod h1:SPU9c2CNn5DSyb86QcKtdZgix9osEtKrHLW4rMhfLCs= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +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/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/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= +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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sasha-s/go-csync v0.0.0-20240107134140-fcbab37b09ad h1:qIQkSlF5vAUHxEmTbaqt1hkJ/t6skqEGYiMag343ucI= +github.com/sasha-s/go-csync v0.0.0-20240107134140-fcbab37b09ad/go.mod h1:/pA7k3zsXKdjjAiUhB5CjuKib9KJGCaLvZwtxGC8U0s= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0 h1:doUP+ExOpH3spVTLS0FcWGLnQrPct/hD/bCPbDRUEAU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0/go.mod h1:rdENBZMT2OE6Ne/KLwpiXudnAsbdrdBaqBvTN8M8BgA= +go.opentelemetry.io/otel v1.23.1 h1:Za4UzOqJYS+MUczKI320AtqZHZb7EqxO00jAHE0jmQY= +go.opentelemetry.io/otel v1.23.1/go.mod h1:Td0134eafDLcTS4y+zQ26GE8u3dEuRBiBCTUIRHaikA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.23.1 h1:o8iWeVFa1BcLtVEV0LzrCxV2/55tB3xLxADr6Kyoey4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.23.1/go.mod h1:SEVfdK4IoBnbT2FXNM/k8yC08MrfbhWk3U4ljM8B3HE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.23.1 h1:cfuy3bXmLJS7M1RZmAL6SuhGtKUp2KEsrm00OlAXkq4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.23.1/go.mod h1:22jr92C6KwlwItJmQzfixzQM3oyyuYLCfHiMY+rpsPU= +go.opentelemetry.io/otel/metric v1.23.1 h1:PQJmqJ9u2QaJLBOELl1cxIdPcpbwzbkjfEyelTl2rlo= +go.opentelemetry.io/otel/metric v1.23.1/go.mod h1:mpG2QPlAfnK8yNhNJAxDZruU9Y1/HubbC+KyH8FaCWI= +go.opentelemetry.io/otel/sdk v1.23.1 h1:O7JmZw0h76if63LQdsBMKQDWNb5oEcOThG9IrxscV+E= +go.opentelemetry.io/otel/sdk v1.23.1/go.mod h1:LzdEVR5am1uKOOwfBWFef2DCi1nu3SA8XQxx2IerWFk= +go.opentelemetry.io/otel/trace v1.23.1 h1:4LrmmEd8AU2rFvU1zegmvqW7+kWarxtNOPyeL6HmYY8= +go.opentelemetry.io/otel/trace v1.23.1/go.mod h1:4IpnpJFwr1mo/6HL8XIPJaE9y0+u1KcVmuW7dwFSVrI= +go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= +go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= +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/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +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-20240205150955-31a09d347014 h1:g/4bk7P6TPMkAUbUhquq98xey1slwvuVJPosdBqYJlU= +google.golang.org/genproto v0.0.0-20240205150955-31a09d347014/go.mod h1:xEgQu1e4stdSSsxPDK8Azkrk/ECl5HvdPf6nbZrTS5M= +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.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0= +google.golang.org/grpc v1.61.0/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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/_examples/otel/main.go b/_examples/otel/main.go new file mode 100644 index 000000000..53805a8e3 --- /dev/null +++ b/_examples/otel/main.go @@ -0,0 +1,99 @@ +package main + +import ( + "context" + "net/http" + "os" + "os/signal" + "syscall" + "time" + + "github.com/disgoorg/disgo" + "github.com/disgoorg/disgo/bot" + "github.com/disgoorg/disgo/discord" + "github.com/disgoorg/disgo/handler" + "github.com/disgoorg/disgo/handler/middleware/otelhandler" + "github.com/disgoorg/disgo/rest" + "github.com/disgoorg/log" + "github.com/disgoorg/snowflake/v2" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" +) + +const ( + Name = "example" + Namespace = "github.com/disgoorg/disgo/_examples/otel" + InstanceID = "1" + Version = "0.0.1" +) + +var ( + token = os.Getenv("disgo_token") + guildID = snowflake.GetEnv("disgo_guild_id") + otelEndpoint = os.Getenv("otel_endpoint") + otelSecure = os.Getenv("otel_secure") + commands = []discord.ApplicationCommandCreate{ + discord.SlashCommandCreate{ + Name: "ping", + Description: "Replies with pong", + }, + } +) + +func main() { + log.SetLevel(log.LevelDebug) + log.SetFlags(log.LstdFlags | log.Lshortfile) + + tracer, err := newTracer() + if err != nil { + log.Fatal("error while getting tracer") + } + + r := handler.New() + r.Use(otelhandler.Middleware("example")) + r.Command("/ping", pingHandler(tracer)) + + client, err := disgo.New(token, + bot.WithDefaultGateway(), + bot.WithRestClientConfigOpts( + rest.WithHTTPClient(&http.Client{ + Transport: otelhttp.NewTransport(nil), + Timeout: 5 * time.Second, + }), + ), + bot.WithEventListeners(r), + ) + if err != nil { + log.Fatal("error while building disgo: ", err) + } + + if err = handler.SyncCommands(client, commands, []snowflake.ID{guildID}); err != nil { + log.Fatal("error while syncing commands: ", err) + } + + defer client.Close(context.TODO()) + + if err = client.OpenGateway(context.TODO()); err != nil { + log.Fatal("errors while connecting to gateway: ", err) + } + + log.Info("example is now running. Press CTRL-C to exit.") + s := make(chan os.Signal, 1) + signal.Notify(s, syscall.SIGINT, syscall.SIGTERM, os.Interrupt) + <-s +} + +func pingHandler(tracer trace.Tracer) func(event *handler.CommandEvent) error { + return func(event *handler.CommandEvent) error { + ctx, span := tracer.Start(event.Ctx, "ping", + trace.WithSpanKind(trace.SpanKindServer), + trace.WithAttributes(attribute.String("my.attribute", "test")), + ) + defer span.End() + + return event.CreateMessage(discord.MessageCreate{ + Content: "pong", + }, rest.WithCtx(ctx)) + } +} diff --git a/_examples/otel/otel.go b/_examples/otel/otel.go new file mode 100644 index 000000000..b09e07268 --- /dev/null +++ b/_examples/otel/otel.go @@ -0,0 +1,45 @@ +package main + +import ( + "context" + "time" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/sdk/resource" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.20.0" + "go.opentelemetry.io/otel/trace" +) + +func newTracer() (trace.Tracer, error) { + opts := []otlptracehttp.Option{ + otlptracehttp.WithEndpoint(otelEndpoint), + } + if otelSecure == "false" { + opts = append(opts, otlptracehttp.WithInsecure()) + } + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + exp, err := otlptracehttp.New(ctx, opts...) + if err != nil { + return nil, err + } + + tp := sdktrace.NewTracerProvider( + sdktrace.WithBatcher(exp), + sdktrace.WithResource(resource.NewWithAttributes( + semconv.SchemaURL, + semconv.ServiceName(Name), + semconv.ServiceNamespace(Namespace), + semconv.ServiceInstanceID(InstanceID), + semconv.ServiceVersion(Version), + )), + ) + otel.SetTracerProvider(tp) + otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) + + return otel.Tracer(Name), nil +} diff --git a/disgo.go b/disgo.go index 02818c932..e71641d20 100644 --- a/disgo.go +++ b/disgo.go @@ -67,6 +67,8 @@ var ( // Version is the currently used version of DisGo Version = getVersion() + SemVersion = "semver:" + Version + // OS is the currently used OS OS = getOS() ) diff --git a/go.mod b/go.mod index a257995f6..b3ff8a0ec 100644 --- a/go.mod +++ b/go.mod @@ -8,13 +8,18 @@ require ( github.com/gorilla/websocket v1.5.1 github.com/sasha-s/go-csync v0.0.0-20240107134140-fcbab37b09ad github.com/stretchr/testify v1.8.4 - golang.org/x/crypto v0.18.0 + go.opentelemetry.io/otel v1.23.1 + go.opentelemetry.io/otel/metric v1.23.1 + go.opentelemetry.io/otel/trace v1.23.1 + golang.org/x/crypto v0.19.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/net v0.20.0 // indirect - golang.org/x/sys v0.16.0 // indirect + golang.org/x/sys v0.17.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 1990bfcdb..06030d263 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,13 @@ github.com/disgoorg/json v1.1.0 h1:7xigHvomlVA9PQw9bMGO02PHGJJPqvX5AnwlYg/Tnys= github.com/disgoorg/json v1.1.0/go.mod h1:BHDwdde0rpQFDVsRLKhma6Y7fTbQKub/zdGO5O9NqqA= github.com/disgoorg/snowflake/v2 v2.0.1 h1:CuUxGLwggUxEswZOmZ+mZ5i0xSumQdXW9tXW7uGqe+0= github.com/disgoorg/snowflake/v2 v2.0.1/go.mod h1:SPU9c2CNn5DSyb86QcKtdZgix9osEtKrHLW4rMhfLCs= +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/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/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -12,12 +19,18 @@ github.com/sasha-s/go-csync v0.0.0-20240107134140-fcbab37b09ad h1:qIQkSlF5vAUHxE github.com/sasha-s/go-csync v0.0.0-20240107134140-fcbab37b09ad/go.mod h1:/pA7k3zsXKdjjAiUhB5CjuKib9KJGCaLvZwtxGC8U0s= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +go.opentelemetry.io/otel v1.23.1 h1:Za4UzOqJYS+MUczKI320AtqZHZb7EqxO00jAHE0jmQY= +go.opentelemetry.io/otel v1.23.1/go.mod h1:Td0134eafDLcTS4y+zQ26GE8u3dEuRBiBCTUIRHaikA= +go.opentelemetry.io/otel/metric v1.23.1 h1:PQJmqJ9u2QaJLBOELl1cxIdPcpbwzbkjfEyelTl2rlo= +go.opentelemetry.io/otel/metric v1.23.1/go.mod h1:mpG2QPlAfnK8yNhNJAxDZruU9Y1/HubbC+KyH8FaCWI= +go.opentelemetry.io/otel/trace v1.23.1 h1:4LrmmEd8AU2rFvU1zegmvqW7+kWarxtNOPyeL6HmYY8= +go.opentelemetry.io/otel/trace v1.23.1/go.mod h1:4IpnpJFwr1mo/6HL8XIPJaE9y0+u1KcVmuW7dwFSVrI= +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/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.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= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/handler/autocomplete.go b/handler/autocomplete.go index f07f4c922..c210b4618 100644 --- a/handler/autocomplete.go +++ b/handler/autocomplete.go @@ -12,8 +12,8 @@ import ( type AutocompleteEvent struct { *events.AutocompleteInteractionCreate - Variables map[string]string - Ctx context.Context + Vars map[string]string + Ctx context.Context } func (e *AutocompleteEvent) GetFollowupMessage(messageID snowflake.ID, opts ...rest.RequestOpt) (*discord.Message, error) { diff --git a/handler/command.go b/handler/command.go index ce03b22d6..b399b8178 100644 --- a/handler/command.go +++ b/handler/command.go @@ -12,8 +12,8 @@ import ( type CommandEvent struct { *events.ApplicationCommandInteractionCreate - Variables map[string]string - Ctx context.Context + Vars map[string]string + Ctx context.Context } func (e *CommandEvent) GetInteractionResponse(opts ...rest.RequestOpt) (*discord.Message, error) { diff --git a/handler/component.go b/handler/component.go index 9195bd509..9502a1287 100644 --- a/handler/component.go +++ b/handler/component.go @@ -12,8 +12,8 @@ import ( type ComponentEvent struct { *events.ComponentInteractionCreate - Variables map[string]string - Ctx context.Context + Vars map[string]string + Ctx context.Context } func (e *ComponentEvent) GetInteractionResponse(opts ...rest.RequestOpt) (*discord.Message, error) { diff --git a/handler/handler.go b/handler/handler.go index d83df9205..952b976d6 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -5,7 +5,7 @@ // Slash Commands can have subcommands, which are nested paths. For example /test/subcommand1 or /test/subcommandgroup/subcommand. // // The handler also supports variables in its path which is especially useful for subcommands, components and modals. -// Variables are defined by curly braces like {variable} and can be accessed in the handler via the Variables map. +// Vars are defined by curly braces like {variable} and can be accessed in the handler via the Vars map. // // You can also register middlewares, which are executed before the handler is called. Middlewares can be used to check permissions, validate input or do other things. // Middlewares can also be attached to sub-routers, which is useful if you want to have a middleware for all subcommands of a command as an example. @@ -81,8 +81,8 @@ func (h *handlerHolder[T]) Handle(ctx context.Context, path string, variables ma ApplicationCommandInteraction: event.Interaction.(discord.ApplicationCommandInteraction), Respond: event.Respond, }, - Variables: variables, - Ctx: ctx, + Vars: variables, + Ctx: ctx, }) case AutocompleteHandler: return handler(&AutocompleteEvent{ @@ -91,8 +91,8 @@ func (h *handlerHolder[T]) Handle(ctx context.Context, path string, variables ma AutocompleteInteraction: event.Interaction.(discord.AutocompleteInteraction), Respond: event.Respond, }, - Variables: variables, - Ctx: ctx, + Vars: variables, + Ctx: ctx, }) case ComponentHandler: return handler(&ComponentEvent{ @@ -101,8 +101,8 @@ func (h *handlerHolder[T]) Handle(ctx context.Context, path string, variables ma ComponentInteraction: event.Interaction.(discord.ComponentInteraction), Respond: event.Respond, }, - Variables: variables, - Ctx: ctx, + Vars: variables, + Ctx: ctx, }) case ModalHandler: return handler(&ModalEvent{ @@ -111,8 +111,8 @@ func (h *handlerHolder[T]) Handle(ctx context.Context, path string, variables ma ModalSubmitInteraction: event.Interaction.(discord.ModalSubmitInteraction), Respond: event.Respond, }, - Variables: variables, - Ctx: ctx, + Vars: variables, + Ctx: ctx, }) } return errors.New("unknown handler type") diff --git a/handler/middleware.go b/handler/middleware.go index 6978c0184..2b04534d0 100644 --- a/handler/middleware.go +++ b/handler/middleware.go @@ -6,9 +6,9 @@ import ( "github.com/disgoorg/disgo/events" ) -type ( - Handler func(ctx context.Context, e *events.InteractionCreate) error +type Handler func(ctx context.Context, e *events.InteractionCreate) error +type ( Middleware func(next Handler) Handler Middlewares []Middleware diff --git a/handler/middleware/defer.go b/handler/middleware/defer.go index ee5fa4455..1a4f26b3a 100644 --- a/handler/middleware/defer.go +++ b/handler/middleware/defer.go @@ -1,6 +1,8 @@ package middleware import ( + "context" + "github.com/disgoorg/disgo/discord" "github.com/disgoorg/disgo/events" "github.com/disgoorg/disgo/handler" @@ -13,7 +15,7 @@ import ( // Note: You can use this middleware in combination with the Go middleware to defer & run in a goroutine. func Defer(interactionType discord.InteractionType, updateMessage bool, ephemeral bool) handler.Middleware { return func(next handler.Handler) handler.Handler { - return func(event *events.InteractionCreate) error { + return func(ctx context.Context, event *events.InteractionCreate) error { if event.Type() == interactionType { responseType := discord.InteractionResponseTypeDeferredCreateMessage if updateMessage { @@ -30,7 +32,7 @@ func Defer(interactionType discord.InteractionType, updateMessage bool, ephemera return err } } - return next(event) + return next(ctx, event) } } } diff --git a/handler/middleware/go.go b/handler/middleware/go.go index 58c81161e..ca07d5dca 100644 --- a/handler/middleware/go.go +++ b/handler/middleware/go.go @@ -1,6 +1,7 @@ package middleware import ( + "context" "log/slog" "github.com/disgoorg/disgo/discord" @@ -23,9 +24,9 @@ func GoDefer(interactionType discord.InteractionType, updateMessage bool, epheme // GoErr is a middleware that runs the next handler in a goroutine and lets you handle the error which may occur. func GoErr(h handler.ErrorHandler) handler.Middleware { return func(next handler.Handler) handler.Handler { - return func(e *events.InteractionCreate) error { + return func(ctx context.Context, e *events.InteractionCreate) error { go func() { - if err := next(e); err != nil { + if err := next(ctx, e); err != nil { h(e, err) } }() diff --git a/handler/middleware/logger.go b/handler/middleware/logger.go index 8c5095993..735280e7a 100644 --- a/handler/middleware/logger.go +++ b/handler/middleware/logger.go @@ -1,6 +1,7 @@ package middleware import ( + "context" "log/slog" "github.com/disgoorg/disgo/events" @@ -8,8 +9,8 @@ import ( ) var Logger handler.Middleware = func(next handler.Handler) handler.Handler { - return func(e *events.InteractionCreate) error { + return func(ctx context.Context, e *events.InteractionCreate) error { e.Client().Logger().Info("handling interaction", slog.Int64("interaction_id", int64(e.Interaction.ID()))) - return next(e) + return next(ctx, e) } } diff --git a/handler/middleware/otelhandler/config.go b/handler/middleware/otelhandler/config.go new file mode 100644 index 000000000..8511fd6b6 --- /dev/null +++ b/handler/middleware/otelhandler/config.go @@ -0,0 +1,50 @@ +package otelhandler + +import ( + "context" + + "github.com/disgoorg/disgo/events" + "go.opentelemetry.io/otel" + otelmetric "go.opentelemetry.io/otel/metric" + oteltrace "go.opentelemetry.io/otel/trace" +) + +func DefaultConfig() *Config { + return &Config{ + TracerProvider: otel.GetTracerProvider(), + MeterProvider: otel.GetMeterProvider(), + Filter: nil, + } +} + +type Config struct { + TracerProvider oteltrace.TracerProvider + MeterProvider otelmetric.MeterProvider + Filter func(ctx context.Context, e *events.InteractionCreate) bool +} + +type ConfigOpt func(config *Config) + +func (c *Config) Apply(opts []ConfigOpt) { + for _, opt := range opts { + opt(c) + } +} + +func WithTracerProvider(provider oteltrace.TracerProvider) ConfigOpt { + return func(cfg *Config) { + cfg.TracerProvider = provider + } +} + +func WithMeterProvider(provider otelmetric.MeterProvider) ConfigOpt { + return func(cfg *Config) { + cfg.MeterProvider = provider + } +} + +func WithFilter(filter func(ctx context.Context, e *events.InteractionCreate) bool) ConfigOpt { + return func(cfg *Config) { + cfg.Filter = filter + } +} diff --git a/handler/middleware/otelhandler/otel.go b/handler/middleware/otelhandler/otel.go new file mode 100644 index 000000000..402851f78 --- /dev/null +++ b/handler/middleware/otelhandler/otel.go @@ -0,0 +1,126 @@ +package otelhandler + +import ( + "context" + "fmt" + + "github.com/disgoorg/disgo" + "github.com/disgoorg/disgo/discord" + "github.com/disgoorg/disgo/events" + "github.com/disgoorg/disgo/handler" + "go.opentelemetry.io/otel/attribute" + otelmetric "go.opentelemetry.io/otel/metric" + oteltrace "go.opentelemetry.io/otel/trace" +) + +func Middleware(serverName string, opts ...ConfigOpt) handler.Middleware { + cfg := DefaultConfig() + cfg.Apply(opts) + + tracer := cfg.TracerProvider.Tracer( + disgo.Module, + oteltrace.WithInstrumentationVersion(disgo.SemVersion), + ) + meter := cfg.MeterProvider.Meter( + disgo.Module, + otelmetric.WithInstrumentationVersion(disgo.SemVersion), + ) + + return func(handler handler.Handler) handler.Handler { + h := &otelHandler{ + serverName: serverName, + handler: handler, + tracer: tracer, + meter: meter, + } + return h.Handle + } +} + +type otelHandler struct { + serverName string + handler handler.Handler + tracer oteltrace.Tracer + meter otelmetric.Meter +} + +func (h *otelHandler) Handle(ctx context.Context, e *events.InteractionCreate) error { + var ( + spanName string + attr []attribute.KeyValue + ) + switch i := e.Interaction.(type) { + case discord.ApplicationCommandInteraction: + switch d := i.Data.(type) { + case discord.SlashCommandInteractionData: + spanName = fmt.Sprintf("SlashCommand: %s", d.CommandPath()) + if d.SubCommandName != nil { + attr = append(attr, attribute.String("interaction.command.subcommand", *d.SubCommandName)) + } + if d.SubCommandGroupName != nil { + attr = append(attr, attribute.String("interaction.command.subcommandgroup", *d.SubCommandGroupName)) + } + attr = append(attr, attribute.String("interaction.command.path", d.CommandPath())) + case discord.UserCommandInteractionData: + spanName = fmt.Sprintf("UserCommand: /%s", d.CommandName()) + attr = append(attr, attribute.String("interaction.command.name", d.CommandName())) + case discord.MessageCommandInteractionData: + spanName = fmt.Sprintf("MessageCommand: /%s", d.CommandName()) + attr = append(attr, attribute.String("interaction.command.name", d.CommandName())) + } + attr = append(attr, + attribute.String("interaction.command.name", i.Data.CommandName()), + attribute.String("interaction.command.id", i.Data.CommandID().String()), + ) + if i.Data.GuildID() != nil { + attr = append(attr, attribute.String("interaction.command.guild.id", i.Data.GuildID().String())) + } + case discord.AutocompleteInteraction: + spanName = fmt.Sprintf("Autocomplete: %s", i.Data.CommandPath()) + attr = append(attr, + attribute.String("interaction.command.path", i.Data.CommandPath()), + attribute.String("interaction.command.name", i.Data.CommandName), + attribute.String("interaction.command.id", i.Data.CommandID.String()), + ) + if i.Data.GuildID != nil { + attr = append(attr, attribute.String("interaction.command.guild.id", i.Data.GuildID.String())) + } + case discord.ComponentInteraction: + switch i.Data.(type) { + case discord.ButtonInteractionData: + spanName = fmt.Sprintf("Button: %s", i.Data.CustomID()) + case discord.SelectMenuInteractionData: + spanName = fmt.Sprintf("SelectMenu: %s", i.Data.CustomID()) + } + attr = append(attr, + attribute.Int("interaction.component.type", int(i.Data.Type())), + attribute.String("interaction.component.customid", i.Data.CustomID()), + ) + + case discord.ModalSubmitInteraction: + spanName = fmt.Sprintf("ModalSubmit: %s", i.Data.CustomID) + attr = append(attr, + attribute.String("interaction.modal.customid", i.Data.CustomID), + ) + default: + spanName = "Unknown" + } + attr = append(attr, + attribute.Int("interaction.type", int(e.Interaction.Type())), + attribute.String("interaction.id", e.Interaction.ID().String()), + attribute.String("interaction.application.id", e.Interaction.ApplicationID().String()), + attribute.String("interaction.user.id", e.Interaction.User().ID.String()), + attribute.String("interaction.channel.id", e.Interaction.Channel().ID().String()), + attribute.String("interaction.createdat", e.Interaction.CreatedAt().String()), + ) + if e.Interaction.GuildID() != nil { + attr = append(attr, attribute.String("interaction.guild.id", e.Interaction.GuildID().String())) + } + + ctx, span := h.tracer.Start(ctx, spanName, + oteltrace.WithSpanKind(oteltrace.SpanKindServer), + oteltrace.WithAttributes(attr...), + ) + defer span.End() + return h.handler(ctx, e) +} diff --git a/handler/middleware/print.go b/handler/middleware/print.go index f209bc83b..d50be44ba 100644 --- a/handler/middleware/print.go +++ b/handler/middleware/print.go @@ -1,15 +1,17 @@ package middleware import ( + "context" + "github.com/disgoorg/disgo/events" "github.com/disgoorg/disgo/handler" ) func Print(content string) handler.Middleware { return func(next handler.Handler) handler.Handler { - return func(event *events.InteractionCreate) error { + return func(ctx context.Context, event *events.InteractionCreate) error { println(content) - return next(event) + return next(ctx, event) } } } diff --git a/handler/modal.go b/handler/modal.go index d14ea9499..69240b869 100644 --- a/handler/modal.go +++ b/handler/modal.go @@ -12,8 +12,8 @@ import ( type ModalEvent struct { *events.ModalSubmitInteractionCreate - Variables map[string]string - Ctx context.Context + Vars map[string]string + Ctx context.Context } func (e *ModalEvent) GetInteractionResponse(opts ...rest.RequestOpt) (*discord.Message, error) { diff --git a/handler/mux.go b/handler/mux.go index 34d41e50e..bb54f0c33 100644 --- a/handler/mux.go +++ b/handler/mux.go @@ -1,8 +1,8 @@ package handler import ( - "log/slog" "context" + "log/slog" "strings" "github.com/disgoorg/disgo/bot" @@ -95,26 +95,26 @@ func (r *Mux) Match(path string, t discord.InteractionType) bool { } // Handle handles the given interaction event. -func (r *Mux) Handle(path string, variables map[string]string, e *events.InteractionCreate) error { - handlerChain := func(event *events.InteractionCreate) error { +func (r *Mux) Handle(ctx context.Context, path string, variables map[string]string, e *events.InteractionCreate) error { + handlerChain := Handler(func(ctx context.Context, event *events.InteractionCreate) error { path = parseVariables(path, r.pattern, variables) for _, route := range r.routes { if route.Match(path, e.Type()) { - return route.Handle(path, variables, e) + return route.Handle(ctx, path, variables, e) } } if r.notFoundHandler != nil { return r.notFoundHandler(e) } return nil - } + }) for i := len(r.middlewares) - 1; i >= 0; i-- { - handlerChain = r.middlewares[i](ctx, handlerChain) + handlerChain = r.middlewares[i](handlerChain) } - return handlerChain(e) + return handlerChain(ctx, e) } // Use adds the given middlewares to the current Router.