daemon: install OpenCensus-to-OTEL trace bridge

Export trace spans from the github.com/microsoft/hcsshim module, which
is instrumented with OpenCensus, to the daemon's OpenTelemetry exporter
to provide more visibility into Windows container lifecycle operations.

Signed-off-by: Cory Snider <csnider@mirantis.com>
This commit is contained in:
Cory Snider
2025-11-13 14:41:12 -05:00
parent 7a3b88d91c
commit 4535d63c91
29 changed files with 1889 additions and 0 deletions

View File

@@ -61,6 +61,7 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"go.opentelemetry.io/otel" "go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/bridge/opencensus"
"go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/propagation"
"tags.cncf.io/container-device-interface/pkg/cdi" "tags.cncf.io/container-device-interface/pkg/cdi"
) )
@@ -240,6 +241,11 @@ func (cli *daemonCLI) start(ctx context.Context) (err error) {
tp, otelShutdown := otelutil.NewTracerProvider(ctx, true) tp, otelShutdown := otelutil.NewTracerProvider(ctx, true)
otel.SetTracerProvider(tp) otel.SetTracerProvider(tp)
log.G(ctx).Logger.AddHook(tracing.NewLogrusHook()) log.G(ctx).Logger.AddHook(tracing.NewLogrusHook())
// The github.com/microsoft/hcsshim module is instrumented with
// OpenCensus, but we use OpenTelemetry for tracing in the daemon.
// Bridge OpenCensus to OpenTelemetry so OC trace spans are exported to
// the daemon's configured OTEL collector.
opencensus.InstallTraceBridge()
pluginStore := plugin.NewStore() pluginStore := plugin.NewStore()

1
go.mod
View File

@@ -101,6 +101,7 @@ require (
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0
go.opentelemetry.io/contrib/processors/baggagecopy v0.4.0 go.opentelemetry.io/contrib/processors/baggagecopy v0.4.0
go.opentelemetry.io/otel v1.38.0 go.opentelemetry.io/otel v1.38.0
go.opentelemetry.io/otel/bridge/opencensus v1.38.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0
go.opentelemetry.io/otel/sdk v1.38.0 go.opentelemetry.io/otel/sdk v1.38.0
go.opentelemetry.io/otel/trace v1.38.0 go.opentelemetry.io/otel/trace v1.38.0

2
go.sum
View File

@@ -665,6 +665,8 @@ go.opentelemetry.io/contrib/processors/baggagecopy v0.4.0 h1:SUsGRzllvPRJK6VKn1S
go.opentelemetry.io/contrib/processors/baggagecopy v0.4.0/go.mod h1:68LCyaHcLhUf3tciKAAbSFKkr4Pkrt24ei0/xHm0No8= go.opentelemetry.io/contrib/processors/baggagecopy v0.4.0/go.mod h1:68LCyaHcLhUf3tciKAAbSFKkr4Pkrt24ei0/xHm0No8=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
go.opentelemetry.io/otel/bridge/opencensus v1.38.0 h1:LUFKh5lYqNalr6E2Wr54fymStzDmlDQoVWp5UlJ8yG0=
go.opentelemetry.io/otel/bridge/opencensus v1.38.0/go.mod h1:84yBtJ/OnEa2I40lMyrGadED8nVH/JfzoK+5p7aYyIY=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk=

19
vendor/go.opencensus.io/metric/metricdata/doc.go generated vendored Normal file
View File

@@ -0,0 +1,19 @@
// Copyright 2018, OpenCensus 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 metricdata contains the metrics data model.
//
// This is an EXPERIMENTAL package, and may change in arbitrary ways without
// notice.
package metricdata // import "go.opencensus.io/metric/metricdata"

38
vendor/go.opencensus.io/metric/metricdata/exemplar.go generated vendored Normal file
View File

@@ -0,0 +1,38 @@
// Copyright 2018, OpenCensus 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 metricdata
import (
"time"
)
// Exemplars keys.
const (
AttachmentKeySpanContext = "SpanContext"
)
// Exemplar is an example data point associated with each bucket of a
// distribution type aggregation.
//
// Their purpose is to provide an example of the kind of thing
// (request, RPC, trace span, etc.) that resulted in that measurement.
type Exemplar struct {
Value float64 // the value that was recorded
Timestamp time.Time // the time the value was recorded
Attachments Attachments // attachments (if any)
}
// Attachments is a map of extra values associated with a recorded data point.
type Attachments map[string]interface{}

35
vendor/go.opencensus.io/metric/metricdata/label.go generated vendored Normal file
View File

@@ -0,0 +1,35 @@
// Copyright 2018, OpenCensus 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 metricdata
// LabelKey represents key of a label. It has optional
// description attribute.
type LabelKey struct {
Key string
Description string
}
// LabelValue represents the value of a label.
// The zero value represents a missing label value, which may be treated
// differently to an empty string value by some back ends.
type LabelValue struct {
Value string // string value of the label
Present bool // flag that indicated whether a value is present or not
}
// NewLabelValue creates a new non-nil LabelValue that represents the given string.
func NewLabelValue(val string) LabelValue {
return LabelValue{Value: val, Present: true}
}

46
vendor/go.opencensus.io/metric/metricdata/metric.go generated vendored Normal file
View File

@@ -0,0 +1,46 @@
// Copyright 2018, OpenCensus 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 metricdata
import (
"time"
"go.opencensus.io/resource"
)
// Descriptor holds metadata about a metric.
type Descriptor struct {
Name string // full name of the metric
Description string // human-readable description
Unit Unit // units for the measure
Type Type // type of measure
LabelKeys []LabelKey // label keys
}
// Metric represents a quantity measured against a resource with different
// label value combinations.
type Metric struct {
Descriptor Descriptor // metric descriptor
Resource *resource.Resource // resource against which this was measured
TimeSeries []*TimeSeries // one time series for each combination of label values
}
// TimeSeries is a sequence of points associated with a combination of label
// values.
type TimeSeries struct {
LabelValues []LabelValue // label values, same order as keys in the metric descriptor
Points []Point // points sequence
StartTime time.Time // time we started recording this time series
}

193
vendor/go.opencensus.io/metric/metricdata/point.go generated vendored Normal file
View File

@@ -0,0 +1,193 @@
// Copyright 2018, OpenCensus 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 metricdata
import (
"time"
)
// Point is a single data point of a time series.
type Point struct {
// Time is the point in time that this point represents in a time series.
Time time.Time
// Value is the value of this point. Prefer using ReadValue to switching on
// the value type, since new value types might be added.
Value interface{}
}
//go:generate stringer -type ValueType
// NewFloat64Point creates a new Point holding a float64 value.
func NewFloat64Point(t time.Time, val float64) Point {
return Point{
Value: val,
Time: t,
}
}
// NewInt64Point creates a new Point holding an int64 value.
func NewInt64Point(t time.Time, val int64) Point {
return Point{
Value: val,
Time: t,
}
}
// NewDistributionPoint creates a new Point holding a Distribution value.
func NewDistributionPoint(t time.Time, val *Distribution) Point {
return Point{
Value: val,
Time: t,
}
}
// NewSummaryPoint creates a new Point holding a Summary value.
func NewSummaryPoint(t time.Time, val *Summary) Point {
return Point{
Value: val,
Time: t,
}
}
// ValueVisitor allows reading the value of a point.
type ValueVisitor interface {
VisitFloat64Value(float64)
VisitInt64Value(int64)
VisitDistributionValue(*Distribution)
VisitSummaryValue(*Summary)
}
// ReadValue accepts a ValueVisitor and calls the appropriate method with the
// value of this point.
// Consumers of Point should use this in preference to switching on the type
// of the value directly, since new value types may be added.
func (p Point) ReadValue(vv ValueVisitor) {
switch v := p.Value.(type) {
case int64:
vv.VisitInt64Value(v)
case float64:
vv.VisitFloat64Value(v)
case *Distribution:
vv.VisitDistributionValue(v)
case *Summary:
vv.VisitSummaryValue(v)
default:
panic("unexpected value type")
}
}
// Distribution contains summary statistics for a population of values. It
// optionally contains a histogram representing the distribution of those
// values across a set of buckets.
type Distribution struct {
// Count is the number of values in the population. Must be non-negative. This value
// must equal the sum of the values in bucket_counts if a histogram is
// provided.
Count int64
// Sum is the sum of the values in the population. If count is zero then this field
// must be zero.
Sum float64
// SumOfSquaredDeviation is the sum of squared deviations from the mean of the values in the
// population. For values x_i this is:
//
// Sum[i=1..n]((x_i - mean)^2)
//
// Knuth, "The Art of Computer Programming", Vol. 2, page 323, 3rd edition
// describes Welford's method for accumulating this sum in one pass.
//
// If count is zero then this field must be zero.
SumOfSquaredDeviation float64
// BucketOptions describes the bounds of the histogram buckets in this
// distribution.
//
// A Distribution may optionally contain a histogram of the values in the
// population.
//
// If nil, there is no associated histogram.
BucketOptions *BucketOptions
// Bucket If the distribution does not have a histogram, then omit this field.
// If there is a histogram, then the sum of the values in the Bucket counts
// must equal the value in the count field of the distribution.
Buckets []Bucket
}
// BucketOptions describes the bounds of the histogram buckets in this
// distribution.
type BucketOptions struct {
// Bounds specifies a set of bucket upper bounds.
// This defines len(bounds) + 1 (= N) buckets. The boundaries for bucket
// index i are:
//
// [0, Bounds[i]) for i == 0
// [Bounds[i-1], Bounds[i]) for 0 < i < N-1
// [Bounds[i-1], +infinity) for i == N-1
Bounds []float64
}
// Bucket represents a single bucket (value range) in a distribution.
type Bucket struct {
// Count is the number of values in each bucket of the histogram, as described in
// bucket_bounds.
Count int64
// Exemplar associated with this bucket (if any).
Exemplar *Exemplar
}
// Summary is a representation of percentiles.
type Summary struct {
// Count is the cumulative count (if available).
Count int64
// Sum is the cumulative sum of values (if available).
Sum float64
// HasCountAndSum is true if Count and Sum are available.
HasCountAndSum bool
// Snapshot represents percentiles calculated over an arbitrary time window.
// The values in this struct can be reset at arbitrary unknown times, with
// the requirement that all of them are reset at the same time.
Snapshot Snapshot
}
// Snapshot represents percentiles over an arbitrary time.
// The values in this struct can be reset at arbitrary unknown times, with
// the requirement that all of them are reset at the same time.
type Snapshot struct {
// Count is the number of values in the snapshot. Optional since some systems don't
// expose this. Set to 0 if not available.
Count int64
// Sum is the sum of values in the snapshot. Optional since some systems don't
// expose this. If count is 0 then this field must be zero.
Sum float64
// Percentiles is a map from percentile (range (0-100.0]) to the value of
// the percentile.
Percentiles map[float64]float64
}
//go:generate stringer -type Type
// Type is the overall type of metric, including its value type and whether it
// represents a cumulative total (since the start time) or if it represents a
// gauge value.
type Type int
// Metric types.
const (
TypeGaugeInt64 Type = iota
TypeGaugeFloat64
TypeGaugeDistribution
TypeCumulativeInt64
TypeCumulativeFloat64
TypeCumulativeDistribution
TypeSummary
)

View File

@@ -0,0 +1,16 @@
// Code generated by "stringer -type Type"; DO NOT EDIT.
package metricdata
import "strconv"
const _Type_name = "TypeGaugeInt64TypeGaugeFloat64TypeGaugeDistributionTypeCumulativeInt64TypeCumulativeFloat64TypeCumulativeDistributionTypeSummary"
var _Type_index = [...]uint8{0, 14, 30, 51, 70, 91, 117, 128}
func (i Type) String() string {
if i < 0 || i >= Type(len(_Type_index)-1) {
return "Type(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _Type_name[_Type_index[i]:_Type_index[i+1]]
}

27
vendor/go.opencensus.io/metric/metricdata/unit.go generated vendored Normal file
View File

@@ -0,0 +1,27 @@
// Copyright 2018, OpenCensus 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 metricdata
// Unit is a string encoded according to the case-sensitive abbreviations from the
// Unified Code for Units of Measure: http://unitsofmeasure.org/ucum.html
type Unit string
// Predefined units. To record against a unit not represented here, create your
// own Unit type constant from a string.
const (
UnitDimensionless Unit = "1"
UnitBytes Unit = "By"
UnitMilliseconds Unit = "ms"
)

View File

@@ -0,0 +1,78 @@
// Copyright 2019, OpenCensus 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 metricproducer
import (
"sync"
)
// Manager maintains a list of active producers. Producers can register
// with the manager to allow readers to read all metrics provided by them.
// Readers can retrieve all producers registered with the manager,
// read metrics from the producers and export them.
type Manager struct {
mu sync.RWMutex
producers map[Producer]struct{}
}
var prodMgr *Manager
var once sync.Once
// GlobalManager is a single instance of producer manager
// that is used by all producers and all readers.
func GlobalManager() *Manager {
once.Do(func() {
prodMgr = &Manager{}
prodMgr.producers = make(map[Producer]struct{})
})
return prodMgr
}
// AddProducer adds the producer to the Manager if it is not already present.
func (pm *Manager) AddProducer(producer Producer) {
if producer == nil {
return
}
pm.mu.Lock()
defer pm.mu.Unlock()
pm.producers[producer] = struct{}{}
}
// DeleteProducer deletes the producer from the Manager if it is present.
func (pm *Manager) DeleteProducer(producer Producer) {
if producer == nil {
return
}
pm.mu.Lock()
defer pm.mu.Unlock()
delete(pm.producers, producer)
}
// GetAll returns a slice of all producer currently registered with
// the Manager. For each call it generates a new slice. The slice
// should not be cached as registration may change at any time. It is
// typically called periodically by exporter to read metrics from
// the producers.
func (pm *Manager) GetAll() []Producer {
pm.mu.Lock()
defer pm.mu.Unlock()
producers := make([]Producer, len(pm.producers))
i := 0
for producer := range pm.producers {
producers[i] = producer
i++
}
return producers
}

View File

@@ -0,0 +1,28 @@
// Copyright 2019, OpenCensus 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 metricproducer
import (
"go.opencensus.io/metric/metricdata"
)
// Producer is a source of metrics.
type Producer interface {
// Read should return the current values of all metrics supported by this
// metric provider.
// The returned metrics should be unique for each combination of name and
// resource.
Read() []*metricdata.Metric
}

164
vendor/go.opencensus.io/resource/resource.go generated vendored Normal file
View File

@@ -0,0 +1,164 @@
// Copyright 2018, OpenCensus 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 resource provides functionality for resource, which capture
// identifying information about the entities for which signals are exported.
package resource
import (
"context"
"fmt"
"os"
"regexp"
"sort"
"strconv"
"strings"
)
// Environment variables used by FromEnv to decode a resource.
const (
EnvVarType = "OC_RESOURCE_TYPE"
EnvVarLabels = "OC_RESOURCE_LABELS"
)
// Resource describes an entity about which identifying information and metadata is exposed.
// For example, a type "k8s.io/container" may hold labels describing the pod name and namespace.
type Resource struct {
Type string
Labels map[string]string
}
// EncodeLabels encodes a labels map to a string as provided via the OC_RESOURCE_LABELS environment variable.
func EncodeLabels(labels map[string]string) string {
sortedKeys := make([]string, 0, len(labels))
for k := range labels {
sortedKeys = append(sortedKeys, k)
}
sort.Strings(sortedKeys)
s := ""
for i, k := range sortedKeys {
if i > 0 {
s += ","
}
s += k + "=" + strconv.Quote(labels[k])
}
return s
}
var labelRegex = regexp.MustCompile(`^\s*([[:ascii:]]{1,256}?)=("[[:ascii:]]{0,256}?")\s*,`)
// DecodeLabels decodes a serialized label map as used in the OC_RESOURCE_LABELS variable.
// A list of labels of the form `<key1>="<value1>",<key2>="<value2>",...` is accepted.
// Domain names and paths are accepted as label keys.
// Most users will want to use FromEnv instead.
func DecodeLabels(s string) (map[string]string, error) {
m := map[string]string{}
// Ensure a trailing comma, which allows us to keep the regex simpler
s = strings.TrimRight(strings.TrimSpace(s), ",") + ","
for len(s) > 0 {
match := labelRegex.FindStringSubmatch(s)
if len(match) == 0 {
return nil, fmt.Errorf("invalid label formatting, remainder: %s", s)
}
v := match[2]
if v == "" {
v = match[3]
} else {
var err error
if v, err = strconv.Unquote(v); err != nil {
return nil, fmt.Errorf("invalid label formatting, remainder: %s, err: %s", s, err)
}
}
m[match[1]] = v
s = s[len(match[0]):]
}
return m, nil
}
// FromEnv is a detector that loads resource information from the OC_RESOURCE_TYPE
// and OC_RESOURCE_labelS environment variables.
func FromEnv(context.Context) (*Resource, error) {
res := &Resource{
Type: strings.TrimSpace(os.Getenv(EnvVarType)),
}
labels := strings.TrimSpace(os.Getenv(EnvVarLabels))
if labels == "" {
return res, nil
}
var err error
if res.Labels, err = DecodeLabels(labels); err != nil {
return nil, err
}
return res, nil
}
var _ Detector = FromEnv
// merge resource information from b into a. In case of a collision, a takes precedence.
func merge(a, b *Resource) *Resource {
if a == nil {
return b
}
if b == nil {
return a
}
res := &Resource{
Type: a.Type,
Labels: map[string]string{},
}
if res.Type == "" {
res.Type = b.Type
}
for k, v := range b.Labels {
res.Labels[k] = v
}
// Labels from resource a overwrite labels from resource b.
for k, v := range a.Labels {
res.Labels[k] = v
}
return res
}
// Detector attempts to detect resource information.
// If the detector cannot find resource information, the returned resource is nil but no
// error is returned.
// An error is only returned on unexpected failures.
type Detector func(context.Context) (*Resource, error)
// MultiDetector returns a Detector that calls all input detectors in order and
// merges each result with the previous one. In case a type of label key is already set,
// the first set value is takes precedence.
// It returns on the first error that a sub-detector encounters.
func MultiDetector(detectors ...Detector) Detector {
return func(ctx context.Context) (*Resource, error) {
return detectAll(ctx, detectors...)
}
}
// detectall calls all input detectors sequentially an merges each result with the previous one.
// It returns on the first error that a sub-detector encounters.
func detectAll(ctx context.Context, detectors ...Detector) (*Resource, error) {
var res *Resource
for _, d := range detectors {
r, err := d(ctx)
if err != nil {
return nil, err
}
res = merge(res, r)
}
return res, nil
}

View File

@@ -0,0 +1,231 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.
--------------------------------------------------------------------------------
Copyright 2009 The Go Authors.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,3 @@
# OpenTelemetry/OpenCensus Bridge
[![PkgGoDev](https://pkg.go.dev/badge/go.opentelemetry.io/otel/bridge/opencensus)](https://pkg.go.dev/go.opentelemetry.io/otel/bridge/opencensus)

View File

@@ -0,0 +1,54 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package opencensus // import "go.opentelemetry.io/otel/bridge/opencensus"
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
)
const scopeName = "go.opentelemetry.io/otel/bridge/opencensus"
// newTraceConfig returns a config configured with options.
func newTraceConfig(options []TraceOption) traceConfig {
conf := traceConfig{tp: otel.GetTracerProvider()}
for _, o := range options {
conf = o.apply(conf)
}
return conf
}
type traceConfig struct {
tp trace.TracerProvider
}
// TraceOption applies a configuration option value to an OpenCensus bridge
// Tracer.
type TraceOption interface {
apply(traceConfig) traceConfig
}
// traceOptionFunc applies a set of options to a config.
type traceOptionFunc func(traceConfig) traceConfig
// apply returns a config with option(s) applied.
func (o traceOptionFunc) apply(conf traceConfig) traceConfig {
return o(conf)
}
// WithTracerProvider specifies a tracer provider to use for creating a tracer.
func WithTracerProvider(tp trace.TracerProvider) TraceOption {
return traceOptionFunc(func(conf traceConfig) traceConfig {
conf.tp = tp
return conf
})
}
type metricConfig struct{}
// MetricOption applies a configuration option value to an OpenCensus bridge
// MetricProducer.
type MetricOption interface {
apply(metricConfig) metricConfig
}

View File

@@ -0,0 +1,48 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package opencensus provides a migration bridge from OpenCensus to
// OpenTelemetry for metrics and traces. The bridge incorporates metrics and
// traces from OpenCensus into the OpenTelemetry SDK, combining them with
// metrics and traces from OpenTelemetry instrumentation.
//
// # Migration Guide
//
// For most applications, it would be difficult to migrate an application
// from OpenCensus to OpenTelemetry all-at-once. Libraries used by the
// application may still be using OpenCensus, and the application itself may
// have many lines of instrumentation.
//
// Bridges help in this situation by allowing your application to have "mixed"
// instrumentation, while incorporating all instrumentation into a single
// export path. To migrate with bridges, a user would:
//
// 1. Configure the OpenTelemetry SDK for metrics and traces, with the OpenTelemetry exporters matching to your current OpenCensus exporters.
// 2. Install this OpenCensus bridge, which sends OpenCensus telemetry to your new OpenTelemetry exporters.
// 3. Over time, migrate your instrumentation from OpenCensus to OpenTelemetry.
// 4. Once all instrumentation is migrated, remove the OpenCensus bridge.
//
// With this approach, you can migrate your telemetry, including in dependent
// libraries over time without disruption.
//
// # Warnings
//
// 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, such as the
// telemetry exporter itself.
//
// # Limitations
//
// There are known limitations to the trace bridge:
//
// - 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, and An error will be sent to the OpenTelemetry ErrorHandler.
//
// There are known limitations to the metric bridge:
// - GaugeDistribution-typed metrics are dropped
// - Histogram's SumOfSquaredDeviation field is dropped
package opencensus // import "go.opentelemetry.io/otel/bridge/opencensus"

View File

@@ -0,0 +1,11 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package internal provides internal functionality for the opencensus package.
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

View File

@@ -0,0 +1,48 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package oc2otel provides conversion from OpenCensus to OpenTelemetry.
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 AttributesFromMap(attr map[string]any) []attribute.KeyValue {
otelAttr := make([]attribute.KeyValue, 0, len(attr))
for k, v := range attr {
otelAttr = append(otelAttr, attribute.KeyValue{
Key: attribute.Key(k),
Value: AttributeValue(v),
})
}
return otelAttr
}
func AttributeValue(ocval any) 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")
}
}

View File

@@ -0,0 +1,34 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package oc2otel // import "go.opentelemetry.io/otel/bridge/opencensus/internal/oc2otel"
import (
"slices"
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
}
entries := slices.Clone(sc.Tracestate.Entries())
slices.Reverse(entries)
tsOtel := trace.TraceState{}
for _, entry := range entries {
tsOtel, _ = tsOtel.Insert(entry.Key, entry.Value)
}
return trace.NewSpanContext(trace.SpanContextConfig{
TraceID: trace.TraceID(sc.TraceID),
SpanID: trace.SpanID(sc.SpanID),
TraceFlags: traceFlags,
TraceState: tsOtel,
})
}

View File

@@ -0,0 +1,35 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
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
}

