mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2025-03-03 14:52:56 +02:00
OTLP trace gRPC exporter (#1922)
* draft: otlp exporter trace grpc * address comments and improve usability * change passthrough example to go 1.15 * code cleanups * copy dependencies to `otlptrace` * fix typo on makefile * address comments * update code with latest changes * fix comments Co-authored-by: Tyler Yahn <MrAlias@users.noreply.github.com>
This commit is contained in:
parent
5a8f7ff74f
commit
dfe2b6f143
20
.github/dependabot.yml
vendored
20
.github/dependabot.yml
vendored
@ -236,3 +236,23 @@ updates:
|
||||
schedule:
|
||||
day: sunday
|
||||
interval: weekly
|
||||
-
|
||||
package-ecosystem: gomod
|
||||
directory: /exporters/otlp/otlptrace
|
||||
labels:
|
||||
- dependencies
|
||||
- go
|
||||
- "Skip Changelog"
|
||||
schedule:
|
||||
day: sunday
|
||||
interval: weekly
|
||||
-
|
||||
package-ecosystem: gomod
|
||||
directory: /exporters/otlp/otlptrace/otlptracegrpc
|
||||
labels:
|
||||
- dependencies
|
||||
- go
|
||||
- "Skip Changelog"
|
||||
schedule:
|
||||
day: sunday
|
||||
interval: weekly
|
||||
|
@ -31,6 +31,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
||||
It can be used to decode a `TraceState` from a `tracestate` header string value. (#1937)
|
||||
- The `Len` method is added to the `TraceState` type in the `go.opentelemetry.io/otel/trace` package.
|
||||
This method returns the number of list-members the `TraceState` holds. (#1937)
|
||||
- Creates package `go.opentelemetry.io/otel/exporters/otlp/otlptrace` that defines a trace exporter that uses a `otlptrace.Client` to send data.
|
||||
Creates package `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc` implementing a gRPC `otlptrace.Client` and offers convenience functions, `NewExportPipeline` and `InstallNewPipeline`, to setup and install a `otlptrace.Exporter` in tracing .(#1922)
|
||||
|
||||
### Changed
|
||||
|
||||
|
1
Makefile
1
Makefile
@ -168,6 +168,7 @@ dependabot-check:
|
||||
); \
|
||||
if [ -n "$$result" ]; then \
|
||||
echo "missing go.mod dependabot check:"; echo "$$result"; \
|
||||
echo "new modules need to be added to the .github/dependabot.yml file"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
|
@ -57,3 +57,7 @@ replace go.opentelemetry.io/otel/sdk/metric => ../../sdk/metric
|
||||
replace go.opentelemetry.io/otel/trace => ../../trace
|
||||
|
||||
replace go.opentelemetry.io/otel/example/passthrough => ../../example/passthrough
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace => ../../exporters/otlp/otlptrace
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc => ../../exporters/otlp/otlptrace/otlptracegrpc
|
||||
|
@ -53,3 +53,7 @@ replace go.opentelemetry.io/otel/sdk/metric => ../../sdk/metric
|
||||
replace go.opentelemetry.io/otel/trace => ../../trace
|
||||
|
||||
replace go.opentelemetry.io/otel/example/passthrough => ../../example/passthrough
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace => ../../exporters/otlp/otlptrace
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc => ../../exporters/otlp/otlptrace/otlptracegrpc
|
||||
|
@ -53,3 +53,7 @@ replace go.opentelemetry.io/otel/sdk/metric => ../../sdk/metric
|
||||
replace go.opentelemetry.io/otel/trace => ../../trace
|
||||
|
||||
replace go.opentelemetry.io/otel/example/passthrough => ../passthrough
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace => ../../exporters/otlp/otlptrace
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc => ../../exporters/otlp/otlptrace/otlptracegrpc
|
||||
|
@ -54,3 +54,7 @@ replace go.opentelemetry.io/otel/sdk/metric => ../../sdk/metric
|
||||
replace go.opentelemetry.io/otel/trace => ../../trace
|
||||
|
||||
replace go.opentelemetry.io/otel/example/passthrough => ../passthrough
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace => ../../exporters/otlp/otlptrace
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc => ../../exporters/otlp/otlptrace/otlptracegrpc
|
||||
|
@ -55,3 +55,7 @@ replace go.opentelemetry.io/otel/sdk/metric => ../../sdk/metric
|
||||
replace go.opentelemetry.io/otel/trace => ../../trace
|
||||
|
||||
replace go.opentelemetry.io/otel/example/passthrough => ../passthrough
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace => ../../exporters/otlp/otlptrace
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc => ../../exporters/otlp/otlptrace/otlptracegrpc
|
||||
|
@ -57,3 +57,7 @@ replace go.opentelemetry.io/otel/sdk/metric => ../../sdk/metric
|
||||
replace go.opentelemetry.io/otel/trace => ../../trace
|
||||
|
||||
replace go.opentelemetry.io/otel/example/passthrough => ../passthrough
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace => ../../exporters/otlp/otlptrace
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc => ../../exporters/otlp/otlptrace/otlptracegrpc
|
||||
|
@ -1,6 +1,6 @@
|
||||
module go.opentelemetry.io/otel/example/passthrough
|
||||
|
||||
go 1.16
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
go.opentelemetry.io/otel v0.20.0
|
||||
@ -55,3 +55,7 @@ replace go.opentelemetry.io/otel/sdk/export/metric => ../../sdk/export/metric
|
||||
replace go.opentelemetry.io/otel/sdk/metric => ../../sdk/metric
|
||||
|
||||
replace go.opentelemetry.io/otel/sdk/trace => ../../sdk/trace
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace => ../../exporters/otlp/otlptrace
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc => ../../exporters/otlp/otlptrace/otlptracegrpc
|
||||
|
@ -56,3 +56,7 @@ replace go.opentelemetry.io/otel/sdk/metric => ../../sdk/metric
|
||||
replace go.opentelemetry.io/otel/trace => ../../trace
|
||||
|
||||
replace go.opentelemetry.io/otel/example/passthrough => ../passthrough
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace => ../../exporters/otlp/otlptrace
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc => ../../exporters/otlp/otlptrace/otlptracegrpc
|
||||
|
@ -53,3 +53,7 @@ replace go.opentelemetry.io/otel/sdk/metric => ../../sdk/metric
|
||||
replace go.opentelemetry.io/otel/trace => ../../trace
|
||||
|
||||
replace go.opentelemetry.io/otel/example/passthrough => ../passthrough
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace => ../../exporters/otlp/otlptrace
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc => ../../exporters/otlp/otlptrace/otlptracegrpc
|
||||
|
@ -54,3 +54,7 @@ replace go.opentelemetry.io/otel/sdk/metric => ../../sdk/metric
|
||||
replace go.opentelemetry.io/otel/trace => ../../trace
|
||||
|
||||
replace go.opentelemetry.io/otel/example/passthrough => ../passthrough
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace => ../../exporters/otlp/otlptrace
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc => ../../exporters/otlp/otlptrace/otlptracegrpc
|
||||
|
@ -58,3 +58,7 @@ replace go.opentelemetry.io/otel/sdk/metric => ../../../sdk/metric
|
||||
replace go.opentelemetry.io/otel/trace => ../../../trace
|
||||
|
||||
replace go.opentelemetry.io/otel/example/passthrough => ../../../example/passthrough
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace => ../../otlp/otlptrace
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc => ../../otlp/otlptrace/otlptracegrpc
|
||||
|
@ -64,4 +64,8 @@ replace go.opentelemetry.io/otel/sdk/metric => ../../sdk/metric
|
||||
|
||||
replace go.opentelemetry.io/otel/trace => ../../trace
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace => ./otlptrace
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc => ./otlptrace/otlptracegrpc
|
||||
|
||||
replace go.opentelemetry.io/otel/example/passthrough => ../../example/passthrough
|
||||
|
43
exporters/otlp/otlptrace/clients.go
Normal file
43
exporters/otlp/otlptrace/clients.go
Normal file
@ -0,0 +1,43 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package otlptrace // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace"
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
tracepb "go.opentelemetry.io/proto/otlp/trace/v1"
|
||||
)
|
||||
|
||||
// Client manages connections to the collector, handles the
|
||||
// transformation of data into wire format, and the transmission of that
|
||||
// data to the collector.
|
||||
type Client interface {
|
||||
// Start should establish connection(s) to endpoint(s). It is
|
||||
// called just once by the exporter, so the implementation
|
||||
// does not need to worry about idempotence and locking.
|
||||
Start(ctx context.Context) error
|
||||
// Stop should close the connections. The function is called
|
||||
// only once by the exporter, so the implementation does not
|
||||
// need to worry about idempotence, but it may be called
|
||||
// concurrently with UploadTraces, so proper
|
||||
// locking is required. The function serves as a
|
||||
// synchronization point - after the function returns, the
|
||||
// process of closing connections is assumed to be finished.
|
||||
Stop(ctx context.Context) error
|
||||
// UploadTraces should transform the passed traces to the wire
|
||||
// format and send it to the collector. May be called
|
||||
// concurrently.
|
||||
UploadTraces(ctx context.Context, protoSpans []*tracepb.ResourceSpans) error
|
||||
}
|
131
exporters/otlp/otlptrace/exporter.go
Normal file
131
exporters/otlp/otlptrace/exporter.go
Normal file
@ -0,0 +1,131 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package otlptrace // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/tracetransform"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
tracesdk "go.opentelemetry.io/otel/sdk/trace"
|
||||
)
|
||||
|
||||
var (
|
||||
errAlreadyStarted = errors.New("already started")
|
||||
)
|
||||
|
||||
// Exporter exports trace data in the OTLP wire format.
|
||||
type Exporter struct {
|
||||
client Client
|
||||
|
||||
mu sync.RWMutex
|
||||
started bool
|
||||
|
||||
startOnce sync.Once
|
||||
stopOnce sync.Once
|
||||
}
|
||||
|
||||
// ExportSpans exports a batch of spans.
|
||||
func (e *Exporter) ExportSpans(ctx context.Context, ss []tracesdk.ReadOnlySpan) error {
|
||||
protoSpans := tracetransform.Spans(ss)
|
||||
if len(protoSpans) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return e.client.UploadTraces(ctx, protoSpans)
|
||||
}
|
||||
|
||||
// Start establishes a connection to the receiving endpoint.
|
||||
func (e *Exporter) Start(ctx context.Context) error {
|
||||
var err = errAlreadyStarted
|
||||
e.startOnce.Do(func() {
|
||||
e.mu.Lock()
|
||||
e.started = true
|
||||
e.mu.Unlock()
|
||||
err = e.client.Start(ctx)
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Shutdown flushes all exports and closes all connections to the receiving endpoint.
|
||||
func (e *Exporter) Shutdown(ctx context.Context) error {
|
||||
e.mu.RLock()
|
||||
started := e.started
|
||||
e.mu.RUnlock()
|
||||
|
||||
if !started {
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
e.stopOnce.Do(func() {
|
||||
err = e.client.Stop(ctx)
|
||||
e.mu.Lock()
|
||||
e.started = false
|
||||
e.mu.Unlock()
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
var _ tracesdk.SpanExporter = (*Exporter)(nil)
|
||||
|
||||
// NewExporter constructs a new Exporter and starts it.
|
||||
func NewExporter(ctx context.Context, client Client) (*Exporter, error) {
|
||||
exp := NewUnstartedExporter(client)
|
||||
if err := exp.Start(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return exp, nil
|
||||
}
|
||||
|
||||
// NewUnstartedExporter constructs a new Exporter and does not start it.
|
||||
func NewUnstartedExporter(client Client) *Exporter {
|
||||
return &Exporter{
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
// NewExportPipeline sets up a complete export pipeline
|
||||
// with the recommended TracerProvider setup.
|
||||
func NewExportPipeline(ctx context.Context, client Client) (*Exporter, *tracesdk.TracerProvider, error) {
|
||||
exp, err := NewExporter(ctx, client)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
tracerProvider := tracesdk.NewTracerProvider(
|
||||
tracesdk.WithBatcher(exp),
|
||||
)
|
||||
|
||||
return exp, tracerProvider, nil
|
||||
}
|
||||
|
||||
// InstallNewPipeline instantiates a NewExportPipeline with the
|
||||
// recommended configuration and registers it globally.
|
||||
func InstallNewPipeline(ctx context.Context, client Client) (*Exporter, *tracesdk.TracerProvider, error) {
|
||||
exp, tp, err := NewExportPipeline(ctx, client)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
otel.SetTracerProvider(tp)
|
||||
return exp, tp, err
|
||||
}
|
66
exporters/otlp/otlptrace/exporter_test.go
Normal file
66
exporters/otlp/otlptrace/exporter_test.go
Normal file
@ -0,0 +1,66 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package otlptrace_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
tracesdk "go.opentelemetry.io/otel/sdk/trace"
|
||||
tracepb "go.opentelemetry.io/proto/otlp/trace/v1"
|
||||
)
|
||||
|
||||
type noopClient struct {
|
||||
}
|
||||
|
||||
var _ otlptrace.Client = (*noopClient)(nil)
|
||||
|
||||
func (m *noopClient) Start(_ context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *noopClient) Stop(ctx context.Context) error {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *noopClient) UploadTraces(_ context.Context, _ []*tracepb.ResourceSpans) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *noopClient) Reset() {
|
||||
}
|
||||
|
||||
func TestInstallNewPipeline(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
_, _, err := otlptrace.InstallNewPipeline(ctx, &noopClient{})
|
||||
assert.NoError(t, err)
|
||||
assert.IsType(t, &tracesdk.TracerProvider{}, otel.GetTracerProvider())
|
||||
}
|
||||
|
||||
func TestNewExportPipeline(t *testing.T) {
|
||||
_, tp, err := otlptrace.NewExportPipeline(context.Background(), &noopClient{})
|
||||
assert.NoError(t, err)
|
||||
assert.NotEqual(t, tp, otel.GetTracerProvider())
|
||||
}
|
67
exporters/otlp/otlptrace/go.mod
Normal file
67
exporters/otlp/otlptrace/go.mod
Normal file
@ -0,0 +1,67 @@
|
||||
module go.opentelemetry.io/otel/exporters/otlp/otlptrace
|
||||
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/cenkalti/backoff/v4 v4.1.0
|
||||
github.com/google/go-cmp v0.5.5
|
||||
github.com/stretchr/testify v1.7.0
|
||||
go.opentelemetry.io/otel v0.20.0
|
||||
go.opentelemetry.io/otel/oteltest v0.20.0
|
||||
go.opentelemetry.io/otel/sdk v0.20.0
|
||||
go.opentelemetry.io/otel/trace v0.20.0
|
||||
go.opentelemetry.io/proto/otlp v0.8.0
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013
|
||||
google.golang.org/grpc v1.37.1
|
||||
google.golang.org/protobuf v1.26.0
|
||||
)
|
||||
|
||||
replace go.opentelemetry.io/otel => ../../..
|
||||
|
||||
replace go.opentelemetry.io/otel/sdk => ../../../sdk
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp => ../
|
||||
|
||||
replace go.opentelemetry.io/otel/metric => ../../../metric
|
||||
|
||||
replace go.opentelemetry.io/otel/oteltest => ../../../oteltest
|
||||
|
||||
replace go.opentelemetry.io/otel/trace => ../../../trace
|
||||
|
||||
replace go.opentelemetry.io/otel/bridge/opencensus => ../../../bridge/opencensus
|
||||
|
||||
replace go.opentelemetry.io/otel/bridge/opentracing => ../../../bridge/opentracing
|
||||
|
||||
replace go.opentelemetry.io/otel/example/jaeger => ../../../example/jaeger
|
||||
|
||||
replace go.opentelemetry.io/otel/example/namedtracer => ../../../example/namedtracer
|
||||
|
||||
replace go.opentelemetry.io/otel/example/opencensus => ../../../example/opencensus
|
||||
|
||||
replace go.opentelemetry.io/otel/example/otel-collector => ../../../example/otel-collector
|
||||
|
||||
replace go.opentelemetry.io/otel/example/prom-collector => ../../../example/prom-collector
|
||||
|
||||
replace go.opentelemetry.io/otel/example/prometheus => ../../../example/prometheus
|
||||
|
||||
replace go.opentelemetry.io/otel/example/zipkin => ../../../example/zipkin
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/metric/prometheus => ../../metric/prometheus
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace => ./
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc => ./otlptracegrpc
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/stdout => ../../stdout
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/trace/jaeger => ../../trace/jaeger
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/trace/zipkin => ../../trace/zipkin
|
||||
|
||||
replace go.opentelemetry.io/otel/internal/tools => ../../../internal/tools
|
||||
|
||||
replace go.opentelemetry.io/otel/sdk/export/metric => ../../../sdk/export/metric
|
||||
|
||||
replace go.opentelemetry.io/otel/sdk/metric => ../../../sdk/metric
|
||||
|
||||
replace go.opentelemetry.io/otel/example/passthrough => ../../../example/passthrough
|
122
exporters/otlp/otlptrace/go.sum
Normal file
122
exporters/otlp/otlptrace/go.sum
Normal file
@ -0,0 +1,122 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/cenkalti/backoff/v4 v4.1.0 h1:c8LkOFQTzuO0WBM/ae5HdGQuZPfPxp7lqBRwQRm4fSc=
|
||||
github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
go.opentelemetry.io/proto/otlp v0.8.0 h1:Y0huTcGvPnmeIUuwORZk07LQ2q2CbrXHBkUidvGIoL0=
|
||||
go.opentelemetry.io/proto/otlp v0.8.0/go.mod h1:4i41ohS2vg3FjjjRpBNqfT/voGvIxREH17c6djRtXx8=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.37.1 h1:ARnQJNWxGyYJpdf/JXscNlQr/uv607ZPU9Z7ogHi+iI=
|
||||
google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
@ -0,0 +1,38 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package connection
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"unsafe"
|
||||
|
||||
ottest "go.opentelemetry.io/otel/internal/internaltest"
|
||||
)
|
||||
|
||||
// Ensure struct alignment prior to running tests.
|
||||
func TestMain(m *testing.M) {
|
||||
fields := []ottest.FieldOffset{
|
||||
{
|
||||
Name: "Connection.lastConnectErrPtr",
|
||||
Offset: unsafe.Offsetof(Connection{}.lastConnectErrPtr),
|
||||
},
|
||||
}
|
||||
if !ottest.Aligned8Byte(fields, os.Stderr) {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
os.Exit(m.Run())
|
||||
}
|
429
exporters/otlp/otlptrace/internal/connection/connection.go
Normal file
429
exporters/otlp/otlptrace/internal/connection/connection.go
Normal file
@ -0,0 +1,429 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package connection
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
"google.golang.org/genproto/googleapis/rpc/errdetails"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"google.golang.org/grpc/encoding/gzip"
|
||||
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/otlpconfig"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
type Connection struct {
|
||||
// Ensure pointer is 64-bit aligned for atomic operations on both 32 and 64 bit machines.
|
||||
lastConnectErrPtr unsafe.Pointer
|
||||
|
||||
// mu protects the Connection as it is accessed by the
|
||||
// exporter goroutines and background Connection goroutine
|
||||
mu sync.Mutex
|
||||
cc *grpc.ClientConn
|
||||
|
||||
// these fields are read-only after constructor is finished
|
||||
cfg otlpconfig.Config
|
||||
SCfg otlpconfig.SignalConfig
|
||||
metadata metadata.MD
|
||||
newConnectionHandler func(cc *grpc.ClientConn)
|
||||
|
||||
// these channels are created once
|
||||
disconnectedCh chan bool
|
||||
backgroundConnectionDoneCh chan struct{}
|
||||
stopCh chan struct{}
|
||||
|
||||
// this is for tests, so they can replace the closing
|
||||
// routine without a worry of modifying some global variable
|
||||
// or changing it back to original after the test is done
|
||||
closeBackgroundConnectionDoneCh func(ch chan struct{})
|
||||
}
|
||||
|
||||
func NewConnection(cfg otlpconfig.Config, sCfg otlpconfig.SignalConfig, handler func(cc *grpc.ClientConn)) *Connection {
|
||||
c := new(Connection)
|
||||
c.newConnectionHandler = handler
|
||||
c.cfg = cfg
|
||||
c.SCfg = sCfg
|
||||
if len(c.SCfg.Headers) > 0 {
|
||||
c.metadata = metadata.New(c.SCfg.Headers)
|
||||
}
|
||||
c.closeBackgroundConnectionDoneCh = func(ch chan struct{}) {
|
||||
close(ch)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Connection) StartConnection(ctx context.Context) error {
|
||||
c.stopCh = make(chan struct{})
|
||||
c.disconnectedCh = make(chan bool, 1)
|
||||
c.backgroundConnectionDoneCh = make(chan struct{})
|
||||
|
||||
if err := c.connect(ctx); err == nil {
|
||||
c.setStateConnected()
|
||||
} else {
|
||||
c.SetStateDisconnected(err)
|
||||
}
|
||||
go c.indefiniteBackgroundConnection()
|
||||
|
||||
// TODO: proper error handling when initializing connections.
|
||||
// We can report permanent errors, e.g., invalid settings.
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Connection) LastConnectError() error {
|
||||
errPtr := (*error)(atomic.LoadPointer(&c.lastConnectErrPtr))
|
||||
if errPtr == nil {
|
||||
return nil
|
||||
}
|
||||
return *errPtr
|
||||
}
|
||||
|
||||
func (c *Connection) saveLastConnectError(err error) {
|
||||
var errPtr *error
|
||||
if err != nil {
|
||||
errPtr = &err
|
||||
}
|
||||
atomic.StorePointer(&c.lastConnectErrPtr, unsafe.Pointer(errPtr))
|
||||
}
|
||||
|
||||
func (c *Connection) SetStateDisconnected(err error) {
|
||||
c.saveLastConnectError(err)
|
||||
select {
|
||||
case c.disconnectedCh <- true:
|
||||
default:
|
||||
}
|
||||
c.newConnectionHandler(nil)
|
||||
}
|
||||
|
||||
func (c *Connection) setStateConnected() {
|
||||
c.saveLastConnectError(nil)
|
||||
}
|
||||
|
||||
func (c *Connection) Connected() bool {
|
||||
return c.LastConnectError() == nil
|
||||
}
|
||||
|
||||
const defaultConnReattemptPeriod = 10 * time.Second
|
||||
|
||||
func (c *Connection) indefiniteBackgroundConnection() {
|
||||
defer func() {
|
||||
c.closeBackgroundConnectionDoneCh(c.backgroundConnectionDoneCh)
|
||||
}()
|
||||
|
||||
connReattemptPeriod := c.cfg.ReconnectionPeriod
|
||||
if connReattemptPeriod <= 0 {
|
||||
connReattemptPeriod = defaultConnReattemptPeriod
|
||||
}
|
||||
|
||||
// No strong seeding required, nano time can
|
||||
// already help with pseudo uniqueness.
|
||||
rng := rand.New(rand.NewSource(time.Now().UnixNano() + rand.Int63n(1024)))
|
||||
|
||||
// maxJitterNanos: 70% of the connectionReattemptPeriod
|
||||
maxJitterNanos := int64(0.7 * float64(connReattemptPeriod))
|
||||
|
||||
for {
|
||||
// Otherwise these will be the normal scenarios to enable
|
||||
// reconnection if we trip out.
|
||||
// 1. If we've stopped, return entirely
|
||||
// 2. Otherwise block until we are disconnected, and
|
||||
// then retry connecting
|
||||
select {
|
||||
case <-c.stopCh:
|
||||
return
|
||||
|
||||
case <-c.disconnectedCh:
|
||||
// Quickly check if we haven't stopped at the
|
||||
// same time.
|
||||
select {
|
||||
case <-c.stopCh:
|
||||
return
|
||||
|
||||
default:
|
||||
}
|
||||
|
||||
// Normal scenario that we'll wait for
|
||||
}
|
||||
|
||||
if err := c.connect(context.Background()); err == nil {
|
||||
c.setStateConnected()
|
||||
} else {
|
||||
// this code is unreachable in most cases
|
||||
// c.connect does not establish Connection
|
||||
c.SetStateDisconnected(err)
|
||||
}
|
||||
|
||||
// Apply some jitter to avoid lockstep retrials of other
|
||||
// collector-exporters. Lockstep retrials could result in an
|
||||
// innocent DDOS, by clogging the machine's resources and network.
|
||||
jitter := time.Duration(rng.Int63n(maxJitterNanos))
|
||||
select {
|
||||
case <-c.stopCh:
|
||||
return
|
||||
case <-time.After(connReattemptPeriod + jitter):
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Connection) connect(ctx context.Context) error {
|
||||
cc, err := c.dialToCollector(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.setConnection(cc)
|
||||
c.newConnectionHandler(cc)
|
||||
return nil
|
||||
}
|
||||
|
||||
// setConnection sets cc as the client Connection and returns true if
|
||||
// the Connection state changed.
|
||||
func (c *Connection) setConnection(cc *grpc.ClientConn) bool {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
// If previous clientConn is same as the current then just return.
|
||||
// This doesn't happen right now as this func is only called with new ClientConn.
|
||||
// It is more about future-proofing.
|
||||
if c.cc == cc {
|
||||
return false
|
||||
}
|
||||
|
||||
// If the previous clientConn was non-nil, close it
|
||||
if c.cc != nil {
|
||||
_ = c.cc.Close()
|
||||
}
|
||||
c.cc = cc
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *Connection) dialToCollector(ctx context.Context) (*grpc.ClientConn, error) {
|
||||
dialOpts := []grpc.DialOption{}
|
||||
if c.cfg.ServiceConfig != "" {
|
||||
dialOpts = append(dialOpts, grpc.WithDefaultServiceConfig(c.cfg.ServiceConfig))
|
||||
}
|
||||
if c.SCfg.GRPCCredentials != nil {
|
||||
dialOpts = append(dialOpts, grpc.WithTransportCredentials(c.SCfg.GRPCCredentials))
|
||||
} else if c.SCfg.Insecure {
|
||||
dialOpts = append(dialOpts, grpc.WithInsecure())
|
||||
}
|
||||
if c.SCfg.Compression == otlpconfig.GzipCompression {
|
||||
dialOpts = append(dialOpts, grpc.WithDefaultCallOptions(grpc.UseCompressor(gzip.Name)))
|
||||
}
|
||||
if len(c.cfg.DialOptions) != 0 {
|
||||
dialOpts = append(dialOpts, c.cfg.DialOptions...)
|
||||
}
|
||||
|
||||
ctx, cancel := c.ContextWithStop(ctx)
|
||||
defer cancel()
|
||||
ctx = c.ContextWithMetadata(ctx)
|
||||
return grpc.DialContext(ctx, c.SCfg.Endpoint, dialOpts...)
|
||||
}
|
||||
|
||||
func (c *Connection) ContextWithMetadata(ctx context.Context) context.Context {
|
||||
if c.metadata.Len() > 0 {
|
||||
return metadata.NewOutgoingContext(ctx, c.metadata)
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (c *Connection) Shutdown(ctx context.Context) error {
|
||||
close(c.stopCh)
|
||||
// Ensure that the backgroundConnector returns
|
||||
select {
|
||||
case <-c.backgroundConnectionDoneCh:
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
cc := c.cc
|
||||
c.cc = nil
|
||||
c.mu.Unlock()
|
||||
|
||||
if cc != nil {
|
||||
return cc.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Connection) ContextWithStop(ctx context.Context) (context.Context, context.CancelFunc) {
|
||||
// Unify the parent context Done signal with the Connection's
|
||||
// stop channel.
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
go func(ctx context.Context, cancel context.CancelFunc) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// Nothing to do, either cancelled or deadline
|
||||
// happened.
|
||||
case <-c.stopCh:
|
||||
cancel()
|
||||
}
|
||||
}(ctx, cancel)
|
||||
return ctx, cancel
|
||||
}
|
||||
|
||||
func (c *Connection) DoRequest(ctx context.Context, fn func(context.Context) error) error {
|
||||
expBackoff := newExponentialBackoff(c.cfg.RetrySettings)
|
||||
|
||||
for {
|
||||
err := fn(ctx)
|
||||
if err == nil {
|
||||
// request succeeded.
|
||||
return nil
|
||||
}
|
||||
|
||||
if !c.cfg.RetrySettings.Enabled {
|
||||
return err
|
||||
}
|
||||
|
||||
// We have an error, check gRPC status code.
|
||||
st := status.Convert(err)
|
||||
if st.Code() == codes.OK {
|
||||
// Not really an error, still success.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Now, this is this a real error.
|
||||
|
||||
if !shouldRetry(st.Code()) {
|
||||
// It is not a retryable error, we should not retry.
|
||||
return err
|
||||
}
|
||||
|
||||
// Need to retry.
|
||||
|
||||
throttle := getThrottleDuration(st)
|
||||
|
||||
backoffDelay := expBackoff.NextBackOff()
|
||||
if backoffDelay == backoff.Stop {
|
||||
// throw away the batch
|
||||
err = fmt.Errorf("max elapsed time expired: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
var delay time.Duration
|
||||
|
||||
if backoffDelay > throttle {
|
||||
delay = backoffDelay
|
||||
} else {
|
||||
if expBackoff.GetElapsedTime()+throttle > expBackoff.MaxElapsedTime {
|
||||
err = fmt.Errorf("max elapsed time expired when respecting server throttle: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Respect server throttling.
|
||||
delay = throttle
|
||||
}
|
||||
|
||||
// back-off, but get interrupted when shutting down or request is cancelled or timed out.
|
||||
err = func() error {
|
||||
dt := time.NewTimer(delay)
|
||||
defer dt.Stop()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-c.stopCh:
|
||||
return fmt.Errorf("interrupted due to shutdown: %w", err)
|
||||
case <-dt.C:
|
||||
}
|
||||
|
||||
return nil
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func shouldRetry(code codes.Code) bool {
|
||||
switch code {
|
||||
case codes.OK:
|
||||
// Success. This function should not be called for this code, the best we
|
||||
// can do is tell the caller not to retry.
|
||||
return false
|
||||
|
||||
case codes.Canceled,
|
||||
codes.DeadlineExceeded,
|
||||
codes.ResourceExhausted,
|
||||
codes.Aborted,
|
||||
codes.OutOfRange,
|
||||
codes.Unavailable,
|
||||
codes.DataLoss:
|
||||
// These are retryable errors.
|
||||
return true
|
||||
|
||||
case codes.Unknown,
|
||||
codes.InvalidArgument,
|
||||
codes.Unauthenticated,
|
||||
codes.PermissionDenied,
|
||||
codes.NotFound,
|
||||
codes.AlreadyExists,
|
||||
codes.FailedPrecondition,
|
||||
codes.Unimplemented,
|
||||
codes.Internal:
|
||||
// These are fatal errors, don't retry.
|
||||
return false
|
||||
|
||||
default:
|
||||
// Don't retry on unknown codes.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func getThrottleDuration(status *status.Status) time.Duration {
|
||||
// See if throttling information is available.
|
||||
for _, detail := range status.Details() {
|
||||
if t, ok := detail.(*errdetails.RetryInfo); ok {
|
||||
if t.RetryDelay.Seconds > 0 || t.RetryDelay.Nanos > 0 {
|
||||
// We are throttled. Wait before retrying as requested by the server.
|
||||
return time.Duration(t.RetryDelay.Seconds)*time.Second + time.Duration(t.RetryDelay.Nanos)*time.Nanosecond
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func newExponentialBackoff(rs otlpconfig.RetrySettings) *backoff.ExponentialBackOff {
|
||||
// Do not use NewExponentialBackOff since it calls Reset and the code here must
|
||||
// call Reset after changing the InitialInterval (this saves an unnecessary call to Now).
|
||||
expBackoff := &backoff.ExponentialBackOff{
|
||||
InitialInterval: rs.InitialInterval,
|
||||
RandomizationFactor: backoff.DefaultRandomizationFactor,
|
||||
Multiplier: backoff.DefaultMultiplier,
|
||||
MaxInterval: rs.MaxInterval,
|
||||
MaxElapsedTime: rs.MaxElapsedTime,
|
||||
Stop: backoff.Stop,
|
||||
Clock: backoff.SystemClock,
|
||||
}
|
||||
expBackoff.Reset()
|
||||
|
||||
return expBackoff
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package connection
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"google.golang.org/genproto/googleapis/rpc/errdetails"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
)
|
||||
|
||||
func TestGetThrottleDuration(t *testing.T) {
|
||||
tts := []struct {
|
||||
stsFn func() (*status.Status, error)
|
||||
throttle time.Duration
|
||||
}{
|
||||
{
|
||||
stsFn: func() (*status.Status, error) {
|
||||
return status.New(
|
||||
codes.OK,
|
||||
"status with no retry info",
|
||||
), nil
|
||||
},
|
||||
throttle: 0,
|
||||
},
|
||||
{
|
||||
stsFn: func() (*status.Status, error) {
|
||||
st := status.New(codes.ResourceExhausted, "status with retry info")
|
||||
return st.WithDetails(
|
||||
&errdetails.RetryInfo{RetryDelay: durationpb.New(15 * time.Millisecond)},
|
||||
)
|
||||
},
|
||||
throttle: 15 * time.Millisecond,
|
||||
},
|
||||
{
|
||||
stsFn: func() (*status.Status, error) {
|
||||
st := status.New(codes.ResourceExhausted, "status with error info detail")
|
||||
return st.WithDetails(
|
||||
&errdetails.ErrorInfo{Reason: "no throttle detail"},
|
||||
)
|
||||
},
|
||||
throttle: 0,
|
||||
},
|
||||
{
|
||||
stsFn: func() (*status.Status, error) {
|
||||
st := status.New(codes.ResourceExhausted, "status with error info and retry info")
|
||||
return st.WithDetails(
|
||||
&errdetails.ErrorInfo{Reason: "no throttle detail"},
|
||||
&errdetails.RetryInfo{RetryDelay: durationpb.New(13 * time.Minute)},
|
||||
)
|
||||
},
|
||||
throttle: 13 * time.Minute,
|
||||
},
|
||||
{
|
||||
stsFn: func() (*status.Status, error) {
|
||||
st := status.New(codes.ResourceExhausted, "status with two retry info should take the first")
|
||||
return st.WithDetails(
|
||||
&errdetails.RetryInfo{RetryDelay: durationpb.New(13 * time.Minute)},
|
||||
&errdetails.RetryInfo{RetryDelay: durationpb.New(18 * time.Minute)},
|
||||
)
|
||||
},
|
||||
throttle: 13 * time.Minute,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tts {
|
||||
sts, _ := tt.stsFn()
|
||||
t.Run(sts.Message(), func(t *testing.T) {
|
||||
th := getThrottleDuration(sts)
|
||||
require.Equal(t, tt.throttle, th)
|
||||
})
|
||||
}
|
||||
}
|
195
exporters/otlp/otlptrace/internal/otlpconfig/envconfig.go
Normal file
195
exporters/otlp/otlptrace/internal/otlpconfig/envconfig.go
Normal file
@ -0,0 +1,195 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package otlpconfig
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
)
|
||||
|
||||
var httpSchemeRegexp = regexp.MustCompile(`(?i)^(http://|https://)`)
|
||||
|
||||
func ApplyGRPCEnvConfigs(cfg *Config) {
|
||||
e := EnvOptionsReader{
|
||||
GetEnv: os.Getenv,
|
||||
ReadFile: ioutil.ReadFile,
|
||||
}
|
||||
|
||||
e.ApplyGRPCEnvConfigs(cfg)
|
||||
}
|
||||
|
||||
func ApplyHTTPEnvConfigs(cfg *Config) {
|
||||
e := EnvOptionsReader{
|
||||
GetEnv: os.Getenv,
|
||||
ReadFile: ioutil.ReadFile,
|
||||
}
|
||||
|
||||
e.ApplyHTTPEnvConfigs(cfg)
|
||||
}
|
||||
|
||||
type EnvOptionsReader struct {
|
||||
GetEnv func(string) string
|
||||
ReadFile func(filename string) ([]byte, error)
|
||||
}
|
||||
|
||||
func (e *EnvOptionsReader) ApplyHTTPEnvConfigs(cfg *Config) {
|
||||
opts := e.GetOptionsFromEnv()
|
||||
for _, opt := range opts {
|
||||
opt.ApplyHTTPOption(cfg)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *EnvOptionsReader) ApplyGRPCEnvConfigs(cfg *Config) {
|
||||
opts := e.GetOptionsFromEnv()
|
||||
for _, opt := range opts {
|
||||
opt.ApplyGRPCOption(cfg)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *EnvOptionsReader) GetOptionsFromEnv() []GenericOption {
|
||||
var opts []GenericOption
|
||||
|
||||
// Endpoint
|
||||
if v, ok := e.getEnvValue("ENDPOINT"); ok {
|
||||
if isInsecureEndpoint(v) {
|
||||
opts = append(opts, WithInsecure())
|
||||
} else {
|
||||
opts = append(opts, WithSecure())
|
||||
}
|
||||
|
||||
opts = append(opts, WithEndpoint(trimSchema(v)))
|
||||
}
|
||||
if v, ok := e.getEnvValue("TRACES_ENDPOINT"); ok {
|
||||
if isInsecureEndpoint(v) {
|
||||
opts = append(opts, WithInsecureTraces())
|
||||
} else {
|
||||
opts = append(opts, WithSecureTraces())
|
||||
}
|
||||
|
||||
opts = append(opts, WithTracesEndpoint(trimSchema(v)))
|
||||
}
|
||||
|
||||
// Certificate File
|
||||
if path, ok := e.getEnvValue("CERTIFICATE"); ok {
|
||||
if tls, err := e.readTLSConfig(path); err == nil {
|
||||
opts = append(opts, WithTLSClientConfig(tls))
|
||||
} else {
|
||||
otel.Handle(fmt.Errorf("failed to configure otlp exporter certificate '%s': %w", path, err))
|
||||
}
|
||||
}
|
||||
if path, ok := e.getEnvValue("TRACES_CERTIFICATE"); ok {
|
||||
if tls, err := e.readTLSConfig(path); err == nil {
|
||||
opts = append(opts, WithTracesTLSClientConfig(tls))
|
||||
} else {
|
||||
otel.Handle(fmt.Errorf("failed to configure otlp traces exporter certificate '%s': %w", path, err))
|
||||
}
|
||||
}
|
||||
|
||||
// Headers
|
||||
if h, ok := e.getEnvValue("HEADERS"); ok {
|
||||
opts = append(opts, WithHeaders(stringToHeader(h)))
|
||||
}
|
||||
if h, ok := e.getEnvValue("TRACES_HEADERS"); ok {
|
||||
opts = append(opts, WithTracesHeaders(stringToHeader(h)))
|
||||
}
|
||||
|
||||
// Compression
|
||||
if c, ok := e.getEnvValue("COMPRESSION"); ok {
|
||||
opts = append(opts, WithCompression(stringToCompression(c)))
|
||||
}
|
||||
if c, ok := e.getEnvValue("TRACES_COMPRESSION"); ok {
|
||||
opts = append(opts, WithTracesCompression(stringToCompression(c)))
|
||||
}
|
||||
// Timeout
|
||||
if t, ok := e.getEnvValue("TIMEOUT"); ok {
|
||||
if d, err := strconv.Atoi(t); err == nil {
|
||||
opts = append(opts, WithTimeout(time.Duration(d)*time.Millisecond))
|
||||
}
|
||||
}
|
||||
if t, ok := e.getEnvValue("TRACES_TIMEOUT"); ok {
|
||||
if d, err := strconv.Atoi(t); err == nil {
|
||||
opts = append(opts, WithTracesTimeout(time.Duration(d)*time.Millisecond))
|
||||
}
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
||||
|
||||
func isInsecureEndpoint(endpoint string) bool {
|
||||
return strings.HasPrefix(strings.ToLower(endpoint), "http://")
|
||||
}
|
||||
|
||||
func trimSchema(endpoint string) string {
|
||||
return httpSchemeRegexp.ReplaceAllString(endpoint, "")
|
||||
}
|
||||
|
||||
// getEnvValue gets an OTLP environment variable value of the specified key using the GetEnv function.
|
||||
// This function already prepends the OTLP prefix to all key lookup.
|
||||
func (e *EnvOptionsReader) getEnvValue(key string) (string, bool) {
|
||||
v := strings.TrimSpace(e.GetEnv(fmt.Sprintf("OTEL_EXPORTER_OTLP_%s", key)))
|
||||
return v, v != ""
|
||||
}
|
||||
|
||||
func (e *EnvOptionsReader) readTLSConfig(path string) (*tls.Config, error) {
|
||||
b, err := e.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return CreateTLSConfig(b)
|
||||
}
|
||||
|
||||
func stringToCompression(value string) Compression {
|
||||
switch value {
|
||||
case "gzip":
|
||||
return GzipCompression
|
||||
}
|
||||
|
||||
return NoCompression
|
||||
}
|
||||
|
||||
func stringToHeader(value string) map[string]string {
|
||||
headersPairs := strings.Split(value, ",")
|
||||
headers := make(map[string]string)
|
||||
|
||||
for _, header := range headersPairs {
|
||||
nameValue := strings.SplitN(header, "=", 2)
|
||||
if len(nameValue) < 2 {
|
||||
continue
|
||||
}
|
||||
name, err := url.QueryUnescape(nameValue[0])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
trimmedName := strings.TrimSpace(name)
|
||||
value, err := url.QueryUnescape(nameValue[1])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
trimmedValue := strings.TrimSpace(value)
|
||||
|
||||
headers[trimmedName] = trimmedValue
|
||||
}
|
||||
|
||||
return headers
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package otlpconfig
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStringToHeader(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
value string
|
||||
want map[string]string
|
||||
}{
|
||||
{
|
||||
name: "simple test",
|
||||
value: "userId=alice",
|
||||
want: map[string]string{"userId": "alice"},
|
||||
},
|
||||
{
|
||||
name: "simple test with spaces",
|
||||
value: " userId = alice ",
|
||||
want: map[string]string{"userId": "alice"},
|
||||
},
|
||||
{
|
||||
name: "multiples headers encoded",
|
||||
value: "userId=alice,serverNode=DF%3A28,isProduction=false",
|
||||
want: map[string]string{
|
||||
"userId": "alice",
|
||||
"serverNode": "DF:28",
|
||||
"isProduction": "false",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid headers format",
|
||||
value: "userId:alice",
|
||||
want: map[string]string{},
|
||||
},
|
||||
{
|
||||
name: "invalid key",
|
||||
value: "%XX=missing,userId=alice",
|
||||
want: map[string]string{
|
||||
"userId": "alice",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid value",
|
||||
value: "missing=%XX,userId=alice",
|
||||
want: map[string]string{
|
||||
"userId": "alice",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := stringToHeader(tt.value); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("stringToHeader() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
315
exporters/otlp/otlptrace/internal/otlpconfig/options.go
Normal file
315
exporters/otlp/otlptrace/internal/otlpconfig/options.go
Normal file
@ -0,0 +1,315 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package otlpconfig // import "go.opentelemetry.io/otel/exporters/otlp/internal/otlpconfig"
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultMaxAttempts describes how many times the driver
|
||||
// should retry the sending of the payload in case of a
|
||||
// retryable error.
|
||||
DefaultMaxAttempts int = 5
|
||||
// DefaultTracesPath is a default URL path for endpoint that
|
||||
// receives spans.
|
||||
DefaultTracesPath string = "/v1/traces"
|
||||
// DefaultBackoff is a default base backoff time used in the
|
||||
// exponential backoff strategy.
|
||||
DefaultBackoff time.Duration = 300 * time.Millisecond
|
||||
// DefaultTimeout is a default max waiting time for the backend to process
|
||||
// each span batch.
|
||||
DefaultTimeout time.Duration = 10 * time.Second
|
||||
)
|
||||
|
||||
var (
|
||||
// defaultRetrySettings is a default settings for the retry policy.
|
||||
defaultRetrySettings = RetrySettings{
|
||||
Enabled: true,
|
||||
InitialInterval: 5 * time.Second,
|
||||
MaxInterval: 30 * time.Second,
|
||||
MaxElapsedTime: time.Minute,
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
SignalConfig struct {
|
||||
Endpoint string
|
||||
Insecure bool
|
||||
TLSCfg *tls.Config
|
||||
Headers map[string]string
|
||||
Compression Compression
|
||||
Timeout time.Duration
|
||||
URLPath string
|
||||
|
||||
// gRPC configurations
|
||||
GRPCCredentials credentials.TransportCredentials
|
||||
}
|
||||
|
||||
Config struct {
|
||||
// Signal specific configurations
|
||||
Traces SignalConfig
|
||||
|
||||
// HTTP configurations
|
||||
Marshaler Marshaler
|
||||
MaxAttempts int
|
||||
Backoff time.Duration
|
||||
|
||||
// gRPC configurations
|
||||
ReconnectionPeriod time.Duration
|
||||
ServiceConfig string
|
||||
DialOptions []grpc.DialOption
|
||||
RetrySettings RetrySettings
|
||||
}
|
||||
)
|
||||
|
||||
func NewDefaultConfig() Config {
|
||||
c := Config{
|
||||
Traces: SignalConfig{
|
||||
Endpoint: fmt.Sprintf("%s:%d", DefaultCollectorHost, DefaultCollectorPort),
|
||||
URLPath: DefaultTracesPath,
|
||||
Compression: NoCompression,
|
||||
Timeout: DefaultTimeout,
|
||||
},
|
||||
MaxAttempts: DefaultMaxAttempts,
|
||||
Backoff: DefaultBackoff,
|
||||
RetrySettings: defaultRetrySettings,
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
type (
|
||||
// GenericOption applies an option to the HTTP or gRPC driver.
|
||||
GenericOption interface {
|
||||
ApplyHTTPOption(*Config)
|
||||
ApplyGRPCOption(*Config)
|
||||
|
||||
// A private method to prevent users implementing the
|
||||
// interface and so future additions to it will not
|
||||
// violate compatibility.
|
||||
private()
|
||||
}
|
||||
|
||||
// HTTPOption applies an option to the HTTP driver.
|
||||
HTTPOption interface {
|
||||
ApplyHTTPOption(*Config)
|
||||
|
||||
// A private method to prevent users implementing the
|
||||
// interface and so future additions to it will not
|
||||
// violate compatibility.
|
||||
private()
|
||||
}
|
||||
|
||||
// GRPCOption applies an option to the gRPC driver.
|
||||
GRPCOption interface {
|
||||
ApplyGRPCOption(*Config)
|
||||
|
||||
// A private method to prevent users implementing the
|
||||
// interface and so future additions to it will not
|
||||
// violate compatibility.
|
||||
private()
|
||||
}
|
||||
)
|
||||
|
||||
// genericOption is an option that applies the same logic
|
||||
// for both gRPC and HTTP.
|
||||
type genericOption struct {
|
||||
fn func(*Config)
|
||||
}
|
||||
|
||||
func (g *genericOption) ApplyGRPCOption(cfg *Config) {
|
||||
g.fn(cfg)
|
||||
}
|
||||
|
||||
func (g *genericOption) ApplyHTTPOption(cfg *Config) {
|
||||
g.fn(cfg)
|
||||
}
|
||||
|
||||
func (genericOption) private() {}
|
||||
|
||||
func newGenericOption(fn func(cfg *Config)) GenericOption {
|
||||
return &genericOption{fn: fn}
|
||||
}
|
||||
|
||||
// splitOption is an option that applies different logics
|
||||
// for gRPC and HTTP.
|
||||
type splitOption struct {
|
||||
httpFn func(*Config)
|
||||
grpcFn func(*Config)
|
||||
}
|
||||
|
||||
func (g *splitOption) ApplyGRPCOption(cfg *Config) {
|
||||
g.grpcFn(cfg)
|
||||
}
|
||||
|
||||
func (g *splitOption) ApplyHTTPOption(cfg *Config) {
|
||||
g.httpFn(cfg)
|
||||
}
|
||||
|
||||
func (splitOption) private() {}
|
||||
|
||||
func newSplitOption(httpFn func(cfg *Config), grpcFn func(cfg *Config)) GenericOption {
|
||||
return &splitOption{httpFn: httpFn, grpcFn: grpcFn}
|
||||
}
|
||||
|
||||
// httpOption is an option that is only applied to the HTTP driver.
|
||||
type httpOption struct {
|
||||
fn func(*Config)
|
||||
}
|
||||
|
||||
func (h *httpOption) ApplyHTTPOption(cfg *Config) {
|
||||
h.fn(cfg)
|
||||
}
|
||||
|
||||
func (httpOption) private() {}
|
||||
|
||||
func NewHTTPOption(fn func(cfg *Config)) HTTPOption {
|
||||
return &httpOption{fn: fn}
|
||||
}
|
||||
|
||||
// grpcOption is an option that is only applied to the gRPC driver.
|
||||
type grpcOption struct {
|
||||
fn func(*Config)
|
||||
}
|
||||
|
||||
func (h *grpcOption) ApplyGRPCOption(cfg *Config) {
|
||||
h.fn(cfg)
|
||||
}
|
||||
|
||||
func (grpcOption) private() {}
|
||||
|
||||
func NewGRPCOption(fn func(cfg *Config)) GRPCOption {
|
||||
return &grpcOption{fn: fn}
|
||||
}
|
||||
|
||||
// Generic Options
|
||||
|
||||
func WithEndpoint(endpoint string) GenericOption {
|
||||
return newGenericOption(func(cfg *Config) {
|
||||
cfg.Traces.Endpoint = endpoint
|
||||
})
|
||||
}
|
||||
|
||||
func WithTracesEndpoint(endpoint string) GenericOption {
|
||||
return newGenericOption(func(cfg *Config) {
|
||||
cfg.Traces.Endpoint = endpoint
|
||||
})
|
||||
}
|
||||
|
||||
func WithCompression(compression Compression) GenericOption {
|
||||
return newGenericOption(func(cfg *Config) {
|
||||
cfg.Traces.Compression = compression
|
||||
})
|
||||
}
|
||||
|
||||
func WithTracesCompression(compression Compression) GenericOption {
|
||||
return newGenericOption(func(cfg *Config) {
|
||||
cfg.Traces.Compression = compression
|
||||
})
|
||||
}
|
||||
|
||||
func WithTracesURLPath(urlPath string) GenericOption {
|
||||
return newGenericOption(func(cfg *Config) {
|
||||
cfg.Traces.URLPath = urlPath
|
||||
})
|
||||
}
|
||||
|
||||
func WithRetry(settings RetrySettings) GenericOption {
|
||||
return newGenericOption(func(cfg *Config) {
|
||||
cfg.RetrySettings = settings
|
||||
})
|
||||
}
|
||||
|
||||
func WithTLSClientConfig(tlsCfg *tls.Config) GenericOption {
|
||||
return newSplitOption(func(cfg *Config) {
|
||||
cfg.Traces.TLSCfg = tlsCfg.Clone()
|
||||
}, func(cfg *Config) {
|
||||
cfg.Traces.GRPCCredentials = credentials.NewTLS(tlsCfg)
|
||||
})
|
||||
}
|
||||
|
||||
func WithTracesTLSClientConfig(tlsCfg *tls.Config) GenericOption {
|
||||
return newSplitOption(func(cfg *Config) {
|
||||
cfg.Traces.TLSCfg = tlsCfg.Clone()
|
||||
}, func(cfg *Config) {
|
||||
cfg.Traces.GRPCCredentials = credentials.NewTLS(tlsCfg)
|
||||
})
|
||||
}
|
||||
|
||||
func WithInsecure() GenericOption {
|
||||
return newGenericOption(func(cfg *Config) {
|
||||
cfg.Traces.Insecure = true
|
||||
})
|
||||
}
|
||||
|
||||
func WithSecure() GenericOption {
|
||||
return newGenericOption(func(cfg *Config) {
|
||||
cfg.Traces.Insecure = false
|
||||
})
|
||||
}
|
||||
|
||||
func WithInsecureTraces() GenericOption {
|
||||
return newGenericOption(func(cfg *Config) {
|
||||
cfg.Traces.Insecure = true
|
||||
})
|
||||
}
|
||||
|
||||
func WithSecureTraces() GenericOption {
|
||||
return newGenericOption(func(cfg *Config) {
|
||||
cfg.Traces.Insecure = false
|
||||
})
|
||||
}
|
||||
|
||||
func WithHeaders(headers map[string]string) GenericOption {
|
||||
return newGenericOption(func(cfg *Config) {
|
||||
cfg.Traces.Headers = headers
|
||||
})
|
||||
}
|
||||
|
||||
func WithTracesHeaders(headers map[string]string) GenericOption {
|
||||
return newGenericOption(func(cfg *Config) {
|
||||
cfg.Traces.Headers = headers
|
||||
})
|
||||
}
|
||||
|
||||
func WithTimeout(duration time.Duration) GenericOption {
|
||||
return newGenericOption(func(cfg *Config) {
|
||||
cfg.Traces.Timeout = duration
|
||||
})
|
||||
}
|
||||
|
||||
func WithTracesTimeout(duration time.Duration) GenericOption {
|
||||
return newGenericOption(func(cfg *Config) {
|
||||
cfg.Traces.Timeout = duration
|
||||
})
|
||||
}
|
||||
|
||||
func WithMaxAttempts(maxAttempts int) GenericOption {
|
||||
return newGenericOption(func(cfg *Config) {
|
||||
cfg.MaxAttempts = maxAttempts
|
||||
})
|
||||
}
|
||||
|
||||
func WithBackoff(duration time.Duration) GenericOption {
|
||||
return newGenericOption(func(cfg *Config) {
|
||||
cfg.Backoff = duration
|
||||
})
|
||||
}
|
460
exporters/otlp/otlptrace/internal/otlpconfig/options_test.go
Normal file
460
exporters/otlp/otlptrace/internal/otlpconfig/options_test.go
Normal file
@ -0,0 +1,460 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package otlpconfig_test
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/otlpconfig"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const (
|
||||
WeakCertificate = `
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBhzCCASygAwIBAgIRANHpHgAWeTnLZpTSxCKs0ggwCgYIKoZIzj0EAwIwEjEQ
|
||||
MA4GA1UEChMHb3RlbC1nbzAeFw0yMTA0MDExMzU5MDNaFw0yMTA0MDExNDU5MDNa
|
||||
MBIxEDAOBgNVBAoTB290ZWwtZ28wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS9
|
||||
nWSkmPCxShxnp43F+PrOtbGV7sNfkbQ/kxzi9Ego0ZJdiXxkmv/C05QFddCW7Y0Z
|
||||
sJCLHGogQsYnWJBXUZOVo2MwYTAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYI
|
||||
KwYBBQUHAwEwDAYDVR0TAQH/BAIwADAsBgNVHREEJTAjgglsb2NhbGhvc3SHEAAA
|
||||
AAAAAAAAAAAAAAAAAAGHBH8AAAEwCgYIKoZIzj0EAwIDSQAwRgIhANwZVVKvfvQ/
|
||||
1HXsTvgH+xTQswOwSSKYJ1cVHQhqK7ZbAiEAus8NxpTRnp5DiTMuyVmhVNPB+bVH
|
||||
Lhnm4N/QDk5rek0=
|
||||
-----END CERTIFICATE-----
|
||||
`
|
||||
WeakPrivateKey = `
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgN8HEXiXhvByrJ1zK
|
||||
SFT6Y2l2KqDWwWzKf+t4CyWrNKehRANCAAS9nWSkmPCxShxnp43F+PrOtbGV7sNf
|
||||
kbQ/kxzi9Ego0ZJdiXxkmv/C05QFddCW7Y0ZsJCLHGogQsYnWJBXUZOV
|
||||
-----END PRIVATE KEY-----
|
||||
`
|
||||
)
|
||||
|
||||
type env map[string]string
|
||||
|
||||
func (e *env) getEnv(env string) string {
|
||||
return (*e)[env]
|
||||
}
|
||||
|
||||
type fileReader map[string][]byte
|
||||
|
||||
func (f *fileReader) readFile(filename string) ([]byte, error) {
|
||||
if b, ok := (*f)[filename]; ok {
|
||||
return b, nil
|
||||
}
|
||||
return nil, errors.New("File not found")
|
||||
}
|
||||
|
||||
func TestConfigs(t *testing.T) {
|
||||
tlsCert, err := otlpconfig.CreateTLSConfig([]byte(WeakCertificate))
|
||||
assert.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
opts []otlpconfig.GenericOption
|
||||
env env
|
||||
fileReader fileReader
|
||||
asserts func(t *testing.T, c *otlpconfig.Config, grpcOption bool)
|
||||
}{
|
||||
{
|
||||
name: "Test default configs",
|
||||
asserts: func(t *testing.T, c *otlpconfig.Config, grpcOption bool) {
|
||||
assert.Equal(t, "localhost:4317", c.Traces.Endpoint)
|
||||
assert.Equal(t, otlpconfig.NoCompression, c.Traces.Compression)
|
||||
assert.Equal(t, map[string]string(nil), c.Traces.Headers)
|
||||
assert.Equal(t, 10*time.Second, c.Traces.Timeout)
|
||||
},
|
||||
},
|
||||
|
||||
// Endpoint Tests
|
||||
{
|
||||
name: "Test With Endpoint",
|
||||
opts: []otlpconfig.GenericOption{
|
||||
otlpconfig.WithEndpoint("someendpoint"),
|
||||
},
|
||||
asserts: func(t *testing.T, c *otlpconfig.Config, grpcOption bool) {
|
||||
assert.Equal(t, "someendpoint", c.Traces.Endpoint)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test With Signal Specific Endpoint",
|
||||
opts: []otlpconfig.GenericOption{
|
||||
otlpconfig.WithEndpoint("overrode_by_signal_specific"),
|
||||
otlpconfig.WithTracesEndpoint("traces_endpoint"),
|
||||
},
|
||||
asserts: func(t *testing.T, c *otlpconfig.Config, grpcOption bool) {
|
||||
assert.Equal(t, "traces_endpoint", c.Traces.Endpoint)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Environment Endpoint",
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "env_endpoint",
|
||||
},
|
||||
asserts: func(t *testing.T, c *otlpconfig.Config, grpcOption bool) {
|
||||
assert.Equal(t, "env_endpoint", c.Traces.Endpoint)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Environment Signal Specific Endpoint",
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "overrode_by_signal_specific",
|
||||
"OTEL_EXPORTER_OTLP_TRACES_ENDPOINT": "env_traces_endpoint",
|
||||
},
|
||||
asserts: func(t *testing.T, c *otlpconfig.Config, grpcOption bool) {
|
||||
assert.Equal(t, "env_traces_endpoint", c.Traces.Endpoint)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Mixed Environment and With Endpoint",
|
||||
opts: []otlpconfig.GenericOption{
|
||||
otlpconfig.WithTracesEndpoint("traces_endpoint"),
|
||||
},
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "env_endpoint",
|
||||
},
|
||||
asserts: func(t *testing.T, c *otlpconfig.Config, grpcOption bool) {
|
||||
assert.Equal(t, "traces_endpoint", c.Traces.Endpoint)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Environment Endpoint with HTTP scheme",
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://env_endpoint",
|
||||
},
|
||||
asserts: func(t *testing.T, c *otlpconfig.Config, grpcOption bool) {
|
||||
assert.Equal(t, "env_endpoint", c.Traces.Endpoint)
|
||||
assert.Equal(t, true, c.Traces.Insecure)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Environment Endpoint with HTTP scheme and leading & trailingspaces",
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": " http://env_endpoint ",
|
||||
},
|
||||
asserts: func(t *testing.T, c *otlpconfig.Config, grpcOption bool) {
|
||||
assert.Equal(t, "env_endpoint", c.Traces.Endpoint)
|
||||
assert.Equal(t, true, c.Traces.Insecure)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Environment Endpoint with HTTPS scheme",
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "https://env_endpoint",
|
||||
},
|
||||
asserts: func(t *testing.T, c *otlpconfig.Config, grpcOption bool) {
|
||||
assert.Equal(t, "env_endpoint", c.Traces.Endpoint)
|
||||
assert.Equal(t, false, c.Traces.Insecure)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Environment Signal Specific Endpoint",
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://overrode_by_signal_specific",
|
||||
"OTEL_EXPORTER_OTLP_TRACES_ENDPOINT": "http://env_traces_endpoint",
|
||||
},
|
||||
asserts: func(t *testing.T, c *otlpconfig.Config, grpcOption bool) {
|
||||
assert.Equal(t, "env_traces_endpoint", c.Traces.Endpoint)
|
||||
assert.Equal(t, true, c.Traces.Insecure)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Environment Signal Specific Endpoint #2",
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://overrode_by_signal_specific",
|
||||
"OTEL_EXPORTER_OTLP_TRACES_ENDPOINT": "http://env_traces_endpoint",
|
||||
},
|
||||
asserts: func(t *testing.T, c *otlpconfig.Config, grpcOption bool) {
|
||||
assert.Equal(t, "env_traces_endpoint", c.Traces.Endpoint)
|
||||
assert.Equal(t, true, c.Traces.Insecure)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Environment Signal Specific Endpoint with uppercase scheme",
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "HTTP://overrode_by_signal_specific",
|
||||
"OTEL_EXPORTER_OTLP_TRACES_ENDPOINT": "HtTp://env_traces_endpoint",
|
||||
},
|
||||
asserts: func(t *testing.T, c *otlpconfig.Config, grpcOption bool) {
|
||||
assert.Equal(t, "env_traces_endpoint", c.Traces.Endpoint)
|
||||
assert.Equal(t, true, c.Traces.Insecure)
|
||||
},
|
||||
},
|
||||
|
||||
// Certificate tests
|
||||
{
|
||||
name: "Test With Certificate",
|
||||
opts: []otlpconfig.GenericOption{
|
||||
otlpconfig.WithTLSClientConfig(tlsCert),
|
||||
},
|
||||
asserts: func(t *testing.T, c *otlpconfig.Config, grpcOption bool) {
|
||||
if grpcOption {
|
||||
//TODO: make sure gRPC's credentials actually works
|
||||
assert.NotNil(t, c.Traces.GRPCCredentials)
|
||||
} else {
|
||||
assert.Equal(t, tlsCert.RootCAs.Subjects(), c.Traces.TLSCfg.RootCAs.Subjects())
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test With Signal Specific Certificate",
|
||||
opts: []otlpconfig.GenericOption{
|
||||
otlpconfig.WithTLSClientConfig(&tls.Config{}),
|
||||
otlpconfig.WithTracesTLSClientConfig(tlsCert),
|
||||
},
|
||||
asserts: func(t *testing.T, c *otlpconfig.Config, grpcOption bool) {
|
||||
|
||||
if grpcOption {
|
||||
assert.NotNil(t, c.Traces.GRPCCredentials)
|
||||
} else {
|
||||
assert.Equal(t, tlsCert.RootCAs.Subjects(), c.Traces.TLSCfg.RootCAs.Subjects())
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Environment Certificate",
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_CERTIFICATE": "cert_path",
|
||||
},
|
||||
fileReader: fileReader{
|
||||
"cert_path": []byte(WeakCertificate),
|
||||
},
|
||||
asserts: func(t *testing.T, c *otlpconfig.Config, grpcOption bool) {
|
||||
if grpcOption {
|
||||
assert.NotNil(t, c.Traces.GRPCCredentials)
|
||||
} else {
|
||||
assert.Equal(t, tlsCert.RootCAs.Subjects(), c.Traces.TLSCfg.RootCAs.Subjects())
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Environment Signal Specific Certificate",
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_CERTIFICATE": "overrode_by_signal_specific",
|
||||
"OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE": "cert_path",
|
||||
},
|
||||
fileReader: fileReader{
|
||||
"cert_path": []byte(WeakCertificate),
|
||||
"invalid_cert": []byte("invalid certificate file."),
|
||||
},
|
||||
asserts: func(t *testing.T, c *otlpconfig.Config, grpcOption bool) {
|
||||
if grpcOption {
|
||||
assert.NotNil(t, c.Traces.GRPCCredentials)
|
||||
} else {
|
||||
assert.Equal(t, tlsCert.RootCAs.Subjects(), c.Traces.TLSCfg.RootCAs.Subjects())
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Mixed Environment and With Certificate",
|
||||
opts: []otlpconfig.GenericOption{},
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_CERTIFICATE": "cert_path",
|
||||
},
|
||||
fileReader: fileReader{
|
||||
"cert_path": []byte(WeakCertificate),
|
||||
},
|
||||
asserts: func(t *testing.T, c *otlpconfig.Config, grpcOption bool) {
|
||||
if grpcOption {
|
||||
assert.NotNil(t, c.Traces.GRPCCredentials)
|
||||
} else {
|
||||
assert.Equal(t, tlsCert.RootCAs.Subjects(), c.Traces.TLSCfg.RootCAs.Subjects())
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
// Headers tests
|
||||
{
|
||||
name: "Test With Headers",
|
||||
opts: []otlpconfig.GenericOption{
|
||||
otlpconfig.WithHeaders(map[string]string{"h1": "v1"}),
|
||||
},
|
||||
asserts: func(t *testing.T, c *otlpconfig.Config, grpcOption bool) {
|
||||
assert.Equal(t, map[string]string{"h1": "v1"}, c.Traces.Headers)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test With Signal Specific Headers",
|
||||
opts: []otlpconfig.GenericOption{
|
||||
otlpconfig.WithHeaders(map[string]string{"overrode": "by_signal_specific"}),
|
||||
otlpconfig.WithTracesHeaders(map[string]string{"t1": "tv1"}),
|
||||
},
|
||||
asserts: func(t *testing.T, c *otlpconfig.Config, grpcOption bool) {
|
||||
assert.Equal(t, map[string]string{"t1": "tv1"}, c.Traces.Headers)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Environment Headers",
|
||||
env: map[string]string{"OTEL_EXPORTER_OTLP_HEADERS": "h1=v1,h2=v2"},
|
||||
asserts: func(t *testing.T, c *otlpconfig.Config, grpcOption bool) {
|
||||
assert.Equal(t, map[string]string{"h1": "v1", "h2": "v2"}, c.Traces.Headers)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Environment Signal Specific Headers",
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_HEADERS": "overrode_by_signal_specific",
|
||||
"OTEL_EXPORTER_OTLP_TRACES_HEADERS": "h1=v1,h2=v2",
|
||||
},
|
||||
asserts: func(t *testing.T, c *otlpconfig.Config, grpcOption bool) {
|
||||
assert.Equal(t, map[string]string{"h1": "v1", "h2": "v2"}, c.Traces.Headers)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Mixed Environment and With Headers",
|
||||
env: map[string]string{"OTEL_EXPORTER_OTLP_HEADERS": "h1=v1,h2=v2"},
|
||||
opts: []otlpconfig.GenericOption{},
|
||||
asserts: func(t *testing.T, c *otlpconfig.Config, grpcOption bool) {
|
||||
assert.Equal(t, map[string]string{"h1": "v1", "h2": "v2"}, c.Traces.Headers)
|
||||
},
|
||||
},
|
||||
|
||||
// Compression Tests
|
||||
{
|
||||
name: "Test With Compression",
|
||||
opts: []otlpconfig.GenericOption{
|
||||
otlpconfig.WithCompression(otlpconfig.GzipCompression),
|
||||
},
|
||||
asserts: func(t *testing.T, c *otlpconfig.Config, grpcOption bool) {
|
||||
assert.Equal(t, otlpconfig.GzipCompression, c.Traces.Compression)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test With Signal Specific Compression",
|
||||
opts: []otlpconfig.GenericOption{
|
||||
otlpconfig.WithCompression(otlpconfig.NoCompression), // overrode by signal specific configs
|
||||
otlpconfig.WithTracesCompression(otlpconfig.GzipCompression),
|
||||
},
|
||||
asserts: func(t *testing.T, c *otlpconfig.Config, grpcOption bool) {
|
||||
assert.Equal(t, otlpconfig.GzipCompression, c.Traces.Compression)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Environment Compression",
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_COMPRESSION": "gzip",
|
||||
},
|
||||
asserts: func(t *testing.T, c *otlpconfig.Config, grpcOption bool) {
|
||||
assert.Equal(t, otlpconfig.GzipCompression, c.Traces.Compression)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Environment Signal Specific Compression",
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_TRACES_COMPRESSION": "gzip",
|
||||
},
|
||||
asserts: func(t *testing.T, c *otlpconfig.Config, grpcOption bool) {
|
||||
assert.Equal(t, otlpconfig.GzipCompression, c.Traces.Compression)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Mixed Environment and With Compression",
|
||||
opts: []otlpconfig.GenericOption{
|
||||
otlpconfig.WithTracesCompression(otlpconfig.NoCompression),
|
||||
},
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_TRACES_COMPRESSION": "gzip",
|
||||
},
|
||||
asserts: func(t *testing.T, c *otlpconfig.Config, grpcOption bool) {
|
||||
assert.Equal(t, otlpconfig.NoCompression, c.Traces.Compression)
|
||||
},
|
||||
},
|
||||
|
||||
// Timeout Tests
|
||||
{
|
||||
name: "Test With Timeout",
|
||||
opts: []otlpconfig.GenericOption{
|
||||
otlpconfig.WithTimeout(time.Duration(5 * time.Second)),
|
||||
},
|
||||
asserts: func(t *testing.T, c *otlpconfig.Config, grpcOption bool) {
|
||||
assert.Equal(t, 5*time.Second, c.Traces.Timeout)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test With Signal Specific Timeout",
|
||||
opts: []otlpconfig.GenericOption{
|
||||
otlpconfig.WithTimeout(time.Duration(5 * time.Second)),
|
||||
otlpconfig.WithTracesTimeout(time.Duration(13 * time.Second)),
|
||||
},
|
||||
asserts: func(t *testing.T, c *otlpconfig.Config, grpcOption bool) {
|
||||
assert.Equal(t, 13*time.Second, c.Traces.Timeout)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Environment Timeout",
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_TIMEOUT": "15000",
|
||||
},
|
||||
asserts: func(t *testing.T, c *otlpconfig.Config, grpcOption bool) {
|
||||
assert.Equal(t, c.Traces.Timeout, 15*time.Second)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Environment Signal Specific Timeout",
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_TIMEOUT": "15000",
|
||||
"OTEL_EXPORTER_OTLP_TRACES_TIMEOUT": "27000",
|
||||
},
|
||||
asserts: func(t *testing.T, c *otlpconfig.Config, grpcOption bool) {
|
||||
assert.Equal(t, c.Traces.Timeout, 27*time.Second)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Mixed Environment and With Timeout",
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_TIMEOUT": "15000",
|
||||
"OTEL_EXPORTER_OTLP_TRACES_TIMEOUT": "27000",
|
||||
},
|
||||
opts: []otlpconfig.GenericOption{
|
||||
otlpconfig.WithTracesTimeout(5 * time.Second),
|
||||
},
|
||||
asserts: func(t *testing.T, c *otlpconfig.Config, grpcOption bool) {
|
||||
assert.Equal(t, c.Traces.Timeout, 5*time.Second)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
e := otlpconfig.EnvOptionsReader{
|
||||
GetEnv: tt.env.getEnv,
|
||||
ReadFile: tt.fileReader.readFile,
|
||||
}
|
||||
|
||||
// Tests Generic options as HTTP Options
|
||||
cfg := otlpconfig.NewDefaultConfig()
|
||||
e.ApplyHTTPEnvConfigs(&cfg)
|
||||
for _, opt := range tt.opts {
|
||||
opt.ApplyHTTPOption(&cfg)
|
||||
}
|
||||
tt.asserts(t, &cfg, false)
|
||||
|
||||
// Tests Generic options as gRPC Options
|
||||
cfg = otlpconfig.NewDefaultConfig()
|
||||
e.ApplyGRPCEnvConfigs(&cfg)
|
||||
for _, opt := range tt.opts {
|
||||
opt.ApplyGRPCOption(&cfg)
|
||||
}
|
||||
tt.asserts(t, &cfg, true)
|
||||
})
|
||||
}
|
||||
}
|
64
exporters/otlp/otlptrace/internal/otlpconfig/optiontypes.go
Normal file
64
exporters/otlp/otlptrace/internal/otlpconfig/optiontypes.go
Normal file
@ -0,0 +1,64 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package otlpconfig // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/otlpconfig"
|
||||
|
||||
import "time"
|
||||
|
||||
const (
|
||||
// DefaultCollectorPort is the port the Exporter will attempt connect to
|
||||
// if no collector port is provided.
|
||||
DefaultCollectorPort uint16 = 4317
|
||||
// DefaultCollectorHost is the host address the Exporter will attempt
|
||||
// connect to if no collector address is provided.
|
||||
DefaultCollectorHost string = "localhost"
|
||||
)
|
||||
|
||||
// Compression describes the compression used for payloads sent to the
|
||||
// collector.
|
||||
type Compression int
|
||||
|
||||
const (
|
||||
// NoCompression tells the driver to send payloads without
|
||||
// compression.
|
||||
NoCompression Compression = iota
|
||||
// GzipCompression tells the driver to send payloads after
|
||||
// compressing them with gzip.
|
||||
GzipCompression
|
||||
)
|
||||
|
||||
// Marshaler describes the kind of message format sent to the collector
|
||||
type Marshaler int
|
||||
|
||||
const (
|
||||
// MarshalProto tells the driver to send using the protobuf binary format.
|
||||
MarshalProto Marshaler = iota
|
||||
// MarshalJSON tells the driver to send using json format.
|
||||
MarshalJSON
|
||||
)
|
||||
|
||||
// RetrySettings defines configuration for retrying batches in case of export failure
|
||||
// using an exponential backoff.
|
||||
type RetrySettings struct {
|
||||
// Enabled indicates whether to not retry sending batches in case of export failure.
|
||||
Enabled bool
|
||||
// InitialInterval the time to wait after the first failure before retrying.
|
||||
InitialInterval time.Duration
|
||||
// MaxInterval is the upper bound on backoff interval. Once this value is reached the delay between
|
||||
// consecutive retries will always be `MaxInterval`.
|
||||
MaxInterval time.Duration
|
||||
// MaxElapsedTime is the maximum amount of time (including retries) spent trying to send a request/batch.
|
||||
// Once this value is reached, the data is discarded.
|
||||
MaxElapsedTime time.Duration
|
||||
}
|
34
exporters/otlp/otlptrace/internal/otlpconfig/tls.go
Normal file
34
exporters/otlp/otlptrace/internal/otlpconfig/tls.go
Normal file
@ -0,0 +1,34 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package otlpconfig
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// CreateTLSConfig creates a tls.Config from a raw certificate bytes
|
||||
// to verify a server certificate.
|
||||
func CreateTLSConfig(certBytes []byte) (*tls.Config, error) {
|
||||
cp := x509.NewCertPool()
|
||||
if ok := cp.AppendCertsFromPEM(certBytes); !ok {
|
||||
return nil, errors.New("failed to append certificate to the cert pool")
|
||||
}
|
||||
|
||||
return &tls.Config{
|
||||
RootCAs: cp,
|
||||
}, nil
|
||||
}
|
116
exporters/otlp/otlptrace/internal/otlptracetest/client.go
Normal file
116
exporters/otlp/otlptrace/internal/otlptracetest/client.go
Normal file
@ -0,0 +1,116 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package otlptracetest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
|
||||
)
|
||||
|
||||
func RunExporterShutdownTest(t *testing.T, factory func() otlptrace.Client) {
|
||||
t.Run("testClientStopHonorsTimeout", func(t *testing.T) {
|
||||
testClientStopHonorsTimeout(t, factory())
|
||||
})
|
||||
|
||||
t.Run("testClientStopHonorsCancel", func(t *testing.T) {
|
||||
testClientStopHonorsCancel(t, factory())
|
||||
})
|
||||
|
||||
t.Run("testClientStopNoError", func(t *testing.T) {
|
||||
testClientStopNoError(t, factory())
|
||||
})
|
||||
|
||||
t.Run("testClientStopManyTimes", func(t *testing.T) {
|
||||
testClientStopManyTimes(t, factory())
|
||||
})
|
||||
}
|
||||
|
||||
func initializeExporter(t *testing.T, client otlptrace.Client) *otlptrace.Exporter {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
e, err := otlptrace.NewExporter(ctx, client)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create exporter")
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
func testClientStopHonorsTimeout(t *testing.T, client otlptrace.Client) {
|
||||
e := initializeExporter(t, client)
|
||||
|
||||
innerCtx, innerCancel := context.WithTimeout(context.Background(), time.Microsecond)
|
||||
<-innerCtx.Done()
|
||||
if err := e.Shutdown(innerCtx); err == nil {
|
||||
t.Error("expected context DeadlineExceeded error, got nil")
|
||||
} else if !errors.Is(err, context.DeadlineExceeded) {
|
||||
t.Errorf("expected context DeadlineExceeded error, got %v", err)
|
||||
}
|
||||
innerCancel()
|
||||
}
|
||||
|
||||
func testClientStopHonorsCancel(t *testing.T, client otlptrace.Client) {
|
||||
e := initializeExporter(t, client)
|
||||
|
||||
ctx, innerCancel := context.WithCancel(context.Background())
|
||||
innerCancel()
|
||||
if err := e.Shutdown(ctx); err == nil {
|
||||
t.Error("expected context canceled error, got nil")
|
||||
} else if !errors.Is(err, context.Canceled) {
|
||||
t.Errorf("expected context canceled error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func testClientStopNoError(t *testing.T, client otlptrace.Client) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
e := initializeExporter(t, client)
|
||||
if err := e.Shutdown(ctx); err != nil {
|
||||
t.Errorf("shutdown errored: expected nil, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func testClientStopManyTimes(t *testing.T, client otlptrace.Client) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
|
||||
defer cancel()
|
||||
e := initializeExporter(t, client)
|
||||
|
||||
ch := make(chan struct{})
|
||||
wg := sync.WaitGroup{}
|
||||
const num int = 20
|
||||
wg.Add(num)
|
||||
errs := make([]error, num)
|
||||
for i := 0; i < num; i++ {
|
||||
go func(idx int) {
|
||||
defer wg.Done()
|
||||
<-ch
|
||||
errs[idx] = e.Shutdown(ctx)
|
||||
}(i)
|
||||
}
|
||||
close(ch)
|
||||
wg.Wait()
|
||||
for _, err := range errs {
|
||||
if err != nil {
|
||||
t.Fatalf("failed to shutdown exporter: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
105
exporters/otlp/otlptrace/internal/otlptracetest/collector.go
Normal file
105
exporters/otlp/otlptrace/internal/otlptracetest/collector.go
Normal file
@ -0,0 +1,105 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package otlptracetest
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
collectortracepb "go.opentelemetry.io/proto/otlp/collector/trace/v1"
|
||||
commonpb "go.opentelemetry.io/proto/otlp/common/v1"
|
||||
resourcepb "go.opentelemetry.io/proto/otlp/resource/v1"
|
||||
tracepb "go.opentelemetry.io/proto/otlp/trace/v1"
|
||||
)
|
||||
|
||||
// TracesCollector mocks a collector for the end-to-end testing.
|
||||
type TracesCollector interface {
|
||||
Stop() error
|
||||
GetResourceSpans() []*tracepb.ResourceSpans
|
||||
}
|
||||
|
||||
// SpansStorage stores the spans. Mock collectors can use it to
|
||||
// store spans they have received.
|
||||
type SpansStorage struct {
|
||||
rsm map[string]*tracepb.ResourceSpans
|
||||
spanCount int
|
||||
}
|
||||
|
||||
// NewSpansStorage creates a new spans storage.
|
||||
func NewSpansStorage() SpansStorage {
|
||||
return SpansStorage{
|
||||
rsm: make(map[string]*tracepb.ResourceSpans),
|
||||
}
|
||||
}
|
||||
|
||||
// AddSpans adds spans to the spans storage.
|
||||
func (s *SpansStorage) AddSpans(request *collectortracepb.ExportTraceServiceRequest) {
|
||||
for _, rs := range request.GetResourceSpans() {
|
||||
rstr := resourceString(rs.Resource)
|
||||
if existingRs, ok := s.rsm[rstr]; !ok {
|
||||
s.rsm[rstr] = rs
|
||||
// TODO (rghetia): Add support for library Info.
|
||||
if len(rs.InstrumentationLibrarySpans) == 0 {
|
||||
rs.InstrumentationLibrarySpans = []*tracepb.InstrumentationLibrarySpans{
|
||||
{
|
||||
Spans: []*tracepb.Span{},
|
||||
},
|
||||
}
|
||||
}
|
||||
s.spanCount += len(rs.InstrumentationLibrarySpans[0].Spans)
|
||||
} else {
|
||||
if len(rs.InstrumentationLibrarySpans) > 0 {
|
||||
newSpans := rs.InstrumentationLibrarySpans[0].GetSpans()
|
||||
existingRs.InstrumentationLibrarySpans[0].Spans =
|
||||
append(existingRs.InstrumentationLibrarySpans[0].Spans,
|
||||
newSpans...)
|
||||
s.spanCount += len(newSpans)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetSpans returns the stored spans.
|
||||
func (s *SpansStorage) GetSpans() []*tracepb.Span {
|
||||
spans := make([]*tracepb.Span, 0, s.spanCount)
|
||||
for _, rs := range s.rsm {
|
||||
spans = append(spans, rs.InstrumentationLibrarySpans[0].Spans...)
|
||||
}
|
||||
return spans
|
||||
}
|
||||
|
||||
// GetResourceSpans returns the stored resource spans.
|
||||
func (s *SpansStorage) GetResourceSpans() []*tracepb.ResourceSpans {
|
||||
rss := make([]*tracepb.ResourceSpans, 0, len(s.rsm))
|
||||
for _, rs := range s.rsm {
|
||||
rss = append(rss, rs)
|
||||
}
|
||||
return rss
|
||||
}
|
||||
|
||||
func resourceString(res *resourcepb.Resource) string {
|
||||
sAttrs := sortedAttributes(res.GetAttributes())
|
||||
rstr := ""
|
||||
for _, attr := range sAttrs {
|
||||
rstr = rstr + attr.String()
|
||||
}
|
||||
return rstr
|
||||
}
|
||||
|
||||
func sortedAttributes(attrs []*commonpb.KeyValue) []*commonpb.KeyValue {
|
||||
sort.Slice(attrs[:], func(i, j int) bool {
|
||||
return attrs[i].Key < attrs[j].Key
|
||||
})
|
||||
return attrs
|
||||
}
|
63
exporters/otlp/otlptrace/internal/otlptracetest/data.go
Normal file
63
exporters/otlp/otlptrace/internal/otlptracetest/data.go
Normal file
@ -0,0 +1,63 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package otlptracetest
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"go.opentelemetry.io/otel/sdk/instrumentation"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
tracesdk "go.opentelemetry.io/otel/sdk/trace"
|
||||
"go.opentelemetry.io/otel/sdk/trace/tracetest"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
// SingleReadOnlySpan returns a one-element slice with a read-only span. It
|
||||
// may be useful for testing driver's trace export.
|
||||
func SingleReadOnlySpan() []tracesdk.ReadOnlySpan {
|
||||
return tracetest.SpanStubs{
|
||||
{
|
||||
SpanContext: trace.NewSpanContext(trace.SpanContextConfig{
|
||||
TraceID: trace.TraceID{2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 4, 5, 6, 7, 8, 9},
|
||||
SpanID: trace.SpanID{3, 4, 5, 6, 7, 8, 9, 0},
|
||||
TraceFlags: trace.FlagsSampled,
|
||||
}),
|
||||
Parent: trace.NewSpanContext(trace.SpanContextConfig{
|
||||
TraceID: trace.TraceID{2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 4, 5, 6, 7, 8, 9},
|
||||
SpanID: trace.SpanID{1, 2, 3, 4, 5, 6, 7, 8},
|
||||
TraceFlags: trace.FlagsSampled,
|
||||
}),
|
||||
SpanKind: trace.SpanKindInternal,
|
||||
Name: "foo",
|
||||
StartTime: time.Date(2020, time.December, 8, 20, 23, 0, 0, time.UTC),
|
||||
EndTime: time.Date(2020, time.December, 0, 20, 24, 0, 0, time.UTC),
|
||||
Attributes: []attribute.KeyValue{},
|
||||
Events: []tracesdk.Event{},
|
||||
Links: []trace.Link{},
|
||||
Status: tracesdk.Status{Code: codes.Ok},
|
||||
DroppedAttributes: 0,
|
||||
DroppedEvents: 0,
|
||||
DroppedLinks: 0,
|
||||
ChildSpanCount: 0,
|
||||
Resource: resource.NewWithAttributes(attribute.String("a", "b")),
|
||||
InstrumentationLibrary: instrumentation.Library{
|
||||
Name: "bar",
|
||||
Version: "0.0.0",
|
||||
},
|
||||
},
|
||||
}.Snapshots()
|
||||
}
|
124
exporters/otlp/otlptrace/internal/otlptracetest/otlptest.go
Normal file
124
exporters/otlp/otlptrace/internal/otlptracetest/otlptest.go
Normal file
@ -0,0 +1,124 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package otlptracetest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
commonpb "go.opentelemetry.io/proto/otlp/common/v1"
|
||||
)
|
||||
|
||||
// RunEndToEndTest can be used by otlptrace.Client tests to validate
|
||||
// themselves.
|
||||
func RunEndToEndTest(ctx context.Context, t *testing.T, exp *otlptrace.Exporter, tracesCollector TracesCollector) {
|
||||
pOpts := []sdktrace.TracerProviderOption{
|
||||
sdktrace.WithSampler(sdktrace.AlwaysSample()),
|
||||
sdktrace.WithBatcher(
|
||||
exp,
|
||||
// add following two options to ensure flush
|
||||
sdktrace.WithBatchTimeout(5*time.Second),
|
||||
sdktrace.WithMaxExportBatchSize(10),
|
||||
),
|
||||
}
|
||||
tp1 := sdktrace.NewTracerProvider(append(pOpts,
|
||||
sdktrace.WithResource(resource.NewWithAttributes(
|
||||
attribute.String("rk1", "rv11)"),
|
||||
attribute.Int64("rk2", 5),
|
||||
)))...)
|
||||
|
||||
tp2 := sdktrace.NewTracerProvider(append(pOpts,
|
||||
sdktrace.WithResource(resource.NewWithAttributes(
|
||||
attribute.String("rk1", "rv12)"),
|
||||
attribute.Float64("rk3", 6.5),
|
||||
)))...)
|
||||
|
||||
tr1 := tp1.Tracer("test-tracer1")
|
||||
tr2 := tp2.Tracer("test-tracer2")
|
||||
// Now create few spans
|
||||
m := 4
|
||||
for i := 0; i < m; i++ {
|
||||
_, span := tr1.Start(ctx, "AlwaysSample")
|
||||
span.SetAttributes(attribute.Int64("i", int64(i)))
|
||||
span.End()
|
||||
|
||||
_, span = tr2.Start(ctx, "AlwaysSample")
|
||||
span.SetAttributes(attribute.Int64("i", int64(i)))
|
||||
span.End()
|
||||
}
|
||||
|
||||
func() {
|
||||
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
defer cancel()
|
||||
if err := tp1.Shutdown(ctx); err != nil {
|
||||
t.Fatalf("failed to shut down a tracer provider 1: %v", err)
|
||||
}
|
||||
if err := tp2.Shutdown(ctx); err != nil {
|
||||
t.Fatalf("failed to shut down a tracer provider 2: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait >2 cycles.
|
||||
<-time.After(40 * time.Millisecond)
|
||||
|
||||
// Now shutdown the exporter
|
||||
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
defer cancel()
|
||||
if err := exp.Shutdown(ctx); err != nil {
|
||||
t.Fatalf("failed to stop the exporter: %v", err)
|
||||
}
|
||||
|
||||
// Shutdown the collector too so that we can begin
|
||||
// verification checks of expected data back.
|
||||
_ = tracesCollector.Stop()
|
||||
|
||||
// Now verify that we only got two resources
|
||||
rss := tracesCollector.GetResourceSpans()
|
||||
if got, want := len(rss), 2; got != want {
|
||||
t.Fatalf("resource span count: got %d, want %d\n", got, want)
|
||||
}
|
||||
|
||||
// Now verify spans and attributes for each resource span.
|
||||
for _, rs := range rss {
|
||||
if len(rs.InstrumentationLibrarySpans) == 0 {
|
||||
t.Fatalf("zero Instrumentation Library Spans")
|
||||
}
|
||||
if got, want := len(rs.InstrumentationLibrarySpans[0].Spans), m; got != want {
|
||||
t.Fatalf("span counts: got %d, want %d", got, want)
|
||||
}
|
||||
attrMap := map[int64]bool{}
|
||||
for _, s := range rs.InstrumentationLibrarySpans[0].Spans {
|
||||
if gotName, want := s.Name, "AlwaysSample"; gotName != want {
|
||||
t.Fatalf("span name: got %s, want %s", gotName, want)
|
||||
}
|
||||
attrMap[s.Attributes[0].Value.Value.(*commonpb.AnyValue_IntValue).IntValue] = true
|
||||
}
|
||||
if got, want := len(attrMap), m; got != want {
|
||||
t.Fatalf("span attribute unique values: got %d want %d", got, want)
|
||||
}
|
||||
for i := 0; i < m; i++ {
|
||||
_, ok := attrMap[int64(i)]
|
||||
if !ok {
|
||||
t.Fatalf("span with attribute %d missing", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
141
exporters/otlp/otlptrace/internal/tracetransform/attribute.go
Normal file
141
exporters/otlp/otlptrace/internal/tracetransform/attribute.go
Normal file
@ -0,0 +1,141 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tracetransform
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
commonpb "go.opentelemetry.io/proto/otlp/common/v1"
|
||||
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
)
|
||||
|
||||
// Attributes transforms a slice of KeyValues into a slice of OTLP attribute key-values.
|
||||
func Attributes(attrs []attribute.KeyValue) []*commonpb.KeyValue {
|
||||
if len(attrs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
out := make([]*commonpb.KeyValue, 0, len(attrs))
|
||||
for _, kv := range attrs {
|
||||
out = append(out, toAttribute(kv))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// ResourceAttributes transforms a Resource into a slice of OTLP attribute key-values.
|
||||
func ResourceAttributes(resource *resource.Resource) []*commonpb.KeyValue {
|
||||
if resource.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
out := make([]*commonpb.KeyValue, 0, resource.Len())
|
||||
for iter := resource.Iter(); iter.Next(); {
|
||||
out = append(out, toAttribute(iter.Attribute()))
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func toAttribute(v attribute.KeyValue) *commonpb.KeyValue {
|
||||
result := &commonpb.KeyValue{
|
||||
Key: string(v.Key),
|
||||
Value: new(commonpb.AnyValue),
|
||||
}
|
||||
switch v.Value.Type() {
|
||||
case attribute.BOOL:
|
||||
result.Value.Value = &commonpb.AnyValue_BoolValue{
|
||||
BoolValue: v.Value.AsBool(),
|
||||
}
|
||||
case attribute.INT64:
|
||||
result.Value.Value = &commonpb.AnyValue_IntValue{
|
||||
IntValue: v.Value.AsInt64(),
|
||||
}
|
||||
case attribute.FLOAT64:
|
||||
result.Value.Value = &commonpb.AnyValue_DoubleValue{
|
||||
DoubleValue: v.Value.AsFloat64(),
|
||||
}
|
||||
case attribute.STRING:
|
||||
result.Value.Value = &commonpb.AnyValue_StringValue{
|
||||
StringValue: v.Value.AsString(),
|
||||
}
|
||||
case attribute.ARRAY:
|
||||
result.Value.Value = &commonpb.AnyValue_ArrayValue{
|
||||
ArrayValue: &commonpb.ArrayValue{
|
||||
Values: arrayValues(v),
|
||||
},
|
||||
}
|
||||
default:
|
||||
result.Value.Value = &commonpb.AnyValue_StringValue{
|
||||
StringValue: "INVALID",
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func arrayValues(kv attribute.KeyValue) []*commonpb.AnyValue {
|
||||
a := kv.Value.AsArray()
|
||||
aType := reflect.TypeOf(a)
|
||||
var valueFunc func(reflect.Value) *commonpb.AnyValue
|
||||
switch aType.Elem().Kind() {
|
||||
case reflect.Bool:
|
||||
valueFunc = func(v reflect.Value) *commonpb.AnyValue {
|
||||
return &commonpb.AnyValue{
|
||||
Value: &commonpb.AnyValue_BoolValue{
|
||||
BoolValue: v.Bool(),
|
||||
},
|
||||
}
|
||||
}
|
||||
case reflect.Int, reflect.Int64:
|
||||
valueFunc = func(v reflect.Value) *commonpb.AnyValue {
|
||||
return &commonpb.AnyValue{
|
||||
Value: &commonpb.AnyValue_IntValue{
|
||||
IntValue: v.Int(),
|
||||
},
|
||||
}
|
||||
}
|
||||
case reflect.Uintptr:
|
||||
valueFunc = func(v reflect.Value) *commonpb.AnyValue {
|
||||
return &commonpb.AnyValue{
|
||||
Value: &commonpb.AnyValue_IntValue{
|
||||
IntValue: int64(v.Uint()),
|
||||
},
|
||||
}
|
||||
}
|
||||
case reflect.Float64:
|
||||
valueFunc = func(v reflect.Value) *commonpb.AnyValue {
|
||||
return &commonpb.AnyValue{
|
||||
Value: &commonpb.AnyValue_DoubleValue{
|
||||
DoubleValue: v.Float(),
|
||||
},
|
||||
}
|
||||
}
|
||||
case reflect.String:
|
||||
valueFunc = func(v reflect.Value) *commonpb.AnyValue {
|
||||
return &commonpb.AnyValue{
|
||||
Value: &commonpb.AnyValue_StringValue{
|
||||
StringValue: v.String(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
results := make([]*commonpb.AnyValue, aType.Len())
|
||||
for i, aValue := 0, reflect.ValueOf(a); i < aValue.Len(); i++ {
|
||||
results[i] = valueFunc(aValue.Index(i))
|
||||
}
|
||||
return results
|
||||
}
|
@ -0,0 +1,256 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tracetransform
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
commonpb "go.opentelemetry.io/proto/otlp/common/v1"
|
||||
)
|
||||
|
||||
type attributeTest struct {
|
||||
attrs []attribute.KeyValue
|
||||
expected []*commonpb.KeyValue
|
||||
}
|
||||
|
||||
func TestAttributes(t *testing.T) {
|
||||
for _, test := range []attributeTest{
|
||||
{nil, nil},
|
||||
{
|
||||
[]attribute.KeyValue{
|
||||
attribute.Int("int to int", 123),
|
||||
attribute.Int64("int64 to int64", 1234567),
|
||||
attribute.Float64("float64 to double", 1.61),
|
||||
attribute.String("string to string", "string"),
|
||||
attribute.Bool("bool to bool", true),
|
||||
},
|
||||
[]*commonpb.KeyValue{
|
||||
{
|
||||
Key: "int to int",
|
||||
Value: &commonpb.AnyValue{
|
||||
Value: &commonpb.AnyValue_IntValue{
|
||||
IntValue: 123,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Key: "int64 to int64",
|
||||
Value: &commonpb.AnyValue{
|
||||
Value: &commonpb.AnyValue_IntValue{
|
||||
IntValue: 1234567,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Key: "float64 to double",
|
||||
Value: &commonpb.AnyValue{
|
||||
Value: &commonpb.AnyValue_DoubleValue{
|
||||
DoubleValue: 1.61,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Key: "string to string",
|
||||
Value: &commonpb.AnyValue{
|
||||
Value: &commonpb.AnyValue_StringValue{
|
||||
StringValue: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Key: "bool to bool",
|
||||
Value: &commonpb.AnyValue{
|
||||
Value: &commonpb.AnyValue_BoolValue{
|
||||
BoolValue: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} {
|
||||
got := Attributes(test.attrs)
|
||||
if !assert.Len(t, got, len(test.expected)) {
|
||||
continue
|
||||
}
|
||||
for i, actual := range got {
|
||||
if a, ok := actual.Value.Value.(*commonpb.AnyValue_DoubleValue); ok {
|
||||
e, ok := test.expected[i].Value.Value.(*commonpb.AnyValue_DoubleValue)
|
||||
if !ok {
|
||||
t.Errorf("expected AnyValue_DoubleValue, got %T", test.expected[i].Value.Value)
|
||||
continue
|
||||
}
|
||||
if !assert.InDelta(t, e.DoubleValue, a.DoubleValue, 0.01) {
|
||||
continue
|
||||
}
|
||||
e.DoubleValue = a.DoubleValue
|
||||
}
|
||||
assert.Equal(t, test.expected[i], actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestArrayAttributes(t *testing.T) {
|
||||
// Array KeyValue supports only arrays of primitive types:
|
||||
// "bool", "int", "int64",
|
||||
// "float64", "string",
|
||||
for _, test := range []attributeTest{
|
||||
{nil, nil},
|
||||
{
|
||||
[]attribute.KeyValue{
|
||||
attribute.Array("invalid", [][]string{{"1", "2"}, {"a"}}),
|
||||
},
|
||||
[]*commonpb.KeyValue{
|
||||
{
|
||||
Key: "invalid",
|
||||
Value: &commonpb.AnyValue{
|
||||
Value: &commonpb.AnyValue_StringValue{
|
||||
StringValue: "INVALID",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
[]attribute.KeyValue{
|
||||
attribute.Array("bool array to bool array", []bool{true, false}),
|
||||
attribute.Array("int array to int64 array", []int{1, 2, 3}),
|
||||
attribute.Array("int64 array to int64 array", []int64{1, 2, 3}),
|
||||
attribute.Array("float64 array to double array", []float64{1.11, 2.22, 3.33}),
|
||||
attribute.Array("string array to string array", []string{"foo", "bar", "baz"}),
|
||||
},
|
||||
[]*commonpb.KeyValue{
|
||||
newOTelBoolArray("bool array to bool array", []bool{true, false}),
|
||||
newOTelIntArray("int array to int64 array", []int64{1, 2, 3}),
|
||||
newOTelIntArray("int64 array to int64 array", []int64{1, 2, 3}),
|
||||
newOTelDoubleArray("float64 array to double array", []float64{1.11, 2.22, 3.33}),
|
||||
newOTelStringArray("string array to string array", []string{"foo", "bar", "baz"}),
|
||||
},
|
||||
},
|
||||
} {
|
||||
actualArrayAttributes := Attributes(test.attrs)
|
||||
expectedArrayAttributes := test.expected
|
||||
if !assert.Len(t, actualArrayAttributes, len(expectedArrayAttributes)) {
|
||||
continue
|
||||
}
|
||||
|
||||
for i, actualArrayAttr := range actualArrayAttributes {
|
||||
expectedArrayAttr := expectedArrayAttributes[i]
|
||||
expectedKey, actualKey := expectedArrayAttr.Key, actualArrayAttr.Key
|
||||
if !assert.Equal(t, expectedKey, actualKey) {
|
||||
continue
|
||||
}
|
||||
|
||||
expected := expectedArrayAttr.Value.GetArrayValue()
|
||||
actual := actualArrayAttr.Value.GetArrayValue()
|
||||
if expected == nil {
|
||||
assert.Nil(t, actual)
|
||||
continue
|
||||
}
|
||||
if assert.NotNil(t, actual, "expected not nil for %s", actualKey) {
|
||||
assertExpectedArrayValues(t, expected.Values, actual.Values)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func assertExpectedArrayValues(t *testing.T, expectedValues, actualValues []*commonpb.AnyValue) {
|
||||
for i, actual := range actualValues {
|
||||
expected := expectedValues[i]
|
||||
if a, ok := actual.Value.(*commonpb.AnyValue_DoubleValue); ok {
|
||||
e, ok := expected.Value.(*commonpb.AnyValue_DoubleValue)
|
||||
if !ok {
|
||||
t.Errorf("expected AnyValue_DoubleValue, got %T", expected.Value)
|
||||
continue
|
||||
}
|
||||
if !assert.InDelta(t, e.DoubleValue, a.DoubleValue, 0.01) {
|
||||
continue
|
||||
}
|
||||
e.DoubleValue = a.DoubleValue
|
||||
}
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func newOTelBoolArray(key string, values []bool) *commonpb.KeyValue {
|
||||
arrayValues := []*commonpb.AnyValue{}
|
||||
for _, b := range values {
|
||||
arrayValues = append(arrayValues, &commonpb.AnyValue{
|
||||
Value: &commonpb.AnyValue_BoolValue{
|
||||
BoolValue: b,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return newOTelArray(key, arrayValues)
|
||||
}
|
||||
|
||||
func newOTelIntArray(key string, values []int64) *commonpb.KeyValue {
|
||||
arrayValues := []*commonpb.AnyValue{}
|
||||
|
||||
for _, i := range values {
|
||||
arrayValues = append(arrayValues, &commonpb.AnyValue{
|
||||
Value: &commonpb.AnyValue_IntValue{
|
||||
IntValue: i,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return newOTelArray(key, arrayValues)
|
||||
}
|
||||
|
||||
func newOTelDoubleArray(key string, values []float64) *commonpb.KeyValue {
|
||||
arrayValues := []*commonpb.AnyValue{}
|
||||
|
||||
for _, d := range values {
|
||||
arrayValues = append(arrayValues, &commonpb.AnyValue{
|
||||
Value: &commonpb.AnyValue_DoubleValue{
|
||||
DoubleValue: d,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return newOTelArray(key, arrayValues)
|
||||
}
|
||||
|
||||
func newOTelStringArray(key string, values []string) *commonpb.KeyValue {
|
||||
arrayValues := []*commonpb.AnyValue{}
|
||||
|
||||
for _, s := range values {
|
||||
arrayValues = append(arrayValues, &commonpb.AnyValue{
|
||||
Value: &commonpb.AnyValue_StringValue{
|
||||
StringValue: s,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return newOTelArray(key, arrayValues)
|
||||
}
|
||||
|
||||
func newOTelArray(key string, arrayValues []*commonpb.AnyValue) *commonpb.KeyValue {
|
||||
return &commonpb.KeyValue{
|
||||
Key: key,
|
||||
Value: &commonpb.AnyValue{
|
||||
Value: &commonpb.AnyValue_ArrayValue{
|
||||
ArrayValue: &commonpb.ArrayValue{
|
||||
Values: arrayValues,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tracetransform
|
||||
|
||||
import (
|
||||
commonpb "go.opentelemetry.io/proto/otlp/common/v1"
|
||||
|
||||
"go.opentelemetry.io/otel/sdk/instrumentation"
|
||||
)
|
||||
|
||||
func InstrumentationLibrary(il instrumentation.Library) *commonpb.InstrumentationLibrary {
|
||||
if il == (instrumentation.Library{}) {
|
||||
return nil
|
||||
}
|
||||
return &commonpb.InstrumentationLibrary{
|
||||
Name: il.Name,
|
||||
Version: il.Version,
|
||||
}
|
||||
}
|
29
exporters/otlp/otlptrace/internal/tracetransform/resource.go
Normal file
29
exporters/otlp/otlptrace/internal/tracetransform/resource.go
Normal file
@ -0,0 +1,29 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tracetransform
|
||||
|
||||
import (
|
||||
resourcepb "go.opentelemetry.io/proto/otlp/resource/v1"
|
||||
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
)
|
||||
|
||||
// Resource transforms a Resource into an OTLP Resource.
|
||||
func Resource(r *resource.Resource) *resourcepb.Resource {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
return &resourcepb.Resource{Attributes: ResourceAttributes(r)}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tracetransform
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
)
|
||||
|
||||
func TestNilResource(t *testing.T) {
|
||||
assert.Empty(t, Resource(nil))
|
||||
}
|
||||
|
||||
func TestEmptyResource(t *testing.T) {
|
||||
assert.Empty(t, Resource(&resource.Resource{}))
|
||||
}
|
||||
|
||||
/*
|
||||
* This does not include any testing on the ordering of Resource Attributes.
|
||||
* They are stored as a map internally to the Resource and their order is not
|
||||
* guaranteed.
|
||||
*/
|
||||
|
||||
func TestResourceAttributes(t *testing.T) {
|
||||
attrs := []attribute.KeyValue{attribute.Int("one", 1), attribute.Int("two", 2)}
|
||||
|
||||
got := Resource(resource.NewWithAttributes(attrs...)).GetAttributes()
|
||||
if !assert.Len(t, attrs, 2) {
|
||||
return
|
||||
}
|
||||
assert.ElementsMatch(t, Attributes(attrs), got)
|
||||
}
|
218
exporters/otlp/otlptrace/internal/tracetransform/span.go
Normal file
218
exporters/otlp/otlptrace/internal/tracetransform/span.go
Normal file
@ -0,0 +1,218 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tracetransform
|
||||
|
||||
import (
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
tracepb "go.opentelemetry.io/proto/otlp/trace/v1"
|
||||
|
||||
"go.opentelemetry.io/otel/sdk/instrumentation"
|
||||
tracesdk "go.opentelemetry.io/otel/sdk/trace"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
maxEventsPerSpan = 128
|
||||
)
|
||||
|
||||
// Spans transforms a slice of OpenTelemetry spans into a slice of OTLP
|
||||
// ResourceSpans.
|
||||
func Spans(sdl []tracesdk.ReadOnlySpan) []*tracepb.ResourceSpans {
|
||||
if len(sdl) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
rsm := make(map[attribute.Distinct]*tracepb.ResourceSpans)
|
||||
|
||||
type ilsKey struct {
|
||||
r attribute.Distinct
|
||||
il instrumentation.Library
|
||||
}
|
||||
ilsm := make(map[ilsKey]*tracepb.InstrumentationLibrarySpans)
|
||||
|
||||
var resources int
|
||||
for _, sd := range sdl {
|
||||
if sd == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
rKey := sd.Resource().Equivalent()
|
||||
iKey := ilsKey{
|
||||
r: rKey,
|
||||
il: sd.InstrumentationLibrary(),
|
||||
}
|
||||
ils, iOk := ilsm[iKey]
|
||||
if !iOk {
|
||||
// Either the resource or instrumentation library were unknown.
|
||||
ils = &tracepb.InstrumentationLibrarySpans{
|
||||
InstrumentationLibrary: InstrumentationLibrary(sd.InstrumentationLibrary()),
|
||||
Spans: []*tracepb.Span{},
|
||||
}
|
||||
}
|
||||
ils.Spans = append(ils.Spans, span(sd))
|
||||
ilsm[iKey] = ils
|
||||
|
||||
rs, rOk := rsm[rKey]
|
||||
if !rOk {
|
||||
resources++
|
||||
// The resource was unknown.
|
||||
rs = &tracepb.ResourceSpans{
|
||||
Resource: Resource(sd.Resource()),
|
||||
InstrumentationLibrarySpans: []*tracepb.InstrumentationLibrarySpans{ils},
|
||||
}
|
||||
rsm[rKey] = rs
|
||||
continue
|
||||
}
|
||||
|
||||
// The resource has been seen before. Check if the instrumentation
|
||||
// library lookup was unknown because if so we need to add it to the
|
||||
// ResourceSpans. Otherwise, the instrumentation library has already
|
||||
// been seen and the append we did above will be included it in the
|
||||
// InstrumentationLibrarySpans reference.
|
||||
if !iOk {
|
||||
rs.InstrumentationLibrarySpans = append(rs.InstrumentationLibrarySpans, ils)
|
||||
}
|
||||
}
|
||||
|
||||
// Transform the categorized map into a slice
|
||||
rss := make([]*tracepb.ResourceSpans, 0, resources)
|
||||
for _, rs := range rsm {
|
||||
rss = append(rss, rs)
|
||||
}
|
||||
return rss
|
||||
}
|
||||
|
||||
// span transforms a Span into an OTLP span.
|
||||
func span(sd tracesdk.ReadOnlySpan) *tracepb.Span {
|
||||
if sd == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
tid := sd.SpanContext().TraceID()
|
||||
sid := sd.SpanContext().SpanID()
|
||||
|
||||
s := &tracepb.Span{
|
||||
TraceId: tid[:],
|
||||
SpanId: sid[:],
|
||||
TraceState: sd.SpanContext().TraceState().String(),
|
||||
Status: status(sd.Status().Code, sd.Status().Description),
|
||||
StartTimeUnixNano: uint64(sd.StartTime().UnixNano()),
|
||||
EndTimeUnixNano: uint64(sd.EndTime().UnixNano()),
|
||||
Links: links(sd.Links()),
|
||||
Kind: spanKind(sd.SpanKind()),
|
||||
Name: sd.Name(),
|
||||
Attributes: Attributes(sd.Attributes()),
|
||||
Events: spanEvents(sd.Events()),
|
||||
DroppedAttributesCount: uint32(sd.DroppedAttributes()),
|
||||
DroppedEventsCount: uint32(sd.DroppedEvents()),
|
||||
DroppedLinksCount: uint32(sd.DroppedLinks()),
|
||||
}
|
||||
|
||||
if psid := sd.Parent().SpanID(); psid.IsValid() {
|
||||
s.ParentSpanId = psid[:]
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// status transform a span code and message into an OTLP span status.
|
||||
func status(status codes.Code, message string) *tracepb.Status {
|
||||
var c tracepb.Status_StatusCode
|
||||
switch status {
|
||||
case codes.Error:
|
||||
c = tracepb.Status_STATUS_CODE_ERROR
|
||||
default:
|
||||
c = tracepb.Status_STATUS_CODE_OK
|
||||
}
|
||||
return &tracepb.Status{
|
||||
Code: c,
|
||||
Message: message,
|
||||
}
|
||||
}
|
||||
|
||||
// links transforms span Links to OTLP span links.
|
||||
func links(links []trace.Link) []*tracepb.Span_Link {
|
||||
if len(links) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
sl := make([]*tracepb.Span_Link, 0, len(links))
|
||||
for _, otLink := range links {
|
||||
// This redefinition is necessary to prevent otLink.*ID[:] copies
|
||||
// being reused -- in short we need a new otLink per iteration.
|
||||
otLink := otLink
|
||||
|
||||
tid := otLink.SpanContext.TraceID()
|
||||
sid := otLink.SpanContext.SpanID()
|
||||
|
||||
sl = append(sl, &tracepb.Span_Link{
|
||||
TraceId: tid[:],
|
||||
SpanId: sid[:],
|
||||
Attributes: Attributes(otLink.Attributes),
|
||||
})
|
||||
}
|
||||
return sl
|
||||
}
|
||||
|
||||
// spanEvents transforms span Events to an OTLP span events.
|
||||
func spanEvents(es []tracesdk.Event) []*tracepb.Span_Event {
|
||||
if len(es) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
evCount := len(es)
|
||||
if evCount > maxEventsPerSpan {
|
||||
evCount = maxEventsPerSpan
|
||||
}
|
||||
events := make([]*tracepb.Span_Event, 0, evCount)
|
||||
nEvents := 0
|
||||
|
||||
// Transform message events
|
||||
for _, e := range es {
|
||||
if nEvents >= maxEventsPerSpan {
|
||||
break
|
||||
}
|
||||
nEvents++
|
||||
events = append(events,
|
||||
&tracepb.Span_Event{
|
||||
Name: e.Name,
|
||||
TimeUnixNano: uint64(e.Time.UnixNano()),
|
||||
Attributes: Attributes(e.Attributes),
|
||||
// TODO (rghetia) : Add Drop Counts when supported.
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return events
|
||||
}
|
||||
|
||||
// spanKind transforms a SpanKind to an OTLP span kind.
|
||||
func spanKind(kind trace.SpanKind) tracepb.Span_SpanKind {
|
||||
switch kind {
|
||||
case trace.SpanKindInternal:
|
||||
return tracepb.Span_SPAN_KIND_INTERNAL
|
||||
case trace.SpanKindClient:
|
||||
return tracepb.Span_SPAN_KIND_CLIENT
|
||||
case trace.SpanKindServer:
|
||||
return tracepb.Span_SPAN_KIND_SERVER
|
||||
case trace.SpanKindProducer:
|
||||
return tracepb.Span_SPAN_KIND_PRODUCER
|
||||
case trace.SpanKindConsumer:
|
||||
return tracepb.Span_SPAN_KIND_CONSUMER
|
||||
default:
|
||||
return tracepb.Span_SPAN_KIND_UNSPECIFIED
|
||||
}
|
||||
}
|
321
exporters/otlp/otlptrace/internal/tracetransform/span_test.go
Normal file
321
exporters/otlp/otlptrace/internal/tracetransform/span_test.go
Normal file
@ -0,0 +1,321 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tracetransform
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel/oteltest"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
tracepb "go.opentelemetry.io/proto/otlp/trace/v1"
|
||||
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"go.opentelemetry.io/otel/sdk/instrumentation"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
tracesdk "go.opentelemetry.io/otel/sdk/trace"
|
||||
"go.opentelemetry.io/otel/sdk/trace/tracetest"
|
||||
)
|
||||
|
||||
func TestSpanKind(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
kind trace.SpanKind
|
||||
expected tracepb.Span_SpanKind
|
||||
}{
|
||||
{
|
||||
trace.SpanKindInternal,
|
||||
tracepb.Span_SPAN_KIND_INTERNAL,
|
||||
},
|
||||
{
|
||||
trace.SpanKindClient,
|
||||
tracepb.Span_SPAN_KIND_CLIENT,
|
||||
},
|
||||
{
|
||||
trace.SpanKindServer,
|
||||
tracepb.Span_SPAN_KIND_SERVER,
|
||||
},
|
||||
{
|
||||
trace.SpanKindProducer,
|
||||
tracepb.Span_SPAN_KIND_PRODUCER,
|
||||
},
|
||||
{
|
||||
trace.SpanKindConsumer,
|
||||
tracepb.Span_SPAN_KIND_CONSUMER,
|
||||
},
|
||||
{
|
||||
trace.SpanKind(-1),
|
||||
tracepb.Span_SPAN_KIND_UNSPECIFIED,
|
||||
},
|
||||
} {
|
||||
assert.Equal(t, test.expected, spanKind(test.kind))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNilSpanEvent(t *testing.T) {
|
||||
assert.Nil(t, spanEvents(nil))
|
||||
}
|
||||
|
||||
func TestEmptySpanEvent(t *testing.T) {
|
||||
assert.Nil(t, spanEvents([]tracesdk.Event{}))
|
||||
}
|
||||
|
||||
func TestSpanEvent(t *testing.T) {
|
||||
attrs := []attribute.KeyValue{attribute.Int("one", 1), attribute.Int("two", 2)}
|
||||
eventTime := time.Date(2020, 5, 20, 0, 0, 0, 0, time.UTC)
|
||||
got := spanEvents([]tracesdk.Event{
|
||||
{
|
||||
Name: "test 1",
|
||||
Attributes: []attribute.KeyValue{},
|
||||
Time: eventTime,
|
||||
},
|
||||
{
|
||||
Name: "test 2",
|
||||
Attributes: attrs,
|
||||
Time: eventTime,
|
||||
},
|
||||
})
|
||||
if !assert.Len(t, got, 2) {
|
||||
return
|
||||
}
|
||||
eventTimestamp := uint64(1589932800 * 1e9)
|
||||
assert.Equal(t, &tracepb.Span_Event{Name: "test 1", Attributes: nil, TimeUnixNano: eventTimestamp}, got[0])
|
||||
// Do not test Attributes directly, just that the return value goes to the correct field.
|
||||
assert.Equal(t, &tracepb.Span_Event{Name: "test 2", Attributes: Attributes(attrs), TimeUnixNano: eventTimestamp}, got[1])
|
||||
}
|
||||
|
||||
func TestExcessiveSpanEvents(t *testing.T) {
|
||||
e := make([]tracesdk.Event, maxEventsPerSpan+1)
|
||||
for i := 0; i < maxEventsPerSpan+1; i++ {
|
||||
e[i] = tracesdk.Event{Name: strconv.Itoa(i)}
|
||||
}
|
||||
assert.Len(t, e, maxEventsPerSpan+1)
|
||||
got := spanEvents(e)
|
||||
assert.Len(t, got, maxEventsPerSpan)
|
||||
// Ensure the drop order.
|
||||
assert.Equal(t, strconv.Itoa(maxEventsPerSpan-1), got[len(got)-1].Name)
|
||||
}
|
||||
|
||||
func TestNilLinks(t *testing.T) {
|
||||
assert.Nil(t, links(nil))
|
||||
}
|
||||
|
||||
func TestEmptyLinks(t *testing.T) {
|
||||
assert.Nil(t, links([]trace.Link{}))
|
||||
}
|
||||
|
||||
func TestLinks(t *testing.T) {
|
||||
attrs := []attribute.KeyValue{attribute.Int("one", 1), attribute.Int("two", 2)}
|
||||
l := []trace.Link{
|
||||
{},
|
||||
{
|
||||
SpanContext: trace.SpanContext{},
|
||||
Attributes: attrs,
|
||||
},
|
||||
}
|
||||
got := links(l)
|
||||
|
||||
// Make sure we get the same number back first.
|
||||
if !assert.Len(t, got, 2) {
|
||||
return
|
||||
}
|
||||
|
||||
// Empty should be empty.
|
||||
expected := &tracepb.Span_Link{
|
||||
TraceId: []uint8{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
|
||||
SpanId: []uint8{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
|
||||
}
|
||||
assert.Equal(t, expected, got[0])
|
||||
|
||||
// Do not test Attributes directly, just that the return value goes to the correct field.
|
||||
expected.Attributes = Attributes(attrs)
|
||||
assert.Equal(t, expected, got[1])
|
||||
|
||||
// Changes to our links should not change the produced links.
|
||||
l[1].SpanContext = l[1].SpanContext.WithTraceID(trace.TraceID{})
|
||||
assert.Equal(t, expected, got[1])
|
||||
}
|
||||
|
||||
func TestStatus(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
code codes.Code
|
||||
message string
|
||||
otlpStatus tracepb.Status_StatusCode
|
||||
}{
|
||||
{
|
||||
codes.Ok,
|
||||
"test Ok",
|
||||
tracepb.Status_STATUS_CODE_OK,
|
||||
},
|
||||
{
|
||||
codes.Unset,
|
||||
"test Unset",
|
||||
tracepb.Status_STATUS_CODE_OK,
|
||||
},
|
||||
{
|
||||
codes.Error,
|
||||
"test Error",
|
||||
tracepb.Status_STATUS_CODE_ERROR,
|
||||
},
|
||||
} {
|
||||
expected := &tracepb.Status{Code: test.otlpStatus, Message: test.message}
|
||||
assert.Equal(t, expected, status(test.code, test.message))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestNilSpan(t *testing.T) {
|
||||
assert.Nil(t, span(nil))
|
||||
}
|
||||
|
||||
func TestNilSpanData(t *testing.T) {
|
||||
assert.Nil(t, Spans(nil))
|
||||
}
|
||||
|
||||
func TestEmptySpanData(t *testing.T) {
|
||||
assert.Nil(t, Spans(nil))
|
||||
}
|
||||
|
||||
func TestSpanData(t *testing.T) {
|
||||
// Full test of span data
|
||||
|
||||
// March 31, 2020 5:01:26 1234nanos (UTC)
|
||||
startTime := time.Unix(1585674086, 1234)
|
||||
endTime := startTime.Add(10 * time.Second)
|
||||
traceState, _ := oteltest.TraceStateFromKeyValues(attribute.String("key1", "val1"), attribute.String("key2", "val2"))
|
||||
spanData := tracetest.SpanStub{
|
||||
SpanContext: trace.NewSpanContext(trace.SpanContextConfig{
|
||||
TraceID: trace.TraceID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
|
||||
SpanID: trace.SpanID{0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8},
|
||||
TraceState: traceState,
|
||||
}),
|
||||
Parent: trace.NewSpanContext(trace.SpanContextConfig{
|
||||
TraceID: trace.TraceID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
|
||||
SpanID: trace.SpanID{0xEF, 0xEE, 0xED, 0xEC, 0xEB, 0xEA, 0xE9, 0xE8},
|
||||
TraceState: traceState,
|
||||
Remote: true,
|
||||
}),
|
||||
SpanKind: trace.SpanKindServer,
|
||||
Name: "span data to span data",
|
||||
StartTime: startTime,
|
||||
EndTime: endTime,
|
||||
Events: []tracesdk.Event{
|
||||
{Time: startTime,
|
||||
Attributes: []attribute.KeyValue{
|
||||
attribute.Int64("CompressedByteSize", 512),
|
||||
},
|
||||
},
|
||||
{Time: endTime,
|
||||
Attributes: []attribute.KeyValue{
|
||||
attribute.String("EventType", "Recv"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Links: []trace.Link{
|
||||
{
|
||||
SpanContext: trace.NewSpanContext(trace.SpanContextConfig{
|
||||
TraceID: trace.TraceID{0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF},
|
||||
SpanID: trace.SpanID{0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7},
|
||||
TraceFlags: 0,
|
||||
}),
|
||||
Attributes: []attribute.KeyValue{
|
||||
attribute.String("LinkType", "Parent"),
|
||||
},
|
||||
},
|
||||
{
|
||||
SpanContext: trace.NewSpanContext(trace.SpanContextConfig{
|
||||
TraceID: trace.TraceID{0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF},
|
||||
SpanID: trace.SpanID{0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7},
|
||||
TraceFlags: 0,
|
||||
}),
|
||||
Attributes: []attribute.KeyValue{
|
||||
attribute.String("LinkType", "Child"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: tracesdk.Status{
|
||||
Code: codes.Error,
|
||||
Description: "utterly unrecognized",
|
||||
},
|
||||
Attributes: []attribute.KeyValue{
|
||||
attribute.Int64("timeout_ns", 12e9),
|
||||
},
|
||||
DroppedAttributes: 1,
|
||||
DroppedEvents: 2,
|
||||
DroppedLinks: 3,
|
||||
Resource: resource.NewWithAttributes(attribute.String("rk1", "rv1"), attribute.Int64("rk2", 5)),
|
||||
InstrumentationLibrary: instrumentation.Library{
|
||||
Name: "go.opentelemetry.io/test/otel",
|
||||
Version: "v0.0.1",
|
||||
},
|
||||
}
|
||||
|
||||
// Not checking resource as the underlying map of our Resource makes
|
||||
// ordering impossible to guarantee on the output. The Resource
|
||||
// transform function has unit tests that should suffice.
|
||||
expectedSpan := &tracepb.Span{
|
||||
TraceId: []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
|
||||
SpanId: []byte{0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8},
|
||||
ParentSpanId: []byte{0xEF, 0xEE, 0xED, 0xEC, 0xEB, 0xEA, 0xE9, 0xE8},
|
||||
TraceState: "key1=val1,key2=val2",
|
||||
Name: spanData.Name,
|
||||
Kind: tracepb.Span_SPAN_KIND_SERVER,
|
||||
StartTimeUnixNano: uint64(startTime.UnixNano()),
|
||||
EndTimeUnixNano: uint64(endTime.UnixNano()),
|
||||
Status: status(spanData.Status.Code, spanData.Status.Description),
|
||||
Events: spanEvents(spanData.Events),
|
||||
Links: links(spanData.Links),
|
||||
Attributes: Attributes(spanData.Attributes),
|
||||
DroppedAttributesCount: 1,
|
||||
DroppedEventsCount: 2,
|
||||
DroppedLinksCount: 3,
|
||||
}
|
||||
|
||||
got := Spans(tracetest.SpanStubs{spanData}.Snapshots())
|
||||
require.Len(t, got, 1)
|
||||
|
||||
assert.Equal(t, got[0].GetResource(), Resource(spanData.Resource))
|
||||
ilSpans := got[0].GetInstrumentationLibrarySpans()
|
||||
require.Len(t, ilSpans, 1)
|
||||
assert.Equal(t, ilSpans[0].GetInstrumentationLibrary(), InstrumentationLibrary(spanData.InstrumentationLibrary))
|
||||
require.Len(t, ilSpans[0].Spans, 1)
|
||||
actualSpan := ilSpans[0].Spans[0]
|
||||
|
||||
if diff := cmp.Diff(expectedSpan, actualSpan, cmp.Comparer(proto.Equal)); diff != "" {
|
||||
t.Fatalf("transformed span differs %v\n", diff)
|
||||
}
|
||||
}
|
||||
|
||||
// Empty parent span ID should be treated as root span.
|
||||
func TestRootSpanData(t *testing.T) {
|
||||
sd := Spans(tracetest.SpanStubs{{}}.Snapshots())
|
||||
require.Len(t, sd, 1)
|
||||
rs := sd[0]
|
||||
got := rs.GetInstrumentationLibrarySpans()[0].GetSpans()[0].GetParentSpanId()
|
||||
|
||||
// Empty means root span.
|
||||
assert.Nil(t, got, "incorrect transform of root parent span ID")
|
||||
}
|
||||
|
||||
func TestSpanDataNilResource(t *testing.T) {
|
||||
assert.NotPanics(t, func() { Spans(tracetest.SpanStubs{{}}.Snapshots()) })
|
||||
}
|
110
exporters/otlp/otlptrace/otlptracegrpc/client.go
Normal file
110
exporters/otlp/otlptrace/otlptracegrpc/client.go
Normal file
@ -0,0 +1,110 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package otlptracegrpc // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/connection"
|
||||
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/otlpconfig"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
|
||||
coltracepb "go.opentelemetry.io/proto/otlp/collector/trace/v1"
|
||||
tracepb "go.opentelemetry.io/proto/otlp/trace/v1"
|
||||
)
|
||||
|
||||
type client struct {
|
||||
connection *connection.Connection
|
||||
|
||||
lock sync.Mutex
|
||||
tracesClient coltracepb.TraceServiceClient
|
||||
}
|
||||
|
||||
var _ otlptrace.Client = (*client)(nil)
|
||||
|
||||
var (
|
||||
errNoClient = errors.New("no client")
|
||||
)
|
||||
|
||||
// NewClient creates a new gRPC trace client.
|
||||
func NewClient(opts ...Option) otlptrace.Client {
|
||||
cfg := otlpconfig.NewDefaultConfig()
|
||||
otlpconfig.ApplyGRPCEnvConfigs(&cfg)
|
||||
for _, opt := range opts {
|
||||
opt.applyGRPCOption(&cfg)
|
||||
}
|
||||
|
||||
c := &client{}
|
||||
c.connection = connection.NewConnection(cfg, cfg.Traces, c.handleNewConnection)
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *client) handleNewConnection(cc *grpc.ClientConn) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
if cc != nil {
|
||||
c.tracesClient = coltracepb.NewTraceServiceClient(cc)
|
||||
} else {
|
||||
c.tracesClient = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Start establishes a connection to the collector.
|
||||
func (c *client) Start(ctx context.Context) error {
|
||||
return c.connection.StartConnection(ctx)
|
||||
}
|
||||
|
||||
// Stop shuts down the connection to the collector.
|
||||
func (c *client) Stop(ctx context.Context) error {
|
||||
return c.connection.Shutdown(ctx)
|
||||
}
|
||||
|
||||
// UploadTraces sends a batch of spans to the collector.
|
||||
func (c *client) UploadTraces(ctx context.Context, protoSpans []*tracepb.ResourceSpans) error {
|
||||
if !c.connection.Connected() {
|
||||
return fmt.Errorf("traces exporter is disconnected from the server %s: %w", c.connection.SCfg.Endpoint, c.connection.LastConnectError())
|
||||
}
|
||||
|
||||
ctx, cancel := c.connection.ContextWithStop(ctx)
|
||||
defer cancel()
|
||||
ctx, tCancel := context.WithTimeout(ctx, c.connection.SCfg.Timeout)
|
||||
defer tCancel()
|
||||
|
||||
ctx = c.connection.ContextWithMetadata(ctx)
|
||||
err := func() error {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
if c.tracesClient == nil {
|
||||
return errNoClient
|
||||
}
|
||||
return c.connection.DoRequest(ctx, func(ctx context.Context) error {
|
||||
_, err := c.tracesClient.Export(ctx, &coltracepb.ExportTraceServiceRequest{
|
||||
ResourceSpans: protoSpans,
|
||||
})
|
||||
return err
|
||||
})
|
||||
}()
|
||||
if err != nil {
|
||||
c.connection.SetStateDisconnected(err)
|
||||
}
|
||||
return err
|
||||
}
|
852
exporters/otlp/otlptrace/otlptracegrpc/client_test.go
Normal file
852
exporters/otlp/otlptrace/otlptracegrpc/client_test.go
Normal file
@ -0,0 +1,852 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
package otlptracegrpc_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/otlptracetest"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
|
||||
|
||||
"google.golang.org/genproto/googleapis/rpc/errdetails"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/encoding/gzip"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
"go.opentelemetry.io/otel/sdk/trace/tracetest"
|
||||
commonpb "go.opentelemetry.io/proto/otlp/common/v1"
|
||||
)
|
||||
|
||||
var roSpans = tracetest.SpanStubs{{Name: "Span 0"}}.Snapshots()
|
||||
|
||||
func TestNewExporter_endToEnd(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
additionalOpts []otlptracegrpc.Option
|
||||
}{
|
||||
{
|
||||
name: "StandardExporter",
|
||||
},
|
||||
{
|
||||
name: "WithCompressor",
|
||||
additionalOpts: []otlptracegrpc.Option{
|
||||
otlptracegrpc.WithCompressor(gzip.Name),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "WithServiceConfig",
|
||||
additionalOpts: []otlptracegrpc.Option{
|
||||
otlptracegrpc.WithServiceConfig("{}"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "WithDialOptions",
|
||||
additionalOpts: []otlptracegrpc.Option{
|
||||
otlptracegrpc.WithDialOption(grpc.WithBlock()),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
newExporterEndToEndTest(t, test.additionalOpts)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func newGRPCExporter(t *testing.T, ctx context.Context, endpoint string, additionalOpts ...otlptracegrpc.Option) *otlptrace.Exporter {
|
||||
opts := []otlptracegrpc.Option{
|
||||
otlptracegrpc.WithInsecure(),
|
||||
otlptracegrpc.WithEndpoint(endpoint),
|
||||
otlptracegrpc.WithReconnectionPeriod(50 * time.Millisecond),
|
||||
}
|
||||
|
||||
opts = append(opts, additionalOpts...)
|
||||
client := otlptracegrpc.NewClient(opts...)
|
||||
exp, err := otlptrace.NewExporter(ctx, client)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create a new collector exporter: %v", err)
|
||||
}
|
||||
return exp
|
||||
}
|
||||
|
||||
func newExporterEndToEndTest(t *testing.T, additionalOpts []otlptracegrpc.Option) {
|
||||
mc := runMockCollectorAtEndpoint(t, "localhost:56561")
|
||||
|
||||
defer func() {
|
||||
_ = mc.stop()
|
||||
}()
|
||||
|
||||
<-time.After(5 * time.Millisecond)
|
||||
|
||||
ctx := context.Background()
|
||||
exp := newGRPCExporter(t, ctx, mc.endpoint, additionalOpts...)
|
||||
defer func() {
|
||||
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
defer cancel()
|
||||
if err := exp.Shutdown(ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
otlptracetest.RunEndToEndTest(ctx, t, exp, mc)
|
||||
}
|
||||
|
||||
func TestExporterShutdown(t *testing.T) {
|
||||
mc := runMockCollectorAtEndpoint(t, "localhost:56561")
|
||||
defer func() {
|
||||
_ = mc.stop()
|
||||
}()
|
||||
|
||||
<-time.After(5 * time.Millisecond)
|
||||
|
||||
otlptracetest.RunExporterShutdownTest(t, func() otlptrace.Client {
|
||||
return otlptracegrpc.NewClient(
|
||||
otlptracegrpc.WithInsecure(),
|
||||
otlptracegrpc.WithEndpoint(mc.endpoint),
|
||||
otlptracegrpc.WithReconnectionPeriod(50*time.Millisecond))
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewExporter_invokeStartThenStopManyTimes(t *testing.T) {
|
||||
mc := runMockCollector(t)
|
||||
defer func() {
|
||||
_ = mc.stop()
|
||||
}()
|
||||
|
||||
ctx := context.Background()
|
||||
exp := newGRPCExporter(t, ctx, mc.endpoint)
|
||||
defer func() {
|
||||
if err := exp.Shutdown(ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Invoke Start numerous times, should return errAlreadyStarted
|
||||
for i := 0; i < 10; i++ {
|
||||
if err := exp.Start(ctx); err == nil || !strings.Contains(err.Error(), "already started") {
|
||||
t.Fatalf("#%d unexpected Start error: %v", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := exp.Shutdown(ctx); err != nil {
|
||||
t.Fatalf("failed to Shutdown the exporter: %v", err)
|
||||
}
|
||||
// Invoke Shutdown numerous times
|
||||
for i := 0; i < 10; i++ {
|
||||
if err := exp.Shutdown(ctx); err != nil {
|
||||
t.Fatalf(`#%d got error (%v) expected none`, i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewExporter_collectorConnectionDiesThenReconnectsWhenInRestMode(t *testing.T) {
|
||||
mc := runMockCollector(t)
|
||||
|
||||
reconnectionPeriod := 20 * time.Millisecond
|
||||
ctx := context.Background()
|
||||
exp := newGRPCExporter(t, ctx, mc.endpoint,
|
||||
otlptracegrpc.WithRetry(otlptracegrpc.RetrySettings{Enabled: false}),
|
||||
otlptracegrpc.WithReconnectionPeriod(reconnectionPeriod))
|
||||
defer func() { require.NoError(t, exp.Shutdown(ctx)) }()
|
||||
|
||||
// Wait for a connection.
|
||||
mc.ln.WaitForConn()
|
||||
|
||||
// We'll now stop the collector right away to simulate a connection
|
||||
// dying in the midst of communication or even not existing before.
|
||||
require.NoError(t, mc.stop())
|
||||
|
||||
// first export, it will send disconnected message to the channel on export failure,
|
||||
// trigger almost immediate reconnection
|
||||
require.Error(t, exp.ExportSpans(ctx, roSpans))
|
||||
|
||||
// second export, it will detect connection issue, change state of exporter to disconnected and
|
||||
// send message to disconnected channel but this time reconnection gouroutine will be in (rest mode, not listening to the disconnected channel)
|
||||
require.Error(t, exp.ExportSpans(ctx, roSpans))
|
||||
|
||||
// as a result we have exporter in disconnected state waiting for disconnection message to reconnect
|
||||
|
||||
// resurrect collector
|
||||
nmc := runMockCollectorAtEndpoint(t, mc.endpoint)
|
||||
|
||||
// make sure reconnection loop hits beginning and goes back to waiting mode
|
||||
// after hitting beginning of the loop it should reconnect
|
||||
nmc.ln.WaitForConn()
|
||||
|
||||
n := 10
|
||||
for i := 0; i < n; i++ {
|
||||
// when disconnected exp.ExportSpans doesnt send disconnected messages again
|
||||
// it just quits and return last connection error
|
||||
require.NoError(t, exp.ExportSpans(ctx, roSpans))
|
||||
}
|
||||
|
||||
nmaSpans := nmc.getSpans()
|
||||
|
||||
// Expecting 10 spans that were sampled, given that
|
||||
if g, w := len(nmaSpans), n; g != w {
|
||||
t.Fatalf("Connected collector: spans: got %d want %d", g, w)
|
||||
}
|
||||
|
||||
dSpans := mc.getSpans()
|
||||
// Expecting 0 spans to have been received by the original but now dead collector
|
||||
if g, w := len(dSpans), 0; g != w {
|
||||
t.Fatalf("Disconnected collector: spans: got %d want %d", g, w)
|
||||
}
|
||||
|
||||
require.NoError(t, nmc.Stop())
|
||||
}
|
||||
|
||||
func TestExporterExportFailureAndRecoveryModes(t *testing.T) {
|
||||
tts := []struct {
|
||||
name string
|
||||
errors []error
|
||||
rs otlptracegrpc.RetrySettings
|
||||
fn func(t *testing.T, ctx context.Context, exp *otlptrace.Exporter, mc *mockCollector)
|
||||
opts []otlptracegrpc.Option
|
||||
}{
|
||||
{
|
||||
name: "Do not retry if succeeded",
|
||||
fn: func(t *testing.T, ctx context.Context, exp *otlptrace.Exporter, mc *mockCollector) {
|
||||
require.NoError(t, exp.ExportSpans(ctx, roSpans))
|
||||
|
||||
span := mc.getSpans()
|
||||
|
||||
require.Len(t, span, 1)
|
||||
require.Equal(t, 1, mc.traceSvc.requests, "trace service must receive 1 success request.")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Do not retry if 'error' is ok",
|
||||
errors: []error{
|
||||
status.Error(codes.OK, ""),
|
||||
},
|
||||
fn: func(t *testing.T, ctx context.Context, exp *otlptrace.Exporter, mc *mockCollector) {
|
||||
require.NoError(t, exp.ExportSpans(ctx, roSpans))
|
||||
|
||||
span := mc.getSpans()
|
||||
|
||||
require.Len(t, span, 0)
|
||||
require.Equal(t, 1, mc.traceSvc.requests, "trace service must receive 1 error OK request.")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Fail three times and succeed",
|
||||
rs: otlptracegrpc.RetrySettings{
|
||||
Enabled: true,
|
||||
MaxElapsedTime: 300 * time.Millisecond,
|
||||
InitialInterval: 2 * time.Millisecond,
|
||||
MaxInterval: 10 * time.Millisecond,
|
||||
},
|
||||
errors: []error{
|
||||
status.Error(codes.Unavailable, "backend under pressure"),
|
||||
status.Error(codes.Unavailable, "backend under pressure"),
|
||||
status.Error(codes.Unavailable, "backend under pressure"),
|
||||
},
|
||||
fn: func(t *testing.T, ctx context.Context, exp *otlptrace.Exporter, mc *mockCollector) {
|
||||
require.NoError(t, exp.ExportSpans(ctx, roSpans))
|
||||
|
||||
span := mc.getSpans()
|
||||
|
||||
require.Len(t, span, 1)
|
||||
require.Equal(t, 4, mc.traceSvc.requests, "trace service must receive 3 failure requests and 1 success request.")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Permanent error should not be retried",
|
||||
rs: otlptracegrpc.RetrySettings{
|
||||
Enabled: true,
|
||||
MaxElapsedTime: 300 * time.Millisecond,
|
||||
InitialInterval: 2 * time.Millisecond,
|
||||
MaxInterval: 10 * time.Millisecond,
|
||||
},
|
||||
errors: []error{
|
||||
status.Error(codes.InvalidArgument, "invalid arguments"),
|
||||
},
|
||||
fn: func(t *testing.T, ctx context.Context, exp *otlptrace.Exporter, mc *mockCollector) {
|
||||
require.Error(t, exp.ExportSpans(ctx, roSpans))
|
||||
|
||||
span := mc.getSpans()
|
||||
|
||||
require.Len(t, span, 0)
|
||||
require.Equal(t, 1, mc.traceSvc.requests, "trace service must receive 1 error requests.")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test all transient errors and succeed",
|
||||
rs: otlptracegrpc.RetrySettings{
|
||||
Enabled: true,
|
||||
MaxElapsedTime: 500 * time.Millisecond,
|
||||
InitialInterval: 1 * time.Millisecond,
|
||||
MaxInterval: 2 * time.Millisecond,
|
||||
},
|
||||
errors: []error{
|
||||
status.Error(codes.Canceled, ""),
|
||||
status.Error(codes.DeadlineExceeded, ""),
|
||||
status.Error(codes.ResourceExhausted, ""),
|
||||
status.Error(codes.Aborted, ""),
|
||||
status.Error(codes.OutOfRange, ""),
|
||||
status.Error(codes.Unavailable, ""),
|
||||
status.Error(codes.DataLoss, ""),
|
||||
},
|
||||
fn: func(t *testing.T, ctx context.Context, exp *otlptrace.Exporter, mc *mockCollector) {
|
||||
require.NoError(t, exp.ExportSpans(ctx, roSpans))
|
||||
|
||||
span := mc.getSpans()
|
||||
|
||||
require.Len(t, span, 1)
|
||||
require.Equal(t, 8, mc.traceSvc.requests, "trace service must receive 9 failure requests and 1 success request.")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Retry should honor server throttling",
|
||||
rs: otlptracegrpc.RetrySettings{
|
||||
Enabled: true,
|
||||
MaxElapsedTime: time.Minute,
|
||||
InitialInterval: time.Nanosecond,
|
||||
MaxInterval: time.Nanosecond,
|
||||
},
|
||||
opts: []otlptracegrpc.Option{
|
||||
otlptracegrpc.WithTimeout(time.Millisecond * 100),
|
||||
},
|
||||
errors: []error{
|
||||
newThrottlingError(codes.ResourceExhausted, time.Second*30),
|
||||
},
|
||||
fn: func(t *testing.T, ctx context.Context, exp *otlptrace.Exporter, mc *mockCollector) {
|
||||
err := exp.ExportSpans(ctx, roSpans)
|
||||
require.Error(t, err)
|
||||
require.Equal(t, "context deadline exceeded", err.Error())
|
||||
|
||||
span := mc.getSpans()
|
||||
|
||||
require.Len(t, span, 0)
|
||||
require.Equal(t, 1, mc.traceSvc.requests, "trace service must receive 1 failure requests and 1 success request.")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Retry should fail if server throttling is higher than the MaxElapsedTime",
|
||||
rs: otlptracegrpc.RetrySettings{
|
||||
Enabled: true,
|
||||
MaxElapsedTime: time.Millisecond * 100,
|
||||
InitialInterval: time.Nanosecond,
|
||||
MaxInterval: time.Nanosecond,
|
||||
},
|
||||
errors: []error{
|
||||
newThrottlingError(codes.ResourceExhausted, time.Minute),
|
||||
},
|
||||
fn: func(t *testing.T, ctx context.Context, exp *otlptrace.Exporter, mc *mockCollector) {
|
||||
err := exp.ExportSpans(ctx, roSpans)
|
||||
require.Error(t, err)
|
||||
require.Equal(t, "max elapsed time expired when respecting server throttle: rpc error: code = ResourceExhausted desc = ", err.Error())
|
||||
|
||||
span := mc.getSpans()
|
||||
|
||||
require.Len(t, span, 0)
|
||||
require.Equal(t, 1, mc.traceSvc.requests, "trace service must receive 1 failure requests and 1 success request.")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Retry stops if takes too long",
|
||||
rs: otlptracegrpc.RetrySettings{
|
||||
Enabled: true,
|
||||
MaxElapsedTime: time.Millisecond * 100,
|
||||
InitialInterval: time.Millisecond * 50,
|
||||
MaxInterval: time.Millisecond * 50,
|
||||
},
|
||||
errors: []error{
|
||||
status.Error(codes.Unavailable, "unavailable"),
|
||||
status.Error(codes.Unavailable, "unavailable"),
|
||||
status.Error(codes.Unavailable, "unavailable"),
|
||||
status.Error(codes.Unavailable, "unavailable"),
|
||||
status.Error(codes.Unavailable, "unavailable"),
|
||||
status.Error(codes.Unavailable, "unavailable"),
|
||||
},
|
||||
fn: func(t *testing.T, ctx context.Context, exp *otlptrace.Exporter, mc *mockCollector) {
|
||||
err := exp.ExportSpans(ctx, roSpans)
|
||||
require.Error(t, err)
|
||||
|
||||
require.Equal(t, "max elapsed time expired: rpc error: code = Unavailable desc = unavailable", err.Error())
|
||||
|
||||
span := mc.getSpans()
|
||||
|
||||
require.Len(t, span, 0)
|
||||
require.LessOrEqual(t, 1, mc.traceSvc.requests, "trace service must receive at least 1 failure requests.")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Disabled retry",
|
||||
rs: otlptracegrpc.RetrySettings{
|
||||
Enabled: false,
|
||||
},
|
||||
errors: []error{
|
||||
status.Error(codes.Unavailable, "unavailable"),
|
||||
},
|
||||
fn: func(t *testing.T, ctx context.Context, exp *otlptrace.Exporter, mc *mockCollector) {
|
||||
err := exp.ExportSpans(ctx, roSpans)
|
||||
require.Error(t, err)
|
||||
|
||||
require.Equal(t, "rpc error: code = Unavailable desc = unavailable", err.Error())
|
||||
|
||||
span := mc.getSpans()
|
||||
|
||||
require.Len(t, span, 0)
|
||||
require.Equal(t, 1, mc.traceSvc.requests, "trace service must receive 1 failure requests.")
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tts {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
mc := runMockCollectorWithConfig(t, &mockConfig{
|
||||
errors: tt.errors,
|
||||
})
|
||||
|
||||
opts := []otlptracegrpc.Option{
|
||||
otlptracegrpc.WithRetry(tt.rs),
|
||||
}
|
||||
|
||||
if len(tt.opts) != 0 {
|
||||
opts = append(opts, tt.opts...)
|
||||
}
|
||||
|
||||
exp := newGRPCExporter(t, ctx, mc.endpoint, opts...)
|
||||
|
||||
tt.fn(t, ctx, exp, mc)
|
||||
|
||||
require.NoError(t, mc.Stop())
|
||||
require.NoError(t, exp.Shutdown(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestPermanentErrorsShouldNotBeRetried(t *testing.T) {
|
||||
permanentErrors := []*status.Status{
|
||||
status.New(codes.Unknown, "Unknown"),
|
||||
status.New(codes.InvalidArgument, "InvalidArgument"),
|
||||
status.New(codes.NotFound, "NotFound"),
|
||||
status.New(codes.AlreadyExists, "AlreadyExists"),
|
||||
status.New(codes.FailedPrecondition, "FailedPrecondition"),
|
||||
status.New(codes.Unimplemented, "Unimplemented"),
|
||||
status.New(codes.Internal, "Internal"),
|
||||
status.New(codes.PermissionDenied, ""),
|
||||
status.New(codes.Unauthenticated, ""),
|
||||
}
|
||||
|
||||
for _, sts := range permanentErrors {
|
||||
t.Run(sts.Code().String(), func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
mc := runMockCollectorWithConfig(t, &mockConfig{
|
||||
errors: []error{sts.Err()},
|
||||
})
|
||||
|
||||
exp := newGRPCExporter(t, ctx, mc.endpoint)
|
||||
|
||||
err := exp.ExportSpans(ctx, roSpans)
|
||||
require.Error(t, err)
|
||||
require.Len(t, mc.getSpans(), 0)
|
||||
require.Equal(t, 1, mc.traceSvc.requests, "trace service must receive 1 permanent error requests.")
|
||||
|
||||
require.NoError(t, mc.Stop())
|
||||
require.NoError(t, exp.Shutdown(ctx))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func newThrottlingError(code codes.Code, duration time.Duration) error {
|
||||
s := status.New(code, "")
|
||||
|
||||
s, _ = s.WithDetails(&errdetails.RetryInfo{RetryDelay: durationpb.New(duration)})
|
||||
|
||||
return s.Err()
|
||||
}
|
||||
|
||||
func TestNewExporter_collectorConnectionDiesThenReconnects(t *testing.T) {
|
||||
mc := runMockCollector(t)
|
||||
|
||||
reconnectionPeriod := 50 * time.Millisecond
|
||||
ctx := context.Background()
|
||||
exp := newGRPCExporter(t, ctx, mc.endpoint,
|
||||
otlptracegrpc.WithRetry(otlptracegrpc.RetrySettings{Enabled: false}),
|
||||
otlptracegrpc.WithReconnectionPeriod(reconnectionPeriod))
|
||||
defer func() { require.NoError(t, exp.Shutdown(ctx)) }()
|
||||
|
||||
mc.ln.WaitForConn()
|
||||
|
||||
// We'll now stop the collector right away to simulate a connection
|
||||
// dying in the midst of communication or even not existing before.
|
||||
require.NoError(t, mc.stop())
|
||||
|
||||
// In the test below, we'll stop the collector many times,
|
||||
// while exporting traces and test to ensure that we can
|
||||
// reconnect.
|
||||
for j := 0; j < 3; j++ {
|
||||
|
||||
// No endpoint up.
|
||||
require.Error(t, exp.ExportSpans(ctx, roSpans))
|
||||
|
||||
// Now resurrect the collector by making a new one but reusing the
|
||||
// old endpoint, and the collector should reconnect automatically.
|
||||
nmc := runMockCollectorAtEndpoint(t, mc.endpoint)
|
||||
|
||||
// Give the exporter sometime to reconnect
|
||||
nmc.ln.WaitForConn()
|
||||
|
||||
n := 10
|
||||
for i := 0; i < n; i++ {
|
||||
require.NoError(t, exp.ExportSpans(ctx, roSpans))
|
||||
}
|
||||
|
||||
nmaSpans := nmc.getSpans()
|
||||
// Expecting 10 spans that were sampled, given that
|
||||
if g, w := len(nmaSpans), n; g != w {
|
||||
t.Fatalf("Round #%d: Connected collector: spans: got %d want %d", j, g, w)
|
||||
}
|
||||
|
||||
dSpans := mc.getSpans()
|
||||
// Expecting 0 spans to have been received by the original but now dead collector
|
||||
if g, w := len(dSpans), 0; g != w {
|
||||
t.Fatalf("Round #%d: Disconnected collector: spans: got %d want %d", j, g, w)
|
||||
}
|
||||
|
||||
// Disconnect for the next try.
|
||||
require.NoError(t, nmc.stop())
|
||||
}
|
||||
}
|
||||
|
||||
// This test takes a long time to run: to skip it, run tests using: -short
|
||||
func TestNewExporter_collectorOnBadConnection(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skipf("Skipping this long running test")
|
||||
}
|
||||
|
||||
ln, err := net.Listen("tcp", "localhost:0")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to grab an available port: %v", err)
|
||||
}
|
||||
// Firstly close the "collector's" channel: optimistically this endpoint won't get reused ASAP
|
||||
// However, our goal of closing it is to simulate an unavailable connection
|
||||
_ = ln.Close()
|
||||
|
||||
_, collectorPortStr, _ := net.SplitHostPort(ln.Addr().String())
|
||||
|
||||
endpoint := fmt.Sprintf("localhost:%s", collectorPortStr)
|
||||
ctx := context.Background()
|
||||
exp := newGRPCExporter(t, ctx, endpoint)
|
||||
_ = exp.Shutdown(ctx)
|
||||
}
|
||||
|
||||
func TestNewExporter_withEndpoint(t *testing.T) {
|
||||
mc := runMockCollector(t)
|
||||
defer func() {
|
||||
_ = mc.stop()
|
||||
}()
|
||||
|
||||
ctx := context.Background()
|
||||
exp := newGRPCExporter(t, ctx, mc.endpoint)
|
||||
_ = exp.Shutdown(ctx)
|
||||
}
|
||||
|
||||
func TestNewExporter_withHeaders(t *testing.T) {
|
||||
mc := runMockCollector(t)
|
||||
defer func() {
|
||||
_ = mc.stop()
|
||||
}()
|
||||
|
||||
ctx := context.Background()
|
||||
exp := newGRPCExporter(t, ctx, mc.endpoint,
|
||||
otlptracegrpc.WithHeaders(map[string]string{"header1": "value1"}))
|
||||
require.NoError(t, exp.ExportSpans(ctx, roSpans))
|
||||
|
||||
defer func() {
|
||||
_ = exp.Shutdown(ctx)
|
||||
}()
|
||||
|
||||
headers := mc.getHeaders()
|
||||
require.Len(t, headers.Get("header1"), 1)
|
||||
assert.Equal(t, "value1", headers.Get("header1")[0])
|
||||
}
|
||||
|
||||
func TestNewExporter_WithTimeout(t *testing.T) {
|
||||
tts := []struct {
|
||||
name string
|
||||
fn func(exp *otlptrace.Exporter) error
|
||||
timeout time.Duration
|
||||
spans int
|
||||
code codes.Code
|
||||
delay bool
|
||||
}{
|
||||
{
|
||||
name: "Timeout Spans",
|
||||
fn: func(exp *otlptrace.Exporter) error {
|
||||
return exp.ExportSpans(context.Background(), roSpans)
|
||||
},
|
||||
timeout: time.Millisecond * 100,
|
||||
code: codes.DeadlineExceeded,
|
||||
delay: true,
|
||||
},
|
||||
|
||||
{
|
||||
name: "No Timeout Spans",
|
||||
fn: func(exp *otlptrace.Exporter) error {
|
||||
return exp.ExportSpans(context.Background(), roSpans)
|
||||
},
|
||||
timeout: time.Minute,
|
||||
spans: 1,
|
||||
code: codes.OK,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tts {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
mc := runMockCollector(t)
|
||||
if tt.delay {
|
||||
mc.traceSvc.delay = time.Second * 10
|
||||
}
|
||||
defer func() {
|
||||
_ = mc.stop()
|
||||
}()
|
||||
|
||||
ctx := context.Background()
|
||||
exp := newGRPCExporter(t, ctx, mc.endpoint, otlptracegrpc.WithTimeout(tt.timeout), otlptracegrpc.WithRetry(otlptracegrpc.RetrySettings{Enabled: false}))
|
||||
defer func() {
|
||||
_ = exp.Shutdown(ctx)
|
||||
}()
|
||||
|
||||
err := tt.fn(exp)
|
||||
|
||||
if tt.code == codes.OK {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
s := status.Convert(err)
|
||||
require.Equal(t, tt.code, s.Code())
|
||||
|
||||
require.Len(t, mc.getSpans(), tt.spans)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewExporter_withInvalidSecurityConfiguration(t *testing.T) {
|
||||
mc := runMockCollector(t)
|
||||
defer func() {
|
||||
_ = mc.stop()
|
||||
}()
|
||||
|
||||
ctx := context.Background()
|
||||
driver := otlptracegrpc.NewClient(otlptracegrpc.WithEndpoint(mc.endpoint))
|
||||
exp, err := otlptrace.NewExporter(ctx, driver)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create a new collector exporter: %v", err)
|
||||
}
|
||||
|
||||
err = exp.ExportSpans(ctx, roSpans)
|
||||
|
||||
expectedErr := fmt.Sprintf("traces exporter is disconnected from the server %s: grpc: no transport security set (use grpc.WithInsecure() explicitly or set credentials)", mc.endpoint)
|
||||
|
||||
require.Error(t, err)
|
||||
require.Equal(t, expectedErr, err.Error())
|
||||
|
||||
defer func() {
|
||||
_ = exp.Shutdown(ctx)
|
||||
}()
|
||||
}
|
||||
|
||||
func TestNewExporter_withMultipleAttributeTypes(t *testing.T) {
|
||||
mc := runMockCollector(t)
|
||||
|
||||
defer func() {
|
||||
_ = mc.stop()
|
||||
}()
|
||||
|
||||
<-time.After(5 * time.Millisecond)
|
||||
|
||||
ctx := context.Background()
|
||||
exp := newGRPCExporter(t, ctx, mc.endpoint)
|
||||
|
||||
defer func() {
|
||||
_ = exp.Shutdown(ctx)
|
||||
}()
|
||||
|
||||
tp := sdktrace.NewTracerProvider(
|
||||
sdktrace.WithSampler(sdktrace.AlwaysSample()),
|
||||
sdktrace.WithBatcher(
|
||||
exp,
|
||||
// add following two options to ensure flush
|
||||
sdktrace.WithBatchTimeout(5*time.Second),
|
||||
sdktrace.WithMaxExportBatchSize(10),
|
||||
),
|
||||
)
|
||||
defer func() { _ = tp.Shutdown(ctx) }()
|
||||
|
||||
tr := tp.Tracer("test-tracer")
|
||||
testKvs := []attribute.KeyValue{
|
||||
attribute.Int("Int", 1),
|
||||
attribute.Int64("Int64", int64(3)),
|
||||
attribute.Float64("Float64", 2.22),
|
||||
attribute.Bool("Bool", true),
|
||||
attribute.String("String", "test"),
|
||||
}
|
||||
_, span := tr.Start(ctx, "AlwaysSample")
|
||||
span.SetAttributes(testKvs...)
|
||||
span.End()
|
||||
|
||||
// Flush and close.
|
||||
func() {
|
||||
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
defer cancel()
|
||||
if err := tp.Shutdown(ctx); err != nil {
|
||||
t.Fatalf("failed to shut down a tracer provider: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait >2 cycles.
|
||||
<-time.After(40 * time.Millisecond)
|
||||
|
||||
// Now shutdown the exporter
|
||||
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
defer cancel()
|
||||
if err := exp.Shutdown(ctx); err != nil {
|
||||
t.Fatalf("failed to stop the exporter: %v", err)
|
||||
}
|
||||
|
||||
// Shutdown the collector too so that we can begin
|
||||
// verification checks of expected data back.
|
||||
_ = mc.stop()
|
||||
|
||||
// Now verify that we only got one span
|
||||
rss := mc.getSpans()
|
||||
if got, want := len(rss), 1; got != want {
|
||||
t.Fatalf("resource span count: got %d, want %d\n", got, want)
|
||||
}
|
||||
|
||||
expected := []*commonpb.KeyValue{
|
||||
{
|
||||
Key: "Int",
|
||||
Value: &commonpb.AnyValue{
|
||||
Value: &commonpb.AnyValue_IntValue{
|
||||
IntValue: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Key: "Int64",
|
||||
Value: &commonpb.AnyValue{
|
||||
Value: &commonpb.AnyValue_IntValue{
|
||||
IntValue: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Key: "Float64",
|
||||
Value: &commonpb.AnyValue{
|
||||
Value: &commonpb.AnyValue_DoubleValue{
|
||||
DoubleValue: 2.22,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Key: "Bool",
|
||||
Value: &commonpb.AnyValue{
|
||||
Value: &commonpb.AnyValue_BoolValue{
|
||||
BoolValue: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Key: "String",
|
||||
Value: &commonpb.AnyValue{
|
||||
Value: &commonpb.AnyValue_StringValue{
|
||||
StringValue: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Verify attributes
|
||||
if !assert.Len(t, rss[0].Attributes, len(expected)) {
|
||||
t.Fatalf("attributes count: got %d, want %d\n", len(rss[0].Attributes), len(expected))
|
||||
}
|
||||
for i, actual := range rss[0].Attributes {
|
||||
if a, ok := actual.Value.Value.(*commonpb.AnyValue_DoubleValue); ok {
|
||||
e, ok := expected[i].Value.Value.(*commonpb.AnyValue_DoubleValue)
|
||||
if !ok {
|
||||
t.Errorf("expected AnyValue_DoubleValue, got %T", expected[i].Value.Value)
|
||||
continue
|
||||
}
|
||||
if !assert.InDelta(t, e.DoubleValue, a.DoubleValue, 0.01) {
|
||||
continue
|
||||
}
|
||||
e.DoubleValue = a.DoubleValue
|
||||
}
|
||||
assert.Equal(t, expected[i], actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDisconnected(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
// The endpoint is whatever, we want to be disconnected. But we
|
||||
// setting a blocking connection, so dialing to the invalid
|
||||
// endpoint actually fails.
|
||||
exp := newGRPCExporter(t, ctx, "invalid",
|
||||
otlptracegrpc.WithReconnectionPeriod(time.Hour),
|
||||
otlptracegrpc.WithDialOption(
|
||||
grpc.WithBlock(),
|
||||
grpc.FailOnNonTempDialError(true),
|
||||
),
|
||||
)
|
||||
defer func() {
|
||||
assert.NoError(t, exp.Shutdown(ctx))
|
||||
}()
|
||||
|
||||
assert.Error(t, exp.ExportSpans(ctx, otlptracetest.SingleReadOnlySpan()))
|
||||
}
|
||||
|
||||
func TestEmptyData(t *testing.T) {
|
||||
mc := runMockCollectorAtEndpoint(t, "localhost:56561")
|
||||
|
||||
defer func() {
|
||||
_ = mc.stop()
|
||||
}()
|
||||
|
||||
<-time.After(5 * time.Millisecond)
|
||||
|
||||
ctx := context.Background()
|
||||
exp := newGRPCExporter(t, ctx, mc.endpoint)
|
||||
defer func() {
|
||||
assert.NoError(t, exp.Shutdown(ctx))
|
||||
}()
|
||||
|
||||
assert.NoError(t, exp.ExportSpans(ctx, nil))
|
||||
}
|
44
exporters/otlp/otlptrace/otlptracegrpc/exporter.go
Normal file
44
exporters/otlp/otlptrace/otlptracegrpc/exporter.go
Normal file
@ -0,0 +1,44 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package otlptracegrpc // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
|
||||
tracesdk "go.opentelemetry.io/otel/sdk/trace"
|
||||
)
|
||||
|
||||
// NewExporter constructs a new Exporter and starts it.
|
||||
func NewExporter(ctx context.Context, opts ...Option) (*otlptrace.Exporter, error) {
|
||||
return otlptrace.NewExporter(ctx, NewClient(opts...))
|
||||
}
|
||||
|
||||
// NewUnstartedExporter constructs a new Exporter and does not start it.
|
||||
func NewUnstartedExporter(opts ...Option) *otlptrace.Exporter {
|
||||
return otlptrace.NewUnstartedExporter(NewClient(opts...))
|
||||
}
|
||||
|
||||
// NewExportPipeline sets up a complete export pipeline
|
||||
// with the recommended TracerProvider setup.
|
||||
func NewExportPipeline(ctx context.Context, opts ...Option) (*otlptrace.Exporter, *tracesdk.TracerProvider, error) {
|
||||
return otlptrace.NewExportPipeline(ctx, NewClient(opts...))
|
||||
}
|
||||
|
||||
// InstallNewPipeline instantiates a NewExportPipeline with the
|
||||
// recommended configuration and registers it globally.
|
||||
func InstallNewPipeline(ctx context.Context, opts ...Option) (*otlptrace.Exporter, *tracesdk.TracerProvider, error) {
|
||||
return otlptrace.InstallNewPipeline(ctx, NewClient(opts...))
|
||||
}
|
64
exporters/otlp/otlptrace/otlptracegrpc/go.mod
Normal file
64
exporters/otlp/otlptrace/otlptracegrpc/go.mod
Normal file
@ -0,0 +1,64 @@
|
||||
module go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc
|
||||
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/stretchr/testify v1.7.0
|
||||
go.opentelemetry.io/otel v0.20.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v0.0.0-00010101000000-000000000000
|
||||
go.opentelemetry.io/otel/sdk v0.20.0
|
||||
go.opentelemetry.io/proto/otlp v0.8.0
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013
|
||||
google.golang.org/grpc v1.37.1
|
||||
google.golang.org/protobuf v1.26.0
|
||||
)
|
||||
|
||||
replace go.opentelemetry.io/otel => ../../../..
|
||||
|
||||
replace go.opentelemetry.io/otel/sdk => ../../../../sdk
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp => ../..
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace => ../
|
||||
|
||||
replace go.opentelemetry.io/otel/metric => ../../../../metric
|
||||
|
||||
replace go.opentelemetry.io/otel/oteltest => ../../../../oteltest
|
||||
|
||||
replace go.opentelemetry.io/otel/trace => ../../../../trace
|
||||
|
||||
replace go.opentelemetry.io/otel/bridge/opencensus => ../../../../bridge/opencensus
|
||||
|
||||
replace go.opentelemetry.io/otel/bridge/opentracing => ../../../../bridge/opentracing
|
||||
|
||||
replace go.opentelemetry.io/otel/example/jaeger => ../../../../example/jaeger
|
||||
|
||||
replace go.opentelemetry.io/otel/example/namedtracer => ../../../../example/namedtracer
|
||||
|
||||
replace go.opentelemetry.io/otel/example/opencensus => ../../../../example/opencensus
|
||||
|
||||
replace go.opentelemetry.io/otel/example/otel-collector => ../../../../example/otel-collector
|
||||
|
||||
replace go.opentelemetry.io/otel/example/prom-collector => ../../../../example/prom-collector
|
||||
|
||||
replace go.opentelemetry.io/otel/example/prometheus => ../../../../example/prometheus
|
||||
|
||||
replace go.opentelemetry.io/otel/example/zipkin => ../../../../example/zipkin
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/metric/prometheus => ../../../metric/prometheus
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc => ./
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/stdout => ../../../stdout
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/trace/jaeger => ../../../trace/jaeger
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/trace/zipkin => ../../../trace/zipkin
|
||||
|
||||
replace go.opentelemetry.io/otel/internal/tools => ../../../../internal/tools
|
||||
|
||||
replace go.opentelemetry.io/otel/sdk/export/metric => ../../../../sdk/export/metric
|
||||
|
||||
replace go.opentelemetry.io/otel/sdk/metric => ../../../../sdk/metric
|
||||
|
||||
replace go.opentelemetry.io/otel/example/passthrough => ../../../../example/passthrough
|
122
exporters/otlp/otlptrace/otlptracegrpc/go.sum
Normal file
122
exporters/otlp/otlptrace/otlptracegrpc/go.sum
Normal file
@ -0,0 +1,122 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/cenkalti/backoff/v4 v4.1.0 h1:c8LkOFQTzuO0WBM/ae5HdGQuZPfPxp7lqBRwQRm4fSc=
|
||||
github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
go.opentelemetry.io/proto/otlp v0.8.0 h1:Y0huTcGvPnmeIUuwORZk07LQ2q2CbrXHBkUidvGIoL0=
|
||||
go.opentelemetry.io/proto/otlp v0.8.0/go.mod h1:4i41ohS2vg3FjjjRpBNqfT/voGvIxREH17c6djRtXx8=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.37.1 h1:ARnQJNWxGyYJpdf/JXscNlQr/uv607ZPU9Z7ogHi+iI=
|
||||
google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
239
exporters/otlp/otlptrace/otlptracegrpc/mock_collector_test.go
Normal file
239
exporters/otlp/otlptrace/otlptracegrpc/mock_collector_test.go
Normal file
@ -0,0 +1,239 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package otlptracegrpc_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/otlptracetest"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/metadata"
|
||||
|
||||
collectortracepb "go.opentelemetry.io/proto/otlp/collector/trace/v1"
|
||||
tracepb "go.opentelemetry.io/proto/otlp/trace/v1"
|
||||
)
|
||||
|
||||
func makeMockCollector(t *testing.T, mockConfig *mockConfig) *mockCollector {
|
||||
return &mockCollector{
|
||||
t: t,
|
||||
traceSvc: &mockTraceService{
|
||||
storage: otlptracetest.NewSpansStorage(),
|
||||
errors: mockConfig.errors,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type mockTraceService struct {
|
||||
collectortracepb.UnimplementedTraceServiceServer
|
||||
|
||||
errors []error
|
||||
requests int
|
||||
mu sync.RWMutex
|
||||
storage otlptracetest.SpansStorage
|
||||
headers metadata.MD
|
||||
delay time.Duration
|
||||
}
|
||||
|
||||
func (mts *mockTraceService) getHeaders() metadata.MD {
|
||||
mts.mu.RLock()
|
||||
defer mts.mu.RUnlock()
|
||||
return mts.headers
|
||||
}
|
||||
|
||||
func (mts *mockTraceService) getSpans() []*tracepb.Span {
|
||||
mts.mu.RLock()
|
||||
defer mts.mu.RUnlock()
|
||||
return mts.storage.GetSpans()
|
||||
}
|
||||
|
||||
func (mts *mockTraceService) getResourceSpans() []*tracepb.ResourceSpans {
|
||||
mts.mu.RLock()
|
||||
defer mts.mu.RUnlock()
|
||||
return mts.storage.GetResourceSpans()
|
||||
}
|
||||
|
||||
func (mts *mockTraceService) Export(ctx context.Context, exp *collectortracepb.ExportTraceServiceRequest) (*collectortracepb.ExportTraceServiceResponse, error) {
|
||||
if mts.delay > 0 {
|
||||
time.Sleep(mts.delay)
|
||||
}
|
||||
|
||||
mts.mu.Lock()
|
||||
defer func() {
|
||||
mts.requests++
|
||||
mts.mu.Unlock()
|
||||
}()
|
||||
|
||||
reply := &collectortracepb.ExportTraceServiceResponse{}
|
||||
if mts.requests < len(mts.errors) {
|
||||
idx := mts.requests
|
||||
return reply, mts.errors[idx]
|
||||
}
|
||||
|
||||
mts.headers, _ = metadata.FromIncomingContext(ctx)
|
||||
mts.storage.AddSpans(exp)
|
||||
return reply, nil
|
||||
}
|
||||
|
||||
type mockCollector struct {
|
||||
t *testing.T
|
||||
|
||||
traceSvc *mockTraceService
|
||||
|
||||
endpoint string
|
||||
ln *listener
|
||||
stopFunc func()
|
||||
stopOnce sync.Once
|
||||
}
|
||||
|
||||
type mockConfig struct {
|
||||
errors []error
|
||||
endpoint string
|
||||
}
|
||||
|
||||
var _ collectortracepb.TraceServiceServer = (*mockTraceService)(nil)
|
||||
|
||||
var errAlreadyStopped = fmt.Errorf("already stopped")
|
||||
|
||||
func (mc *mockCollector) stop() error {
|
||||
var err = errAlreadyStopped
|
||||
mc.stopOnce.Do(func() {
|
||||
err = nil
|
||||
if mc.stopFunc != nil {
|
||||
mc.stopFunc()
|
||||
}
|
||||
})
|
||||
// Give it sometime to shutdown.
|
||||
<-time.After(160 * time.Millisecond)
|
||||
|
||||
// Getting the lock ensures the traceSvc is done flushing.
|
||||
mc.traceSvc.mu.Lock()
|
||||
defer mc.traceSvc.mu.Unlock()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (mc *mockCollector) Stop() error {
|
||||
return mc.stop()
|
||||
}
|
||||
|
||||
func (mc *mockCollector) getSpans() []*tracepb.Span {
|
||||
return mc.traceSvc.getSpans()
|
||||
}
|
||||
|
||||
func (mc *mockCollector) getResourceSpans() []*tracepb.ResourceSpans {
|
||||
return mc.traceSvc.getResourceSpans()
|
||||
}
|
||||
|
||||
func (mc *mockCollector) GetResourceSpans() []*tracepb.ResourceSpans {
|
||||
return mc.getResourceSpans()
|
||||
}
|
||||
|
||||
func (mc *mockCollector) getHeaders() metadata.MD {
|
||||
return mc.traceSvc.getHeaders()
|
||||
}
|
||||
|
||||
// runMockCollector is a helper function to create a mock Collector
|
||||
func runMockCollector(t *testing.T) *mockCollector {
|
||||
return runMockCollectorAtEndpoint(t, "localhost:0")
|
||||
}
|
||||
|
||||
func runMockCollectorAtEndpoint(t *testing.T, endpoint string) *mockCollector {
|
||||
return runMockCollectorWithConfig(t, &mockConfig{endpoint: endpoint})
|
||||
}
|
||||
|
||||
func runMockCollectorWithConfig(t *testing.T, mockConfig *mockConfig) *mockCollector {
|
||||
ln, err := net.Listen("tcp", mockConfig.endpoint)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get an endpoint: %v", err)
|
||||
}
|
||||
|
||||
srv := grpc.NewServer()
|
||||
mc := makeMockCollector(t, mockConfig)
|
||||
collectortracepb.RegisterTraceServiceServer(srv, mc.traceSvc)
|
||||
mc.ln = newListener(ln)
|
||||
go func() {
|
||||
_ = srv.Serve((net.Listener)(mc.ln))
|
||||
}()
|
||||
|
||||
mc.endpoint = ln.Addr().String()
|
||||
// srv.Stop calls Close on mc.ln.
|
||||
mc.stopFunc = srv.Stop
|
||||
|
||||
return mc
|
||||
}
|
||||
|
||||
type listener struct {
|
||||
closeOnce sync.Once
|
||||
wrapped net.Listener
|
||||
C chan struct{}
|
||||
}
|
||||
|
||||
func newListener(wrapped net.Listener) *listener {
|
||||
return &listener{
|
||||
wrapped: wrapped,
|
||||
C: make(chan struct{}, 1),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *listener) Close() error { return l.wrapped.Close() }
|
||||
|
||||
func (l *listener) Addr() net.Addr { return l.wrapped.Addr() }
|
||||
|
||||
// Accept waits for and returns the next connection to the listener. It will
|
||||
// send a signal on l.C that a connection has been made before returning.
|
||||
func (l *listener) Accept() (net.Conn, error) {
|
||||
conn, err := l.wrapped.Accept()
|
||||
if err != nil {
|
||||
// Go 1.16 exported net.ErrClosed that could clean up this check, but to
|
||||
// remain backwards compatible with previous versions of Go that we
|
||||
// support the following string evaluation is used instead to keep in line
|
||||
// with the previously recommended way to check this:
|
||||
// https://github.com/golang/go/issues/4373#issuecomment-353076799
|
||||
if strings.Contains(err.Error(), "use of closed network connection") {
|
||||
// If the listener has been closed, do not allow callers of
|
||||
// WaitForConn to wait for a connection that will never come.
|
||||
l.closeOnce.Do(func() { close(l.C) })
|
||||
}
|
||||
return conn, err
|
||||
}
|
||||
|
||||
select {
|
||||
case l.C <- struct{}{}:
|
||||
default:
|
||||
// If C is full, assume nobody is listening and move on.
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// WaitForConn will wait indefintely for a connection to be estabilished with
|
||||
// the listener before returning.
|
||||
func (l *listener) WaitForConn() {
|
||||
for {
|
||||
select {
|
||||
case <-l.C:
|
||||
return
|
||||
default:
|
||||
runtime.Gosched()
|
||||
}
|
||||
}
|
||||
}
|
177
exporters/otlp/otlptrace/otlptracegrpc/options.go
Normal file
177
exporters/otlp/otlptrace/otlptracegrpc/options.go
Normal file
@ -0,0 +1,177 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package otlptracegrpc // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/otlpconfig"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
)
|
||||
|
||||
// Option applies an option to the gRPC driver.
|
||||
type Option interface {
|
||||
applyGRPCOption(*otlpconfig.Config)
|
||||
}
|
||||
|
||||
// RetrySettings defines configuration for retrying batches in case of export failure
|
||||
// using an exponential backoff.
|
||||
type RetrySettings otlpconfig.RetrySettings
|
||||
|
||||
type wrappedOption struct {
|
||||
otlpconfig.GRPCOption
|
||||
}
|
||||
|
||||
func (w wrappedOption) applyGRPCOption(cfg *otlpconfig.Config) {
|
||||
w.ApplyGRPCOption(cfg)
|
||||
}
|
||||
|
||||
// WithInsecure disables client transport security for the exporter's gRPC connection
|
||||
// just like grpc.WithInsecure() https://pkg.go.dev/google.golang.org/grpc#WithInsecure
|
||||
// does. Note, by default, client security is required unless WithInsecure is used.
|
||||
func WithInsecure() Option {
|
||||
return wrappedOption{otlpconfig.WithInsecure()}
|
||||
}
|
||||
|
||||
// WithTracesInsecure disables client transport security for the traces exporter's gRPC connection
|
||||
// just like grpc.WithInsecure() https://pkg.go.dev/google.golang.org/grpc#WithInsecure
|
||||
// does. Note, by default, client security is required unless WithInsecure is used.
|
||||
func WithTracesInsecure() Option {
|
||||
return wrappedOption{otlpconfig.WithInsecureTraces()}
|
||||
}
|
||||
|
||||
// WithEndpoint allows one to set the endpoint that the exporter will
|
||||
// connect to the collector on. If unset, it will instead try to use
|
||||
// connect to DefaultCollectorHost:DefaultCollectorPort.
|
||||
func WithEndpoint(endpoint string) Option {
|
||||
return wrappedOption{otlpconfig.WithEndpoint(endpoint)}
|
||||
}
|
||||
|
||||
// WithTracesEndpoint allows one to set the traces endpoint that the exporter will
|
||||
// connect to the collector on. If unset, it will instead try to use
|
||||
// connect to DefaultCollectorHost:DefaultCollectorPort.
|
||||
func WithTracesEndpoint(endpoint string) Option {
|
||||
return wrappedOption{otlpconfig.WithTracesEndpoint(endpoint)}
|
||||
}
|
||||
|
||||
// WithReconnectionPeriod allows one to set the delay between next connection attempt
|
||||
// after failing to connect with the collector.
|
||||
func WithReconnectionPeriod(rp time.Duration) Option {
|
||||
return wrappedOption{otlpconfig.NewGRPCOption(func(cfg *otlpconfig.Config) {
|
||||
cfg.ReconnectionPeriod = rp
|
||||
})}
|
||||
}
|
||||
|
||||
func compressorToCompression(compressor string) otlpconfig.Compression {
|
||||
switch compressor {
|
||||
case "gzip":
|
||||
return otlpconfig.GzipCompression
|
||||
}
|
||||
|
||||
otel.Handle(fmt.Errorf("invalid compression type: '%s', using no compression as default", compressor))
|
||||
return otlpconfig.NoCompression
|
||||
}
|
||||
|
||||
// WithCompressor will set the compressor for the gRPC client to use when sending requests.
|
||||
// It is the responsibility of the caller to ensure that the compressor set has been registered
|
||||
// with google.golang.org/grpc/encoding. This can be done by encoding.RegisterCompressor. Some
|
||||
// compressors auto-register on import, such as gzip, which can be registered by calling
|
||||
// `import _ "google.golang.org/grpc/encoding/gzip"`.
|
||||
func WithCompressor(compressor string) Option {
|
||||
return wrappedOption{otlpconfig.WithCompression(compressorToCompression(compressor))}
|
||||
}
|
||||
|
||||
// WithTracesCompression will set the compressor for the gRPC client to use when sending traces requests.
|
||||
// It is the responsibility of the caller to ensure that the compressor set has been registered
|
||||
// with google.golang.org/grpc/encoding. This can be done by encoding.RegisterCompressor. Some
|
||||
// compressors auto-register on import, such as gzip, which can be registered by calling
|
||||
// `import _ "google.golang.org/grpc/encoding/gzip"`.
|
||||
func WithTracesCompression(compressor string) Option {
|
||||
return wrappedOption{otlpconfig.WithTracesCompression(compressorToCompression(compressor))}
|
||||
}
|
||||
|
||||
// WithHeaders will send the provided headers with gRPC requests.
|
||||
func WithHeaders(headers map[string]string) Option {
|
||||
return wrappedOption{otlpconfig.WithHeaders(headers)}
|
||||
}
|
||||
|
||||
// WithTracesHeaders will send the provided headers with gRPC traces requests.
|
||||
func WithTracesHeaders(headers map[string]string) Option {
|
||||
return wrappedOption{otlpconfig.WithTracesHeaders(headers)}
|
||||
}
|
||||
|
||||
// WithTLSCredentials allows the connection to use TLS credentials
|
||||
// when talking to the server. It takes in grpc.TransportCredentials instead
|
||||
// of say a Certificate file or a tls.Certificate, because the retrieving of
|
||||
// these credentials can be done in many ways e.g. plain file, in code tls.Config
|
||||
// or by certificate rotation, so it is up to the caller to decide what to use.
|
||||
func WithTLSCredentials(creds credentials.TransportCredentials) Option {
|
||||
return wrappedOption{otlpconfig.NewGRPCOption(func(cfg *otlpconfig.Config) {
|
||||
cfg.Traces.GRPCCredentials = creds
|
||||
})}
|
||||
}
|
||||
|
||||
// WithTracesTLSCredentials allows the connection to use TLS credentials
|
||||
// when talking to the traces server. It takes in grpc.TransportCredentials instead
|
||||
// of say a Certificate file or a tls.Certificate, because the retrieving of
|
||||
// these credentials can be done in many ways e.g. plain file, in code tls.Config
|
||||
// or by certificate rotation, so it is up to the caller to decide what to use.
|
||||
func WithTracesTLSCredentials(creds credentials.TransportCredentials) Option {
|
||||
return wrappedOption{otlpconfig.NewGRPCOption(func(cfg *otlpconfig.Config) {
|
||||
cfg.Traces.GRPCCredentials = creds
|
||||
})}
|
||||
}
|
||||
|
||||
// WithServiceConfig defines the default gRPC service config used.
|
||||
func WithServiceConfig(serviceConfig string) Option {
|
||||
return wrappedOption{otlpconfig.NewGRPCOption(func(cfg *otlpconfig.Config) {
|
||||
cfg.ServiceConfig = serviceConfig
|
||||
})}
|
||||
}
|
||||
|
||||
// WithDialOption opens support to any grpc.DialOption to be used. If it conflicts
|
||||
// with some other configuration the GRPC specified via the collector the ones here will
|
||||
// take preference since they are set last.
|
||||
func WithDialOption(opts ...grpc.DialOption) Option {
|
||||
return wrappedOption{otlpconfig.NewGRPCOption(func(cfg *otlpconfig.Config) {
|
||||
cfg.DialOptions = opts
|
||||
})}
|
||||
}
|
||||
|
||||
// WithTimeout tells the driver the max waiting time for the backend to process
|
||||
// each spans batch. If unset, the default will be 10 seconds.
|
||||
func WithTimeout(duration time.Duration) Option {
|
||||
return wrappedOption{otlpconfig.WithTimeout(duration)}
|
||||
}
|
||||
|
||||
// WithTracesTimeout tells the driver the max waiting time for the backend to process
|
||||
// each spans batch. If unset, the default will be 10 seconds.
|
||||
func WithTracesTimeout(duration time.Duration) Option {
|
||||
return wrappedOption{otlpconfig.WithTracesTimeout(duration)}
|
||||
}
|
||||
|
||||
// WithRetry configures the retry policy for transient errors that may occurs when
|
||||
// exporting traces. An exponential back-off algorithm is used to
|
||||
// ensure endpoints are not overwhelmed with retries. If unset, the default
|
||||
// ensure endpoints are not overwhelmed with retries. If unset, the default
|
||||
// retry policy will retry after 5 seconds and increase exponentially after each
|
||||
// error for a total of 1 minute.
|
||||
func WithRetry(settings RetrySettings) Option {
|
||||
return wrappedOption{otlpconfig.WithRetry(otlpconfig.RetrySettings(settings))}
|
||||
}
|
@ -59,3 +59,7 @@ replace go.opentelemetry.io/otel/sdk/metric => ../../sdk/metric
|
||||
replace go.opentelemetry.io/otel/trace => ../../trace
|
||||
|
||||
replace go.opentelemetry.io/otel/example/passthrough => ../../example/passthrough
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace => ../otlp/otlptrace
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc => ../otlp/otlptrace/otlptracegrpc
|
||||
|
@ -56,3 +56,7 @@ replace go.opentelemetry.io/otel/sdk/metric => ../../../sdk/metric
|
||||
replace go.opentelemetry.io/otel/trace => ../../../trace
|
||||
|
||||
replace go.opentelemetry.io/otel/example/passthrough => ../../../example/passthrough
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace => ../../otlp/otlptrace
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc => ../../otlp/otlptrace/otlptracegrpc
|
||||
|
@ -57,3 +57,7 @@ replace go.opentelemetry.io/otel/sdk/metric => ../../../sdk/metric
|
||||
replace go.opentelemetry.io/otel/trace => ../../../trace
|
||||
|
||||
replace go.opentelemetry.io/otel/example/passthrough => ../../../example/passthrough
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace => ../../otlp/otlptrace
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc => ../../otlp/otlptrace/otlptracegrpc
|
||||
|
4
go.mod
4
go.mod
@ -55,3 +55,7 @@ replace go.opentelemetry.io/otel/sdk/metric => ./sdk/metric
|
||||
replace go.opentelemetry.io/otel/trace => ./trace
|
||||
|
||||
replace go.opentelemetry.io/otel/example/passthrough => ./example/passthrough
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace => ./exporters/otlp/otlptrace
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc => ./exporters/otlp/otlptrace/otlptracegrpc
|
||||
|
@ -56,3 +56,7 @@ replace go.opentelemetry.io/otel/sdk/metric => ../../sdk/metric
|
||||
replace go.opentelemetry.io/otel/trace => ../../trace
|
||||
|
||||
replace go.opentelemetry.io/otel/example/passthrough => ../../example/passthrough
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace => ../../exporters/otlp/otlptrace
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc => ../../exporters/otlp/otlptrace/otlptracegrpc
|
||||
|
@ -54,3 +54,7 @@ require (
|
||||
)
|
||||
|
||||
replace go.opentelemetry.io/otel/example/passthrough => ../example/passthrough
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace => ../exporters/otlp/otlptrace
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc => ../exporters/otlp/otlptrace/otlptracegrpc
|
||||
|
@ -54,3 +54,7 @@ require (
|
||||
)
|
||||
|
||||
replace go.opentelemetry.io/otel/example/passthrough => ../example/passthrough
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace => ../exporters/otlp/otlptrace
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc => ../exporters/otlp/otlptrace/otlptracegrpc
|
||||
|
@ -54,3 +54,7 @@ require (
|
||||
)
|
||||
|
||||
replace go.opentelemetry.io/otel/example/passthrough => ../../../example/passthrough
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace => ../../../exporters/otlp/otlptrace
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc => ../../../exporters/otlp/otlptrace/otlptracegrpc
|
||||
|
@ -55,3 +55,7 @@ replace go.opentelemetry.io/otel/sdk/metric => ./metric
|
||||
replace go.opentelemetry.io/otel/trace => ../trace
|
||||
|
||||
replace go.opentelemetry.io/otel/example/passthrough => ../example/passthrough
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace => ../exporters/otlp/otlptrace
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc => ../exporters/otlp/otlptrace/otlptracegrpc
|
||||
|
@ -56,3 +56,7 @@ require (
|
||||
)
|
||||
|
||||
replace go.opentelemetry.io/otel/example/passthrough => ../../example/passthrough
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace => ../../exporters/otlp/otlptrace
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc => ../../exporters/otlp/otlptrace/otlptracegrpc
|
||||
|
@ -53,3 +53,7 @@ require (
|
||||
)
|
||||
|
||||
replace go.opentelemetry.io/otel/example/passthrough => ../example/passthrough
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace => ../exporters/otlp/otlptrace
|
||||
|
||||
replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc => ../exporters/otlp/otlptrace/otlptracegrpc
|
||||
|
Loading…
x
Reference in New Issue
Block a user