Skip to content

Commit

Permalink
add opencensus tracing bridge
Browse files Browse the repository at this point in the history
  • Loading branch information
dashpole committed Nov 4, 2020
1 parent 0341956 commit 93d25a9
Show file tree
Hide file tree
Showing 6 changed files with 582 additions and 0 deletions.
10 changes: 10 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,16 @@ updates:
schedule:
day: sunday
interval: weekly
-
package-ecosystem: gomod
directory: /bridge/opencensus
labels:
- dependencies
- go
- "Skip Changelog"
schedule:
day: sunday
interval: weekly
-
package-ecosystem: gomod
directory: /example/basic
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- `EventOption` and the related `NewEventConfig` function are added to the `go.opentelemetry.io/otel` package to configure Span events. (#1254)
- A `TextMapPropagator` and associated `TextMapCarrier` are added to the `go.opentelemetry.io/otel/oteltest` package to test TextMap type propagators and their use. (#1259)
- `SpanContextFromContext` returns `SpanContext` from context. (#1255)
- Add an opencensus to opentelemetry tracing bridge.

### Changed

Expand Down
222 changes: 222 additions & 0 deletions bridge/opencensus/bridge.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
// 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 opencensus

import (
"context"
"fmt"

"go.opentelemetry.io/otel/global"

octrace "go.opencensus.io/trace"

"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/label"
)

// NewTracer returns an implementation of the OpenCensus Tracer interface which
// uses OpenTelemetry APIs. Using this implementation of Tracer "upgrades"
// libraries that use OpenCensus to OpenTelemetry to facilitate a migration.
func NewTracer(tracer otel.Tracer) octrace.Tracer {
return &otelTracer{tracer: tracer}
}

type otelTracer struct {
tracer otel.Tracer
}

func (o *otelTracer) StartSpan(ctx context.Context, name string, s ...octrace.StartOption) (context.Context, *octrace.Span) {
ctx, sp := o.tracer.Start(ctx, name, convertStartOptions(s, name)...)
return ctx, octrace.NewSpan(&span{otSpan: sp})
}

func convertStartOptions(optFns []octrace.StartOption, name string) []otel.SpanOption {
var ocOpts octrace.StartOptions
for _, fn := range optFns {
fn(&ocOpts)
}
otOpts := []otel.SpanOption{}
switch ocOpts.SpanKind {
case octrace.SpanKindClient:
otOpts = append(otOpts, otel.WithSpanKind(otel.SpanKindClient))
case octrace.SpanKindServer:
otOpts = append(otOpts, otel.WithSpanKind(otel.SpanKindServer))
case octrace.SpanKindUnspecified:
otOpts = append(otOpts, otel.WithSpanKind(otel.SpanKindUnspecified))
}

if ocOpts.Sampler != nil {
// OTel doesn't allow setting a sampler in SpanOptions
global.Handle(fmt.Errorf("ignoring custom sampler for span %q created by OpenCensus because OpenTelemetry does not support creating a span with a custom sampler", name))
}
return otOpts
}

func (o *otelTracer) StartSpanWithRemoteParent(ctx context.Context, name string, parent octrace.SpanContext, s ...octrace.StartOption) (context.Context, *octrace.Span) {
// make sure span context is zero'd out so we use the remote parent
ctx = otel.ContextWithSpan(ctx, nil)
ctx = otel.ContextWithRemoteSpanContext(ctx, ocSpanContextToOtel(parent))
return o.StartSpan(ctx, name, s...)
}

func (o *otelTracer) FromContext(ctx context.Context) *octrace.Span {
otSpan := otel.SpanFromContext(ctx)
return octrace.NewSpan(&span{otSpan: otSpan})
}

func (o *otelTracer) NewContext(parent context.Context, s *octrace.Span) context.Context {
if otSpan, ok := s.Internal().(*span); ok {
return otel.ContextWithSpan(parent, otSpan.otSpan)
}
// The user must have created the octrace Span using a different tracer, and we don't know how to store it.
global.Handle(fmt.Errorf("unable to create context with span %q, since it was created using a different tracer", s.String()))
return parent
}

type span struct {
// We can't implement the unexported functions, so add the interface here.
octrace.Span
otSpan otel.Span
}

func (s *span) IsRecordingEvents() bool {
return s.otSpan.IsRecording()
}

func (s *span) End() {
s.otSpan.End()
}

func (s *span) SpanContext() octrace.SpanContext {
return otelSpanContextToOc(s.otSpan.SpanContext())
}

func (s *span) SetName(name string) {
s.otSpan.SetName(name)
}

func (s *span) SetStatus(status octrace.Status) {
s.otSpan.SetStatus(codes.Code(status.Code), status.Message)
}

func (s *span) AddAttributes(attributes ...octrace.Attribute) {
s.otSpan.SetAttributes(convertAttributes(attributes)...)
}

func convertAttributes(attributes []octrace.Attribute) []label.KeyValue {
otAttributes := make([]label.KeyValue, len(attributes))
for i, a := range attributes {
otAttributes[i] = label.KeyValue{
Key: label.Key(a.Key()),
Value: convertValue(a.Value()),
}
}
return otAttributes
}

func convertValue(ocval interface{}) label.Value {
switch v := ocval.(type) {
case bool:
return label.BoolValue(v)
case int64:
return label.Int64Value(v)
case float64:
return label.Float64Value(v)
case string:
return label.StringValue(v)
default:
return label.StringValue("unknown")
}
}

func (s *span) Annotate(attributes []octrace.Attribute, str string) {
s.otSpan.AddEvent(str, otel.WithAttributes(convertAttributes(attributes)...))
}

func (s *span) Annotatef(attributes []octrace.Attribute, format string, a ...interface{}) {
s.Annotate(attributes, fmt.Sprintf(format, a...))
}

var (
uncompressedKey = label.Key("uncompressed byte size")
compressedKey = label.Key("compressed byte size")
)

func (s *span) AddMessageSendEvent(messageID, uncompressedByteSize, compressedByteSize int64) {
s.otSpan.AddEvent("message send",
otel.WithAttributes(
label.KeyValue{
Key: uncompressedKey,
Value: label.Int64Value(uncompressedByteSize),
},
label.KeyValue{
Key: compressedKey,
Value: label.Int64Value(compressedByteSize),
}),
)
}

func (s *span) AddMessageReceiveEvent(messageID, uncompressedByteSize, compressedByteSize int64) {
s.otSpan.AddEvent("message receive",
otel.WithAttributes(
label.KeyValue{
Key: uncompressedKey,
Value: label.Int64Value(uncompressedByteSize),
},
label.KeyValue{
Key: compressedKey,
Value: label.Int64Value(compressedByteSize),
}),
)
}

func (s *span) AddLink(l octrace.Link) {
global.Handle(fmt.Errorf("ignoring OpenCensus link %+v for span %q because OpenTelemetry doesn't support setting links after creation", l, s.String()))
}

func (s *span) String() string {
return fmt.Sprintf("span %s",
s.otSpan.SpanContext().SpanID.String())
}

func otelSpanContextToOc(sc otel.SpanContext) octrace.SpanContext {
if sc.IsDebug() || sc.IsDeferred() {
// OTel don't support these options
global.Handle(fmt.Errorf("ignoring OpenTelemetry Debug or Deferred trace flags for span %q because they are not supported by OpenCensus", sc.SpanID))
}
var to octrace.TraceOptions
if sc.IsSampled() {
// OpenCensus doesn't expose functions to directly set sampled
to = 0x1
}
return octrace.SpanContext{
TraceID: octrace.TraceID(sc.TraceID),
SpanID: octrace.SpanID(sc.SpanID),
TraceOptions: to,
}
}

func ocSpanContextToOtel(sc octrace.SpanContext) otel.SpanContext {
var traceFlags byte
if sc.IsSampled() {
traceFlags = otel.FlagsSampled
}
return otel.SpanContext{
TraceID: otel.TraceID(sc.TraceID),
SpanID: otel.SpanID(sc.SpanID),
TraceFlags: traceFlags,
}
}
Loading

0 comments on commit 93d25a9

Please sign in to comment.