-
Notifications
You must be signed in to change notification settings - Fork 508
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add go-kit instrumentation library (#456)
* 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
1 parent
839e505
commit cc31f43
Showing
14 changed files
with
1,495 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
129
instrumentation/github.com/go-kit/kit/otelkit/endpoint.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} | ||
} |
Oops, something went wrong.