Skip to content

Add go-kit instrumentation library #456

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Mar 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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