diff --git a/build/docker-compose.yml b/build/docker-compose.yml index c43d4e622..106e501a9 100644 --- a/build/docker-compose.yml +++ b/build/docker-compose.yml @@ -6,6 +6,10 @@ services: dockerfile: build/Dockerfile ports: - "8080:3000" + environment: + - JAEGER_HTTP_URL=http://jaeger:14268/api/traces + depends_on: + - jaeger swagger-ui: build: context: ../ @@ -14,4 +18,10 @@ services: - "8002:8080" volumes: - ../docs/swagger.yaml:/app/swagger.yaml - command: ["serve", "/app/swagger.yaml", "--no-open", "--port", "8080"] \ No newline at end of file + command: ["serve", "/app/swagger.yaml", "--no-open", "--port", "8080"] + jaeger: + image: jaegertracing/all-in-one:latest + ports: + - "6831:6831/udp" + - "16686:16686" + - "14268:14268" \ No newline at end of file diff --git a/cmd/main.go b/cmd/main.go index d8d77aa50..3bce403ef 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -14,6 +14,12 @@ import ( "github.com/sirupsen/logrus" "github.com/tbd54566975/ssi-service/config" "github.com/tbd54566975/ssi-service/pkg/server" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/jaeger" + "go.opentelemetry.io/otel/sdk/resource" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.10.0" ) const ( @@ -29,6 +35,30 @@ func init() { logrus.SetOutput(os.Stdout) } + +// tracerProvider returns an OpenTelemetry TracerProvider configured to use +// the Jaeger exporter that will send spans to the provided url. The returned +// TracerProvider will also use a Resource configured with all the information +// about the application. +func newTracerProvider() (*sdktrace.TracerProvider, error) { + // Create the Jaeger exporter + exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(os.Getenv("JAEGER_HTTP_URL")))) + if err != nil { + return nil, err + } + tp := sdktrace.NewTracerProvider( + // Always be sure to batch in production. + sdktrace.WithBatcher(exp), + // Record information about this application in a Resource. + sdktrace.WithResource(resource.NewWithAttributes( + semconv.SchemaURL, + semconv.ServiceNameKey.String("SSI Service"), + semconv.ServiceVersionKey.String("0.1"), + )), + ) + return tp, nil +} + // @title SSI Service API // @version 0.1 // @description https://github.com/TBD54566975/ssi-service @@ -104,6 +134,14 @@ func run() error { serverErrors := make(chan error, 1) + // Create a new tracer provider with a batch span processor and the given exporter. + tp, err := newTracerProvider() + if err != nil { + logrus.Fatalf("failed to initialize exporter: %s", err) + } + + otel.SetTracerProvider(tp) + go func() { logrus.Infof("main: server started and listening on -> %s", api.Addr) @@ -118,6 +156,9 @@ func run() error { ctx, cancel := context.WithTimeout(context.Background(), cfg.Server.ShutdownTimeout) defer cancel() + + // Handle shutdown properly so nothing leaks. + defer func() { _ = tp.Shutdown(ctx) }() if err := api.Shutdown(ctx); err != nil { api.Close() diff --git a/go.mod b/go.mod index 8f43f4c99..d6eb289a4 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.8.1 github.com/stretchr/testify v1.8.0 + go.opentelemetry.io/otel/exporters/jaeger v1.8.0 go.opentelemetry.io/otel/trace v1.8.0 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d gopkg.in/go-playground/validator.v9 v9.31.0 @@ -25,6 +26,8 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect + github.com/go-logr/logr v1.2.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-playground/validator/v10 v10.11.0 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect @@ -45,8 +48,9 @@ require ( github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect - go.opentelemetry.io/otel v1.8.0 // indirect - golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e // indirect + go.opentelemetry.io/otel v1.8.0 + go.opentelemetry.io/otel/sdk v1.8.0 + golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect golang.org/x/text v0.3.7 // indirect gopkg.in/go-playground/assert.v1 v1.2.1 // indirect diff --git a/go.sum b/go.sum index 53efc21ff..4664fcc9c 100644 --- a/go.sum +++ b/go.sum @@ -18,7 +18,9 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeC github.com/dimfeld/httptreemux/v5 v5.4.0 h1:IiHYEjh+A7pYbhWyjmGnj5HZK6gpOOvyBXCJ+BE8/Gs= github.com/dimfeld/httptreemux/v5 v5.4.0/go.mod h1:QeEylH57C0v3VO0tkKraVz9oD3Uu93CKPnTLbsidvSw= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +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.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= @@ -95,6 +97,7 @@ github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4 github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -113,6 +116,10 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17 github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= go.opentelemetry.io/otel v1.8.0 h1:zcvBFizPbpa1q7FehvFiHbQwGzmPILebO0tyqIR5Djg= go.opentelemetry.io/otel v1.8.0/go.mod h1:2pkj+iMj0o03Y+cW6/m8Y4WkRdYN3AvCXCnzRMp9yvM= +go.opentelemetry.io/otel/exporters/jaeger v1.8.0 h1:TLLqD6kDhLPziEC7pgPrMvP9lAqdk3n1gf8DiFSnfW8= +go.opentelemetry.io/otel/exporters/jaeger v1.8.0/go.mod h1:GbWg+ng88rDtx+id26C34QLqw2erqJeAjsCx9AFeHfE= +go.opentelemetry.io/otel/sdk v1.8.0 h1:xwu69/fNuwbSHWe/0PGS888RmjWY181OmcXDQKu7ZQk= +go.opentelemetry.io/otel/sdk v1.8.0/go.mod h1:uPSfc+yfDH2StDM/Rm35WE8gXSNdvCg023J6HeGNO0c= go.opentelemetry.io/otel/trace v1.8.0 h1:cSy0DF9eGI5WIfNwZ1q2iUyGj00tGzP24dE1lOlHrfY= go.opentelemetry.io/otel/trace v1.8.0/go.mod h1:0Bt3PXY8w+3pheS3hQUt+wow8b1ojPaTBoTCh2zIFI4= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -123,11 +130,12 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e h1:CsOuNlbOuf0mzxJIefr6Q4uAUetRUwZE4qt7VfzP+xo= -golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= diff --git a/pkg/server/framework/server.go b/pkg/server/framework/server.go index 652811eac..bdbe88f48 100644 --- a/pkg/server/framework/server.go +++ b/pkg/server/framework/server.go @@ -3,18 +3,22 @@ package framework import ( "context" - "github.com/dimfeld/httptreemux/v5" "net/http" "os" "syscall" "time" + "github.com/dimfeld/httptreemux/v5" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "github.com/google/uuid" ) type ctxKey int const KeyRequestState ctxKey = 1 +var tracer = otel.Tracer("SSI SERVICE") type RequestState struct { TraceID string @@ -59,9 +63,20 @@ func (s *Server) Handle(method string, path string, handler Handler, mw ...Middl TraceID: uuid.New().String(), Now: time.Now(), } - ctx := context.WithValue(r.Context(), KeyRequestState, &requestState) + // init a span + ctx, span := tracer.Start(ctx, path) + span.SetAttributes( + attribute.String("method", method), + attribute.String("path", path), + attribute.String("host", r.Host), + attribute.String("prot", r.Proto), + attribute.String("body", StreamToString(r.Body)), + ) + + defer span.End() + // onion the request through all the registered middleware if err := handler(ctx, w, r); err != nil { s.SignalShutdown() diff --git a/pkg/server/framework/util.go b/pkg/server/framework/util.go index 1556994f4..b4a0fa6c0 100644 --- a/pkg/server/framework/util.go +++ b/pkg/server/framework/util.go @@ -1,7 +1,9 @@ package framework import ( + "bytes" "context" + "io" "net/http" "github.com/dimfeld/httptreemux/v5" @@ -25,3 +27,10 @@ func GetQueryValue(r *http.Request, param string) *string { } return &v } + +// Convert stream to string +func StreamToString(stream io.Reader) string { + buf := new(bytes.Buffer) + buf.ReadFrom(stream) + return buf.String() +} \ No newline at end of file