View File

@@ -0,0 +1,425 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package internal provides internal functionality for the opencensus package.
package internal // import "go.opentelemetry.io/otel/bridge/opencensus/internal/ocmetric"
import (
"cmp"
"errors"
"fmt"
"math"
"reflect"
"slices"
"strconv"
ocmetricdata "go.opencensus.io/metric/metricdata"
octrace "go.opencensus.io/trace"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
)
var (
errAggregationType = errors.New("unsupported OpenCensus aggregation type")
errMismatchedValueTypes = errors.New("wrong value type for data point")
errNegativeCount = errors.New("distribution or summary count is negative")
errNegativeBucketCount = errors.New("distribution bucket count is negative")
errMismatchedAttributeKeyValues = errors.New("mismatched number of attribute keys and values")
errInvalidExemplarSpanContext = errors.New(
"span context exemplar attachment does not contain an OpenCensus SpanContext",
)
)
// ConvertMetrics converts metric data from OpenCensus to OpenTelemetry.
func ConvertMetrics(ocmetrics []*ocmetricdata.Metric) ([]metricdata.Metrics, error) {
otelMetrics := make([]metricdata.Metrics, 0, len(ocmetrics))
var err error
for _, ocm := range ocmetrics {
if ocm == nil {
continue
}
agg, aggregationErr := convertAggregation(ocm)
if aggregationErr != nil {
err = errors.Join(err, fmt.Errorf("error converting metric %v: %w", ocm.Descriptor.Name, aggregationErr))
continue
}
otelMetrics = append(otelMetrics, metricdata.Metrics{
Name: ocm.Descriptor.Name,
Description: ocm.Descriptor.Description,
Unit: string(ocm.Descriptor.Unit),
Data: agg,
})
}
if err != nil {
return otelMetrics, fmt.Errorf("error converting from OpenCensus to OpenTelemetry: %w", err)
}
return otelMetrics, nil
}
// convertAggregation produces an aggregation based on the OpenCensus Metric.
func convertAggregation(metric *ocmetricdata.Metric) (metricdata.Aggregation, error) {
labelKeys := metric.Descriptor.LabelKeys
switch metric.Descriptor.Type {
case ocmetricdata.TypeGaugeInt64:
return convertGauge[int64](labelKeys, metric.TimeSeries)
case ocmetricdata.TypeGaugeFloat64:
return convertGauge[float64](labelKeys, metric.TimeSeries)
case ocmetricdata.TypeCumulativeInt64:
return convertSum[int64](labelKeys, metric.TimeSeries)
case ocmetricdata.TypeCumulativeFloat64:
return convertSum[float64](labelKeys, metric.TimeSeries)
case ocmetricdata.TypeCumulativeDistribution:
return convertHistogram(labelKeys, metric.TimeSeries)
case ocmetricdata.TypeSummary:
return convertSummary(labelKeys, metric.TimeSeries)
}
return nil, fmt.Errorf("%w: %q", errAggregationType, metric.Descriptor.Type)
}
// convertGauge converts an OpenCensus gauge to an OpenTelemetry gauge aggregation.
func convertGauge[N int64 | float64](
labelKeys []ocmetricdata.LabelKey,
ts []*ocmetricdata.TimeSeries,
) (metricdata.Gauge[N], error) {
points, err := convertNumberDataPoints[N](labelKeys, ts)
return metricdata.Gauge[N]{DataPoints: points}, err
}
// convertSum converts an OpenCensus cumulative to an OpenTelemetry sum aggregation.
func convertSum[N int64 | float64](
labelKeys []ocmetricdata.LabelKey,
ts []*ocmetricdata.TimeSeries,
) (metricdata.Sum[N], error) {
points, err := convertNumberDataPoints[N](labelKeys, ts)
// OpenCensus sums are always Cumulative
return metricdata.Sum[N]{DataPoints: points, Temporality: metricdata.CumulativeTemporality, IsMonotonic: true}, err
}
// convertNumberDataPoints converts OpenCensus TimeSeries to OpenTelemetry DataPoints.
func convertNumberDataPoints[N int64 | float64](
labelKeys []ocmetricdata.LabelKey,
ts []*ocmetricdata.TimeSeries,
) ([]metricdata.DataPoint[N], error) {
var points []metricdata.DataPoint[N]
var err error
for _, t := range ts {
attrs, attrsErr := convertAttrs(labelKeys, t.LabelValues)
if attrsErr != nil {
err = errors.Join(err, attrsErr)
continue
}
for _, p := range t.Points {
v, ok := p.Value.(N)
if !ok {
err = errors.Join(err, fmt.Errorf("%w: %q", errMismatchedValueTypes, p.Value))
continue
}
points = append(points, metricdata.DataPoint[N]{
Attributes: attrs,
StartTime: t.StartTime,
Time: p.Time,
Value: v,
})
}
}
return points, err
}
// convertHistogram converts OpenCensus Distribution timeseries to an
// OpenTelemetry Histogram aggregation.
func convertHistogram(
labelKeys []ocmetricdata.LabelKey,
ts []*ocmetricdata.TimeSeries,
) (metricdata.Histogram[float64], error) {
points := make([]metricdata.HistogramDataPoint[float64], 0, len(ts))
var err error
for _, t := range ts {
attrs, attrsErr := convertAttrs(labelKeys, t.LabelValues)
if attrsErr != nil {
err = errors.Join(err, attrsErr)
continue
}
for _, p := range t.Points {
dist, ok := p.Value.(*ocmetricdata.Distribution)
if !ok {
err = errors.Join(err, fmt.Errorf("%w: %d", errMismatchedValueTypes, p.Value))
continue
}
bucketCounts, exemplars, bucketErr := convertBuckets(dist.Buckets)
if bucketErr != nil {
err = errors.Join(err, bucketErr)
continue
}
if dist.Count < 0 {
err = errors.Join(err, fmt.Errorf("%w: %d", errNegativeCount, dist.Count))
continue
}
points = append(points, metricdata.HistogramDataPoint[float64]{
Attributes: attrs,
StartTime: t.StartTime,
Time: p.Time,
Count: uint64(max(0, dist.Count)), // nolint:gosec // A count should never be negative.
Sum: dist.Sum,
Bounds: dist.BucketOptions.Bounds,
BucketCounts: bucketCounts,
Exemplars: exemplars,
})
}
}
return metricdata.Histogram[float64]{DataPoints: points, Temporality: metricdata.CumulativeTemporality}, err
}
// convertBuckets converts from OpenCensus bucket counts to slice of uint64,
// and converts OpenCensus exemplars to OpenTelemetry exemplars.
func convertBuckets(buckets []ocmetricdata.Bucket) ([]uint64, []metricdata.Exemplar[float64], error) {
bucketCounts := make([]uint64, len(buckets))
exemplars := []metricdata.Exemplar[float64]{}
var err error
for i, bucket := range buckets {
if bucket.Count < 0 {
err = errors.Join(err, fmt.Errorf("%w: %q", errNegativeBucketCount, bucket.Count))
continue
}
bucketCounts[i] = uint64(max(0, bucket.Count)) // nolint:gosec // A count should never be negative.
if bucket.Exemplar != nil {
exemplar, exemplarErr := convertExemplar(bucket.Exemplar)
if exemplarErr != nil {
err = errors.Join(err, exemplarErr)
continue
}
exemplars = append(exemplars, exemplar)
}
}
return bucketCounts, exemplars, err
}
// convertExemplar converts an OpenCensus exemplar to an OpenTelemetry exemplar.
func convertExemplar(ocExemplar *ocmetricdata.Exemplar) (metricdata.Exemplar[float64], error) {
exemplar := metricdata.Exemplar[float64]{
Value: ocExemplar.Value,
Time: ocExemplar.Timestamp,
}
var err error
for k, v := range ocExemplar.Attachments {
switch k {
case ocmetricdata.AttachmentKeySpanContext:
sc, ok := v.(octrace.SpanContext)
if !ok {
err = errors.Join(err, fmt.Errorf("%w; type: %v", errInvalidExemplarSpanContext, reflect.TypeOf(v)))
continue
}
exemplar.SpanID = sc.SpanID[:]
exemplar.TraceID = sc.TraceID[:]
default:
exemplar.FilteredAttributes = append(exemplar.FilteredAttributes, convertKV(k, v))
}
}
slices.SortFunc(exemplar.FilteredAttributes, func(a, b attribute.KeyValue) int {
return cmp.Compare(a.Key, b.Key)
})
return exemplar, err
}
// convertKV converts an OpenCensus Attachment to an OpenTelemetry KeyValue.
func convertKV(key string, value any) attribute.KeyValue {
switch typedVal := value.(type) {
case bool:
return attribute.Bool(key, typedVal)
case int:
return attribute.Int(key, typedVal)
case int8:
return attribute.Int(key, int(typedVal))
case int16:
return attribute.Int(key, int(typedVal))
case int32:
return attribute.Int(key, int(typedVal))
case int64:
return attribute.Int64(key, typedVal)
case uint:
return uintKV(key, typedVal)
case uint8:
return uintKV(key, uint(typedVal))
case uint16:
return uintKV(key, uint(typedVal))
case uint32:
return uintKV(key, uint(typedVal))
case uintptr:
return uint64KV(key, uint64(typedVal))
case uint64:
return uint64KV(key, typedVal)
case float32:
return attribute.Float64(key, float64(typedVal))
case float64:
return attribute.Float64(key, typedVal)
case complex64:
return attribute.String(key, complexToString(typedVal))
case complex128:
return attribute.String(key, complexToString(typedVal))
case string:
return attribute.String(key, typedVal)
case []bool:
return attribute.BoolSlice(key, typedVal)
case []int:
return attribute.IntSlice(key, typedVal)
case []int8:
return intSliceKV(key, typedVal)
case []int16:
return intSliceKV(key, typedVal)
case []int32:
return intSliceKV(key, typedVal)
case []int64:
return attribute.Int64Slice(key, typedVal)
case []uint:
return uintSliceKV(key, typedVal)
case []uint8:
return uintSliceKV(key, typedVal)
case []uint16:
return uintSliceKV(key, typedVal)
case []uint32:
return uintSliceKV(key, typedVal)
case []uintptr:
return uintSliceKV(key, typedVal)
case []uint64:
return uintSliceKV(key, typedVal)
case []float32:
floatSlice := make([]float64, len(typedVal))
for i := range typedVal {
floatSlice[i] = float64(typedVal[i])
}
return attribute.Float64Slice(key, floatSlice)
case []float64:
return attribute.Float64Slice(key, typedVal)
case []complex64:
return complexSliceKV(key, typedVal)
case []complex128:
return complexSliceKV(key, typedVal)
case []string:
return attribute.StringSlice(key, typedVal)
case fmt.Stringer:
return attribute.Stringer(key, typedVal)
default:
return attribute.String(key, fmt.Sprintf("unhandled attribute value: %+v", value))
}
}
func intSliceKV[N int8 | int16 | int32](key string, val []N) attribute.KeyValue {
intSlice := make([]int, len(val))
for i := range val {
intSlice[i] = int(val[i])
}
return attribute.IntSlice(key, intSlice)
}
func uintKV(key string, val uint) attribute.KeyValue {
if val > uint(math.MaxInt) {
return attribute.String(key, strconv.FormatUint(uint64(val), 10))
}
return attribute.Int(key, int(val)) // nolint: gosec // Overflow checked above.
}
func uintSliceKV[N uint | uint8 | uint16 | uint32 | uint64 | uintptr](key string, val []N) attribute.KeyValue {
strSlice := make([]string, len(val))
for i := range val {
strSlice[i] = strconv.FormatUint(uint64(val[i]), 10)
}
return attribute.StringSlice(key, strSlice)
}
func uint64KV(key string, val uint64) attribute.KeyValue {
const maxInt64 = ^uint64(0) >> 1
if val > maxInt64 {
return attribute.String(key, strconv.FormatUint(val, 10))
}
return attribute.Int64(key, int64(val)) // nolint: gosec // Overflow checked above.
}
func complexSliceKV[N complex64 | complex128](key string, val []N) attribute.KeyValue {
strSlice := make([]string, len(val))
for i := range val {
strSlice[i] = complexToString(val[i])
}
return attribute.StringSlice(key, strSlice)
}
func complexToString[N complex64 | complex128](val N) string {
return strconv.FormatComplex(complex128(val), 'f', -1, 64)
}
// convertSummary converts OpenCensus Summary timeseries to an
// OpenTelemetry Summary.
func convertSummary(labelKeys []ocmetricdata.LabelKey, ts []*ocmetricdata.TimeSeries) (metricdata.Summary, error) {
points := make([]metricdata.SummaryDataPoint, 0, len(ts))
var err error
for _, t := range ts {
attrs, attrErr := convertAttrs(labelKeys, t.LabelValues)
if attrErr != nil {
err = errors.Join(err, attrErr)
continue
}
for _, p := range t.Points {
summary, ok := p.Value.(*ocmetricdata.Summary)
if !ok {
err = errors.Join(err, fmt.Errorf("%w: %d", errMismatchedValueTypes, p.Value))
continue
}
if summary.Count < 0 {
err = errors.Join(err, fmt.Errorf("%w: %d", errNegativeCount, summary.Count))
continue
}
point := metricdata.SummaryDataPoint{
Attributes: attrs,
StartTime: t.StartTime,
Time: p.Time,
Count: uint64(max(0, summary.Count)), // nolint:gosec // A count should never be negative.
QuantileValues: convertQuantiles(summary.Snapshot),
Sum: summary.Sum,
}
points = append(points, point)
}
}
return metricdata.Summary{DataPoints: points}, err
}
// convertQuantiles converts an OpenCensus summary snapshot to
// OpenTelemetry quantiles.
func convertQuantiles(snapshot ocmetricdata.Snapshot) []metricdata.QuantileValue {
quantileValues := make([]metricdata.QuantileValue, 0, len(snapshot.Percentiles))
for quantile, value := range snapshot.Percentiles {
quantileValues = append(quantileValues, metricdata.QuantileValue{
// OpenCensus quantiles are range (0-100.0], but OpenTelemetry
// quantiles are range [0.0, 1.0].
Quantile: quantile / 100.0,
Value: value,
})
}
slices.SortFunc(quantileValues, func(a, b metricdata.QuantileValue) int {
return cmp.Compare(a.Quantile, b.Quantile)
})
return quantileValues
}
// convertAttrs converts from OpenCensus attribute keys and values to an
// OpenTelemetry attribute Set.
func convertAttrs(keys []ocmetricdata.LabelKey, values []ocmetricdata.LabelValue) (attribute.Set, error) {
if len(keys) != len(values) {
return attribute.NewSet(), fmt.Errorf(
"%w: keys(%q) values(%q)",
errMismatchedAttributeKeyValues,
len(keys),
len(values),
)
}
attrs := []attribute.KeyValue{}
for i, lv := range values {
if !lv.Present {
continue
}
attrs = append(attrs, attribute.KeyValue{
Key: attribute.Key(keys[i].Key),
Value: attribute.StringValue(lv.Value),
})
}
return attribute.NewSet(attrs...), nil
}

