diff --git a/otel/README.md b/otel/README.md index 472c51e2b..394329288 100644 --- a/otel/README.md +++ b/otel/README.md @@ -1,97 +1,28 @@ -# OpenTelemetry Plugin - -The `otel` plugin is a [Goa](https://github.com/goadesign/goa/tree/v3) plugin that instruments -HTTP endpoints with OpenTelemetry configuring it with the endpoint route pattern. This -plugin is used in conjunction with the [Clue](https://github.com/goadesign/clue) project to -instrument services with OpenTelemetry. - -## Usage - -Simply import the plugin in the service design package. Use the blank identifier `_` as explicit -package name: - -```go -package design - -import . "goa.design/goa/v3/dsl" -import _ "goa.design/plugins/v3/otel" # Enables the otel plugin - -var _ = API("... -``` - -and generate as usual: - -```bash -goa gen PACKAGE -``` - -where `PACKAGE` is the Go import path of the design package. - -## Effects of the Plugin - -Importing the `otel` package changes the behavior of the `gen` command of the -`goa` tool. The `gen` command output is modified so that the generated HTTP -handlers are wrapped with a call to the `otelhttp.WithRouteTag`. - -That is the code generated in `gen/http//server/server.go` that -mounts an endpoint handler onto the HTTP mux changes from: - -```go -mux.Handle("", "", f) -``` - -to: - -```go -mux.Handle("", otelhttp.WithRouteTag("", f).ServeHTTP)) -``` - -## OpenTelemetry Configuration - -For the code generated by the plugin to work the OpenTelemetry SDK must be -initialized. This can be done by importing the `clue` package and calling -`clue.ConfigureOpenTelemetry` in the `main` function of the service: - -```go -package main - -import ( - "context" - "net/http" - - "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" - "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" - "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" - "goa.design/clue/clue" - "goa.design/clue/log" -) - -func main() { - // Create OpenTelemetry metric and span exporters - // Use stdout exporters for demo. - metricExporter, err := stdoutmetric.New() - if err != nil { - panic(err) - } - spanExporter, err := stdouttrace.New() - if err != nil { - panic(err) - } - - // Configure OpenTelemetry. - ctx := log.Context(context.Background()) - cfg, err := clue.NewConfig(ctx, "service", "1.0.0", metricExporter, spanExporter) - if err != nil { - panic(err) - } - clue.ConfigureOpenTelemetry(ctx, cfg) - - // Create HTTP handler and start server. - var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte("Hello, World!")) - }) - handler = otelhttp.NewHandler(handler, "service") // Instrument handler. - http.ListenAndServe(":8080", handler) -} -``` - +# OpenTelemetry Plugin (Deprecated) + +> **Deprecated**: This plugin is no longer necessary and will be removed in a +> future release. The Goa HTTP muxer now sets `r.Pattern` on every matched +> request (using the Go 1.22+ convention), which `otelhttp` v0.65.0+ reads +> automatically to tag spans and metrics with the matched route. +> +> **Migration**: Remove the blank import from your design package and +> regenerate: +> +> ```diff +> - import _ "goa.design/plugins/v3/otel" +> ``` +> +> No other changes are needed — route tagging now happens automatically in +> the Goa muxer. + +## Background + +The `otel` plugin was a [Goa](https://github.com/goadesign/goa/tree/v3) plugin +that wrapped generated HTTP handlers with `otelhttp.WithRouteTag` to set the +`http.route` attribute on OpenTelemetry spans and metrics. + +`otelhttp.WithRouteTag` was +[removed](https://github.com/open-telemetry/opentelemetry-go-contrib/pull/8268) +in `otelhttp` v0.65.0 because `otelhttp` now reads `r.Pattern` (added in Go +1.22) to obtain the route automatically. Goa's muxer has been updated to set +`r.Pattern` on every dispatched request, making this plugin unnecessary. diff --git a/otel/generate.go b/otel/generate.go index 41747f3a5..3a7c4d630 100644 --- a/otel/generate.go +++ b/otel/generate.go @@ -1,9 +1,16 @@ +// Package otel was a Goa plugin that instrumented HTTP handlers with +// otelhttp.WithRouteTag to set the http.route attribute on spans and metrics. +// +// Deprecated: As of Goa v3.x.x the default HTTP muxer sets r.Pattern on every +// matched request, which otelhttp (v0.65.0+) reads automatically to tag spans +// and metrics with the matched route. This plugin is no longer necessary and +// will be removed in a future release. Remove the blank import from your +// design package: +// +// import _ "goa.design/plugins/v3/otel" // ← delete this line package otel import ( - "path/filepath" - "strings" - "goa.design/goa/v3/codegen" "goa.design/goa/v3/eval" ) @@ -13,27 +20,10 @@ func init() { codegen.RegisterPluginLast("otel", "gen", nil, Generate) } -// Generate generates the call to otelhttp.WithRouteTag -func Generate(genpkg string, roots []eval.Root, files []*codegen.File) ([]*codegen.File, error) { - for _, f := range files { - if filepath.Base(f.Path) == "server.go" { - for _, s := range f.SectionTemplates { - if s.Name == "server-handler" { - s.Source = strings.Replace( - s.Source, - `mux.Handle("{{ .Verb }}", "{{ .Path }}", f)`, - `mux.Handle("{{ .Verb }}", "{{ .Path }}", otelhttp.WithRouteTag("{{ .Path }}", f).ServeHTTP)`, - 1, - ) - } - } - imports := f.SectionTemplates[0].Data.(map[string]any)["Imports"].([]*codegen.ImportSpec) - imports = append(imports, &codegen.ImportSpec{ - Path: "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp", - Name: "otelhttp", - }) - f.SectionTemplates[0].Data.(map[string]any)["Imports"] = imports - } - } +// Generate is a no-op kept for backward compatibility. The Goa HTTP muxer now +// sets r.Pattern on every request, making explicit route tagging unnecessary. +// +// Deprecated: Remove the otel plugin import from your design package. +func Generate(_ string, _ []eval.Root, files []*codegen.File) ([]*codegen.File, error) { return files, nil } diff --git a/otel/generate_test.go b/otel/generate_test.go index f2e9142d1..d79e3e86e 100644 --- a/otel/generate_test.go +++ b/otel/generate_test.go @@ -37,6 +37,10 @@ func TestOtel(t *testing.T) { fs, err := Generate("", []eval.Root{root}, serverFiles) assert.NoError(t, err) require.Len(t, fs, 2) + // Generate is a no-op: the returned files must be identical + // to the input server files. + assert.Same(t, serverFiles[0], fs[0]) + assert.Same(t, serverFiles[1], fs[1]) sections := fs[0].Section("server-handler") require.Len(t, sections, 1) section := sections[0] diff --git a/otel/testdata/multiple routes.golden b/otel/testdata/multiple routes.golden index 575f94c24..7605fbd68 100644 --- a/otel/testdata/multiple routes.golden +++ b/otel/testdata/multiple routes.golden @@ -1,4 +1,3 @@ - // MountMethodHandler configures the mux to serve the "Service2" service // "Method" endpoint. func MountMethodHandler(mux goahttp.Muxer, h http.Handler) { @@ -8,6 +7,6 @@ func MountMethodHandler(mux goahttp.Muxer, h http.Handler) { h.ServeHTTP(w, r) } } - mux.Handle("GET", "/", otelhttp.WithRouteTag("/", f).ServeHTTP) - mux.Handle("GET", "/other", otelhttp.WithRouteTag("/other", f).ServeHTTP) + mux.Handle("GET", "/", f) + mux.Handle("GET", "/other", f) } diff --git a/otel/testdata/one route.golden b/otel/testdata/one route.golden index deded923c..827900a3b 100644 --- a/otel/testdata/one route.golden +++ b/otel/testdata/one route.golden @@ -1,4 +1,3 @@ - // MountMethodHandler configures the mux to serve the "Service" service // "Method" endpoint. func MountMethodHandler(mux goahttp.Muxer, h http.Handler) { @@ -8,5 +7,5 @@ func MountMethodHandler(mux goahttp.Muxer, h http.Handler) { h.ServeHTTP(w, r) } } - mux.Handle("GET", "/", otelhttp.WithRouteTag("/", f).ServeHTTP) + mux.Handle("GET", "/", f) }