diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5188d840365..9d71a107d20 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -19,6 +19,15 @@ updates: schedule: interval: weekly day: sunday + - package-ecosystem: gomod + directory: /bridge/opencensus + labels: + - dependencies + - go + - Skip Changelog + schedule: + interval: weekly + day: sunday - package-ecosystem: gomod directory: /bridge/opencensus/opencensusmetric labels: @@ -28,6 +37,15 @@ updates: schedule: interval: weekly day: sunday + - package-ecosystem: gomod + directory: /bridge/opencensus/test + labels: + - dependencies + - go + - Skip Changelog + schedule: + interval: weekly + day: sunday - package-ecosystem: gomod directory: /bridge/opentracing labels: diff --git a/bridge/opencensus/README.md b/bridge/opencensus/README.md new file mode 100644 index 00000000000..3df9dc7eb07 --- /dev/null +++ b/bridge/opencensus/README.md @@ -0,0 +1,81 @@ +# OpenCensus Bridge + +The OpenCensus Bridge helps facilitate the migration of an application from OpenCensus to OpenTelemetry. + +## Caveat about OpenCensus + +Installing a metric or tracing bridge will cause OpenCensus telemetry to be exported by OpenTelemetry exporters. Since OpenCensus telemetry uses globals, installing a bridge will result in telemetry collection from _all_ libraries that use OpenCensus, including some you may not expect. For example ([#1928](https://github.com/open-telemetry/opentelemetry-go/issues/1928)), if a client library generates traces with OpenCensus, installing the bridge will cause those traces to be exported by OpenTelemetry. + +## Tracing + +### The Problem: Mixing OpenCensus and OpenTelemetry libraries + +In a perfect world, one would simply migrate their entire go application --including custom instrumentation, libraries, and exporters-- from OpenCensus to OpenTelemetry all at once. In the real world, dependency constraints, third-party ownership of libraries, or other reasons may require mixing OpenCensus and OpenTelemetry libraries in a single application. + +However, if you create the following spans in a go application: + +```go +ctx, ocSpan := opencensus.StartSpan(context.Background(), "OuterSpan") +defer ocSpan.End() +ctx, otSpan := opentelemetryTracer.Start(ctx, "MiddleSpan") +defer otSpan.End() +ctx, ocSpan := opencensus.StartSpan(ctx, "InnerSpan") +defer ocSpan.End() +``` + +OpenCensus reports (to OpenCensus exporters): + +``` +[--------OuterSpan------------] + [----InnerSpan------] +``` + +OpenTelemetry reports (to OpenTelemetry exporters): + +``` + [-----MiddleSpan--------] +``` + +Instead, I would prefer (to a single set of exporters): + +``` +[--------OuterSpan------------] + [-----MiddleSpan--------] + [----InnerSpan------] +``` + +### The bridge solution + +The bridge implements the OpenCensus trace API using OpenTelemetry. This would cause, for example, a span recorded with OpenCensus' `StartSpan()` method to be equivalent to recording a span using OpenTelemetry's `tracer.Start()` method. Funneling all tracing API calls to OpenTelemetry APIs results in the desired unified span hierarchy. + +### User Journey + +Starting from an application using entirely OpenCensus APIs: + +1. Instantiate OpenTelemetry SDK and Exporters +2. Override OpenCensus' DefaultTracer with the bridge +3. Migrate libraries individually from OpenCensus to OpenTelemetry +4. Remove OpenCensus exporters and configuration + +To override OpenCensus' DefaultTracer with the bridge: + +```go +import ( + octrace "go.opencensus.io/trace" + "go.opentelemetry.io/otel/bridge/opencensus" + "go.opentelemetry.io/otel" +) + +tracer := otel.GetTracerProvider().Tracer("bridge") +octrace.DefaultTracer = opencensus.NewTracer(tracer) +``` + +Be sure to set the `Tracer` name to your instrumentation package name instead of `"bridge"`. + +#### Incompatibilities + +OpenCensus and OpenTelemetry APIs are not entirely compatible. If the bridge finds any incompatibilities, it will log them. Incompatibilities include: + +* Custom OpenCensus Samplers specified during StartSpan are ignored. +* Links cannot be added to OpenCensus spans. +* OpenTelemetry Debug or Deferred trace flags are dropped after an OpenCensus span is created. diff --git a/bridge/opencensus/bridge.go b/bridge/opencensus/bridge.go new file mode 100644 index 00000000000..7e6f31202d6 --- /dev/null +++ b/bridge/opencensus/bridge.go @@ -0,0 +1,44 @@ +// 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 "go.opentelemetry.io/otel/bridge/opencensus" + +import ( + octrace "go.opencensus.io/trace" + + "go.opentelemetry.io/otel/bridge/opencensus/internal" + "go.opentelemetry.io/otel/bridge/opencensus/internal/oc2otel" + "go.opentelemetry.io/otel/bridge/opencensus/internal/otel2oc" + "go.opentelemetry.io/otel/trace" +) + +// 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 trace.Tracer) octrace.Tracer { + return internal.NewTracer(tracer) +} + +// OTelSpanContextToOC converts from an OpenTelemetry SpanContext to an +// OpenCensus SpanContext, and handles any incompatibilities with the global +// error handler. +func OTelSpanContextToOC(sc trace.SpanContext) octrace.SpanContext { + return otel2oc.SpanContext(sc) +} + +// OCSpanContextToOTel converts from an OpenCensus SpanContext to an +// OpenTelemetry SpanContext. +func OCSpanContextToOTel(sc octrace.SpanContext) trace.SpanContext { + return oc2otel.SpanContext(sc) +} diff --git a/bridge/opencensus/doc.go b/bridge/opencensus/doc.go new file mode 100644 index 00000000000..80d80da6f78 --- /dev/null +++ b/bridge/opencensus/doc.go @@ -0,0 +1,35 @@ +// 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 provides a migration bridge from OpenCensus to +// OpenTelemetry. The NewTracer function should be used to create an +// OpenCensus Tracer from an OpenTelemetry Tracer. This Tracer can be use in +// place of any existing OpenCensus Tracer and will generate OpenTelemetry +// spans for traces. These spans will be exported by the OpenTelemetry +// TracerProvider the original OpenTelemetry Tracer came from. +// +// There are known limitations to this bridge: +// +// - The AddLink method for OpenCensus Spans is not compatible with the +// OpenTelemetry Span. No link can be added to an OpenTelemetry Span once it +// is started. Any calls to this method for the OpenCensus Span will result +// in an error being sent to the OpenTelemetry default ErrorHandler. +// +// - The NewContext method of the OpenCensus Tracer cannot embed an OpenCensus +// Span in a context unless that Span was created by that Tracer. +// +// - Conversion of custom OpenCensus Samplers to OpenTelemetry is not +// implemented. An error will be sent to the OpenTelemetry default +// ErrorHandler if this is attempted. +package opencensus // import "go.opentelemetry.io/otel/bridge/opencensus" diff --git a/bridge/opencensus/go.mod b/bridge/opencensus/go.mod new file mode 100644 index 00000000000..42c24593f9e --- /dev/null +++ b/bridge/opencensus/go.mod @@ -0,0 +1,19 @@ +module go.opentelemetry.io/otel/bridge/opencensus + +go 1.17 + +require ( + go.opencensus.io v0.22.6-0.20201102222123-380f4078db9f + go.opentelemetry.io/otel v1.9.0 + go.opentelemetry.io/otel/trace v1.9.0 +) + +require ( + github.com/go-logr/logr v1.2.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 // indirect +) + +replace go.opentelemetry.io/otel => ../.. + +replace go.opentelemetry.io/otel/trace => ../../trace diff --git a/bridge/opencensus/go.sum b/bridge/opencensus/go.sum new file mode 100644 index 00000000000..2e90b382eb9 --- /dev/null +++ b/bridge/opencensus/go.sum @@ -0,0 +1,58 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +go.opencensus.io v0.22.6-0.20201102222123-380f4078db9f h1:IUmbcoP9XyEXW+R9AbrZgDvaYVfTbISN92Y5RIV+Mx4= +go.opencensus.io v0.22.6-0.20201102222123-380f4078db9f/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/bridge/opencensus/internal/handler.go b/bridge/opencensus/internal/handler.go new file mode 100644 index 00000000000..6bf5496169d --- /dev/null +++ b/bridge/opencensus/internal/handler.go @@ -0,0 +1,21 @@ +// 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 internal // import "go.opentelemetry.io/otel/bridge/opencensus/internal" + +import "go.opentelemetry.io/otel" + +// Handle is the package level function to handle errors. It can be +// overwritten for testing. +var Handle = otel.Handle diff --git a/bridge/opencensus/internal/oc2otel/attributes.go b/bridge/opencensus/internal/oc2otel/attributes.go new file mode 100644 index 00000000000..3e1cebdb800 --- /dev/null +++ b/bridge/opencensus/internal/oc2otel/attributes.go @@ -0,0 +1,47 @@ +// 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 oc2otel // import "go.opentelemetry.io/otel/bridge/opencensus/internal/oc2otel" + +import ( + octrace "go.opencensus.io/trace" + + "go.opentelemetry.io/otel/attribute" +) + +func Attributes(attr []octrace.Attribute) []attribute.KeyValue { + otelAttr := make([]attribute.KeyValue, len(attr)) + for i, a := range attr { + otelAttr[i] = attribute.KeyValue{ + Key: attribute.Key(a.Key()), + Value: AttributeValue(a.Value()), + } + } + return otelAttr +} + +func AttributeValue(ocval interface{}) attribute.Value { + switch v := ocval.(type) { + case bool: + return attribute.BoolValue(v) + case int64: + return attribute.Int64Value(v) + case float64: + return attribute.Float64Value(v) + case string: + return attribute.StringValue(v) + default: + return attribute.StringValue("unknown") + } +} diff --git a/bridge/opencensus/internal/oc2otel/attributes_test.go b/bridge/opencensus/internal/oc2otel/attributes_test.go new file mode 100644 index 00000000000..44d40ec899a --- /dev/null +++ b/bridge/opencensus/internal/oc2otel/attributes_test.go @@ -0,0 +1,56 @@ +// 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 oc2otel + +import ( + "testing" + + octrace "go.opencensus.io/trace" + + "go.opentelemetry.io/otel/attribute" +) + +func TestAttributes(t *testing.T) { + in := []octrace.Attribute{ + octrace.BoolAttribute("bool", true), + octrace.Int64Attribute("int64", 49), + octrace.Float64Attribute("float64", 1.618), + octrace.StringAttribute("key", "val"), + } + + want := []attribute.KeyValue{ + attribute.Bool("bool", true), + attribute.Int64("int64", 49), + attribute.Float64("float64", 1.618), + attribute.String("key", "val"), + } + got := Attributes(in) + + if len(got) != len(want) { + t.Errorf("Attributes conversion failed: want %#v, got %#v", want, got) + } + for i := range got { + if g, w := got[i], want[i]; g != w { + t.Errorf("Attributes conversion: want %#v, got %#v", w, g) + } + } +} + +func TestAttributeValueUnknown(t *testing.T) { + got := AttributeValue([]byte{}) + if got != attribute.StringValue("unknown") { + t.Errorf("AttributeValue of unknown wrong: %#v", got) + } +} diff --git a/bridge/opencensus/internal/oc2otel/span_context.go b/bridge/opencensus/internal/oc2otel/span_context.go new file mode 100644 index 00000000000..006226f30ab --- /dev/null +++ b/bridge/opencensus/internal/oc2otel/span_context.go @@ -0,0 +1,33 @@ +// 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 oc2otel // import "go.opentelemetry.io/otel/bridge/opencensus/internal/oc2otel" + +import ( + octrace "go.opencensus.io/trace" + + "go.opentelemetry.io/otel/trace" +) + +func SpanContext(sc octrace.SpanContext) trace.SpanContext { + var traceFlags trace.TraceFlags + if sc.IsSampled() { + traceFlags = trace.FlagsSampled + } + return trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: trace.TraceID(sc.TraceID), + SpanID: trace.SpanID(sc.SpanID), + TraceFlags: traceFlags, + }) +} diff --git a/bridge/opencensus/internal/oc2otel/span_context_test.go b/bridge/opencensus/internal/oc2otel/span_context_test.go new file mode 100644 index 00000000000..ba0f1d0bfcc --- /dev/null +++ b/bridge/opencensus/internal/oc2otel/span_context_test.go @@ -0,0 +1,80 @@ +// 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 oc2otel + +import ( + "testing" + + octrace "go.opencensus.io/trace" + "go.opencensus.io/trace/tracestate" + + "go.opentelemetry.io/otel/trace" +) + +func TestSpanContextConversion(t *testing.T) { + for _, tc := range []struct { + description string + input octrace.SpanContext + expected trace.SpanContext + }{ + { + description: "empty", + }, + { + description: "sampled", + input: octrace.SpanContext{ + TraceID: octrace.TraceID([16]byte{1}), + SpanID: octrace.SpanID([8]byte{2}), + TraceOptions: octrace.TraceOptions(0x1), + }, + expected: trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: trace.TraceID([16]byte{1}), + SpanID: trace.SpanID([8]byte{2}), + TraceFlags: trace.FlagsSampled, + }), + }, + { + description: "not sampled", + input: octrace.SpanContext{ + TraceID: octrace.TraceID([16]byte{1}), + SpanID: octrace.SpanID([8]byte{2}), + TraceOptions: octrace.TraceOptions(0), + }, + expected: trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: trace.TraceID([16]byte{1}), + SpanID: trace.SpanID([8]byte{2}), + }), + }, + { + description: "trace state is ignored", + input: octrace.SpanContext{ + TraceID: octrace.TraceID([16]byte{1}), + SpanID: octrace.SpanID([8]byte{2}), + Tracestate: &tracestate.Tracestate{}, + }, + expected: trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: trace.TraceID([16]byte{1}), + SpanID: trace.SpanID([8]byte{2}), + }), + }, + } { + t.Run(tc.description, func(t *testing.T) { + output := SpanContext(tc.input) + if !output.Equal(tc.expected) { + t.Fatalf("Got %+v spancontext, exepected %+v.", output, tc.expected) + } + }) + } +} diff --git a/bridge/opencensus/internal/oc2otel/tracer_start_options.go b/bridge/opencensus/internal/oc2otel/tracer_start_options.go new file mode 100644 index 00000000000..030df047f35 --- /dev/null +++ b/bridge/opencensus/internal/oc2otel/tracer_start_options.go @@ -0,0 +1,46 @@ +// 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 oc2otel // import "go.opentelemetry.io/otel/bridge/opencensus/internal/oc2otel" + +import ( + "fmt" + + octrace "go.opencensus.io/trace" + + "go.opentelemetry.io/otel/trace" +) + +func StartOptions(optFuncs []octrace.StartOption) ([]trace.SpanStartOption, error) { + var ocOpts octrace.StartOptions + for _, fn := range optFuncs { + fn(&ocOpts) + } + + var otelOpts []trace.SpanStartOption + switch ocOpts.SpanKind { + case octrace.SpanKindClient: + otelOpts = append(otelOpts, trace.WithSpanKind(trace.SpanKindClient)) + case octrace.SpanKindServer: + otelOpts = append(otelOpts, trace.WithSpanKind(trace.SpanKindServer)) + case octrace.SpanKindUnspecified: + otelOpts = append(otelOpts, trace.WithSpanKind(trace.SpanKindUnspecified)) + } + + var err error + if ocOpts.Sampler != nil { + err = fmt.Errorf("unsupported sampler: %v", ocOpts.Sampler) + } + return otelOpts, err +} diff --git a/bridge/opencensus/internal/oc2otel/tracer_start_options_test.go b/bridge/opencensus/internal/oc2otel/tracer_start_options_test.go new file mode 100644 index 00000000000..6a525424cab --- /dev/null +++ b/bridge/opencensus/internal/oc2otel/tracer_start_options_test.go @@ -0,0 +1,52 @@ +// 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 oc2otel + +import ( + "testing" + + octrace "go.opencensus.io/trace" + + "go.opentelemetry.io/otel/trace" +) + +func TestStartOptionsSpanKind(t *testing.T) { + conv := map[int]trace.SpanKind{ + octrace.SpanKindClient: trace.SpanKindClient, + octrace.SpanKindServer: trace.SpanKindServer, + octrace.SpanKindUnspecified: trace.SpanKindUnspecified, + } + + for oc, otel := range conv { + ocOpts := []octrace.StartOption{octrace.WithSpanKind(oc)} + otelOpts, err := StartOptions(ocOpts) + if err != nil { + t.Errorf("StartOptions errored: %v", err) + continue + } + c := trace.NewSpanStartConfig(otelOpts...) + if c.SpanKind() != otel { + t.Errorf("conversion of SpanKind start option: got %v, want %v", c.SpanKind(), otel) + } + } +} + +func TestStartOptionsSamplerErrors(t *testing.T) { + ocOpts := []octrace.StartOption{octrace.WithSampler(octrace.AlwaysSample())} + _, err := StartOptions(ocOpts) + if err == nil { + t.Error("StartOptions should error Sampler option") + } +} diff --git a/bridge/opencensus/internal/otel2oc/span_context.go b/bridge/opencensus/internal/otel2oc/span_context.go new file mode 100644 index 00000000000..6355eb555fb --- /dev/null +++ b/bridge/opencensus/internal/otel2oc/span_context.go @@ -0,0 +1,34 @@ +// 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 otel2oc // import "go.opentelemetry.io/otel/bridge/opencensus/internal/otel2oc" + +import ( + octrace "go.opencensus.io/trace" + + "go.opentelemetry.io/otel/trace" +) + +func SpanContext(sc trace.SpanContext) octrace.SpanContext { + 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, + } +} diff --git a/bridge/opencensus/internal/otel2oc/span_context_test.go b/bridge/opencensus/internal/otel2oc/span_context_test.go new file mode 100644 index 00000000000..236aa89689a --- /dev/null +++ b/bridge/opencensus/internal/otel2oc/span_context_test.go @@ -0,0 +1,67 @@ +// 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 otel2oc + +import ( + "testing" + + octrace "go.opencensus.io/trace" + + "go.opentelemetry.io/otel/trace" +) + +func TestSpanContextConversion(t *testing.T) { + for _, tc := range []struct { + description string + input trace.SpanContext + expected octrace.SpanContext + }{ + { + description: "empty", + }, + { + description: "sampled", + input: trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: trace.TraceID([16]byte{1}), + SpanID: trace.SpanID([8]byte{2}), + TraceFlags: trace.FlagsSampled, + }), + expected: octrace.SpanContext{ + TraceID: octrace.TraceID([16]byte{1}), + SpanID: octrace.SpanID([8]byte{2}), + TraceOptions: octrace.TraceOptions(0x1), + }, + }, + { + description: "not sampled", + input: trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: trace.TraceID([16]byte{1}), + SpanID: trace.SpanID([8]byte{2}), + }), + expected: octrace.SpanContext{ + TraceID: octrace.TraceID([16]byte{1}), + SpanID: octrace.SpanID([8]byte{2}), + TraceOptions: octrace.TraceOptions(0), + }, + }, + } { + t.Run(tc.description, func(t *testing.T) { + output := SpanContext(tc.input) + if output != tc.expected { + t.Fatalf("Got %+v spancontext, exepected %+v.", output, tc.expected) + } + }) + } +} diff --git a/bridge/opencensus/internal/span.go b/bridge/opencensus/internal/span.go new file mode 100644 index 00000000000..21449aa3d3a --- /dev/null +++ b/bridge/opencensus/internal/span.go @@ -0,0 +1,131 @@ +// 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 internal // import "go.opentelemetry.io/otel/bridge/opencensus/internal" + +import ( + "fmt" + + octrace "go.opencensus.io/trace" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/bridge/opencensus/internal/oc2otel" + "go.opentelemetry.io/otel/bridge/opencensus/internal/otel2oc" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" +) + +const ( + // MessageSendEvent is the name of the message send event. + MessageSendEvent = "message send" + // MessageReceiveEvent is the name of the message receive event. + MessageReceiveEvent = "message receive" +) + +var ( + // UncompressedKey is used for the uncompressed byte size attribute. + UncompressedKey = attribute.Key("uncompressed byte size") + // CompressedKey is used for the compressed byte size attribute. + CompressedKey = attribute.Key("compressed byte size") +) + +// Span is an OpenCensus SpanInterface wrapper for an OpenTelemetry Span. +type Span struct { + otelSpan trace.Span +} + +// NewSpan returns an OpenCensus Span wrapping an OpenTelemetry Span. +func NewSpan(s trace.Span) *octrace.Span { + return octrace.NewSpan(&Span{otelSpan: s}) +} + +// IsRecordingEvents returns true if events are being recorded for this span. +func (s *Span) IsRecordingEvents() bool { + return s.otelSpan.IsRecording() +} + +// End ends thi span. +func (s *Span) End() { + s.otelSpan.End() +} + +// SpanContext returns the SpanContext of this span. +func (s *Span) SpanContext() octrace.SpanContext { + return otel2oc.SpanContext(s.otelSpan.SpanContext()) +} + +// SetName sets the name of this span, if it is recording events. +func (s *Span) SetName(name string) { + s.otelSpan.SetName(name) +} + +// SetStatus sets the status of this span, if it is recording events. +func (s *Span) SetStatus(status octrace.Status) { + s.otelSpan.SetStatus(codes.Code(status.Code), status.Message) +} + +// AddAttributes sets attributes in this span. +func (s *Span) AddAttributes(attributes ...octrace.Attribute) { + s.otelSpan.SetAttributes(oc2otel.Attributes(attributes)...) +} + +// Annotate adds an annotation with attributes to this span. +func (s *Span) Annotate(attributes []octrace.Attribute, str string) { + s.otelSpan.AddEvent(str, trace.WithAttributes(oc2otel.Attributes(attributes)...)) +} + +// Annotatef adds a formatted annotation with attributes to this span. +func (s *Span) Annotatef(attributes []octrace.Attribute, format string, a ...interface{}) { + s.Annotate(attributes, fmt.Sprintf(format, a...)) +} + +// AddMessageSendEvent adds a message send event to this span. +func (s *Span) AddMessageSendEvent(messageID, uncompressedByteSize, compressedByteSize int64) { + s.otelSpan.AddEvent(MessageSendEvent, + trace.WithAttributes( + attribute.KeyValue{ + Key: UncompressedKey, + Value: attribute.Int64Value(uncompressedByteSize), + }, + attribute.KeyValue{ + Key: CompressedKey, + Value: attribute.Int64Value(compressedByteSize), + }), + ) +} + +// AddMessageReceiveEvent adds a message receive event to this span. +func (s *Span) AddMessageReceiveEvent(messageID, uncompressedByteSize, compressedByteSize int64) { + s.otelSpan.AddEvent(MessageReceiveEvent, + trace.WithAttributes( + attribute.KeyValue{ + Key: UncompressedKey, + Value: attribute.Int64Value(uncompressedByteSize), + }, + attribute.KeyValue{ + Key: CompressedKey, + Value: attribute.Int64Value(compressedByteSize), + }), + ) +} + +// AddLink adds a link to this span. +func (s *Span) AddLink(l octrace.Link) { + Handle(fmt.Errorf("ignoring OpenCensus link %+v for span %q because OpenTelemetry doesn't support setting links after creation", l, s.String())) +} + +// String prints a string representation of this span. +func (s *Span) String() string { + return fmt.Sprintf("span %s", s.otelSpan.SpanContext().SpanID().String()) +} diff --git a/bridge/opencensus/internal/span_test.go b/bridge/opencensus/internal/span_test.go new file mode 100644 index 00000000000..7aa023e71c2 --- /dev/null +++ b/bridge/opencensus/internal/span_test.go @@ -0,0 +1,268 @@ +// 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 internal_test + +import ( + "testing" + + octrace "go.opencensus.io/trace" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/bridge/opencensus/internal" + "go.opentelemetry.io/otel/bridge/opencensus/internal/oc2otel" + "go.opentelemetry.io/otel/bridge/opencensus/internal/otel2oc" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" +) + +type span struct { + trace.Span + + recording bool + ended bool + sc trace.SpanContext + name string + sCode codes.Code + sMsg string + attrs []attribute.KeyValue + eName string + eOpts []trace.EventOption +} + +func (s *span) IsRecording() bool { return s.recording } +func (s *span) End(...trace.SpanEndOption) { s.ended = true } +func (s *span) SpanContext() trace.SpanContext { return s.sc } +func (s *span) SetName(n string) { s.name = n } +func (s *span) SetStatus(c codes.Code, d string) { s.sCode, s.sMsg = c, d } +func (s *span) SetAttributes(a ...attribute.KeyValue) { s.attrs = a } +func (s *span) AddEvent(n string, o ...trace.EventOption) { s.eName, s.eOpts = n, o } + +func TestSpanIsRecordingEvents(t *testing.T) { + s := &span{recording: true} + ocS := internal.NewSpan(s) + if !ocS.IsRecordingEvents() { + t.Errorf("span.IsRecordingEvents() = false, want true") + } + s.recording = false + if ocS.IsRecordingEvents() { + t.Errorf("span.IsRecordingEvents() = true, want false") + } +} + +func TestSpanEnd(t *testing.T) { + s := new(span) + ocS := internal.NewSpan(s) + if s.ended { + t.Fatal("new span already ended") + } + + ocS.End() + if !s.ended { + t.Error("span.End() did not end OpenTelemetry span") + } +} + +func TestSpanSpanContext(t *testing.T) { + sc := trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: [16]byte{1}, + SpanID: [8]byte{1}, + }) + // Do not test the conversion, only that the method is called. + converted := otel2oc.SpanContext(sc) + + s := &span{sc: sc} + ocS := internal.NewSpan(s) + if ocS.SpanContext() != converted { + t.Error("span.SpanContext did not use OpenTelemetry SpanContext") + } +} + +func TestSpanSetName(t *testing.T) { + // OpenCensus does not set a name if not recording. + s := &span{recording: true} + ocS := internal.NewSpan(s) + name := "test name" + ocS.SetName(name) + if s.name != name { + t.Error("span.SetName did not set OpenTelemetry span name") + } +} + +func TestSpanSetStatus(t *testing.T) { + // OpenCensus does not set a status if not recording. + s := &span{recording: true} + ocS := internal.NewSpan(s) + + c, d := codes.Error, "error" + status := octrace.Status{Code: int32(c), Message: d} + ocS.SetStatus(status) + + if s.sCode != c { + t.Error("span.SetStatus failed to set OpenTelemetry status code") + } + if s.sMsg != d { + t.Error("span.SetStatus failed to set OpenTelemetry status description") + } +} + +func TestSpanAddAttributes(t *testing.T) { + attrs := []octrace.Attribute{ + octrace.BoolAttribute("a", true), + } + // Do not test the conversion, only that the method is called. + converted := oc2otel.Attributes(attrs) + + // OpenCensus does not set attributes if not recording. + s := &span{recording: true} + ocS := internal.NewSpan(s) + ocS.AddAttributes(attrs...) + + if len(s.attrs) != len(converted) || s.attrs[0] != converted[0] { + t.Error("span.AddAttributes failed to set OpenTelemetry attributes") + } +} + +func TestSpanAnnotate(t *testing.T) { + name := "annotation" + attrs := []octrace.Attribute{ + octrace.BoolAttribute("a", true), + } + // Do not test the conversion, only that the method is called. + want := oc2otel.Attributes(attrs) + + // OpenCensus does not set events if not recording. + s := &span{recording: true} + ocS := internal.NewSpan(s) + ocS.Annotate(attrs, name) + + if s.eName != name { + t.Error("span.Annotate did not set event name") + } + + config := trace.NewEventConfig(s.eOpts...) + got := config.Attributes() + if len(want) != len(got) || want[0] != got[0] { + t.Error("span.Annotate did not set event options") + } +} + +func TestSpanAnnotatef(t *testing.T) { + format := "annotation %s" + attrs := []octrace.Attribute{ + octrace.BoolAttribute("a", true), + } + // Do not test the conversion, only that the method is called. + want := oc2otel.Attributes(attrs) + + // OpenCensus does not set events if not recording. + s := &span{recording: true} + ocS := internal.NewSpan(s) + ocS.Annotatef(attrs, format, "a") + + if s.eName != "annotation a" { + t.Error("span.Annotatef did not set event name") + } + + config := trace.NewEventConfig(s.eOpts...) + got := config.Attributes() + if len(want) != len(got) || want[0] != got[0] { + t.Error("span.Annotatef did not set event options") + } +} + +func TestSpanAddMessageSendEvent(t *testing.T) { + var u, c int64 = 1, 2 + + // OpenCensus does not set events if not recording. + s := &span{recording: true} + ocS := internal.NewSpan(s) + ocS.AddMessageSendEvent(0, u, c) + + if s.eName != internal.MessageSendEvent { + t.Error("span.AddMessageSendEvent did not set event name") + } + + config := trace.NewEventConfig(s.eOpts...) + got := config.Attributes() + if len(got) != 2 { + t.Fatalf("span.AddMessageSendEvent set %d attributes, want 2", len(got)) + } + + want := attribute.KeyValue{Key: internal.UncompressedKey, Value: attribute.Int64Value(u)} + if got[0] != want { + t.Errorf("span.AddMessageSendEvent wrong uncompressed attribute: %v", got[0]) + } + + want = attribute.KeyValue{Key: internal.CompressedKey, Value: attribute.Int64Value(c)} + if got[1] != want { + t.Errorf("span.AddMessageSendEvent wrong compressed attribute: %v", got[1]) + } +} + +func TestSpanAddMessageReceiveEvent(t *testing.T) { + var u, c int64 = 3, 4 + + // OpenCensus does not set events if not recording. + s := &span{recording: true} + ocS := internal.NewSpan(s) + ocS.AddMessageReceiveEvent(0, u, c) + + if s.eName != internal.MessageReceiveEvent { + t.Error("span.AddMessageReceiveEvent did not set event name") + } + + config := trace.NewEventConfig(s.eOpts...) + got := config.Attributes() + if len(got) != 2 { + t.Fatalf("span.AddMessageReceiveEvent set %d attributes, want 2", len(got)) + } + + want := attribute.KeyValue{Key: internal.UncompressedKey, Value: attribute.Int64Value(u)} + if got[0] != want { + t.Errorf("span.AddMessageReceiveEvent wrong uncompressed attribute: %v", got[0]) + } + + want = attribute.KeyValue{Key: internal.CompressedKey, Value: attribute.Int64Value(c)} + if got[1] != want { + t.Errorf("span.AddMessageReceiveEvent wrong compressed attribute: %v", got[1]) + } +} + +func TestSpanAddLinkFails(t *testing.T) { + h, restore := withHandler() + defer restore() + + // OpenCensus does not try to set links if not recording. + s := &span{recording: true} + ocS := internal.NewSpan(s) + ocS.AddLink(octrace.Link{}) + + if h.err == nil { + t.Error("span.AddLink failed to raise an error") + } +} + +func TestSpanString(t *testing.T) { + sc := trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: [16]byte{1}, + SpanID: [8]byte{1}, + }) + + s := &span{sc: sc} + ocS := internal.NewSpan(s) + if expected := "span 0100000000000000"; ocS.String() != expected { + t.Errorf("span.String = %q, not %q", ocS.String(), expected) + } +} diff --git a/bridge/opencensus/internal/tracer.go b/bridge/opencensus/internal/tracer.go new file mode 100644 index 00000000000..4ed70a30afe --- /dev/null +++ b/bridge/opencensus/internal/tracer.go @@ -0,0 +1,69 @@ +// 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 internal // import "go.opentelemetry.io/otel/bridge/opencensus/internal" + +import ( + "context" + "fmt" + + octrace "go.opencensus.io/trace" + + "go.opentelemetry.io/otel/bridge/opencensus/internal/oc2otel" + "go.opentelemetry.io/otel/trace" +) + +// Tracer is an OpenCensus Tracer that wraps an OpenTelemetry Tracer. +type Tracer struct { + otelTracer trace.Tracer +} + +// NewTracer returns an OpenCensus Tracer that wraps the OpenTelemetry tracer. +func NewTracer(tracer trace.Tracer) octrace.Tracer { + return &Tracer{otelTracer: tracer} +} + +// StartSpan starts a new child span of the current span in the context. If +// there is no span in the context, it creates a new trace and span. +func (o *Tracer) StartSpan(ctx context.Context, name string, s ...octrace.StartOption) (context.Context, *octrace.Span) { + otelOpts, err := oc2otel.StartOptions(s) + if err != nil { + Handle(fmt.Errorf("starting span %q: %w", name, err)) + } + ctx, sp := o.otelTracer.Start(ctx, name, otelOpts...) + return ctx, NewSpan(sp) +} + +// StartSpanWithRemoteParent starts a new child span of the span from the +// given parent. +func (o *Tracer) 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 = trace.ContextWithSpan(ctx, nil) + ctx = trace.ContextWithRemoteSpanContext(ctx, oc2otel.SpanContext(parent)) + return o.StartSpan(ctx, name, s...) +} + +// FromContext returns the Span stored in a context. +func (o *Tracer) FromContext(ctx context.Context) *octrace.Span { + return NewSpan(trace.SpanFromContext(ctx)) +} + +// NewContext returns a new context with the given Span attached. +func (o *Tracer) NewContext(parent context.Context, s *octrace.Span) context.Context { + if otSpan, ok := s.Internal().(*Span); ok { + return trace.ContextWithSpan(parent, otSpan.otelSpan) + } + Handle(fmt.Errorf("unable to create context with span %q, since it was created using a different tracer", s.String())) + return parent +} diff --git a/bridge/opencensus/internal/tracer_test.go b/bridge/opencensus/internal/tracer_test.go new file mode 100644 index 00000000000..1e3518a7efe --- /dev/null +++ b/bridge/opencensus/internal/tracer_test.go @@ -0,0 +1,161 @@ +// 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 internal_test + +import ( + "context" + "testing" + + octrace "go.opencensus.io/trace" + + "go.opentelemetry.io/otel/bridge/opencensus/internal" + "go.opentelemetry.io/otel/bridge/opencensus/internal/oc2otel" + "go.opentelemetry.io/otel/bridge/opencensus/internal/otel2oc" + "go.opentelemetry.io/otel/trace" +) + +type handler struct{ err error } + +func (h *handler) Handle(e error) { h.err = e } + +func withHandler() (*handler, func()) { + h := new(handler) + original := internal.Handle + internal.Handle = h.Handle + return h, func() { internal.Handle = original } +} + +type tracer struct { + ctx context.Context + name string + opts []trace.SpanStartOption +} + +func (t *tracer) Start(ctx context.Context, name string, opts ...trace.SpanStartOption) (context.Context, trace.Span) { + t.ctx, t.name, t.opts = ctx, name, opts + noop := trace.NewNoopTracerProvider().Tracer("testing") + return noop.Start(ctx, name, opts...) +} + +type ctxKey string + +func TestTracerStartSpan(t *testing.T) { + h, restore := withHandler() + defer restore() + + otelTracer := &tracer{} + ocTracer := internal.NewTracer(otelTracer) + + ctx := context.WithValue(context.Background(), ctxKey("key"), "value") + name := "testing span" + ocTracer.StartSpan(ctx, name, octrace.WithSpanKind(octrace.SpanKindClient)) + if h.err != nil { + t.Fatalf("OC tracer.StartSpan errored: %v", h.err) + } + + if otelTracer.ctx != ctx { + t.Error("OTel tracer.Start called with wrong context") + } + if otelTracer.name != name { + t.Error("OTel tracer.Start called with wrong name") + } + sk := trace.SpanKindClient + c := trace.NewSpanStartConfig(otelTracer.opts...) + if c.SpanKind() != sk { + t.Errorf("OTel tracer.Start called with wrong options: %#v", c) + } +} + +func TestTracerStartSpanReportsErrors(t *testing.T) { + h, restore := withHandler() + defer restore() + + ocTracer := internal.NewTracer(&tracer{}) + ocTracer.StartSpan(context.Background(), "", octrace.WithSampler(octrace.AlwaysSample())) + if h.err == nil { + t.Error("OC tracer.StartSpan no error when converting Sampler") + } +} + +func TestTracerStartSpanWithRemoteParent(t *testing.T) { + otelTracer := new(tracer) + ocTracer := internal.NewTracer(otelTracer) + sc := octrace.SpanContext{TraceID: [16]byte{1}, SpanID: [8]byte{1}} + converted := oc2otel.SpanContext(sc).WithRemote(true) + + ocTracer.StartSpanWithRemoteParent(context.Background(), "", sc) + + got := trace.SpanContextFromContext(otelTracer.ctx) + if !got.Equal(converted) { + t.Error("tracer.StartSpanWithRemoteParent failed to set remote parent") + } +} + +func TestTracerFromContext(t *testing.T) { + sc := trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: [16]byte{1}, + SpanID: [8]byte{1}, + }) + ctx := trace.ContextWithSpanContext(context.Background(), sc) + + noop := trace.NewNoopTracerProvider().Tracer("TestTracerFromContext") + // Test using the fact that the No-Op span will propagate a span context . + ctx, _ = noop.Start(ctx, "test") + + got := internal.NewTracer(noop).FromContext(ctx).SpanContext() + // Do not test the convedsion, only that the propagtion. + want := otel2oc.SpanContext(sc) + if got != want { + t.Errorf("tracer.FromContext returned wrong context: %#v", got) + } +} + +func TestTracerNewContext(t *testing.T) { + sc := trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: [16]byte{1}, + SpanID: [8]byte{1}, + }) + ctx := trace.ContextWithSpanContext(context.Background(), sc) + + noop := trace.NewNoopTracerProvider().Tracer("TestTracerNewContext") + // Test using the fact that the No-Op span will propagate a span context . + _, s := noop.Start(ctx, "test") + + ocTracer := internal.NewTracer(noop) + ctx = ocTracer.NewContext(context.Background(), internal.NewSpan(s)) + got := trace.SpanContextFromContext(ctx) + + if !got.Equal(sc) { + t.Error("tracer.NewContext did not attach Span to context") + } +} + +type differentSpan struct { + octrace.SpanInterface +} + +func (s *differentSpan) String() string { return "testing span" } + +func TestTracerNewContextErrors(t *testing.T) { + h, restore := withHandler() + defer restore() + + ocTracer := internal.NewTracer(&tracer{}) + ocSpan := octrace.NewSpan(&differentSpan{}) + ocTracer.NewContext(context.Background(), ocSpan) + if h.err == nil { + t.Error("tracer.NewContext did not error for unrecognized span") + } +} diff --git a/bridge/opencensus/test/bridge_test.go b/bridge/opencensus/test/bridge_test.go new file mode 100644 index 00000000000..fb8647bd204 --- /dev/null +++ b/bridge/opencensus/test/bridge_test.go @@ -0,0 +1,289 @@ +// 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 test + +import ( + "context" + "testing" + + octrace "go.opencensus.io/trace" + + "go.opentelemetry.io/otel/attribute" + ocbridge "go.opentelemetry.io/otel/bridge/opencensus" + "go.opentelemetry.io/otel/bridge/opencensus/internal" + "go.opentelemetry.io/otel/codes" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/sdk/trace/tracetest" + "go.opentelemetry.io/otel/trace" +) + +func TestMixedAPIs(t *testing.T) { + sr := tracetest.NewSpanRecorder() + tp := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) + tracer := tp.Tracer("mixedapitracer") + octrace.DefaultTracer = ocbridge.NewTracer(tracer) + + func() { + ctx := context.Background() + var ocspan1 *octrace.Span + ctx, ocspan1 = octrace.StartSpan(ctx, "OpenCensusSpan1") + defer ocspan1.End() + + var otspan1 trace.Span + ctx, otspan1 = tracer.Start(ctx, "OpenTelemetrySpan1") + defer otspan1.End() + + var ocspan2 *octrace.Span + ctx, ocspan2 = octrace.StartSpan(ctx, "OpenCensusSpan2") + defer ocspan2.End() + + var otspan2 trace.Span + _, otspan2 = tracer.Start(ctx, "OpenTelemetrySpan2") + defer otspan2.End() + }() + + spans := sr.Ended() + + if len(spans) != 4 { + for _, span := range spans { + t.Logf("Span: %s", span.Name()) + } + t.Fatalf("Got %d spans, exepected %d.", len(spans), 4) + } + + var parent trace.SpanContext + for i := len(spans) - 1; i >= 0; i-- { + // Verify that OpenCensus spans and OpenTelemetry spans have each + // other as parents. + if psid := spans[i].Parent().SpanID(); psid != parent.SpanID() { + t.Errorf("Span %v had parent %v. Expected %v", spans[i].Name(), psid, parent.SpanID()) + } + parent = spans[i].SpanContext() + } +} + +func TestStartOptions(t *testing.T) { + sr := tracetest.NewSpanRecorder() + tp := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) + octrace.DefaultTracer = ocbridge.NewTracer(tp.Tracer("startoptionstracer")) + + ctx := context.Background() + _, span := octrace.StartSpan(ctx, "OpenCensusSpan", octrace.WithSpanKind(octrace.SpanKindClient)) + span.End() + + spans := sr.Ended() + + if len(spans) != 1 { + t.Fatalf("Got %d spans, exepected %d", len(spans), 1) + } + + if spans[0].SpanKind() != trace.SpanKindClient { + t.Errorf("Got span kind %v, exepected %d", spans[0].SpanKind(), trace.SpanKindClient) + } +} + +func TestStartSpanWithRemoteParent(t *testing.T) { + sr := tracetest.NewSpanRecorder() + tp := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) + tracer := tp.Tracer("remoteparent") + octrace.DefaultTracer = ocbridge.NewTracer(tracer) + + ctx := context.Background() + ctx, parent := tracer.Start(ctx, "OpenTelemetrySpan1") + + _, span := octrace.StartSpanWithRemoteParent(ctx, "OpenCensusSpan", ocbridge.OTelSpanContextToOC(parent.SpanContext())) + span.End() + + spans := sr.Ended() + + if len(spans) != 1 { + t.Fatalf("Got %d spans, exepected %d", len(spans), 1) + } + + if psid := spans[0].Parent().SpanID(); psid != parent.SpanContext().SpanID() { + t.Errorf("Span %v, had parent %v. Expected %d", spans[0].Name(), psid, parent.SpanContext().SpanID()) + } +} + +func TestToFromContext(t *testing.T) { + sr := tracetest.NewSpanRecorder() + tp := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) + tracer := tp.Tracer("tofromcontext") + octrace.DefaultTracer = ocbridge.NewTracer(tracer) + + func() { + ctx := context.Background() + + _, otSpan1 := tracer.Start(ctx, "OpenTelemetrySpan1") + defer otSpan1.End() + + // Use NewContext instead of the context from Start + ctx = octrace.NewContext(ctx, internal.NewSpan(otSpan1)) + + ctx, _ = tracer.Start(ctx, "OpenTelemetrySpan2") + + // Get the opentelemetry span using the OpenCensus FromContext, and end it + otSpan2 := octrace.FromContext(ctx) + defer otSpan2.End() + }() + + spans := sr.Ended() + + if len(spans) != 2 { + t.Fatalf("Got %d spans, exepected %d.", len(spans), 2) + } + + var parent trace.SpanContext + for i := len(spans) - 1; i >= 0; i-- { + // Verify that OpenCensus spans and OpenTelemetry spans have each + // other as parents. + if psid := spans[i].Parent().SpanID(); psid != parent.SpanID() { + t.Errorf("Span %v had parent %v. Expected %v", spans[i].Name(), psid, parent.SpanID()) + } + parent = spans[i].SpanContext() + } +} + +func TestIsRecordingEvents(t *testing.T) { + sr := tracetest.NewSpanRecorder() + tp := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) + octrace.DefaultTracer = ocbridge.NewTracer(tp.Tracer("isrecordingevents")) + + ctx := context.Background() + _, ocspan := octrace.StartSpan(ctx, "OpenCensusSpan1") + if !ocspan.IsRecordingEvents() { + t.Errorf("Got %v, expected true", ocspan.IsRecordingEvents()) + } +} + +func attrsMap(s []attribute.KeyValue) map[attribute.Key]attribute.Value { + m := make(map[attribute.Key]attribute.Value, len(s)) + for _, a := range s { + m[a.Key] = a.Value + } + return m +} + +func TestSetThings(t *testing.T) { + sr := tracetest.NewSpanRecorder() + tp := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) + octrace.DefaultTracer = ocbridge.NewTracer(tp.Tracer("setthings")) + + ctx := context.Background() + _, ocspan := octrace.StartSpan(ctx, "OpenCensusSpan1") + ocspan.SetName("span-foo") + ocspan.SetStatus(octrace.Status{Code: 1, Message: "foo"}) + ocspan.AddAttributes( + octrace.BoolAttribute("bool", true), + octrace.Int64Attribute("int64", 12345), + octrace.Float64Attribute("float64", 12.345), + octrace.StringAttribute("string", "stringval"), + ) + ocspan.Annotate( + []octrace.Attribute{octrace.StringAttribute("string", "annotateval")}, + "annotate", + ) + ocspan.Annotatef( + []octrace.Attribute{ + octrace.Int64Attribute("int64", 12345), + octrace.Float64Attribute("float64", 12.345), + }, + "annotate%d", 67890, + ) + ocspan.AddMessageSendEvent(123, 456, 789) + ocspan.AddMessageReceiveEvent(246, 135, 369) + ocspan.End() + + spans := sr.Ended() + + if len(spans) != 1 { + t.Fatalf("Got %d spans, exepected %d.", len(spans), 1) + } + s := spans[0] + + if s.Name() != "span-foo" { + t.Errorf("Got name %v, expected span-foo", s.Name()) + } + + if s.Status().Code != codes.Error { + t.Errorf("Got code %v, expected %v", s.Status().Code, codes.Error) + } + + if s.Status().Description != "foo" { + t.Errorf("Got code %v, expected foo", s.Status().Description) + } + + attrs := attrsMap(s.Attributes()) + if v := attrs[attribute.Key("bool")]; !v.AsBool() { + t.Errorf("Got attributes[bool] %v, expected true", v.AsBool()) + } + if v := attrs[attribute.Key("int64")]; v.AsInt64() != 12345 { + t.Errorf("Got attributes[int64] %v, expected 12345", v.AsInt64()) + } + if v := attrs[attribute.Key("float64")]; v.AsFloat64() != 12.345 { + t.Errorf("Got attributes[float64] %v, expected 12.345", v.AsFloat64()) + } + if v := attrs[attribute.Key("string")]; v.AsString() != "stringval" { + t.Errorf("Got attributes[string] %v, expected stringval", v.AsString()) + } + + if len(s.Events()) != 4 { + t.Fatalf("Got len(events) = %v, expected 4", len(s.Events())) + } + annotateEvent := s.Events()[0] + aeAttrs := attrsMap(annotateEvent.Attributes) + annotatefEvent := s.Events()[1] + afeAttrs := attrsMap(annotatefEvent.Attributes) + sendEvent := s.Events()[2] + receiveEvent := s.Events()[3] + if v := aeAttrs[attribute.Key("string")]; v.AsString() != "annotateval" { + t.Errorf("Got annotateEvent.Attributes[string] = %v, expected annotateval", v.AsString()) + } + if annotateEvent.Name != "annotate" { + t.Errorf("Got annotateEvent.Name = %v, expected annotate", annotateEvent.Name) + } + if v := afeAttrs[attribute.Key("int64")]; v.AsInt64() != 12345 { + t.Errorf("Got annotatefEvent.Attributes[int64] = %v, expected 12345", v.AsInt64()) + } + if v := afeAttrs[attribute.Key("float64")]; v.AsFloat64() != 12.345 { + t.Errorf("Got annotatefEvent.Attributes[float64] = %v, expected 12.345", v.AsFloat64()) + } + if annotatefEvent.Name != "annotate67890" { + t.Errorf("Got annotatefEvent.Name = %v, expected annotate67890", annotatefEvent.Name) + } + if v := aeAttrs[attribute.Key("string")]; v.AsString() != "annotateval" { + t.Errorf("Got annotateEvent.Attributes[string] = %v, expected annotateval", v.AsString()) + } + seAttrs := attrsMap(sendEvent.Attributes) + reAttrs := attrsMap(receiveEvent.Attributes) + if sendEvent.Name != internal.MessageSendEvent { + t.Errorf("Got sendEvent.Name = %v, expected message send", sendEvent.Name) + } + if v := seAttrs[internal.UncompressedKey]; v.AsInt64() != 456 { + t.Errorf("Got sendEvent.Attributes[uncompressedKey] = %v, expected 456", v.AsInt64()) + } + if v := seAttrs[internal.CompressedKey]; v.AsInt64() != 789 { + t.Errorf("Got sendEvent.Attributes[compressedKey] = %v, expected 789", v.AsInt64()) + } + if receiveEvent.Name != internal.MessageReceiveEvent { + t.Errorf("Got receiveEvent.Name = %v, expected message receive", receiveEvent.Name) + } + if v := reAttrs[internal.UncompressedKey]; v.AsInt64() != 135 { + t.Errorf("Got receiveEvent.Attributes[uncompressedKey] = %v, expected 135", v.AsInt64()) + } + if v := reAttrs[internal.CompressedKey]; v.AsInt64() != 369 { + t.Errorf("Got receiveEvent.Attributes[compressedKey] = %v, expected 369", v.AsInt64()) + } +} diff --git a/bridge/opencensus/test/go.mod b/bridge/opencensus/test/go.mod new file mode 100644 index 00000000000..9a40bac93c6 --- /dev/null +++ b/bridge/opencensus/test/go.mod @@ -0,0 +1,26 @@ +module go.opentelemetry.io/otel/bridge/opencensus/test + +go 1.17 + +require ( + go.opencensus.io v0.23.0 + go.opentelemetry.io/otel v1.9.0 + go.opentelemetry.io/otel/bridge/opencensus v0.31.0 + go.opentelemetry.io/otel/sdk v1.9.0 + go.opentelemetry.io/otel/trace v1.9.0 +) + +require ( + github.com/go-logr/logr v1.2.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect + golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7 // indirect +) + +replace go.opentelemetry.io/otel => ../../.. + +replace go.opentelemetry.io/otel/bridge/opencensus => ../ + +replace go.opentelemetry.io/otel/sdk => ../../../sdk + +replace go.opentelemetry.io/otel/trace => ../../../trace diff --git a/bridge/opencensus/test/go.sum b/bridge/opencensus/test/go.sum new file mode 100644 index 00000000000..c7ddc1e1307 --- /dev/null +++ b/bridge/opencensus/test/go.sum @@ -0,0 +1,99 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +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/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7 h1:iGu644GcxtEcrInvDsQRCwJjtCIOlT2V7IRt6ah2Whw= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=