View File

@@ -0,0 +1,34 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package otel2oc provides conversions from OpenTelemetry to OpenCensus.
package otel2oc // import "go.opentelemetry.io/otel/bridge/opencensus/internal/otel2oc"
import (
octrace "go.opencensus.io/trace"
"go.opencensus.io/trace/tracestate"
"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
}
entries := make([]tracestate.Entry, 0, sc.TraceState().Len())
sc.TraceState().Walk(func(key, value string) bool {
entries = append(entries, tracestate.Entry{Key: key, Value: value})
return true
})
tsOc, _ := tracestate.New(nil, entries...)
return octrace.SpanContext{
TraceID: octrace.TraceID(sc.TraceID()),
SpanID: octrace.SpanID(sc.SpanID()),
TraceOptions: to,
Tracestate: tsOc,
}
}

View File

@@ -0,0 +1,132 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
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 reports whether events are being recorded for this span.
func (s *Span) IsRecordingEvents() bool {
return s.otelSpan.IsRecording()
}
// End ends this 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(max(0, status.Code)), status.Message) // nolint:gosec // Overflow checked.
}
// 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 ...any) {
s.Annotate(attributes, fmt.Sprintf(format, a...))
}
// AddMessageSendEvent adds a message send event to this span.
func (s *Span) AddMessageSendEvent(_, 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(_, 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.
// This drops the OpenCensus LinkType because there is no such concept in OpenTelemetry.
func (s *Span) AddLink(l octrace.Link) {
s.otelSpan.AddLink(trace.Link{
SpanContext: trace.NewSpanContext(trace.SpanContextConfig{
TraceID: trace.TraceID(l.TraceID),
SpanID: trace.SpanID(l.SpanID),
// We don't know if this was sampled or not.
// Mark it as sampled, since sampled means
// "the caller may have recorded trace data":
// https://www.w3.org/TR/trace-context/#sampled-flag
TraceFlags: trace.FlagsSampled,
}),
Attributes: oc2otel.AttributesFromMap(l.Attributes),
})
}
// String prints a string representation of this span.
func (s *Span) String() string {
return "span " + s.otelSpan.SpanContext().SpanID().String()
}

View File

@@ -0,0 +1,69 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
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 zeroed 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 (*Tracer) FromContext(ctx context.Context) *octrace.Span {
return NewSpan(trace.SpanFromContext(ctx))
}
// NewContext returns a new context with the given Span attached.
func (*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
}

View File

@@ -0,0 +1,53 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package opencensus // import "go.opentelemetry.io/otel/bridge/opencensus"
import (
"context"
ocmetricdata "go.opencensus.io/metric/metricdata"
"go.opencensus.io/metric/metricproducer"
internal "go.opentelemetry.io/otel/bridge/opencensus/internal/ocmetric"
"go.opentelemetry.io/otel/sdk/instrumentation"
"go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
)
// MetricProducer implements the [go.opentelemetry.io/otel/sdk/metric.Producer] to provide metrics
// from OpenCensus to the OpenTelemetry SDK.
type MetricProducer struct {
manager *metricproducer.Manager
}
// NewMetricProducer returns a metric.Producer that fetches metrics from
// OpenCensus.
func NewMetricProducer(...MetricOption) *MetricProducer {
return &MetricProducer{
manager: metricproducer.GlobalManager(),
}
}
var _ metric.Producer = (*MetricProducer)(nil)
// Produce fetches metrics from the OpenCensus manager,
// translates them to OpenTelemetry's data model, and returns them.
func (p *MetricProducer) Produce(context.Context) ([]metricdata.ScopeMetrics, error) {
producers := p.manager.GetAll()
data := []*ocmetricdata.Metric{}
for _, ocProducer := range producers {
data = append(data, ocProducer.Read()...)
}
otelmetrics, err := internal.ConvertMetrics(data)
if len(otelmetrics) == 0 {
return nil, err
}
return []metricdata.ScopeMetrics{{
Scope: instrumentation.Scope{
Name: scopeName,
Version: Version(),
},
Metrics: otelmetrics,
}}, err
}

View File

@@ -0,0 +1,40 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
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"
)
// InstallTraceBridge installs the OpenCensus trace bridge, which overwrites
// the global OpenCensus tracer implementation. Once the bridge is installed,
// spans recorded using OpenCensus are redirected to the OpenTelemetry SDK.
func InstallTraceBridge(opts ...TraceOption) {
octrace.DefaultTracer = newTraceBridge(opts)
}
func newTraceBridge(opts []TraceOption) octrace.Tracer {
cfg := newTraceConfig(opts)
return internal.NewTracer(
cfg.tp.Tracer(scopeName, trace.WithInstrumentationVersion(Version())),
)
}
// 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)
}

