Skip to content

Commit

Permalink
Add go-kit instrumentation library (#456)
Browse files Browse the repository at this point in the history
* Add go-kit instrumentation library

Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>

* Add missing dependabot check

Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>

* Improve test coverage

Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>

* Add otelkit example

Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>

* Add otelkit to documentation

Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>

* Add example to dependabot

Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>

* Use RecordError for load balancer retried errors

Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
  • Loading branch information
sagikazarmark committed Mar 5, 2021
1 parent 839e505 commit cc31f43
Show file tree
Hide file tree
Showing 14 changed files with 1,495 additions and 0 deletions.
20 changes: 20 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -456,3 +456,23 @@ updates:
schedule:
interval: "weekly"
day: "sunday"
-
package-ecosystem: "gomod"
directory: "/instrumentation/github.com/go-kit/kit/otelkit"
labels:
- dependencies
- go
- "Skip Changelog"
schedule:
interval: "weekly"
day: "sunday"
-
package-ecosystem: "gomod"
directory: "/instrumentation/github.com/go-kit/kit/otelkit/example"
labels:
- dependencies
- go
- "Skip Changelog"
schedule:
interval: "weekly"
day: "sunday"
1 change: 1 addition & 0 deletions instrumentation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ The following instrumentation packages are provided for popular Go packages and
| [github.com/bradfitz/gomemcache](./github.com/bradfitz/gomemcache/memcache/otelmemcache) | ||
| [github.com/emicklei/go-restful](./github.com/emicklei/go-restful/otelrestful) | ||
| [github.com/gin-gonic/gin](./github.com/gin-gonic/gin/otelgin) | ||
| [github.com/go-kit/kit](./github.com/go-kit/kit/otelkit) | ||
| [github.com/gocql/gocql](./github.com/gocql/gocql/otelgocql) |||
| [github.com/gorilla/mux](./github.com/gorilla/mux/otelmux) | ||
| [github.com/labstack/echo](./github.com/labstack/echo/otelecho) | ||
Expand Down
98 changes: 98 additions & 0 deletions instrumentation/github.com/go-kit/kit/otelkit/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Based on https://github.com/go-kit/kit/blob/3796a6b25f5c6c545454d3ed7187c4ced258083d/tracing/opencensus/endpoint_options.go

package otelkit

import (
"context"

"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
)

// config holds the options for tracing an endpoint.
type config struct {
// TracerProvider provides access to instrumentation Tracers.
TracerProvider trace.TracerProvider

// IgnoreBusinessError if set to true will not treat a business error
// identified through the endpoint.Failer interface as a span error.
IgnoreBusinessError bool

// Operation identifies the current operation and serves as a span name.
Operation string

// GetOperation is an optional function that can set the span name based on the existing operation
// for the endpoint and information in the context.
//
// If the function is nil, or the returned operation is empty, the existing operation for the endpoint is used.
GetOperation func(ctx context.Context, operation string) string

// Attributes holds the default attributes for each span created by this middleware.
Attributes []attribute.KeyValue

// GetAttributes is an optional function that can extract trace attributes
// from the context and add them to the span.
GetAttributes func(ctx context.Context) []attribute.KeyValue
}

// Option configures an EndpointMiddleware.
type Option func(*config)

// WithTracerProvider specifies a tracer provider to use for creating a tracer.
// If none is specified, the global provider is used.
func WithTracerProvider(provider trace.TracerProvider) Option {
return func(o *config) {
o.TracerProvider = provider
}
}

// WithIgnoreBusinessError if set to true will not treat a business error
// identified through the endpoint.Failer interface as a span error.
func WithIgnoreBusinessError(val bool) Option {
return func(o *config) {
o.IgnoreBusinessError = val
}
}

// WithOperation sets an operation name for an endpoint.
// Use this when you register a middleware for each endpoint.
func WithOperation(operation string) Option {
return func(o *config) {
o.Operation = operation
}
}

// WithOperationGetter sets an operation name getter function in config.
func WithOperationGetter(fn func(ctx context.Context, name string) string) Option {
return func(o *config) {
o.GetOperation = fn
}
}

// WithAttributes sets the default attributes for the spans created by the Endpoint tracer.
func WithAttributes(attrs ...attribute.KeyValue) Option {
return func(o *config) {
o.Attributes = attrs
}
}

// WithAttributeGetter extracts additional attributes from the context.
func WithAttributeGetter(fn func(ctx context.Context) []attribute.KeyValue) Option {
return func(o *config) {
o.GetAttributes = fn
}
}
22 changes: 22 additions & 0 deletions instrumentation/github.com/go-kit/kit/otelkit/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package otelkit instruments the github.com/go-kit/kit package.
//
// Compared to other instrumentation libraries provided by go-kit itself,
// this package only provides instrumentation for the endpoint layer.
// For instrumenting the transport layer,
// look at the instrumentation libraries provided by go.opentelemetry.io/contrib.
// Learn more about go-kit's layers at https://gokit.io/faq/#architecture-and-design.
package otelkit // import "go.opentelemetry.io/contrib/instrumentation/github.com/go-kit/kit/otelkit"
129 changes: 129 additions & 0 deletions instrumentation/github.com/go-kit/kit/otelkit/endpoint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Based on https://github.com/go-kit/kit/blob/3796a6b25f5c6c545454d3ed7187c4ced258083d/tracing/opencensus/endpoint.go

package otelkit

import (
"context"

"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"

otelcontrib "go.opentelemetry.io/contrib"

"github.com/go-kit/kit/endpoint"
"github.com/go-kit/kit/sd/lb"
)

const (
tracerName = "go.opentelemetry.io/contrib/instrumentation/github.com/go-kit/kit/otelkit"

// defaultSpanName is the default endpoint span name to use.
defaultSpanName = "gokit/endpoint"
)

// EndpointMiddleware returns an Endpoint middleware, tracing a Go kit endpoint.
// This endpoint middleware should be used in combination with a Go kit Transport
// tracing middleware, generic OpenTelemetry transport middleware or custom before
// and after transport functions.
func EndpointMiddleware(options ...Option) endpoint.Middleware {
cfg := &config{}

for _, o := range options {
o(cfg)
}

if cfg.TracerProvider == nil {
cfg.TracerProvider = otel.GetTracerProvider()
}

tracer := cfg.TracerProvider.Tracer(
tracerName,
trace.WithInstrumentationVersion(otelcontrib.SemVersion()),
)

return func(next endpoint.Endpoint) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
operation := cfg.Operation
if cfg.GetOperation != nil {
if newOperation := cfg.GetOperation(ctx, operation); newOperation != "" {
operation = newOperation
}
}

spanName := operation
if spanName == "" {
spanName = defaultSpanName
}

opts := []trace.SpanOption{
trace.WithAttributes(cfg.Attributes...),
trace.WithSpanKind(trace.SpanKindServer),
}

if cfg.GetAttributes != nil {
opts = append(opts, trace.WithAttributes(cfg.GetAttributes(ctx)...))
}

ctx, span := tracer.Start(ctx, spanName, opts...)
defer span.End()

defer func() {
if err != nil {
if lberr, ok := err.(lb.RetryError); ok {
// Handle errors originating from lb.Retry.
for idx, rawErr := range lberr.RawErrors {
span.RecordError(rawErr, trace.WithAttributes(
attribute.Int("gokit.lb.retry.count", idx+1),
))
}

span.RecordError(lberr.Final)

return
}

// generic error
span.RecordError(err)

return
}

// Test for business error. Business errors are often
// successful requests carrying a business failure that
// the client can act upon and therefore do not count
// as failed requests.
if res, ok := response.(endpoint.Failer); ok && res.Failed() != nil {
span.RecordError(res.Failed())

if cfg.IgnoreBusinessError {
span.SetStatus(codes.Unset, "")
}

return
}

// no errors identified
}()

response, err = next(ctx, request)

return
}
}
}
Loading

0 comments on commit cc31f43

Please sign in to comment.