1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2025-02-05 13:15:41 +02:00

Populate Jaeger's Span.Process from Resource (#1673)

* Jaeger exporter now populate Jaeger's Span Process from Resource

* Remove jaeger.WithProcess

* Fix tests

* Change the type of default service name into string

* Add tests

* Update CHANGELOG

* Use the API from `Set` to fetch service name in exporter

* Fix nits

* Add more test cases for jaegerBatchList function

* precommit

Co-authored-by: Anthony Mirabella <a9@aneurysm9.com>
This commit is contained in:
Sam Xie 2021-03-17 00:04:46 +08:00 committed by GitHub
parent 28eaaa9a91
commit 62cbf0f240
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 465 additions and 89 deletions

View File

@ -13,6 +13,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Added `Marshaler` config option to `otlphttp` to enable otlp over json or protobufs. (#1586) - Added `Marshaler` config option to `otlphttp` to enable otlp over json or protobufs. (#1586)
- A `ForceFlush` method to the `"go.opentelemetry.io/otel/sdk/trace".TracerProvider` to flush all registered `SpanProcessor`s. (#1608) - A `ForceFlush` method to the `"go.opentelemetry.io/otel/sdk/trace".TracerProvider` to flush all registered `SpanProcessor`s. (#1608)
- Added `WithDefaultSampler` and `WithSpanLimits` to tracer provider. (#1633) - Added `WithDefaultSampler` and `WithSpanLimits` to tracer provider. (#1633)
- Jaeger exporter falls back to `resource.Default`'s `service.name` if the exported Span does not have one. (#1673)
### Changed ### Changed
@ -25,6 +26,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- `trace.SpanContext` is now immutable and has no exported fields. (#1573) - `trace.SpanContext` is now immutable and has no exported fields. (#1573)
- `trace.NewSpanContext()` can be used in conjunction with the `trace.SpanContextConfig` struct to initialize a new `SpanContext` where all values are known. - `trace.NewSpanContext()` can be used in conjunction with the `trace.SpanContextConfig` struct to initialize a new `SpanContext` where all values are known.
- Renamed the `LabelSet` method of `"go.opentelemetry.io/otel/sdk/resource".Resource` to `Set`. (#1692) - Renamed the `LabelSet` method of `"go.opentelemetry.io/otel/sdk/resource".Resource` to `Set`. (#1692)
- Jaeger exporter populates Jaeger's Span Process from Resource. (#1673)
### Removed ### Removed
@ -33,6 +35,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
These are now returned as a SpanProcessor interface from their respective constructors. (#1638) These are now returned as a SpanProcessor interface from their respective constructors. (#1638)
- Removed setting status to `Error` while recording an error as a span event in `RecordError`. (#1663) - Removed setting status to `Error` while recording an error as a span event in `RecordError`. (#1663)
- Removed `WithConfig` from tracer provider to avoid overriding configuration. (#1633) - Removed `WithConfig` from tracer provider to avoid overriding configuration. (#1633)
- Removed `jaeger.WithProcess`. (#1673)
### Fixed ### Fixed

View File

@ -8,7 +8,6 @@ github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/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.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=

View File

@ -21,6 +21,8 @@ import (
"log" "log"
"go.opentelemetry.io/otel" "go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/sdk/resource"
"go.opentelemetry.io/otel/semconv"
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/trace/jaeger" "go.opentelemetry.io/otel/exporters/trace/jaeger"
@ -32,14 +34,14 @@ func initTracer() func() {
// Create and install Jaeger export pipeline. // Create and install Jaeger export pipeline.
flush, err := jaeger.InstallNewPipeline( flush, err := jaeger.InstallNewPipeline(
jaeger.WithCollectorEndpoint("http://localhost:14268/api/traces"), jaeger.WithCollectorEndpoint("http://localhost:14268/api/traces"),
jaeger.WithProcess(jaeger.Process{ jaeger.WithSDK(&sdktrace.Config{
ServiceName: "trace-demo", DefaultSampler: sdktrace.AlwaysSample(),
Tags: []attribute.KeyValue{ Resource: resource.NewWithAttributes(
semconv.ServiceNameKey.String("trace-demo"),
attribute.String("exporter", "jaeger"), attribute.String("exporter", "jaeger"),
attribute.Float64("float", 312.23), attribute.Float64("float", 312.23),
}, ),
}), }),
jaeger.WithSDK(&sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()}),
) )
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
@ -63,6 +65,7 @@ func main() {
func bar(ctx context.Context) { func bar(ctx context.Context) {
tr := otel.Tracer("component-bar") tr := otel.Tracer("component-bar")
_, span := tr.Start(ctx, "bar") _, span := tr.Start(ctx, "bar")
span.SetAttributes(attribute.Key("testset").String("value"))
defer span.End() defer span.End()
// Do bar... // Do bar...

View File

@ -234,8 +234,8 @@ func TestNewRawExporterWithEnv(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, false, exp.o.Disabled) assert.Equal(t, false, exp.o.Disabled)
assert.EqualValues(t, serviceName, exp.process.ServiceName) assert.EqualValues(t, serviceName, exp.o.Process.ServiceName)
assert.Len(t, exp.process.Tags, 1) assert.Len(t, exp.o.Process.Tags, 1)
require.IsType(t, &collectorUploader{}, exp.uploader) require.IsType(t, &collectorUploader{}, exp.uploader)
uploader := exp.uploader.(*collectorUploader) uploader := exp.uploader.(*collectorUploader)
@ -276,8 +276,8 @@ func TestNewRawExporterWithEnvImplicitly(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
// NewRawExporter will ignore Disabled env // NewRawExporter will ignore Disabled env
assert.Equal(t, true, exp.o.Disabled) assert.Equal(t, true, exp.o.Disabled)
assert.EqualValues(t, serviceName, exp.process.ServiceName) assert.EqualValues(t, serviceName, exp.o.Process.ServiceName)
assert.Len(t, exp.process.Tags, 1) assert.Len(t, exp.o.Process.Tags, 1)
require.IsType(t, &collectorUploader{}, exp.uploader) require.IsType(t, &collectorUploader{}, exp.uploader)
uploader := exp.uploader.(*collectorUploader) uploader := exp.uploader.(*collectorUploader)

View File

@ -28,13 +28,13 @@ import (
"go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/codes"
gen "go.opentelemetry.io/otel/exporters/trace/jaeger/internal/gen-go/jaeger" gen "go.opentelemetry.io/otel/exporters/trace/jaeger/internal/gen-go/jaeger"
export "go.opentelemetry.io/otel/sdk/export/trace" export "go.opentelemetry.io/otel/sdk/export/trace"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace" sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/semconv"
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
) )
const ( const (
defaultServiceName = "OpenTelemetry"
keyInstrumentationLibraryName = "otel.library.name" keyInstrumentationLibraryName = "otel.library.name"
keyInstrumentationLibraryVersion = "otel.library.version" keyInstrumentationLibraryVersion = "otel.library.version"
) )
@ -57,13 +57,6 @@ type options struct {
Disabled bool Disabled bool
} }
// WithProcess sets the process with the information about the exporting process.
func WithProcess(process Process) Option {
return func(o *options) {
o.Process = process
}
}
// WithBufferMaxCount defines the total number of traces that can be buffered in memory // WithBufferMaxCount defines the total number of traces that can be buffered in memory
func WithBufferMaxCount(bufferMaxCount int) Option { func WithBufferMaxCount(bufferMaxCount int) Option {
return func(o *options) { return func(o *options) {
@ -109,27 +102,24 @@ func NewRawExporter(endpointOption EndpointOption, opts ...Option) (*Exporter, e
opt(&o) opt(&o)
} }
service := o.Process.ServiceName // Fetch default service.name from default resource for backup
if service == "" { var defaultServiceName string
service = defaultServiceName defaultResource := resource.Default()
if value, exists := defaultResource.Set().Value(semconv.ServiceNameKey); exists {
defaultServiceName = value.AsString()
} }
tags := make([]*gen.Tag, 0, len(o.Process.Tags)) if defaultServiceName == "" {
for _, tag := range o.Process.Tags { return nil, fmt.Errorf("failed to get service name from default resource")
t := keyValueToTag(tag)
if t != nil {
tags = append(tags, t)
}
} }
e := &Exporter{ e := &Exporter{
uploader: uploader, uploader: uploader,
process: &gen.Process{ o: o,
ServiceName: service, defaultServiceName: defaultServiceName,
Tags: tags, resourceFromProcess: processToResource(o.Process),
},
o: o,
} }
bundler := bundler.NewBundler((*gen.Span)(nil), func(bundle interface{}) { bundler := bundler.NewBundler((*export.SpanSnapshot)(nil), func(bundle interface{}) {
if err := e.upload(bundle.([]*gen.Span)); err != nil { if err := e.upload(bundle.([]*export.SpanSnapshot)); err != nil {
otel.Handle(err) otel.Handle(err)
} }
}) })
@ -205,13 +195,15 @@ type Process struct {
// Exporter is an implementation of an OTel SpanSyncer that uploads spans to // Exporter is an implementation of an OTel SpanSyncer that uploads spans to
// Jaeger. // Jaeger.
type Exporter struct { type Exporter struct {
process *gen.Process
bundler *bundler.Bundler bundler *bundler.Bundler
uploader batchUploader uploader batchUploader
o options o options
stoppedMu sync.RWMutex stoppedMu sync.RWMutex
stopped bool stopped bool
defaultServiceName string
resourceFromProcess *resource.Resource
} }
var _ export.SpanExporter = (*Exporter)(nil) var _ export.SpanExporter = (*Exporter)(nil)
@ -227,7 +219,7 @@ func (e *Exporter) ExportSpans(ctx context.Context, ss []*export.SpanSnapshot) e
for _, span := range ss { for _, span := range ss {
// TODO(jbd): Handle oversized bundlers. // TODO(jbd): Handle oversized bundlers.
err := e.bundler.Add(spanSnapshotToThrift(span), 1) err := e.bundler.Add(span, 1)
if err != nil { if err != nil {
return fmt.Errorf("failed to bundle %q: %w", span.Name, err) return fmt.Errorf("failed to bundle %q: %w", span.Name, err)
} }
@ -275,17 +267,6 @@ func spanSnapshotToThrift(ss *export.SpanSnapshot) *gen.Span {
} }
} }
// TODO (jmacd): OTel has a broad "last value wins"
// semantic. Should resources be appended before span
// attributes, above, to allow span attributes to
// overwrite resource attributes?
if ss.Resource != nil {
for iter := ss.Resource.Iter(); iter.Next(); {
if tag := keyValueToTag(iter.Attribute()); tag != nil {
tags = append(tags, tag)
}
}
}
if il := ss.InstrumentationLibrary; il.Name != "" { if il := ss.InstrumentationLibrary; il.Name != "" {
tags = append(tags, getStringTag(keyInstrumentationLibraryName, il.Name)) tags = append(tags, getStringTag(keyInstrumentationLibraryName, il.Name))
if il.Version != "" { if il.Version != "" {
@ -429,11 +410,94 @@ func (e *Exporter) Flush() {
flush(e) flush(e)
} }
func (e *Exporter) upload(spans []*gen.Span) error { func (e *Exporter) upload(spans []*export.SpanSnapshot) error {
batch := &gen.Batch{ batchList := jaegerBatchList(spans, e.defaultServiceName, e.resourceFromProcess)
Spans: spans, for _, batch := range batchList {
Process: e.process, err := e.uploader.upload(batch)
if err != nil {
return err
}
} }
return e.uploader.upload(batch) return nil
}
// jaegerBatchList transforms a slice of SpanSnapshot into a slice of jaeger
// Batch.
func jaegerBatchList(ssl []*export.SpanSnapshot, defaultServiceName string, resourceFromProcess *resource.Resource) []*gen.Batch {
if len(ssl) == 0 {
return nil
}
batchDict := make(map[attribute.Distinct]*gen.Batch)
for _, ss := range ssl {
if ss == nil {
continue
}
newResource := ss.Resource
if resourceFromProcess != nil {
// The value from process will overwrite the value from span's resources
newResource = resource.Merge(ss.Resource, resourceFromProcess)
}
resourceKey := newResource.Equivalent()
batch, bOK := batchDict[resourceKey]
if !bOK {
batch = &gen.Batch{
Process: process(newResource, defaultServiceName),
Spans: []*gen.Span{},
}
}
batch.Spans = append(batch.Spans, spanSnapshotToThrift(ss))
batchDict[resourceKey] = batch
}
// Transform the categorized map into a slice
batchList := make([]*gen.Batch, 0, len(batchDict))
for _, batch := range batchDict {
batchList = append(batchList, batch)
}
return batchList
}
// process transforms an OTel Resource into a jaeger Process.
func process(res *resource.Resource, defaultServiceName string) *gen.Process {
var process gen.Process
var serviceName attribute.KeyValue
if res != nil {
for iter := res.Iter(); iter.Next(); {
if iter.Attribute().Key == semconv.ServiceNameKey {
serviceName = iter.Attribute()
// Don't convert service.name into tag.
continue
}
if tag := keyValueToTag(iter.Attribute()); tag != nil {
process.Tags = append(process.Tags, tag)
}
}
}
// If no service.name is contained in a Span's Resource,
// that field MUST be populated from the default Resource.
if serviceName.Value.AsString() == "" {
serviceName = semconv.ServiceVersionKey.String(defaultServiceName)
}
process.ServiceName = serviceName.Value.AsString()
return &process
}
func processToResource(process Process) *resource.Resource {
var attrs []attribute.KeyValue
if process.ServiceName != "" {
attrs = append(attrs, semconv.ServiceNameKey.String(process.ServiceName))
}
attrs = append(attrs, process.Tags...)
if len(attrs) == 0 {
return nil
}
return resource.NewWithAttributes(attrs...)
} }

View File

@ -19,6 +19,7 @@ import (
"encoding/binary" "encoding/binary"
"os" "os"
"sort" "sort"
"strings"
"testing" "testing"
"time" "time"
@ -36,6 +37,7 @@ import (
"go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/instrumentation"
"go.opentelemetry.io/otel/sdk/resource" "go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace" sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/semconv"
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
) )
@ -185,32 +187,14 @@ func TestNewRawExporter(t *testing.T) {
{ {
name: "default exporter", name: "default exporter",
endpoint: WithCollectorEndpoint(collectorEndpoint), endpoint: WithCollectorEndpoint(collectorEndpoint),
expectedServiceName: defaultServiceName, expectedServiceName: "unknown_service",
expectedBufferMaxCount: bundler.DefaultBufferedByteLimit, expectedBufferMaxCount: bundler.DefaultBufferedByteLimit,
expectedBatchMaxCount: bundler.DefaultBundleCountThreshold, expectedBatchMaxCount: bundler.DefaultBundleCountThreshold,
}, },
{ {
name: "default exporter with agent endpoint", name: "default exporter with agent endpoint",
endpoint: WithAgentEndpoint(agentEndpoint), endpoint: WithAgentEndpoint(agentEndpoint),
expectedServiceName: defaultServiceName, expectedServiceName: "unknown_service",
expectedBufferMaxCount: bundler.DefaultBufferedByteLimit,
expectedBatchMaxCount: bundler.DefaultBundleCountThreshold,
},
{
name: "with process",
endpoint: WithCollectorEndpoint(collectorEndpoint),
options: []Option{
WithProcess(
Process{
ServiceName: "jaeger-test",
Tags: []attribute.KeyValue{
attribute.String("key", "val"),
},
},
),
},
expectedServiceName: "jaeger-test",
expectedTagsLen: 1,
expectedBufferMaxCount: bundler.DefaultBufferedByteLimit, expectedBufferMaxCount: bundler.DefaultBufferedByteLimit,
expectedBatchMaxCount: bundler.DefaultBundleCountThreshold, expectedBatchMaxCount: bundler.DefaultBundleCountThreshold,
}, },
@ -218,15 +202,10 @@ func TestNewRawExporter(t *testing.T) {
name: "with buffer and batch max count", name: "with buffer and batch max count",
endpoint: WithCollectorEndpoint(collectorEndpoint), endpoint: WithCollectorEndpoint(collectorEndpoint),
options: []Option{ options: []Option{
WithProcess(
Process{
ServiceName: "jaeger-test",
},
),
WithBufferMaxCount(99), WithBufferMaxCount(99),
WithBatchMaxCount(99), WithBatchMaxCount(99),
}, },
expectedServiceName: "jaeger-test", expectedServiceName: "unknown_service",
expectedBufferMaxCount: 99, expectedBufferMaxCount: 99,
expectedBatchMaxCount: 99, expectedBatchMaxCount: 99,
}, },
@ -240,10 +219,10 @@ func TestNewRawExporter(t *testing.T) {
) )
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, tc.expectedServiceName, exp.process.ServiceName)
assert.Len(t, exp.process.Tags, tc.expectedTagsLen)
assert.Equal(t, tc.expectedBufferMaxCount, exp.bundler.BufferedByteLimit) assert.Equal(t, tc.expectedBufferMaxCount, exp.bundler.BufferedByteLimit)
assert.Equal(t, tc.expectedBatchMaxCount, exp.bundler.BundleCountThreshold) assert.Equal(t, tc.expectedBatchMaxCount, exp.bundler.BundleCountThreshold)
assert.NotEmpty(t, exp.defaultServiceName)
assert.True(t, strings.HasPrefix(exp.defaultServiceName, tc.expectedServiceName))
}) })
} }
} }
@ -327,11 +306,11 @@ func TestExporter_ExportSpan(t *testing.T) {
// Create Jaeger Exporter // Create Jaeger Exporter
exp, err := NewRawExporter( exp, err := NewRawExporter(
withTestCollectorEndpoint(), withTestCollectorEndpoint(),
WithProcess(Process{ WithSDK(&sdktrace.Config{
ServiceName: serviceName, Resource: resource.NewWithAttributes(
Tags: []attribute.KeyValue{ semconv.ServiceNameKey.String(serviceName),
attribute.String(tagKey, tagVal), attribute.String(tagKey, tagVal),
}, ),
}), }),
) )
@ -409,7 +388,6 @@ func Test_spanSnapshotToThrift(t *testing.T) {
StatusCode: codes.Error, StatusCode: codes.Error,
StatusMessage: statusMessage, StatusMessage: statusMessage,
SpanKind: trace.SpanKindClient, SpanKind: trace.SpanKindClient,
Resource: resource.NewWithAttributes(attribute.String("rk1", rv1), attribute.Int64("rk2", rv2)),
InstrumentationLibrary: instrumentation.Library{ InstrumentationLibrary: instrumentation.Library{
Name: instrLibName, Name: instrLibName,
Version: instrLibVersion, Version: instrLibVersion,
@ -432,8 +410,6 @@ func Test_spanSnapshotToThrift(t *testing.T) {
{Key: "status.code", VType: gen.TagType_LONG, VLong: &statusCodeValue}, {Key: "status.code", VType: gen.TagType_LONG, VLong: &statusCodeValue},
{Key: "status.message", VType: gen.TagType_STRING, VStr: &statusMessage}, {Key: "status.message", VType: gen.TagType_STRING, VStr: &statusMessage},
{Key: "span.kind", VType: gen.TagType_STRING, VStr: &spanKind}, {Key: "span.kind", VType: gen.TagType_STRING, VStr: &spanKind},
{Key: "rk1", VType: gen.TagType_STRING, VStr: &rv1},
{Key: "rk2", VType: gen.TagType_LONG, VLong: &rv2},
}, },
References: []*gen.SpanRef{ References: []*gen.SpanRef{
{ {
@ -516,6 +492,44 @@ func Test_spanSnapshotToThrift(t *testing.T) {
}, },
}, },
}, },
{
name: "resources do not affect the tags",
data: &export.SpanSnapshot{
ParentSpanID: parentSpanID,
SpanContext: trace.NewSpanContext(trace.SpanContextConfig{
TraceID: traceID,
SpanID: spanID,
}),
Name: "/foo",
StartTime: now,
EndTime: now,
Resource: resource.NewWithAttributes(
attribute.String("rk1", rv1),
attribute.Int64("rk2", rv2),
semconv.ServiceNameKey.String("service name"),
),
StatusCode: codes.Unset,
StatusMessage: statusMessage,
SpanKind: trace.SpanKindInternal,
InstrumentationLibrary: instrumentation.Library{
Name: instrLibName,
Version: instrLibVersion,
},
},
want: &gen.Span{
TraceIdLow: 651345242494996240,
TraceIdHigh: 72623859790382856,
SpanId: 72623859790382856,
ParentSpanId: 578437695752307201,
OperationName: "/foo",
StartTime: now.UnixNano() / 1000,
Duration: 0,
Tags: []*gen.Tag{
{Key: "otel.library.name", VType: gen.TagType_STRING, VStr: &instrLibName},
{Key: "otel.library.version", VType: gen.TagType_STRING, VStr: &instrLibVersion},
},
},
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
@ -579,3 +593,296 @@ func TestErrorOnExportShutdownExporter(t *testing.T) {
assert.NoError(t, e.Shutdown(context.Background())) assert.NoError(t, e.Shutdown(context.Background()))
assert.NoError(t, e.ExportSpans(context.Background(), nil)) assert.NoError(t, e.ExportSpans(context.Background(), nil))
} }
func TestJaegerBatchList(t *testing.T) {
newString := func(value string) *string {
return &value
}
spanKind := "unspecified"
now := time.Now()
testCases := []struct {
name string
spanSnapshotList []*export.SpanSnapshot
defaultServiceName string
resourceFromProcess *resource.Resource
expectedBatchList []*gen.Batch
}{
{
name: "no span shots",
spanSnapshotList: nil,
expectedBatchList: nil,
},
{
name: "span's snapshot contains nil span",
spanSnapshotList: []*export.SpanSnapshot{
{
Name: "s1",
Resource: resource.NewWithAttributes(
semconv.ServiceNameKey.String("name"),
attribute.Key("r1").String("v1"),
),
StartTime: now,
EndTime: now,
},
nil,
},
expectedBatchList: []*gen.Batch{
{
Process: &gen.Process{
ServiceName: "name",
Tags: []*gen.Tag{
{Key: "r1", VType: gen.TagType_STRING, VStr: newString("v1")},
},
},
Spans: []*gen.Span{
{
OperationName: "s1",
Tags: []*gen.Tag{
{Key: "span.kind", VType: gen.TagType_STRING, VStr: &spanKind},
},
StartTime: now.UnixNano() / 1000,
},
},
},
},
},
{
name: "merge spans that have the same resources",
spanSnapshotList: []*export.SpanSnapshot{
{
Name: "s1",
Resource: resource.NewWithAttributes(
semconv.ServiceNameKey.String("name"),
attribute.Key("r1").String("v1"),
),
StartTime: now,
EndTime: now,
},
{
Name: "s2",
Resource: resource.NewWithAttributes(
semconv.ServiceNameKey.String("name"),
attribute.Key("r1").String("v1"),
),
StartTime: now,
EndTime: now,
},
{
Name: "s3",
Resource: resource.NewWithAttributes(
semconv.ServiceNameKey.String("name"),
attribute.Key("r2").String("v2"),
),
StartTime: now,
EndTime: now,
},
},
expectedBatchList: []*gen.Batch{
{
Process: &gen.Process{
ServiceName: "name",
Tags: []*gen.Tag{
{Key: "r1", VType: gen.TagType_STRING, VStr: newString("v1")},
},
},
Spans: []*gen.Span{
{
OperationName: "s1",
Tags: []*gen.Tag{
{Key: "span.kind", VType: gen.TagType_STRING, VStr: &spanKind},
},
StartTime: now.UnixNano() / 1000,
},
{
OperationName: "s2",
Tags: []*gen.Tag{
{Key: "span.kind", VType: gen.TagType_STRING, VStr: &spanKind},
},
StartTime: now.UnixNano() / 1000,
},
},
},
{
Process: &gen.Process{
ServiceName: "name",
Tags: []*gen.Tag{
{Key: "r2", VType: gen.TagType_STRING, VStr: newString("v2")},
},
},
Spans: []*gen.Span{
{
OperationName: "s3",
Tags: []*gen.Tag{
{Key: "span.kind", VType: gen.TagType_STRING, VStr: &spanKind},
},
StartTime: now.UnixNano() / 1000,
},
},
},
},
},
{
name: "merge resources that come from process",
spanSnapshotList: []*export.SpanSnapshot{
{
Name: "s1",
Resource: resource.NewWithAttributes(
semconv.ServiceNameKey.String("name"),
attribute.Key("r1").String("v1"),
attribute.Key("r2").String("v2"),
),
StartTime: now,
EndTime: now,
},
},
resourceFromProcess: resource.NewWithAttributes(
semconv.ServiceNameKey.String("new-name"),
attribute.Key("r1").String("v2"),
),
expectedBatchList: []*gen.Batch{
{
Process: &gen.Process{
ServiceName: "new-name",
Tags: []*gen.Tag{
{Key: "r1", VType: gen.TagType_STRING, VStr: newString("v2")},
{Key: "r2", VType: gen.TagType_STRING, VStr: newString("v2")},
},
},
Spans: []*gen.Span{
{
OperationName: "s1",
Tags: []*gen.Tag{
{Key: "span.kind", VType: gen.TagType_STRING, VStr: &spanKind},
},
StartTime: now.UnixNano() / 1000,
},
},
},
},
},
{
name: "span's snapshot contains no service name but resourceFromProcess does",
spanSnapshotList: []*export.SpanSnapshot{
{
Name: "s1",
Resource: resource.NewWithAttributes(
attribute.Key("r1").String("v1"),
),
StartTime: now,
EndTime: now,
},
nil,
},
resourceFromProcess: resource.NewWithAttributes(
semconv.ServiceNameKey.String("new-name"),
),
expectedBatchList: []*gen.Batch{
{
Process: &gen.Process{
ServiceName: "new-name",
Tags: []*gen.Tag{
{Key: "r1", VType: gen.TagType_STRING, VStr: newString("v1")},
},
},
Spans: []*gen.Span{
{
OperationName: "s1",
Tags: []*gen.Tag{
{Key: "span.kind", VType: gen.TagType_STRING, VStr: &spanKind},
},
StartTime: now.UnixNano() / 1000,
},
},
},
},
},
{
name: "no service name in spans and resourceFromProcess",
spanSnapshotList: []*export.SpanSnapshot{
{
Name: "s1",
Resource: resource.NewWithAttributes(
attribute.Key("r1").String("v1"),
),
StartTime: now,
EndTime: now,
},
nil,
},
defaultServiceName: "default service name",
expectedBatchList: []*gen.Batch{
{
Process: &gen.Process{
ServiceName: "default service name",
Tags: []*gen.Tag{
{Key: "r1", VType: gen.TagType_STRING, VStr: newString("v1")},
},
},
Spans: []*gen.Span{
{
OperationName: "s1",
Tags: []*gen.Tag{
{Key: "span.kind", VType: gen.TagType_STRING, VStr: &spanKind},
},
StartTime: now.UnixNano() / 1000,
},
},
},
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
batchList := jaegerBatchList(tc.spanSnapshotList, tc.defaultServiceName, tc.resourceFromProcess)
assert.ElementsMatch(t, tc.expectedBatchList, batchList)
})
}
}
func TestProcess(t *testing.T) {
v1 := "v1"
testCases := []struct {
name string
res *resource.Resource
defaultServiceName string
expectedProcess *gen.Process
}{
{
name: "resources contain service name",
res: resource.NewWithAttributes(
semconv.ServiceNameKey.String("service name"),
attribute.Key("r1").String("v1"),
),
defaultServiceName: "default service name",
expectedProcess: &gen.Process{
ServiceName: "service name",
Tags: []*gen.Tag{
{Key: "r1", VType: gen.TagType_STRING, VStr: &v1},
},
},
},
{
name: "resources don't have service name",
res: resource.NewWithAttributes(attribute.Key("r1").String("v1")),
defaultServiceName: "default service name",
expectedProcess: &gen.Process{
ServiceName: "default service name",
Tags: []*gen.Tag{
{Key: "r1", VType: gen.TagType_STRING, VStr: &v1},
},
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
pro := process(tc.res, tc.defaultServiceName)
assert.Equal(t, tc.expectedProcess, pro)
})
}
}