View File

@@ -0,0 +1,9 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package opencensus // import "go.opentelemetry.io/otel/bridge/opencensus"
// Version is the current release version of the opencensus bridge.
func Version() string {
return "1.38.0"
}

10
vendor/modules.txt vendored
View File

@@ -1414,6 +1414,9 @@ go.etcd.io/raft/v3/tracker
## explicit; go 1.13 ## explicit; go 1.13
go.opencensus.io go.opencensus.io
go.opencensus.io/internal go.opencensus.io/internal
go.opencensus.io/metric/metricdata
go.opencensus.io/metric/metricproducer
go.opencensus.io/resource
go.opencensus.io/trace go.opencensus.io/trace
go.opencensus.io/trace/internal go.opencensus.io/trace/internal
go.opencensus.io/trace/tracestate go.opencensus.io/trace/tracestate
@@ -1455,6 +1458,13 @@ go.opentelemetry.io/otel/semconv/v1.26.0
go.opentelemetry.io/otel/semconv/v1.30.0 go.opentelemetry.io/otel/semconv/v1.30.0
go.opentelemetry.io/otel/semconv/v1.37.0 go.opentelemetry.io/otel/semconv/v1.37.0
go.opentelemetry.io/otel/semconv/v1.37.0/otelconv go.opentelemetry.io/otel/semconv/v1.37.0/otelconv
# go.opentelemetry.io/otel/bridge/opencensus v1.38.0
## explicit; go 1.23.0
go.opentelemetry.io/otel/bridge/opencensus
go.opentelemetry.io/otel/bridge/opencensus/internal
go.opentelemetry.io/otel/bridge/opencensus/internal/oc2otel
go.opentelemetry.io/otel/bridge/opencensus/internal/ocmetric
go.opentelemetry.io/otel/bridge/opencensus/internal/otel2oc
# go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 # go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0
## explicit; go 1.23.0 ## explicit; go 1.23.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc