add logic for otel
This commit is contained in:
7
go.mod
7
go.mod
@@ -9,7 +9,7 @@ require (
|
||||
github.com/docker/spdystream v0.0.0-20170912183627-bc6354cbbc29 // indirect
|
||||
github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f // indirect
|
||||
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 // indirect
|
||||
github.com/google/go-cmp v0.5.2
|
||||
github.com/google/go-cmp v0.5.7
|
||||
github.com/gorilla/mux v1.7.3
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
@@ -18,8 +18,11 @@ require (
|
||||
github.com/spf13/cobra v1.0.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
go.opencensus.io v0.22.2
|
||||
go.opentelemetry.io/otel v1.7.0
|
||||
go.opentelemetry.io/otel/sdk v1.7.0
|
||||
go.opentelemetry.io/otel/trace v1.7.0
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
|
||||
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd
|
||||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7
|
||||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e
|
||||
google.golang.org/api v0.15.1 // indirect
|
||||
gotest.tools v2.2.0+incompatible
|
||||
|
||||
22
go.sum
22
go.sum
@@ -127,8 +127,12 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
||||
github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
|
||||
github.com/go-logr/logr v0.3.0 h1:q4c+kbcR0d5rSurhBR8dIgieOaYpXtsdTYfx22Cu6rs=
|
||||
github.com/go-logr/logr v0.3.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
|
||||
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/go-logr/zapr v0.2.0 h1:v6Ji8yBW77pva6NkJKQdHLAJKrIJKRHz0RXwPqCHSR4=
|
||||
github.com/go-logr/zapr v0.2.0/go.mod h1:qhKdvif7YF5GI9NWEpyxTSSBdGmzkNguibrdCNVPunU=
|
||||
github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
|
||||
@@ -214,8 +218,9 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a
|
||||
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.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
|
||||
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
@@ -399,8 +404,9 @@ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoH
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
@@ -424,6 +430,12 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opentelemetry.io/otel v1.7.0 h1:Z2lA3Tdch0iDcrhJXDIlC94XE+bxok1F9B+4Lz/lGsM=
|
||||
go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk=
|
||||
go.opentelemetry.io/otel/sdk v1.7.0 h1:4OmStpcKVOfvDOgCt7UriAPtKolwIhxpnSNI/yK+1B0=
|
||||
go.opentelemetry.io/otel/sdk v1.7.0/go.mod h1:uTEOTwaqIVuTGiJN7ii13Ibp75wJmYUDe374q6cZwUU=
|
||||
go.opentelemetry.io/otel/trace v1.7.0 h1:O37Iogk1lEkMRXewVtZ1BBTVn5JEp8GrJvP92bJqC6o=
|
||||
go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
|
||||
@@ -546,8 +558,9 @@ golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY=
|
||||
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/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.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
@@ -669,6 +682,7 @@ gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||
|
||||
276
trace/opentelemetry/opentelemetry.go
Normal file
276
trace/opentelemetry/opentelemetry.go
Normal file
@@ -0,0 +1,276 @@
|
||||
// Copyright © 2022 The virtual-kubelet 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 openTelemetry implements a github.com/virtual-kubelet/virtual-kubelet/trace.Tracer
|
||||
// using openTelemetry as a backend.
|
||||
//
|
||||
// Use this by setting `trace.T = Adapter{}`
|
||||
//
|
||||
// For customizing trace provider used in Adapter, set trace provider by
|
||||
//`otel.SetTracerProvider(*sdktrace.TracerProvider)`. Examples of customize are setting service name,
|
||||
// use your own exporter (e.g. jaeger, otlp, prometheus, zipkin, and stdout) etc. Do not forget
|
||||
// to call TracerProvider.Shutdown() when you create your TracerProvider to avoid memory leak.
|
||||
package openTelemetry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
|
||||
"github.com/virtual-kubelet/virtual-kubelet/log"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/trace"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
ot "go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
type logLevel string
|
||||
|
||||
const (
|
||||
lDebug logLevel = "DEBUG"
|
||||
lInfo logLevel = "INFO"
|
||||
lWarn logLevel = "WARN"
|
||||
lErr logLevel = "ERROR"
|
||||
lFatal logLevel = "FATAL"
|
||||
)
|
||||
|
||||
func (l logLevel) string() string {
|
||||
return string(l)
|
||||
}
|
||||
|
||||
// Adapter implements the trace.Tracer interface for openTelemetry
|
||||
type Adapter struct{}
|
||||
|
||||
// StartSpan creates a new span from openTelemetry using the given name.
|
||||
func (Adapter) StartSpan(ctx context.Context, name string) (context.Context, trace.Span) {
|
||||
ctx, ots := otel.Tracer(name).Start(ctx, name)
|
||||
l := log.G(ctx).WithField("method", name)
|
||||
|
||||
s := &span{s: ots, l: l}
|
||||
ctx = log.WithLogger(ctx, s.Logger())
|
||||
|
||||
return ctx, s
|
||||
}
|
||||
|
||||
type span struct {
|
||||
mu sync.Mutex
|
||||
s ot.Span
|
||||
l log.Logger
|
||||
}
|
||||
|
||||
func (s *span) End() {
|
||||
s.s.End()
|
||||
}
|
||||
|
||||
func (s *span) SetStatus(err error) {
|
||||
if !s.s.IsRecording() {
|
||||
return
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
s.s.SetStatus(codes.Ok, "")
|
||||
} else {
|
||||
s.s.SetStatus(codes.Error, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (s *span) WithField(ctx context.Context, key string, val interface{}) context.Context {
|
||||
s.mu.Lock()
|
||||
s.l = s.l.WithField(key, val)
|
||||
ctx = log.WithLogger(ctx, &logger{s: s.s, l: s.l})
|
||||
s.mu.Unlock()
|
||||
|
||||
if s.s.IsRecording() {
|
||||
s.s.SetAttributes(makeAttribute(key, val))
|
||||
}
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (s *span) WithFields(ctx context.Context, f log.Fields) context.Context {
|
||||
s.mu.Lock()
|
||||
s.l = s.l.WithFields(f)
|
||||
ctx = log.WithLogger(ctx, &logger{s: s.s, l: s.l})
|
||||
s.mu.Unlock()
|
||||
|
||||
if s.s.IsRecording() {
|
||||
attrs := make([]attribute.KeyValue, 0, len(f))
|
||||
for k, v := range f {
|
||||
attrs = append(attrs, makeAttribute(k, v))
|
||||
}
|
||||
s.s.SetAttributes(attrs...)
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (s *span) Logger() log.Logger {
|
||||
return &logger{s: s.s, l: s.l}
|
||||
}
|
||||
|
||||
type logger struct {
|
||||
s ot.Span
|
||||
l log.Logger
|
||||
a []attribute.KeyValue
|
||||
}
|
||||
|
||||
func (l *logger) Debug(args ...interface{}) {
|
||||
l.logEvent(lDebug, args...)
|
||||
}
|
||||
|
||||
func (l *logger) Debugf(f string, args ...interface{}) {
|
||||
l.logEventf(lDebug, f, args...)
|
||||
}
|
||||
|
||||
func (l *logger) Info(args ...interface{}) {
|
||||
l.logEvent(lInfo, args...)
|
||||
}
|
||||
|
||||
func (l *logger) Infof(f string, args ...interface{}) {
|
||||
l.logEventf(lInfo, f, args...)
|
||||
}
|
||||
|
||||
func (l *logger) Warn(args ...interface{}) {
|
||||
l.logEvent(lWarn, args...)
|
||||
}
|
||||
|
||||
func (l *logger) Warnf(f string, args ...interface{}) {
|
||||
l.logEventf(lWarn, f, args...)
|
||||
}
|
||||
|
||||
func (l *logger) Error(args ...interface{}) {
|
||||
l.logEvent(lErr, args...)
|
||||
}
|
||||
|
||||
func (l *logger) Errorf(f string, args ...interface{}) {
|
||||
l.logEventf(lErr, f, args...)
|
||||
}
|
||||
|
||||
func (l *logger) Fatal(args ...interface{}) {
|
||||
l.logEvent(lFatal, args...)
|
||||
}
|
||||
|
||||
func (l *logger) Fatalf(f string, args ...interface{}) {
|
||||
l.logEventf(lFatal, f, args...)
|
||||
}
|
||||
|
||||
func (l *logger) logEvent(ll logLevel, args ...interface{}) {
|
||||
msg := fmt.Sprint(args...)
|
||||
switch ll {
|
||||
case lDebug:
|
||||
l.l.Debug(msg)
|
||||
case lInfo:
|
||||
l.l.Info(msg)
|
||||
case lWarn:
|
||||
l.l.Warn(msg)
|
||||
case lErr:
|
||||
l.l.Error(msg)
|
||||
case lFatal:
|
||||
l.l.Fatal(msg)
|
||||
}
|
||||
|
||||
if !l.s.IsRecording() {
|
||||
return
|
||||
}
|
||||
|
||||
//TODO Add log event to span once it is available.
|
||||
// The Logs signal is not supported in opentelemetry-go yet.
|
||||
// ref: https://github.com/open-telemetry/opentelemetry-go
|
||||
}
|
||||
|
||||
func (l *logger) logEventf(ll logLevel, f string, args ...interface{}) {
|
||||
switch ll {
|
||||
case lDebug:
|
||||
l.l.Debugf(f, args...)
|
||||
case lInfo:
|
||||
l.l.Infof(f, args...)
|
||||
case lWarn:
|
||||
l.l.Warnf(f, args...)
|
||||
case lErr:
|
||||
l.l.Errorf(f, args...)
|
||||
case lFatal:
|
||||
l.l.Fatalf(f, args...)
|
||||
}
|
||||
|
||||
if !l.s.IsRecording() {
|
||||
return
|
||||
}
|
||||
|
||||
//TODO Add log event to span once it is available.
|
||||
// The Logs signal is not supported in opentelemetry-go yet.
|
||||
// ref: https://github.com/open-telemetry/opentelemetry-go
|
||||
}
|
||||
|
||||
func (l *logger) WithError(err error) log.Logger {
|
||||
if err == nil {
|
||||
return l
|
||||
}
|
||||
return l.WithField("err", err)
|
||||
}
|
||||
|
||||
func (l *logger) WithField(k string, value interface{}) log.Logger {
|
||||
var attrs []attribute.KeyValue
|
||||
if l.s.IsRecording() {
|
||||
attrs = make([]attribute.KeyValue, len(l.a)+1)
|
||||
copy(attrs, l.a)
|
||||
attrs[len(attrs)-1] = makeAttribute(k, value)
|
||||
}
|
||||
l.a = attrs
|
||||
return &logger{s: l.s, a: attrs, l: l.l.WithField(k, value)}
|
||||
}
|
||||
|
||||
func (l *logger) WithFields(fields log.Fields) log.Logger {
|
||||
var attrs []attribute.KeyValue
|
||||
if l.s.IsRecording() {
|
||||
attrs = make([]attribute.KeyValue, len(l.a), len(l.a)+len(fields))
|
||||
copy(attrs, l.a)
|
||||
for k, v := range fields {
|
||||
attrs = append(attrs, makeAttribute(k, v))
|
||||
}
|
||||
}
|
||||
l.a = attrs
|
||||
return &logger{s: l.s, a: attrs, l: l.l.WithFields(fields)}
|
||||
}
|
||||
|
||||
func makeAttribute(key string, val interface{}) (attr attribute.KeyValue) {
|
||||
switch v := val.(type) {
|
||||
case string:
|
||||
return attribute.String(key, v)
|
||||
case []string:
|
||||
return attribute.StringSlice(key, v)
|
||||
case fmt.Stringer:
|
||||
return attribute.Stringer(key, v)
|
||||
case int:
|
||||
return attribute.Int(key, v)
|
||||
case []int:
|
||||
return attribute.IntSlice(key, v)
|
||||
case int64:
|
||||
return attribute.Int64(key, v)
|
||||
case float64:
|
||||
return attribute.Float64(key, v)
|
||||
case []float64:
|
||||
return attribute.Float64Slice(key, v)
|
||||
case []int64:
|
||||
return attribute.Int64Slice(key, v)
|
||||
case bool:
|
||||
return attribute.Bool(key, v)
|
||||
case []bool:
|
||||
return attribute.BoolSlice(key, v)
|
||||
case error:
|
||||
return attribute.String(key, v.Error())
|
||||
default:
|
||||
return attribute.String(key, fmt.Sprintf("%+v", val))
|
||||
}
|
||||
}
|
||||
572
trace/opentelemetry/opentelemetry_test.go
Normal file
572
trace/opentelemetry/opentelemetry_test.go
Normal file
@@ -0,0 +1,572 @@
|
||||
// Copyright © 2022 The virtual-kubelet 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 openTelemetry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/log"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.10.0"
|
||||
"gotest.tools/assert"
|
||||
"gotest.tools/assert/cmp"
|
||||
//"github.com/virtual-kubelet/virtual-kubelet/trace"
|
||||
//octrace "go.opencensus.io/trace"
|
||||
//"gotest.tools/assert"
|
||||
//is "gotest.tools/assert/cmp"
|
||||
)
|
||||
|
||||
func TestStartSpan(t *testing.T) {
|
||||
t.Run("addField", func(t *testing.T) {
|
||||
tearDown, p, _ := setupSuite()
|
||||
defer tearDown(p)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
a := Adapter{}
|
||||
ctx, s := a.StartSpan(ctx, "name")
|
||||
s.End()
|
||||
})
|
||||
}
|
||||
|
||||
func TestSetStatus(t *testing.T) {
|
||||
testCases := []struct {
|
||||
description string
|
||||
spanName string
|
||||
inputStatus error
|
||||
expectedCode codes.Code
|
||||
expectedDescription string
|
||||
}{
|
||||
{
|
||||
description: "error status",
|
||||
spanName: "test",
|
||||
inputStatus: errors.New("fake msg"),
|
||||
expectedCode: codes.Error,
|
||||
expectedDescription: "fake msg",
|
||||
}, {
|
||||
description: "non-error status",
|
||||
spanName: "test",
|
||||
inputStatus: nil,
|
||||
expectedCode: codes.Ok,
|
||||
expectedDescription: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
tearDown, p, e := setupSuite()
|
||||
defer tearDown(p)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
ctx, ots := otel.Tracer(tt.spanName).Start(ctx, tt.spanName)
|
||||
l := log.G(ctx).WithField("method", tt.spanName)
|
||||
|
||||
s := &span{s: ots, l: l}
|
||||
s.SetStatus(tt.inputStatus)
|
||||
assert.Assert(t, s.s.IsRecording())
|
||||
|
||||
s.End()
|
||||
|
||||
assert.Assert(t, !s.s.IsRecording())
|
||||
assert.Assert(t, e.status.Code == tt.expectedCode)
|
||||
assert.Assert(t, e.status.Description == tt.expectedDescription)
|
||||
|
||||
s.SetStatus(tt.inputStatus) // should not be panic even if span is ended.
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithField(t *testing.T) {
|
||||
type field struct {
|
||||
key string
|
||||
value interface{}
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
description string
|
||||
spanName string
|
||||
fields []field
|
||||
expectedAttributes []attribute.KeyValue
|
||||
}{
|
||||
{
|
||||
description: "single field",
|
||||
spanName: "test",
|
||||
fields: []field{{key: "testKey1", value: "value1"}},
|
||||
expectedAttributes: []attribute.KeyValue{{Key: "testKey1", Value: attribute.StringValue("value1")}},
|
||||
}, {
|
||||
description: "multiple unique fields",
|
||||
spanName: "test",
|
||||
fields: []field{{key: "testKey1", value: "value1"}, {key: "testKey2", value: "value2"}},
|
||||
expectedAttributes: []attribute.KeyValue{{Key: "testKey1", Value: attribute.StringValue("value1")}, {Key: "testKey2", Value: attribute.StringValue("value2")}},
|
||||
}, {
|
||||
description: "duplicated fields",
|
||||
spanName: "test",
|
||||
fields: []field{{key: "testKey1", value: "value1"}, {key: "testKey1", value: "value2"}},
|
||||
expectedAttributes: []attribute.KeyValue{{Key: "testKey1", Value: attribute.StringValue("value2")}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
tearDown, p, e := setupSuite()
|
||||
defer tearDown(p)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
ctx, ots := otel.Tracer(tt.spanName).Start(ctx, tt.spanName)
|
||||
l := log.G(ctx).WithField("method", tt.spanName)
|
||||
s := &span{s: ots, l: l}
|
||||
|
||||
for _, f := range tt.fields {
|
||||
ctx = s.WithField(ctx, f.key, f.value)
|
||||
}
|
||||
s.End()
|
||||
|
||||
assert.Assert(t, len(e.attributes) == len(tt.expectedAttributes))
|
||||
for i, a := range tt.expectedAttributes {
|
||||
assert.Assert(t, e.attributes[i].Key == a.Key)
|
||||
assert.Assert(t, e.attributes[i].Value == a.Value)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithFields(t *testing.T) {
|
||||
testCases := []struct {
|
||||
description string
|
||||
spanName string
|
||||
fields log.Fields
|
||||
expectedAttributes []attribute.KeyValue
|
||||
}{
|
||||
{
|
||||
description: "single field",
|
||||
spanName: "test",
|
||||
fields: log.Fields{"testKey1": "value1"},
|
||||
expectedAttributes: []attribute.KeyValue{{Key: "testKey1", Value: attribute.StringValue("value1")}},
|
||||
}, {
|
||||
description: "multiple unique fields",
|
||||
spanName: "test",
|
||||
fields: log.Fields{"testKey1": "value1", "testKey2": "value2"},
|
||||
expectedAttributes: []attribute.KeyValue{{Key: "testKey1", Value: attribute.StringValue("value1")}, {Key: "testKey2", Value: attribute.StringValue("value2")}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
tearDown, p, e := setupSuite()
|
||||
defer tearDown(p)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
ctx, ots := otel.Tracer(tt.spanName).Start(ctx, tt.spanName)
|
||||
l := log.G(ctx).WithField("method", tt.spanName)
|
||||
s := &span{s: ots, l: l}
|
||||
|
||||
ctx = s.WithFields(ctx, tt.fields)
|
||||
s.End()
|
||||
|
||||
assert.Assert(t, len(e.attributes) == len(tt.expectedAttributes))
|
||||
for i, a := range tt.expectedAttributes {
|
||||
assert.Assert(t, e.attributes[i].Key == a.Key)
|
||||
assert.Assert(t, e.attributes[i].Value == a.Value)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLog(t *testing.T) {
|
||||
testCases := []struct {
|
||||
description string
|
||||
spanName string
|
||||
logLevel logLevel
|
||||
fields log.Fields
|
||||
msg string
|
||||
expectedAttributes []attribute.KeyValue
|
||||
}{
|
||||
{
|
||||
description: "debug",
|
||||
spanName: "test",
|
||||
logLevel: lDebug,
|
||||
fields: log.Fields{"testKey1": "value1"},
|
||||
msg: "message",
|
||||
expectedAttributes: []attribute.KeyValue{{Key: "testKey1", Value: attribute.StringValue("value1")}},
|
||||
}, {
|
||||
description: "info",
|
||||
spanName: "test",
|
||||
logLevel: lInfo,
|
||||
fields: log.Fields{"testKey1": "value1"},
|
||||
msg: "message",
|
||||
expectedAttributes: []attribute.KeyValue{{Key: "testKey1", Value: attribute.StringValue("value1")}},
|
||||
}, {
|
||||
description: "warn",
|
||||
spanName: "test",
|
||||
logLevel: lWarn,
|
||||
fields: log.Fields{"testKey1": "value1"},
|
||||
msg: "message",
|
||||
expectedAttributes: []attribute.KeyValue{{Key: "testKey1", Value: attribute.StringValue("value1")}},
|
||||
}, {
|
||||
description: "error",
|
||||
spanName: "test",
|
||||
logLevel: lErr,
|
||||
fields: log.Fields{"testKey1": "value1"},
|
||||
msg: "message",
|
||||
expectedAttributes: []attribute.KeyValue{{Key: "testKey1", Value: attribute.StringValue("value1")}},
|
||||
}, {
|
||||
description: "fatal",
|
||||
spanName: "test",
|
||||
logLevel: lFatal,
|
||||
fields: log.Fields{"testKey1": "value1"},
|
||||
msg: "message",
|
||||
expectedAttributes: []attribute.KeyValue{{Key: "testKey1", Value: attribute.StringValue("value1")}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
tearDown, p, _ := setupSuite()
|
||||
defer tearDown(p)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
ctx, s := otel.Tracer(tt.spanName).Start(ctx, tt.spanName)
|
||||
l := logger{s: s, l: log.G(ctx), a: make([]attribute.KeyValue, 0)}
|
||||
switch tt.logLevel {
|
||||
case lDebug:
|
||||
l.WithFields(tt.fields).Debug(tt.msg)
|
||||
case lInfo:
|
||||
l.WithFields(tt.fields).Info(tt.msg)
|
||||
case lWarn:
|
||||
l.WithFields(tt.fields).Warn(tt.msg)
|
||||
case lErr:
|
||||
l.WithFields(tt.fields).Error(tt.msg)
|
||||
case lFatal:
|
||||
l.WithFields(tt.fields).Fatal(tt.msg)
|
||||
}
|
||||
s.End()
|
||||
|
||||
//TODO add assertion with exporter here once logic for recoding log event is added.
|
||||
assert.Assert(t, len(l.a) == len(tt.expectedAttributes))
|
||||
for i, a := range tt.expectedAttributes {
|
||||
assert.Assert(t, l.a[i].Key == a.Key)
|
||||
assert.Assert(t, l.a[i].Value == a.Value)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogf(t *testing.T) {
|
||||
testCases := []struct {
|
||||
description string
|
||||
spanName string
|
||||
logLevel logLevel
|
||||
msg string
|
||||
fields log.Fields
|
||||
args []interface{}
|
||||
expectedAttributes []attribute.KeyValue
|
||||
}{
|
||||
{
|
||||
description: "debug",
|
||||
spanName: "test",
|
||||
logLevel: lDebug,
|
||||
msg: "k1: %s, k2: %v, k3: %d, k4: %v",
|
||||
fields: map[string]interface{}{"k1": "test", "k2": []string{"test"}, "k3": 1, "k4": []int{1}},
|
||||
expectedAttributes: []attribute.KeyValue{
|
||||
attribute.String("k1", "test"),
|
||||
attribute.StringSlice("k2", []string{"test"}),
|
||||
attribute.Int("k3", 1),
|
||||
attribute.IntSlice("k4", []int{1}),
|
||||
},
|
||||
}, {
|
||||
description: "info",
|
||||
spanName: "test",
|
||||
logLevel: lInfo,
|
||||
msg: "k1: %d, k2: %v, k3: %f, k4: %v",
|
||||
fields: map[string]interface{}{"k1": int64(3), "k2": []int64{4}, "k3": float64(2), "k4": []float64{4}},
|
||||
expectedAttributes: []attribute.KeyValue{
|
||||
attribute.Int64("k1", 1),
|
||||
attribute.Int64Slice("k2", []int64{2}),
|
||||
attribute.Float64("k3", 3),
|
||||
attribute.Float64Slice("k2", []float64{4}),
|
||||
},
|
||||
}, {
|
||||
description: "warn",
|
||||
spanName: "test",
|
||||
logLevel: lWarn,
|
||||
msg: "k1: %v",
|
||||
fields: map[string]interface{}{"k1": map[int]int{1: 1}},
|
||||
expectedAttributes: []attribute.KeyValue{
|
||||
attribute.String("k1", "{1:1}"),
|
||||
},
|
||||
}, {
|
||||
description: "error",
|
||||
spanName: "test",
|
||||
logLevel: lErr,
|
||||
msg: "k1: %t, k2: %v",
|
||||
fields: map[string]interface{}{"k1": true, "k2": []bool{true}, "k3": errors.New("fake")},
|
||||
expectedAttributes: []attribute.KeyValue{
|
||||
attribute.Bool("k1", true),
|
||||
attribute.BoolSlice("k2", []bool{true}),
|
||||
attribute.String("k3", "fake"),
|
||||
},
|
||||
}, {
|
||||
description: "fatal",
|
||||
spanName: "test",
|
||||
logLevel: lFatal,
|
||||
expectedAttributes: []attribute.KeyValue{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
tearDown, p, _ := setupSuite()
|
||||
defer tearDown(p)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
ctx, s := otel.Tracer(tt.spanName).Start(ctx, tt.spanName)
|
||||
l := logger{s: s, l: log.G(ctx), a: make([]attribute.KeyValue, 0)}
|
||||
switch tt.logLevel {
|
||||
case lDebug:
|
||||
l.WithFields(tt.fields).Debugf(tt.msg, tt.args)
|
||||
case lInfo:
|
||||
l.WithFields(tt.fields).Infof(tt.msg, tt.args)
|
||||
case lWarn:
|
||||
l.WithFields(tt.fields).Warnf(tt.msg, tt.args)
|
||||
case lErr:
|
||||
l.WithFields(tt.fields).Errorf(tt.msg, tt.args)
|
||||
case lFatal:
|
||||
l.WithFields(tt.fields).Fatalf(tt.msg, tt.args)
|
||||
}
|
||||
s.End()
|
||||
|
||||
//TODO add assertion with exporter here once logic for recoding log event is added.
|
||||
assert.Assert(t, len(l.a) == len(tt.expectedAttributes))
|
||||
for _, a := range tt.expectedAttributes {
|
||||
cmp.Contains(l.a, a)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogWithField(t *testing.T) {
|
||||
type field struct {
|
||||
key string
|
||||
value interface{}
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
description string
|
||||
spanName string
|
||||
fields []field
|
||||
expectedAttributes []attribute.KeyValue
|
||||
}{
|
||||
{
|
||||
description: "single field",
|
||||
spanName: "test",
|
||||
fields: []field{{key: "testKey1", value: "value1"}},
|
||||
expectedAttributes: []attribute.KeyValue{{Key: "testKey1", Value: attribute.StringValue("value1")}},
|
||||
}, {
|
||||
description: "multiple unique fields",
|
||||
spanName: "test",
|
||||
fields: []field{{key: "testKey1", value: "value1"}, {key: "testKey2", value: "value2"}},
|
||||
expectedAttributes: []attribute.KeyValue{{Key: "testKey1", Value: attribute.StringValue("value1")}, {Key: "testKey2", Value: attribute.StringValue("value2")}},
|
||||
}, {
|
||||
description: "duplicated fields",
|
||||
spanName: "test",
|
||||
fields: []field{{key: "testKey1", value: "value1"}, {key: "testKey1", value: "value2"}},
|
||||
expectedAttributes: []attribute.KeyValue{{Key: "testKey1", Value: attribute.StringValue("value1")}, {Key: "testKey2", Value: attribute.StringValue("value2")}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
tearDown, p, _ := setupSuite()
|
||||
defer tearDown(p)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
ctx, s := otel.Tracer(tt.spanName).Start(ctx, tt.spanName)
|
||||
l := logger{s: s, l: log.G(ctx), a: make([]attribute.KeyValue, 0)}
|
||||
|
||||
for _, f := range tt.fields {
|
||||
l.WithField(f.key, f.value).Info("")
|
||||
}
|
||||
s.End()
|
||||
|
||||
assert.Assert(t, len(l.a) == len(tt.expectedAttributes))
|
||||
for _, a := range tt.expectedAttributes {
|
||||
cmp.Contains(l.a, a)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogWithError(t *testing.T) {
|
||||
testCases := []struct {
|
||||
description string
|
||||
spanName string
|
||||
err error
|
||||
expectedAttributes []attribute.KeyValue
|
||||
}{
|
||||
{
|
||||
description: "normal",
|
||||
spanName: "test",
|
||||
err: errors.New("fake"),
|
||||
expectedAttributes: []attribute.KeyValue{{Key: "err", Value: attribute.StringValue("fake")}},
|
||||
}, {
|
||||
description: "nil error",
|
||||
spanName: "test",
|
||||
err: nil,
|
||||
expectedAttributes: []attribute.KeyValue{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
tearDown, p, _ := setupSuite()
|
||||
defer tearDown(p)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
ctx, s := otel.Tracer(tt.spanName).Start(ctx, tt.spanName)
|
||||
l := logger{s: s, l: log.G(ctx), a: make([]attribute.KeyValue, 0)}
|
||||
|
||||
l.WithError(tt.err).Error("")
|
||||
s.End()
|
||||
|
||||
assert.Assert(t, len(l.a) == len(tt.expectedAttributes))
|
||||
for _, a := range tt.expectedAttributes {
|
||||
cmp.Contains(l.a, a)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogWithFields(t *testing.T) {
|
||||
testCases := []struct {
|
||||
description string
|
||||
spanName string
|
||||
fields log.Fields
|
||||
expectedAttributes []attribute.KeyValue
|
||||
}{
|
||||
{
|
||||
description: "single field",
|
||||
spanName: "test",
|
||||
fields: log.Fields{"testKey1": "value1"},
|
||||
expectedAttributes: []attribute.KeyValue{{Key: "testKey1", Value: attribute.StringValue("value1")}},
|
||||
}, {
|
||||
description: "multiple unique fields",
|
||||
spanName: "test",
|
||||
fields: log.Fields{"testKey1": "value1", "testKey2": "value2"},
|
||||
expectedAttributes: []attribute.KeyValue{{Key: "testKey1", Value: attribute.StringValue("value1")}, {Key: "testKey2", Value: attribute.StringValue("value2")}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
tearDown, p, _ := setupSuite()
|
||||
defer tearDown(p)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
ctx, s := otel.Tracer(tt.spanName).Start(ctx, tt.spanName)
|
||||
l := logger{s: s, l: log.G(ctx), a: make([]attribute.KeyValue, 0)}
|
||||
|
||||
l.WithFields(tt.fields).Debug("")
|
||||
s.End()
|
||||
|
||||
assert.Assert(t, len(l.a) == len(tt.expectedAttributes))
|
||||
for _, a := range tt.expectedAttributes {
|
||||
cmp.Contains(l.a, a)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func setupSuite() (func(provider *sdktrace.TracerProvider), *sdktrace.TracerProvider, *fakeExporter) {
|
||||
r := NewResource("virtual-kubelet", "1.2.3")
|
||||
e := &fakeExporter{}
|
||||
p := sdktrace.NewTracerProvider(
|
||||
sdktrace.WithSyncer(e),
|
||||
sdktrace.WithResource(r),
|
||||
)
|
||||
otel.SetTracerProvider(p)
|
||||
|
||||
// Return a function to teardown the test
|
||||
return func(provider *sdktrace.TracerProvider) {
|
||||
_ = p.Shutdown(context.Background())
|
||||
}, p, e
|
||||
}
|
||||
|
||||
func NewResource(name, version string) *resource.Resource {
|
||||
return resource.NewWithAttributes(
|
||||
semconv.SchemaURL,
|
||||
semconv.ServiceNameKey.String(name),
|
||||
semconv.ServiceVersionKey.String(version),
|
||||
)
|
||||
}
|
||||
|
||||
type fakeExporter struct {
|
||||
sync.Mutex
|
||||
attributes []attribute.KeyValue
|
||||
// Links returns all the links the span has to other spans.
|
||||
links []sdktrace.Link
|
||||
// Events returns all the events that occurred within in the spans
|
||||
// lifetime.
|
||||
events []sdktrace.Event
|
||||
// Status returns the spans status.
|
||||
status sdktrace.Status
|
||||
}
|
||||
|
||||
func (f *fakeExporter) ExportSpans(_ context.Context, spans []sdktrace.ReadOnlySpan) (err error) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
f.attributes = make([]attribute.KeyValue, 0)
|
||||
f.links = make([]sdktrace.Link, 0)
|
||||
f.events = make([]sdktrace.Event, 0)
|
||||
for _, s := range spans {
|
||||
f.attributes = append(f.attributes, s.Attributes()...)
|
||||
f.links = append(f.links, s.Links()...)
|
||||
f.events = append(f.events, s.Events()...)
|
||||
f.status = s.Status()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (f *fakeExporter) Shutdown(_ context.Context) (err error) {
|
||||
f.attributes = make([]attribute.KeyValue, 0)
|
||||
f.links = make([]sdktrace.Link, 0)
|
||||
f.events = make([]sdktrace.Event, 0)
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user