Skip to content

Commit

Permalink
Finish OpenTelemetry plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
raphael committed Jan 19, 2024
1 parent ead785b commit fa90b03
Show file tree
Hide file tree
Showing 9 changed files with 167 additions and 4 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ PLUGINS=\
docs \
goakit \
i18n \
otel \
types \
zaplogger \
zerologger
Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,20 @@ require (
github.com/go-kit/kit v0.13.0
github.com/go-kit/log v0.2.1
github.com/gorilla/websocket v1.5.1
github.com/stretchr/testify v1.8.4
go.uber.org/zap v1.26.0
goa.design/goa/v3 v3.14.6
)

require (
github.com/AnatolyRugalev/goregen v0.1.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598 // indirect
github.com/go-chi/chi/v5 v5.0.11 // indirect
github.com/go-logfmt/logfmt v0.5.1 // indirect
github.com/google/uuid v1.5.0 // indirect
github.com/manveru/faker v0.0.0-20171103152722-9fbc68a78c4d // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/sergi/go-diff v1.3.1 // indirect
go.uber.org/multierr v1.10.0 // indirect
golang.org/x/mod v0.14.0 // indirect
Expand Down
7 changes: 7 additions & 0 deletions otel/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#! /usr/bin/make
#
# Makefile for Goa otel plugin

# include common Makefile content for plugins
GOPATH=$(shell go env GOPATH)
include ../plugins.mk
54 changes: 53 additions & 1 deletion otel/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# 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.
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

Expand Down Expand Up @@ -30,3 +32,53 @@ where `PACKAGE` is the Go import path of the design package.
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`
## 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)
}
```
5 changes: 2 additions & 3 deletions otel/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,8 @@ func Generate(genpkg string, roots []eval.Root, files []*codegen.File) ([]*codeg
if s.Name == "server-handler" {
s.Source = strings.Replace(
s.Source,
"{{- range Routes }}",
`f = otelhttp.WithRouteTag("{{ .Path }}", f).ServeHTTP
{{- range Routes }}`,
`mux.Handle("{{ .Verb }}", "{{ .Path }}", f)`,
`mux.Handle("{{ .Verb }}", "{{ .Path }}", otelhttp.WithRouteTag("{{ .Path }}", f).ServeHTTP)`,
1,
)
}
Expand Down
50 changes: 50 additions & 0 deletions otel/generate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package otel

import (
"bytes"
"flag"
"fmt"
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"goa.design/goa/v3/codegen"
"goa.design/goa/v3/eval"
httpcodegen "goa.design/goa/v3/http/codegen"
"goa.design/plugins/v3/otel/testdata"
)

var update = flag.Bool("update", false, "update golden files")

func TestOtel(t *testing.T) {
cases := []struct {
Name string
DSL func()
}{
{"one route", testdata.OneRoute},
{"multiple routes", testdata.MultipleRoutes},
}
for _, c := range cases {
t.Run(c.Name, func(t *testing.T) {
root := codegen.RunDSL(t, c.DSL)
serverFiles := httpcodegen.ServerFiles("gen", root)
require.Len(t, serverFiles, 2)
fs, err := Generate("", []eval.Root{root}, serverFiles)
assert.NoError(t, err)
require.Len(t, fs, 2)
sections := fs[0].Section("server-handler")
require.Len(t, sections, 1)
section := sections[0]
var buf bytes.Buffer
assert.NoError(t, section.Write(&buf))
golden := filepath.Join("testdata", fmt.Sprintf("%s.golden", c.Name))
if *update {
assert.NoError(t, os.WriteFile(golden, buf.Bytes(), 0644))
}
expected, _ := os.ReadFile(golden)
assert.Equal(t, buf.String(), string(expected))
})
}
}
28 changes: 28 additions & 0 deletions otel/testdata/dsls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package testdata

import (
. "goa.design/goa/v3/dsl"
_ "goa.design/plugins/v3/docs"
)

var OneRoute = func() {
Service("Service", func() {
Method("Method", func() {
HTTP(func() {
GET("/")
})
})
})
}

var MultipleRoutes = func() {
Service("Service2", func() {
Method("Method", func() {
HTTP(func() {
GET("/")
GET("/other")
})
GRPC(func() {})
})
})
}
12 changes: 12 additions & 0 deletions otel/testdata/multiple routes.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// MountMethodHandler configures the mux to serve the "Service2" service
// "Method" endpoint.
func MountMethodHandler(mux goahttp.Muxer, h http.Handler) {
f, ok := h.(http.HandlerFunc)
if !ok {
f = func(w http.ResponseWriter, r *http.Request) {
h.ServeHTTP(w, r)
}
}
mux.Handle("GET", "/", otelhttp.WithRouteTag("/", f).ServeHTTP)
mux.Handle("GET", "/other", otelhttp.WithRouteTag("/other", f).ServeHTTP)
}
11 changes: 11 additions & 0 deletions otel/testdata/one route.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// MountMethodHandler configures the mux to serve the "Service" service
// "Method" endpoint.
func MountMethodHandler(mux goahttp.Muxer, h http.Handler) {
f, ok := h.(http.HandlerFunc)
if !ok {
f = func(w http.ResponseWriter, r *http.Request) {
h.ServeHTTP(w, r)
}
}
mux.Handle("GET", "/", otelhttp.WithRouteTag("/", f).ServeHTTP)
}

0 comments on commit fa90b03

Please sign in to comment.