diff --git a/api/propagation/binary_propagator.go b/api/propagation/binary_propagator.go deleted file mode 100644 index 4ec599f286e..00000000000 --- a/api/propagation/binary_propagator.go +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2019, 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 propagation - -import ( - "go.opentelemetry.io/otel/api/core" -) - -// BinaryFormat is an interface that specifies methods to convert SpanContext -// to/from byte array. -type BinaryFormat interface { - // ToBytes serializes span context into a byte array and returns the array. - ToBytes(sc core.SpanContext) []byte - - // FromBytes de-serializes byte array into span context and returns the span context. - FromBytes([]byte) core.SpanContext -} - -var _ BinaryFormat = binary{} - -type binary struct{} - -// Binary creates a new propagator. The propagator implements -// ToBytes and FromBytes method to transform SpanContext to/from byte array. -func Binary() BinaryFormat { - return binary{} -} - -// ToBytes implements ToBytes method of propagators.BinaryFormat. -// It serializes core.SpanContext into a byte array. -func (bp binary) ToBytes(sc core.SpanContext) []byte { - if sc == core.EmptySpanContext() { - return nil - } - var b [29]byte - copy(b[2:18], sc.TraceID[:]) - b[18] = 1 - copy(b[19:27], sc.SpanID[:]) - b[27] = 2 - b[28] = sc.TraceFlags - return b[:] -} - -// FromBytes implements FromBytes method of propagators.BinaryFormat. -// It de-serializes bytes into core.SpanContext. -func (bp binary) FromBytes(b []byte) (sc core.SpanContext) { - if len(b) == 0 { - return core.EmptySpanContext() - } - b = b[1:] - if len(b) >= 17 && b[0] == 0 { - copy(sc.TraceID[:], b[1:17]) - b = b[17:] - } else { - return core.EmptySpanContext() - } - if len(b) >= 9 && b[0] == 1 { - copy(sc.SpanID[:], b[1:9]) - b = b[9:] - } - if len(b) >= 2 && b[0] == 2 { - sc.TraceFlags = b[1] - } - if sc.IsValid() { - return sc - } - return core.EmptySpanContext() -} diff --git a/api/propagation/binary_propagator_test.go b/api/propagation/binary_propagator_test.go deleted file mode 100644 index 65df8e5b460..00000000000 --- a/api/propagation/binary_propagator_test.go +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright 2019, 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 propagation_test - -import ( - "testing" - - "github.com/google/go-cmp/cmp" - - "go.opentelemetry.io/otel/api/core" - "go.opentelemetry.io/otel/api/propagation" -) - -func TestExtractSpanContextFromBytes(t *testing.T) { - traceID, _ := core.TraceIDFromHex("4bf92f3577b34da6a3ce929d0e0e4736") - spanID, _ := core.SpanIDFromHex("00f067aa0ba902b7") - - propagator := propagation.Binary() - tests := []struct { - name string - bytes []byte - wantSc core.SpanContext - }{ - { - name: "future version of the proto", - bytes: []byte{ - 0x02, 0x00, 0x4b, 0xf9, 0x2f, 0x35, 0x77, 0xb3, 0x4d, 0xa6, 0xa3, 0xce, 0x92, 0x9d, 0x0e, 0x0e, 0x47, 0x36, - 0x01, 0x00, 0xf0, 0x67, 0xaa, 0x0b, 0xa9, 0x02, 0xb7, - 0x02, 0x01, - }, - wantSc: core.SpanContext{ - TraceID: traceID, - SpanID: spanID, - TraceFlags: core.TraceFlagsSampled, - }, - }, - { - name: "current version with valid SpanContext and with Sampled bit set", - bytes: []byte{ - 0x00, 0x00, 0x4b, 0xf9, 0x2f, 0x35, 0x77, 0xb3, 0x4d, 0xa6, 0xa3, 0xce, 0x92, 0x9d, 0x0e, 0x0e, 0x47, 0x36, - 0x01, 0x00, 0xf0, 0x67, 0xaa, 0x0b, 0xa9, 0x02, 0xb7, - 0x02, 0x01, - }, - wantSc: core.SpanContext{ - TraceID: traceID, - SpanID: spanID, - TraceFlags: core.TraceFlagsSampled, - }, - }, - { - name: "valid SpanContext without option", - bytes: []byte{ - 0x00, 0x00, 0x4b, 0xf9, 0x2f, 0x35, 0x77, 0xb3, 0x4d, 0xa6, 0xa3, 0xce, 0x92, 0x9d, 0x0e, 0x0e, 0x47, 0x36, - 0x01, 0x00, 0xf0, 0x67, 0xaa, 0x0b, 0xa9, 0x02, 0xb7, - }, - wantSc: core.SpanContext{ - TraceID: traceID, - SpanID: spanID, - }, - }, - { - name: "zero trace ID", - bytes: []byte{ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x02, 0x01, - }, - wantSc: core.EmptySpanContext(), - }, - { - name: "zero span ID", - bytes: []byte{ - 0x00, 0x00, 0x4b, 0xf9, 0x2f, 0x35, 0x77, 0xb3, 0x4d, 0xa6, 0xa3, 0xce, 0x92, 0x9d, 0x0e, 0x0e, 0x47, 0x36, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x02, 0x01, - }, - wantSc: core.EmptySpanContext(), - }, - { - name: "wrong trace ID field number", - bytes: []byte{ - 0x00, 0x01, 0x4b, 0xf9, 0x2f, 0x35, 0x77, 0xb3, 0x4d, 0xa6, 0xa3, 0xce, 0x92, 0x9d, 0x0e, 0x0e, 0x47, 0x36, - 0x01, 0x00, 0xf0, 0x67, 0xaa, 0x0b, 0xa9, 0x02, 0xb7, - }, - wantSc: core.EmptySpanContext(), - }, - { - name: "short byte array", - bytes: []byte{ - 0x00, 0x00, 0x4b, 0xf9, 0x2f, 0x35, 0x77, 0xb3, 0x4d, - }, - wantSc: core.EmptySpanContext(), - }, - { - name: "nil byte array", - wantSc: core.EmptySpanContext(), - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - gotSc := propagator.FromBytes(tt.bytes) - if diff := cmp.Diff(gotSc, tt.wantSc); diff != "" { - t.Errorf("Deserialize SpanContext from byte array: %s: -got +want %s", tt.name, diff) - } - }) - } -} - -func TestConvertSpanContextToBytes(t *testing.T) { - traceID, _ := core.TraceIDFromHex("4bf92f3577b34da6a3ce929d0e0e4736") - spanID, _ := core.SpanIDFromHex("00f067aa0ba902b7") - - propagator := propagation.Binary() - tests := []struct { - name string - sc core.SpanContext - bytes []byte - }{ - { - name: "valid SpanContext, with sampling bit set", - sc: core.SpanContext{ - TraceID: traceID, - SpanID: spanID, - TraceFlags: core.TraceFlagsSampled, - }, - bytes: []byte{ - 0x00, 0x00, 0x4b, 0xf9, 0x2f, 0x35, 0x77, 0xb3, 0x4d, 0xa6, 0xa3, 0xce, 0x92, 0x9d, 0x0e, 0x0e, 0x47, 0x36, - 0x01, 0x00, 0xf0, 0x67, 0xaa, 0x0b, 0xa9, 0x02, 0xb7, - 0x02, 0x01, - }, - }, - { - name: "valid SpanContext, with sampling bit cleared", - sc: core.SpanContext{ - TraceID: traceID, - SpanID: spanID, - }, - bytes: []byte{ - 0x00, 0x00, 0x4b, 0xf9, 0x2f, 0x35, 0x77, 0xb3, 0x4d, 0xa6, 0xa3, 0xce, 0x92, 0x9d, 0x0e, 0x0e, 0x47, 0x36, - 0x01, 0x00, 0xf0, 0x67, 0xaa, 0x0b, 0xa9, 0x02, 0xb7, - 0x02, 0x00, - }, - }, - { - name: "invalid spancontext", - sc: core.EmptySpanContext(), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - gotBytes := propagator.ToBytes(tt.sc) - if diff := cmp.Diff(gotBytes, tt.bytes); diff != "" { - t.Errorf("Serialize SpanContext to byte array: %s: -got +want %s", tt.name, diff) - } - }) - } -} diff --git a/api/propagation/doc.go b/api/propagation/doc.go index 5b74bf4b812..d17ceea944e 100644 --- a/api/propagation/doc.go +++ b/api/propagation/doc.go @@ -12,6 +12,5 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Package propagation contains interface definition for BinaryFormat and -// TextFormat propagators. +// Package propagation contains interface definition for HTTP propagators. package propagation // import "go.opentelemetry.io/otel/api/propagation" diff --git a/api/propagation/propagation.go b/api/propagation/propagation.go new file mode 100644 index 00000000000..84ef6fb6f0c --- /dev/null +++ b/api/propagation/propagation.go @@ -0,0 +1,143 @@ +// Copyright 2019, 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 propagation + +import ( + "context" +) + +// HTTPSupplier is an interface that specifies methods to retrieve and +// store a single value for a key to an associated carrier. It is +// implemented by http.Headers. +type HTTPSupplier interface { + // Get method retrieves a single value for a given key. + Get(key string) string + // Set method stores a single value for a given key. Note that + // this should not be appending a value to some array, but + // rather overwrite the old value. + Set(key string, value string) +} + +// HTTPExtractor extracts information from a HTTPSupplier into a +// context. +type HTTPExtractor interface { + // Extract method retrieves encoded information using supplier + // from the associated carrier, decodes it and creates a new + // context containing the decoded information. + // + // Information can be a correlation context or a remote span + // context. In case of span context, the propagator should + // store it in the context using + // trace.ContextWithRemoteSpanContext. In case of correlation + // context, the propagator should use correlation.WithMap to + // store it in the context. + Extract(context.Context, HTTPSupplier) context.Context +} + +// HTTPInjector injects information into a HTTPSupplier. +type HTTPInjector interface { + // Inject method retrieves information from the context, + // encodes it into propagator specific format and then injects + // the encoded information using supplier into an associated + // carrier. + Inject(context.Context, HTTPSupplier) +} + +// Config contains the current set of extractors and injectors. +type Config struct { + httpEx []HTTPExtractor + httpIn []HTTPInjector +} + +// Propagators is the interface to a set of injectors and extractors +// for all supported carrier formats. It can be used to chain multiple +// propagators into a single entity. +type Propagators interface { + // HTTPExtractors returns the configured extractors. + HTTPExtractors() []HTTPExtractor + + // HTTPInjectors returns the configured injectors. + HTTPInjectors() []HTTPInjector +} + +// HTTPPropagator is the interface to inject to and extract from +// HTTPSupplier. +type HTTPPropagator interface { + HTTPInjector + HTTPExtractor + + // GetAllKeys returns the HTTP header names used. + GetAllKeys() []string +} + +// Option support passing configuration parameters to New(). +type Option func(*Config) + +// propagators is the default Propagators implementation. +type propagators struct { + config Config +} + +// New returns a standard Propagators implementation. +func New(options ...Option) Propagators { + config := Config{} + for _, opt := range options { + opt(&config) + } + return &propagators{ + config: config, + } +} + +// WithInjectors appends to the optional injector set. +func WithInjectors(inj ...HTTPInjector) Option { + return func(config *Config) { + config.httpIn = append(config.httpIn, inj...) + } +} + +// WithExtractors appends to the optional extractor set. +func WithExtractors(ext ...HTTPExtractor) Option { + return func(config *Config) { + config.httpEx = append(config.httpEx, ext...) + } +} + +// HTTPExtractors implements Propagators. +func (p *propagators) HTTPExtractors() []HTTPExtractor { + return p.config.httpEx +} + +// HTTPInjectors implements Propagators. +func (p *propagators) HTTPInjectors() []HTTPInjector { + return p.config.httpIn +} + +// ExtractHTTP applies props.HTTPExtractors() to the passed context +// and the supplier and returns the combined result context. +func ExtractHTTP(ctx context.Context, props Propagators, supplier HTTPSupplier) context.Context { + for _, ex := range props.HTTPExtractors() { + ctx = ex.Extract(ctx, supplier) + } + return ctx +} + +// InjectHTTP applies props.HTTPInjectors() to the passed context and +// the supplier. +func InjectHTTP(ctx context.Context, props Propagators, supplier HTTPSupplier) { + for _, in := range props.HTTPInjectors() { + in.Inject(ctx, supplier) + } +} diff --git a/api/propagation/text_noop_propagator.go b/api/propagation/text_noop_propagator.go deleted file mode 100644 index bea0e6dfabb..00000000000 --- a/api/propagation/text_noop_propagator.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2019, 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 propagation - -import ( - "context" - - "go.opentelemetry.io/otel/api/core" - "go.opentelemetry.io/otel/api/correlation" -) - -// NoopTextFormat implements TextFormat that does nothing. -type NoopTextFormat struct{} - -var _ TextFormat = NoopTextFormat{} - -// Inject does nothing. -func (np NoopTextFormat) Inject(ctx context.Context, supplier Supplier) { -} - -// Extract does nothing and returns an empty SpanContext -func (np NoopTextFormat) Extract(ctx context.Context, supplier Supplier) (core.SpanContext, correlation.Map) { - return core.EmptySpanContext(), correlation.NewEmptyMap() -} - -// GetAllKeys returns empty list of strings. -func (np NoopTextFormat) GetAllKeys() []string { - return []string{} -} diff --git a/api/propagation/text_propagator.go b/api/propagation/text_propagator.go deleted file mode 100644 index 96c8d8bb97c..00000000000 --- a/api/propagation/text_propagator.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2019, 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 propagation - -import ( - "context" - - "go.opentelemetry.io/otel/api/core" - "go.opentelemetry.io/otel/api/correlation" -) - -// TextFormat is an interface that specifies methods to inject and extract SpanContext -// and distributed context into/from a carrier using Supplier interface. -// For example, HTTP Trace Context propagator would encode SpanContext into W3C Trace -// Context Header and set the header into HttpRequest. -type TextFormat interface { - // Inject method retrieves current SpanContext from the ctx, encodes it into propagator - // specific format and then injects the encoded SpanContext using supplier into a carrier - // associated with the supplier. It also takes a correlationCtx whose values will be - // injected into a carrier using the supplier. - Inject(ctx context.Context, supplier Supplier) - - // Extract method retrieves encoded SpanContext using supplier from the associated carrier. - // It decodes the SpanContext and returns it and a baggage of correlated context. - // If no SpanContext was retrieved OR if the retrieved SpanContext is invalid then - // an empty SpanContext is returned. - Extract(ctx context.Context, supplier Supplier) (core.SpanContext, correlation.Map) - - // GetAllKeys returns all the keys that this propagator injects/extracts into/from a - // carrier. The use cases for this are - // * allow pre-allocation of fields, especially in systems like gRPC Metadata - // * allow a single-pass over an iterator (ex OpenTracing has no getter in TextMap) - GetAllKeys() []string -} - -// Supplier is an interface that specifies methods to retrieve and store -// value for a key to an associated carrier. -// Get method retrieves the value for a given key. -// Set method stores the value for a given key. -type Supplier interface { - Get(key string) string - Set(key string, value string) -} diff --git a/api/trace/b3_propagator.go b/api/trace/b3_propagator.go index 9cee1bc4d07..4cefdbc9a65 100644 --- a/api/trace/b3_propagator.go +++ b/api/trace/b3_propagator.go @@ -20,7 +20,6 @@ import ( "strings" "go.opentelemetry.io/otel/api/core" - "go.opentelemetry.io/otel/api/correlation" "go.opentelemetry.io/otel/api/propagation" ) @@ -50,9 +49,9 @@ type B3 struct { SingleHeader bool } -var _ propagation.TextFormat = B3{} +var _ propagation.HTTPPropagator = B3{} -func (b3 B3) Inject(ctx context.Context, supplier propagation.Supplier) { +func (b3 B3) Inject(ctx context.Context, supplier propagation.HTTPSupplier) { sc := SpanFromContext(ctx).SpanContext() if sc.IsValid() { if b3.SingleHeader { @@ -76,21 +75,17 @@ func (b3 B3) Inject(ctx context.Context, supplier propagation.Supplier) { } // Extract retrieves B3 Headers from the supplier -func (b3 B3) Extract(ctx context.Context, supplier propagation.Supplier) (core.SpanContext, correlation.Map) { +func (b3 B3) Extract(ctx context.Context, supplier propagation.HTTPSupplier) context.Context { + var sc core.SpanContext if b3.SingleHeader { - return b3.extractSingleHeader(supplier), correlation.NewEmptyMap() + sc = b3.extractSingleHeader(supplier) + } else { + sc = b3.extract(supplier) } - return b3.extract(supplier), correlation.NewEmptyMap() + return ContextWithRemoteSpanContext(ctx, sc) } -func (b3 B3) GetAllKeys() []string { - if b3.SingleHeader { - return []string{B3SingleHeader} - } - return []string{B3TraceIDHeader, B3SpanIDHeader, B3SampledHeader} -} - -func (b3 B3) extract(supplier propagation.Supplier) core.SpanContext { +func (b3 B3) extract(supplier propagation.HTTPSupplier) core.SpanContext { tid, err := core.TraceIDFromHex(supplier.Get(B3TraceIDHeader)) if err != nil { return core.EmptySpanContext() @@ -125,10 +120,10 @@ func (b3 B3) extract(supplier propagation.Supplier) core.SpanContext { return sc } -func (b3 B3) extractSingleHeader(supplier propagation.Supplier) core.SpanContext { +func (b3 B3) extractSingleHeader(supplier propagation.HTTPSupplier) core.SpanContext { h := supplier.Get(B3SingleHeader) if h == "" || h == "0" { - core.EmptySpanContext() + return core.EmptySpanContext() } sc := core.SpanContext{} parts := strings.Split(h, "-") @@ -202,3 +197,10 @@ func (b3 B3) extracDebugFlag(debug string) (flag byte, ok bool) { } return 0, false } + +func (b3 B3) GetAllKeys() []string { + if b3.SingleHeader { + return []string{B3SingleHeader} + } + return []string{B3TraceIDHeader, B3SpanIDHeader, B3SampledHeader} +} diff --git a/api/trace/testtrace/b3_propagator_benchmark_test.go b/api/trace/testtrace/b3_propagator_benchmark_test.go index b6c7c7117e5..eefc7ab0e76 100644 --- a/api/trace/testtrace/b3_propagator_benchmark_test.go +++ b/api/trace/testtrace/b3_propagator_benchmark_test.go @@ -63,7 +63,7 @@ func BenchmarkExtractB3(b *testing.B) { b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { - _, _ = propagator.Extract(ctx, req.Header) + _ = propagator.Extract(ctx, req.Header) } }) } diff --git a/api/trace/testtrace/b3_propagator_test.go b/api/trace/testtrace/b3_propagator_test.go index 827e1c0ed2d..958059a0226 100644 --- a/api/trace/testtrace/b3_propagator_test.go +++ b/api/trace/testtrace/b3_propagator_test.go @@ -21,6 +21,7 @@ import ( "github.com/google/go-cmp/cmp" + "go.opentelemetry.io/otel/api/propagation" "go.opentelemetry.io/otel/api/trace" mocktrace "go.opentelemetry.io/otel/internal/trace" ) @@ -55,6 +56,8 @@ func TestExtractB3(t *testing.T) { for _, tg := range testGroup { propagator := trace.B3{SingleHeader: tg.singleHeader} + props := propagation.New(propagation.WithExtractors(propagator)) + for _, tt := range tg.tests { t.Run(tt.name, func(t *testing.T) { req, _ := http.NewRequest("GET", "http://example.com", nil) @@ -63,7 +66,8 @@ func TestExtractB3(t *testing.T) { } ctx := context.Background() - gotSc, _ := propagator.Extract(ctx, req.Header) + ctx = propagation.ExtractHTTP(ctx, props, req.Header) + gotSc := trace.RemoteSpanContextFromContext(ctx) if diff := cmp.Diff(gotSc, tt.wantSc); diff != "" { t.Errorf("%s: %s: -got +want %s", tg.name, tt.name, diff) } @@ -99,6 +103,7 @@ func TestInjectB3(t *testing.T) { for _, tg := range testGroup { id = 0 propagator := trace.B3{SingleHeader: tg.singleHeader} + props := propagation.New(propagation.WithInjectors(propagator)) for _, tt := range tg.tests { t.Run(tt.name, func(t *testing.T) { req, _ := http.NewRequest("GET", "http://example.com", nil) @@ -107,7 +112,7 @@ func TestInjectB3(t *testing.T) { ctx = trace.ContextWithRemoteSpanContext(ctx, tt.parentSc) } ctx, _ = mockTracer.Start(ctx, "inject") - propagator.Inject(ctx, req.Header) + propagation.InjectHTTP(ctx, props, req.Header) for h, v := range tt.wantHeaders { got, want := req.Header.Get(h), v diff --git a/api/trace/testtrace/trace_context_propagator_test.go b/api/trace/testtrace/trace_context_propagator_test.go index 5711c2403f7..a3d3e86c574 100644 --- a/api/trace/testtrace/trace_context_propagator_test.go +++ b/api/trace/testtrace/trace_context_propagator_test.go @@ -25,6 +25,7 @@ import ( "go.opentelemetry.io/otel/api/core" "go.opentelemetry.io/otel/api/correlation" "go.opentelemetry.io/otel/api/key" + "go.opentelemetry.io/otel/api/propagation" "go.opentelemetry.io/otel/api/trace" mocktrace "go.opentelemetry.io/otel/internal/trace" ) @@ -45,7 +46,7 @@ func mustSpanIDFromHex(s string) (t core.SpanID) { } func TestExtractValidTraceContextFromHTTPReq(t *testing.T) { - var propagator trace.TraceContext + props := propagation.New(propagation.WithExtractors(trace.TraceContext{})) tests := []struct { name string header string @@ -129,7 +130,8 @@ func TestExtractValidTraceContextFromHTTPReq(t *testing.T) { req.Header.Set("traceparent", tt.header) ctx := context.Background() - gotSc, _ := propagator.Extract(ctx, req.Header) + ctx = propagation.ExtractHTTP(ctx, props, req.Header) + gotSc := trace.RemoteSpanContextFromContext(ctx) if diff := cmp.Diff(gotSc, tt.wantSc); diff != "" { t.Errorf("Extract Tracecontext: %s: -got +want %s", tt.name, diff) } @@ -138,8 +140,8 @@ func TestExtractValidTraceContextFromHTTPReq(t *testing.T) { } func TestExtractInvalidTraceContextFromHTTPReq(t *testing.T) { - var propagator trace.TraceContext wantSc := core.EmptySpanContext() + props := propagation.New(propagation.WithExtractors(trace.TraceContext{})) tests := []struct { name string header string @@ -216,7 +218,8 @@ func TestExtractInvalidTraceContextFromHTTPReq(t *testing.T) { req.Header.Set("traceparent", tt.header) ctx := context.Background() - gotSc, _ := propagator.Extract(ctx, req.Header) + ctx = propagation.ExtractHTTP(ctx, props, req.Header) + gotSc := trace.RemoteSpanContextFromContext(ctx) if diff := cmp.Diff(gotSc, wantSc); diff != "" { t.Errorf("Extract Tracecontext: %s: -got +want %s", tt.name, diff) } @@ -230,7 +233,7 @@ func TestInjectTraceContextToHTTPReq(t *testing.T) { Sampled: false, StartSpanID: &id, } - var propagator trace.TraceContext + props := propagation.New(propagation.WithInjectors(trace.TraceContext{})) tests := []struct { name string sc core.SpanContext @@ -276,7 +279,7 @@ func TestInjectTraceContextToHTTPReq(t *testing.T) { ctx = trace.ContextWithRemoteSpanContext(ctx, tt.sc) ctx, _ = mockTracer.Start(ctx, "inject") } - propagator.Inject(ctx, req.Header) + propagation.InjectHTTP(ctx, props, req.Header) gotHeader := req.Header.Get("traceparent") if diff := cmp.Diff(gotHeader, tt.wantHeader); diff != "" { @@ -288,6 +291,7 @@ func TestInjectTraceContextToHTTPReq(t *testing.T) { func TestExtractValidDistributedContextFromHTTPReq(t *testing.T) { propagator := trace.TraceContext{} + props := propagation.New(propagation.WithExtractors(propagator)) tests := []struct { name string header string @@ -349,7 +353,8 @@ func TestExtractValidDistributedContextFromHTTPReq(t *testing.T) { req.Header.Set("Correlation-Context", tt.header) ctx := context.Background() - _, gotCorCtx := propagator.Extract(ctx, req.Header) + ctx = propagation.ExtractHTTP(ctx, props, req.Header) + gotCorCtx := correlation.FromContext(ctx) wantCorCtx := correlation.NewMap(correlation.MapUpdate{MultiKV: tt.wantKVs}) if gotCorCtx.Len() != wantCorCtx.Len() { t.Errorf( @@ -376,6 +381,7 @@ func TestExtractValidDistributedContextFromHTTPReq(t *testing.T) { func TestExtractInvalidDistributedContextFromHTTPReq(t *testing.T) { propagator := trace.TraceContext{} + props := propagation.New(propagation.WithExtractors(propagator)) tests := []struct { name string header string @@ -392,7 +398,8 @@ func TestExtractInvalidDistributedContextFromHTTPReq(t *testing.T) { req.Header.Set("Correlation-Context", tt.header) ctx := context.Background() - _, gotCorCtx := propagator.Extract(ctx, req.Header) + ctx = propagation.ExtractHTTP(ctx, props, req.Header) + gotCorCtx := correlation.FromContext(ctx) if gotCorCtx.Len() != 0 { t.Errorf("Got and Want CorCtx are not the same size %d != %d", gotCorCtx.Len(), 0) } @@ -402,6 +409,7 @@ func TestExtractInvalidDistributedContextFromHTTPReq(t *testing.T) { func TestInjectCorrelationContextToHTTPReq(t *testing.T) { propagator := trace.TraceContext{} + props := propagation.New(propagation.WithInjectors(propagator)) tests := []struct { name string kvs []core.KeyValue @@ -454,7 +462,7 @@ func TestInjectCorrelationContextToHTTPReq(t *testing.T) { t.Run(tt.name, func(t *testing.T) { req, _ := http.NewRequest("GET", "http://example.com", nil) ctx := correlation.WithMap(context.Background(), correlation.NewMap(correlation.MapUpdate{MultiKV: tt.kvs})) - propagator.Inject(ctx, req.Header) + propagation.InjectHTTP(ctx, props, req.Header) gotHeader := req.Header.Get("Correlation-Context") wantedLen := len(strings.Join(tt.wantInHeader, ",")) diff --git a/api/trace/trace_context_propagator.go b/api/trace/trace_context_propagator.go index 71ca86dab53..d586322d8a0 100644 --- a/api/trace/trace_context_propagator.go +++ b/api/trace/trace_context_propagator.go @@ -39,15 +39,15 @@ const ( //nolint:golint type TraceContext struct{} -var _ propagation.TextFormat = TraceContext{} +var _ propagation.HTTPPropagator = TraceContext{} var traceCtxRegExp = regexp.MustCompile("^[0-9a-f]{2}-[a-f0-9]{32}-[a-f0-9]{16}-[a-f0-9]{2}-?") -// DefaultPropagator returns the default trace propagator. -func DefaultPropagator() propagation.TextFormat { +// DefaultHTTPPropagator returns the default trace HTTP propagator. +func DefaultHTTPPropagator() propagation.HTTPPropagator { return TraceContext{} } -func (hp TraceContext) Inject(ctx context.Context, supplier propagation.Supplier) { +func (TraceContext) Inject(ctx context.Context, supplier propagation.HTTPSupplier) { sc := SpanFromContext(ctx).SpanContext() if sc.IsValid() { h := fmt.Sprintf("%.2x-%s-%.16x-%.2x", @@ -77,15 +77,11 @@ func (hp TraceContext) Inject(ctx context.Context, supplier propagation.Supplier } } -func (hp TraceContext) Extract( - ctx context.Context, supplier propagation.Supplier, -) (core.SpanContext, correlation.Map) { - return hp.extractSpanContext(ctx, supplier), hp.extractCorrelationCtx(ctx, supplier) +func (tc TraceContext) Extract(ctx context.Context, supplier propagation.HTTPSupplier) context.Context { + return correlation.WithMap(ContextWithRemoteSpanContext(ctx, tc.extractSpanContext(supplier)), tc.extractCorrelationCtx(supplier)) } -func (hp TraceContext) extractSpanContext( - ctx context.Context, supplier propagation.Supplier, -) core.SpanContext { +func (TraceContext) extractSpanContext(supplier propagation.HTTPSupplier) core.SpanContext { h := supplier.Get(TraceparentHeader) if h == "" { return core.EmptySpanContext() @@ -152,7 +148,7 @@ func (hp TraceContext) extractSpanContext( return sc } -func (hp TraceContext) extractCorrelationCtx(ctx context.Context, supplier propagation.Supplier) correlation.Map { +func (TraceContext) extractCorrelationCtx(supplier propagation.HTTPSupplier) correlation.Map { correlationContext := supplier.Get(CorrelationContextHeader) if correlationContext == "" { return correlation.NewEmptyMap() @@ -196,6 +192,6 @@ func (hp TraceContext) extractCorrelationCtx(ctx context.Context, supplier propa }) } -func (hp TraceContext) GetAllKeys() []string { +func (TraceContext) GetAllKeys() []string { return []string{TraceparentHeader, CorrelationContextHeader} } diff --git a/plugin/grpctrace/grpctrace.go b/plugin/grpctrace/grpctrace.go index 5e17039e80d..130d3eef882 100644 --- a/plugin/grpctrace/grpctrace.go +++ b/plugin/grpctrace/grpctrace.go @@ -20,11 +20,14 @@ import ( "google.golang.org/grpc/metadata" "go.opentelemetry.io/otel/api/core" + "go.opentelemetry.io/otel/api/correlation" + "go.opentelemetry.io/otel/api/propagation" "go.opentelemetry.io/otel/api/trace" ) var ( - propagator = trace.DefaultPropagator() + propagator = trace.DefaultHTTPPropagator() + propagators = propagation.New(propagation.WithInjectors(propagator), propagation.WithExtractors(propagator)) ) type metadataSupplier struct { @@ -47,7 +50,7 @@ func (s *metadataSupplier) Set(key string, value string) { // metadata object. This function is meant to be used on outgoing // requests. func Inject(ctx context.Context, metadata *metadata.MD) { - propagator.Inject(ctx, &metadataSupplier{ + propagation.InjectHTTP(ctx, propagators, &metadataSupplier{ metadata: metadata, }) } @@ -56,12 +59,13 @@ func Inject(ctx context.Context, metadata *metadata.MD) { // another service encoded in the gRPC metadata object with Inject. // This function is meant to be used on incoming requests. func Extract(ctx context.Context, metadata *metadata.MD) ([]core.KeyValue, core.SpanContext) { - spanContext, correlationCtx := propagator.Extract(ctx, &metadataSupplier{ + ctx = propagation.ExtractHTTP(ctx, propagators, &metadataSupplier{ metadata: metadata, }) + spanContext := trace.RemoteSpanContextFromContext(ctx) var correlationCtxKVs []core.KeyValue - correlationCtx.Foreach(func(kv core.KeyValue) bool { + correlation.FromContext(ctx).Foreach(func(kv core.KeyValue) bool { correlationCtxKVs = append(correlationCtxKVs, kv) return true }) diff --git a/plugin/httptrace/httptrace.go b/plugin/httptrace/httptrace.go index 70773afd84e..dda76c6a4d7 100644 --- a/plugin/httptrace/httptrace.go +++ b/plugin/httptrace/httptrace.go @@ -19,7 +19,9 @@ import ( "net/http" "go.opentelemetry.io/otel/api/core" + "go.opentelemetry.io/otel/api/correlation" "go.opentelemetry.io/otel/api/key" + "go.opentelemetry.io/otel/api/propagation" "go.opentelemetry.io/otel/api/trace" ) @@ -27,12 +29,13 @@ var ( HostKey = key.New("http.host") URLKey = key.New("http.url") - propagator = trace.DefaultPropagator() + propagator = trace.DefaultHTTPPropagator() + propagators = propagation.New(propagation.WithInjectors(propagator), propagation.WithExtractors(propagator)) ) // Returns the Attributes, Context Entries, and SpanContext that were encoded by Inject. func Extract(ctx context.Context, req *http.Request) ([]core.KeyValue, []core.KeyValue, core.SpanContext) { - sc, correlationCtx := propagator.Extract(ctx, req.Header) + ctx = propagation.ExtractHTTP(ctx, propagators, req.Header) attrs := []core.KeyValue{ URLKey.String(req.URL.String()), @@ -40,14 +43,14 @@ func Extract(ctx context.Context, req *http.Request) ([]core.KeyValue, []core.Ke } var correlationCtxKVs []core.KeyValue - correlationCtx.Foreach(func(kv core.KeyValue) bool { + correlation.FromContext(ctx).Foreach(func(kv core.KeyValue) bool { correlationCtxKVs = append(correlationCtxKVs, kv) return true }) - return attrs, correlationCtxKVs, sc + return attrs, correlationCtxKVs, trace.RemoteSpanContextFromContext(ctx) } func Inject(ctx context.Context, req *http.Request) { - propagator.Inject(ctx, req.Header) + propagation.InjectHTTP(ctx, propagators, req.Header) } diff --git a/plugin/othttp/handler.go b/plugin/othttp/handler.go index 972809fc7f9..7a45e6d785f 100644 --- a/plugin/othttp/handler.go +++ b/plugin/othttp/handler.go @@ -50,7 +50,7 @@ type Handler struct { handler http.Handler tracer trace.Tracer - prop propagation.TextFormat + props propagation.Propagators spanStartOptions []trace.StartOption public bool readEvent bool @@ -77,12 +77,12 @@ func WithPublicEndpoint() Option { } } -// WithPropagator configures the Handler with a specific propagator. If this -// option isn't specificed then -// go.opentelemetry.io/otel/api/trace.DefaultPropagator is used. -func WithPropagator(p propagation.TextFormat) Option { +// WithPropagators configures the Handler with specific propagators. If this +// option isn't specified then Propagators with +// go.opentelemetry.io/otel/api/trace.DefaultHTTPPropagator are used. +func WithPropagators(ps propagation.Propagators) Option { return func(h *Handler) { - h.prop = p + h.props = ps } } @@ -128,9 +128,10 @@ func WithMessageEvents(events ...event) Option { // named after the operation and with any provided HandlerOptions. func NewHandler(handler http.Handler, operation string, opts ...Option) http.Handler { h := Handler{handler: handler, operation: operation} + propagator := trace.DefaultHTTPPropagator() defaultOpts := []Option{ WithTracer(global.TraceProvider().Tracer("go.opentelemetry.io/plugin/othttp")), - WithPropagator(trace.DefaultPropagator()), + WithPropagators(propagation.New(propagation.WithInjectors(propagator), propagation.WithExtractors(propagator))), WithSpanOptions(trace.WithSpanKind(trace.SpanKindServer)), } @@ -145,9 +146,10 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { opts := append([]trace.StartOption{}, h.spanStartOptions...) // start with the configured options // TODO: do something with the correlation context - sc, _ := h.prop.Extract(r.Context(), r.Header) - ctx := r.Context() - if sc.IsValid() { // not a valid span context, so no link / parent relationship to establish + ctx := propagation.ExtractHTTP(r.Context(), h.props, r.Header) + + // not a valid span context, so no link / parent relationship to establish + if sc := trace.RemoteSpanContextFromContext(ctx); sc.IsValid() { var opt trace.StartOption if h.public { // If the endpoint is a public endpoint, it should start a new trace @@ -178,7 +180,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } } - rww := &respWriterWrapper{ResponseWriter: w, record: writeRecordFunc, ctx: ctx, injector: h.prop} + rww := &respWriterWrapper{ResponseWriter: w, record: writeRecordFunc, ctx: ctx, props: h.props} // Setup basic span attributes before calling handler.ServeHTTP so that they // are available to be mutated by the handler if needed. diff --git a/plugin/othttp/wrap.go b/plugin/othttp/wrap.go index 171eb2afddc..a799f5b1560 100644 --- a/plugin/othttp/wrap.go +++ b/plugin/othttp/wrap.go @@ -49,10 +49,6 @@ func (w *bodyWrapper) Close() error { var _ http.ResponseWriter = &respWriterWrapper{} -type injector interface { - Inject(context.Context, propagation.Supplier) -} - // respWriterWrapper wraps a http.ResponseWriter in order to track the number of // bytes written, the last error, and to catch the returned statusCode // TODO: The wrapped http.ResponseWriter doesn't implement any of the optional @@ -64,7 +60,8 @@ type respWriterWrapper struct { // used to inject the header ctx context.Context - injector + + props propagation.Propagators written int64 statusCode int @@ -95,6 +92,6 @@ func (w *respWriterWrapper) WriteHeader(statusCode int) { } w.wroteHeader = true w.statusCode = statusCode - w.injector.Inject(w.ctx, w.Header()) + propagation.InjectHTTP(w.ctx, w.props, w.Header()) w.ResponseWriter.WriteHeader(statusCode) }