1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2024-12-24 20:14:40 +02:00

Add propagator interface and W3C propagator (#85)

* add propagation api.

* add http propagator interface and w3c propagator implementation.

* remove Extract api from trace.

* remove Extract interface for tracer.

* fix copyright.

* fix variable names and comments.

* move inject/extract out of trace.

* replace INVALID_SPAN_CONTEXT with EmptySpanContext function.

* fix tag.Map.

* make carrier as interface instead of http.Request.

* rename structs and update doc comments..

* add doc.go

* update doc.

* add noop propagator.

* add new propagation api with Supplier interface.
- added Default Tracer which simply propagates SpanContext.
- added CopyOfRemote option to simply create remote span.

* remove old propagator.

* rename propagator to TextFormatPropagator.

* rename default tracer/span as pass_through tracer/span.

* add test for pass through tracer.

* add missing interface to pass through tracer.

* return SpanContext instead of contex.Context from Extract interface.
- also remove PassThroughTracer

* fix review comments.

* add more test cases for traceContext extraction.

* remove tidy temporarily from circle-ci target to avoid build failure.

* allow header ending in dash '-'.

* add inject test for non-zero value other than 01 for traceoption

* add AddLink and Link interface to MockSpan

* fix running go mod tidy on every build.
This commit is contained in:
rghetia 2019-09-23 11:51:32 -07:00 committed by GitHub
parent b13362e37c
commit 83935b2558
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 749 additions and 109 deletions

16
api/propagation/doc.go Normal file
View File

@ -0,0 +1,16 @@
// 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 contains interface definition for Binary and TextFormat propagators.
package propagation // import "go.opentelemetry.io/api/propagation"

View File

@ -0,0 +1,40 @@
// 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/api/core"
)
// NoopTextFormatPropagator implements TextFormatPropagator that does nothing.
type NoopTextFormatPropagator struct{}
var _ TextFormatPropagator = NoopTextFormatPropagator{}
// Inject does nothing.
func (np NoopTextFormatPropagator) Inject(ctx context.Context, supplier Supplier) {
}
// Extract does nothing and returns an empty SpanContext
func (np NoopTextFormatPropagator) Extract(ctx context.Context, supplier Supplier) core.SpanContext {
return core.EmptySpanContext()
}
// GetAllKeys returns empty list of strings.
func (np NoopTextFormatPropagator) GetAllKeys() []string {
return []string{}
}

View File

@ -0,0 +1,52 @@
// 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/api/core"
)
// TextFormatPropagator is an interface that specifies methods to inject and extract SpanContext
// 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 TextFormatPropagator 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.
Inject(ctx context.Context, supplier Supplier)
// Extract method retrieves encoded SpanContext using supplier from the associated carrier.
// It decodes the SpanContext and returns it. 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
// 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)
}

View File

@ -44,9 +44,6 @@ type Tracer interface {
// WithResources attaches resource attributes to the Tracer. // WithResources attaches resource attributes to the Tracer.
WithResources(res ...core.KeyValue) Tracer WithResources(res ...core.KeyValue) Tracer
// Note: see https://github.com/opentracing/opentracing-go/issues/127
Inject(context.Context, Span, Injector)
} }
type FinishOptions struct { type FinishOptions struct {
@ -102,13 +99,6 @@ type Span interface {
ModifyAttributes(...tag.Mutator) ModifyAttributes(...tag.Mutator)
} }
type Injector interface {
// Inject serializes span context and tag.Map and inserts them in to
// carrier associated with the injector. For example in case of http request,
// span context could added to the request (carrier) as W3C Trace context header.
Inject(core.SpanContext, tag.Map)
}
// SpanOption apply changes to SpanOptions. // SpanOption apply changes to SpanOptions.
type SpanOption func(*SpanOptions) type SpanOption func(*SpanOptions)
@ -157,18 +147,6 @@ func Start(ctx context.Context, name string, opts ...SpanOption) (context.Contex
return GlobalTracer().Start(ctx, name, opts...) return GlobalTracer().Start(ctx, name, opts...)
} }
// Inject is convenient function to inject current span context using injector.
// Injector is expected to serialize span context and inject it in to a carrier.
// An example of a carrier is http request.
func Inject(ctx context.Context, injector Injector) {
span := CurrentSpan(ctx)
if span == nil {
return
}
span.Tracer().Inject(ctx, span, injector)
}
// WithStartTime sets the start time of the span to provided time t, when it is started. // WithStartTime sets the start time of the span to provided time t, when it is started.
// In absensce of this option, wall clock time is used as start time. // In absensce of this option, wall clock time is used as start time.
// This option is typically used when starting of the span is delayed. // This option is typically used when starting of the span is delayed.

View File

@ -49,7 +49,3 @@ func (NoopTracer) Start(ctx context.Context, name string, opts ...SpanOption) (c
span := NoopSpan{} span := NoopSpan{}
return SetCurrentSpan(ctx, span), span return SetCurrentSpan(ctx, span), span
} }
// Inject does nothing.
func (NoopTracer) Inject(ctx context.Context, span Span, injector Injector) {
}

View File

@ -77,7 +77,6 @@ github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgo
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lightstep/tracecontext.go v0.0.0-20181129014701-1757c391b1ac/go.mod h1:Frd2bnT3w5FB5q49ENTfVlztJES+1k/7lyWX2+9gq/M=
github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/magiconair/properties v1.7.6/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.7.6/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=

View File

@ -17,6 +17,7 @@ package main
import ( import (
"context" "context"
"fmt" "fmt"
"go.opentelemetry.io/plugin/httptrace"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
@ -25,7 +26,6 @@ import (
"go.opentelemetry.io/api/key" "go.opentelemetry.io/api/key"
"go.opentelemetry.io/api/tag" "go.opentelemetry.io/api/tag"
"go.opentelemetry.io/api/trace" "go.opentelemetry.io/api/trace"
"go.opentelemetry.io/plugin/httptrace"
) )
var ( var (
@ -50,9 +50,8 @@ func main() {
func(ctx context.Context) error { func(ctx context.Context) error {
req, _ := http.NewRequest("GET", "http://localhost:7777/hello", nil) req, _ := http.NewRequest("GET", "http://localhost:7777/hello", nil)
ctx, req, inj := httptrace.W3C(ctx, req) ctx, req = httptrace.W3C(ctx, req)
httptrace.Inject(ctx, req)
trace.Inject(ctx, inj)
res, err := client.Do(req) res, err := client.Do(req)
if err != nil { if err != nil {

View File

@ -81,8 +81,6 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lightstep/tracecontext.go v0.0.0-20181129014701-1757c391b1ac h1:+2b6iGRJe3hvV/yVXrd41yVEjxuFHxasJqDhkIjS4gk=
github.com/lightstep/tracecontext.go v0.0.0-20181129014701-1757c391b1ac/go.mod h1:Frd2bnT3w5FB5q49ENTfVlztJES+1k/7lyWX2+9gq/M=
github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/magiconair/properties v1.7.6/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.7.6/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=

View File

@ -35,7 +35,7 @@ var (
func main() { func main() {
helloHandler := func(w http.ResponseWriter, req *http.Request) { helloHandler := func(w http.ResponseWriter, req *http.Request) {
attrs, tags, spanCtx := httptrace.Extract(req) attrs, tags, spanCtx := httptrace.Extract(req.Context(), req)
req = req.WithContext(tag.WithMap(req.Context(), tag.NewMap(tag.MapUpdate{ req = req.WithContext(tag.WithMap(req.Context(), tag.NewMap(tag.MapUpdate{
MultiKV: tags, MultiKV: tags,

View File

@ -20,7 +20,6 @@ import (
"go.opentelemetry.io/api/core" "go.opentelemetry.io/api/core"
"go.opentelemetry.io/api/key" "go.opentelemetry.io/api/key"
"go.opentelemetry.io/api/tag"
"go.opentelemetry.io/api/trace" "go.opentelemetry.io/api/trace"
apitrace "go.opentelemetry.io/api/trace" apitrace "go.opentelemetry.io/api/trace"
"go.opentelemetry.io/experimental/streaming/exporter/observer" "go.opentelemetry.io/experimental/streaming/exporter/observer"
@ -125,7 +124,3 @@ func (t *tracer) Start(ctx context.Context, name string, opts ...apitrace.SpanOp
} }
return trace.SetCurrentSpan(ctx, span), span return trace.SetCurrentSpan(ctx, span), span
} }
func (t *tracer) Inject(ctx context.Context, span apitrace.Span, injector apitrace.Injector) {
injector.Inject(span.SpanContext(), tag.FromContext(ctx))
}

1
go.mod
View File

@ -8,7 +8,6 @@ require (
github.com/golangci/golangci-lint v1.17.1 github.com/golangci/golangci-lint v1.17.1
github.com/google/go-cmp v0.3.0 github.com/google/go-cmp v0.3.0
github.com/hashicorp/golang-lru v0.5.3 github.com/hashicorp/golang-lru v0.5.3
github.com/lightstep/tracecontext.go v0.0.0-20181129014701-1757c391b1ac
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135
google.golang.org/api v0.9.0 google.golang.org/api v0.9.0
google.golang.org/grpc v1.22.1 google.golang.org/grpc v1.22.1

2
go.sum
View File

@ -123,8 +123,6 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lightstep/tracecontext.go v0.0.0-20181129014701-1757c391b1ac h1:+2b6iGRJe3hvV/yVXrd41yVEjxuFHxasJqDhkIjS4gk=
github.com/lightstep/tracecontext.go v0.0.0-20181129014701-1757c391b1ac/go.mod h1:Frd2bnT3w5FB5q49ENTfVlztJES+1k/7lyWX2+9gq/M=
github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/magiconair/properties v1.7.6 h1:U+1DqNen04MdEPgFiIwdOUiqZ8qPa37xgogX/sd3+54= github.com/magiconair/properties v1.7.6 h1:U+1DqNen04MdEPgFiIwdOUiqZ8qPa37xgogX/sd3+54=
github.com/magiconair/properties v1.7.6/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.7.6/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=

View File

@ -0,0 +1,96 @@
// 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 trace
import (
"context"
"google.golang.org/grpc/codes"
"go.opentelemetry.io/api/core"
"go.opentelemetry.io/api/tag"
apitrace "go.opentelemetry.io/api/trace"
)
// MockSpan is a mock span used in association with MockTracer for testing purpose only.
type MockSpan struct {
sc core.SpanContext
tracer apitrace.Tracer
}
var _ apitrace.Span = (*MockSpan)(nil)
// SpanContext returns associated core.SpanContext. If the receiver is nil it returns
// an empty core.SpanContext
func (ms *MockSpan) SpanContext() core.SpanContext {
if ms == nil {
core.EmptySpanContext()
}
return ms.sc
}
// IsRecordingEvents always returns false for MockSpan.
func (ms *MockSpan) IsRecordingEvents() bool {
return false
}
// SetStatus does nothing.
func (ms *MockSpan) SetStatus(status codes.Code) {
}
// SetError does nothing.
func (ms *MockSpan) SetError(v bool) {
}
// SetAttribute does nothing.
func (ms *MockSpan) SetAttribute(attribute core.KeyValue) {
}
// SetAttributes does nothing.
func (ms *MockSpan) SetAttributes(attributes ...core.KeyValue) {
}
// ModifyAttribute does nothing.
func (ms *MockSpan) ModifyAttribute(mutator tag.Mutator) {
}
// ModifyAttributes does nothing.
func (ms *MockSpan) ModifyAttributes(mutators ...tag.Mutator) {
}
// Finish does nothing.
func (ms *MockSpan) Finish(options ...apitrace.FinishOption) {
}
// SetName does nothing.
func (ms *MockSpan) SetName(name string) {
}
// Tracer returns MockTracer implementation of Tracer.
func (ms *MockSpan) Tracer() apitrace.Tracer {
return ms.tracer
}
// AddEvent does nothing.
func (ms *MockSpan) AddEvent(ctx context.Context, msg string, attrs ...core.KeyValue) {
}
// AddLink does nothing.
func (ms *MockSpan) AddLink(link apitrace.Link) {
}
// Link does nothing.
func (ms *MockSpan) Link(sc core.SpanContext, attrs ...core.KeyValue) {
}

View File

@ -0,0 +1,91 @@
// 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 trace
import (
"context"
"math/rand"
"sync/atomic"
"go.opentelemetry.io/api/core"
apitrace "go.opentelemetry.io/api/trace"
)
// MockTracer is a simple tracer used for testing purpose only.
// It only supports ChildOf option. SpanId is atomically increased every time a
// new span is created.
type MockTracer struct {
// Sampled specifies if the new span should be sampled or not.
Sampled bool
// StartSpanId is used to initialize spanId. It is incremented by one
// every time a new span is created.
StartSpanId *uint64
}
var _ apitrace.Tracer = (*MockTracer)(nil)
// WithResources does nothing and returns MockTracer implementation of Tracer.
func (mt *MockTracer) WithResources(attributes ...core.KeyValue) apitrace.Tracer {
return mt
}
// WithComponent does nothing and returns MockTracer implementation of Tracer.
func (mt *MockTracer) WithComponent(name string) apitrace.Tracer {
return mt
}
// WithService does nothing and returns MockTracer implementation of Tracer.
func (mt *MockTracer) WithService(name string) apitrace.Tracer {
return mt
}
// WithSpan does nothing except executing the body.
func (mt *MockTracer) WithSpan(ctx context.Context, name string, body func(context.Context) error) error {
return body(ctx)
}
// Start starts a MockSpan. It creates a new Span based on Reference SpanContext option.
// TracdID is used from Reference Span Context and SpanID is assigned.
// If Reference SpanContext option is not specified then random TraceID is used.
// No other options are supported.
func (mt *MockTracer) Start(ctx context.Context, name string, o ...apitrace.SpanOption) (context.Context, apitrace.Span) {
var opts apitrace.SpanOptions
for _, op := range o {
op(&opts)
}
var span *MockSpan
var sc core.SpanContext
if !opts.Reference.SpanContext.IsValid() {
sc = core.SpanContext{
TraceID: core.TraceID{
High: rand.Uint64(),
Low: rand.Uint64(),
},
}
if mt.Sampled {
sc.TraceOptions = core.TraceOptionSampled
}
} else {
sc = opts.Reference.SpanContext
}
sc.SpanID = atomic.AddUint64(mt.StartSpanId, 1)
span = &MockSpan{
sc: sc,
tracer: mt,
}
return apitrace.SetCurrentSpan(ctx, span), span
}

View File

@ -18,12 +18,10 @@ import (
"context" "context"
"net/http" "net/http"
"net/http/httptrace" "net/http/httptrace"
"go.opentelemetry.io/api/trace"
) )
// Client // Client
func W3C(ctx context.Context, req *http.Request) (context.Context, *http.Request, trace.Injector) { func W3C(ctx context.Context, req *http.Request) (context.Context, *http.Request) {
t := newClientTracer(ctx) t := newClientTracer(ctx)
t.GetConn = t.getConn t.GetConn = t.getConn
@ -45,5 +43,5 @@ func W3C(ctx context.Context, req *http.Request) (context.Context, *http.Request
ctx = httptrace.WithClientTrace(ctx, &t.ClientTrace) ctx = httptrace.WithClientTrace(ctx, &t.ClientTrace)
req = req.WithContext(ctx) req = req.WithContext(ctx)
return ctx, req, hinjector{req} return ctx, req
} }

View File

@ -15,15 +15,12 @@
package httptrace package httptrace
import ( import (
"encoding/binary" "context"
"net/http" "net/http"
"github.com/lightstep/tracecontext.go"
"github.com/lightstep/tracecontext.go/tracestate"
"go.opentelemetry.io/api/core" "go.opentelemetry.io/api/core"
"go.opentelemetry.io/api/key" "go.opentelemetry.io/api/key"
"go.opentelemetry.io/api/tag" "go.opentelemetry.io/propagation"
) )
const ( const (
@ -34,68 +31,21 @@ var (
HostKey = key.New("http.host") HostKey = key.New("http.host")
URLKey = key.New("http.url") URLKey = key.New("http.url")
encoding = binary.BigEndian propagator = propagation.HttpTraceContextPropagator()
) )
// Returns the Attributes, Context Tags, and SpanContext that were encoded by Inject. // Returns the Attributes, Context Tags, and SpanContext that were encoded by Inject.
func Extract(req *http.Request) ([]core.KeyValue, []core.KeyValue, core.SpanContext) { func Extract(ctx context.Context, req *http.Request) ([]core.KeyValue, []core.KeyValue, core.SpanContext) {
tc, err := tracecontext.FromHeaders(req.Header) sc := propagator.Extract(ctx, req.Header)
if err != nil {
return nil, nil, core.SpanContext{}
}
var sc core.SpanContext
sc.SpanID = encoding.Uint64(tc.TraceParent.SpanID[0:8])
sc.TraceID.High = encoding.Uint64(tc.TraceParent.TraceID[0:8])
sc.TraceID.Low = encoding.Uint64(tc.TraceParent.TraceID[8:16])
attrs := []core.KeyValue{ attrs := []core.KeyValue{
URLKey.String(req.URL.String()), URLKey.String(req.URL.String()),
// Etc. // Etc.
} }
var tags []core.KeyValue return attrs, nil, sc
for _, ts := range tc.TraceState {
if ts.Vendor != Vendor {
continue
}
// TODO: max-hops, type conversion questions answered,
// case-conversion questions.
tags = append(tags, key.New(ts.Tenant).String(ts.Value))
} }
return attrs, tags, sc func Inject(ctx context.Context, req *http.Request) {
} propagator.Inject(ctx, req.Header)
type hinjector struct {
*http.Request
}
func (h hinjector) Inject(sc core.SpanContext, tags tag.Map) {
var tc tracecontext.TraceContext
var sid [8]byte
var tid [16]byte
encoding.PutUint64(sid[0:8], sc.SpanID)
encoding.PutUint64(tid[0:8], sc.TraceID.High)
encoding.PutUint64(tid[8:16], sc.TraceID.Low)
tc.TraceParent.Version = tracecontext.Version
tc.TraceParent.TraceID = tid
tc.TraceParent.SpanID = sid
tc.TraceParent.Flags.Recorded = true // Note: not implemented.
tags.Foreach(func(kv core.KeyValue) bool {
// TODO: implement MaxHops
tc.TraceState = append(tc.TraceState, tracestate.Member{
Vendor: Vendor,
Tenant: kv.Key.Name,
Value: kv.Value.Emit(),
})
return true
})
tc.SetHeaders(h.Header)
} }

16
propagation/doc.go Normal file
View File

@ -0,0 +1,16 @@
// 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 contains propagators for different format and carriers.
package propagation // import "go.opentelemetry.io/propagation"

View File

@ -0,0 +1,138 @@
// 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"
"encoding/hex"
"fmt"
"regexp"
"strconv"
"strings"
"go.opentelemetry.io/api/trace"
"go.opentelemetry.io/api/core"
apipropagation "go.opentelemetry.io/api/propagation"
)
const (
supportedVersion = 0
maxVersion = 254
traceparentHeader = "traceparent"
)
type httpTraceContextPropagator struct{}
var _ apipropagation.TextFormatPropagator = httpTraceContextPropagator{}
var traceCtxRegExp = regexp.MustCompile("^[0-9a-f]{2}-[a-f0-9]{32}-[a-f0-9]{16}-[a-f0-9]{2}-?")
func (hp httpTraceContextPropagator) Inject(ctx context.Context, supplier apipropagation.Supplier) {
sc := trace.CurrentSpan(ctx).SpanContext()
if sc.IsValid() {
h := fmt.Sprintf("%.2x-%.16x%.16x-%.16x-%.2x",
supportedVersion,
sc.TraceID.High,
sc.TraceID.Low,
sc.SpanID,
sc.TraceOptions&core.TraceOptionSampled)
supplier.Set(traceparentHeader, h)
}
}
func (hp httpTraceContextPropagator) Extract(ctx context.Context, supplier apipropagation.Supplier) core.SpanContext {
h := supplier.Get(traceparentHeader)
if h == "" {
return core.EmptySpanContext()
}
h = strings.Trim(h, "-")
if !traceCtxRegExp.MatchString(h) {
return core.EmptySpanContext()
}
sections := strings.Split(h, "-")
if len(sections) < 4 {
return core.EmptySpanContext()
}
if len(sections[0]) != 2 {
return core.EmptySpanContext()
}
ver, err := hex.DecodeString(sections[0])
if err != nil {
return core.EmptySpanContext()
}
version := int(ver[0])
if version > maxVersion {
return core.EmptySpanContext()
}
if version == 0 && len(sections) != 4 {
return core.EmptySpanContext()
}
if len(sections[1]) != 32 {
return core.EmptySpanContext()
}
result, err := strconv.ParseUint(sections[1][0:16], 16, 64)
if err != nil {
return core.EmptySpanContext()
}
var sc core.SpanContext
sc.TraceID.High = result
result, err = strconv.ParseUint(sections[1][16:32], 16, 64)
if err != nil {
return core.EmptySpanContext()
}
sc.TraceID.Low = result
if len(sections[2]) != 16 {
return core.EmptySpanContext()
}
result, err = strconv.ParseUint(sections[2][0:], 16, 64)
if err != nil {
return core.EmptySpanContext()
}
sc.SpanID = result
if len(sections[3]) != 2 {
return core.EmptySpanContext()
}
opts, err := hex.DecodeString(sections[3])
if err != nil || len(opts) < 1 || (version == 0 && opts[0] > 2) {
return core.EmptySpanContext()
}
sc.TraceOptions = opts[0] &^ core.TraceOptionUnused
if !sc.IsValid() {
return core.EmptySpanContext()
}
return sc
}
func (hp httpTraceContextPropagator) GetAllKeys() []string {
return []string{traceparentHeader}
}
// HttpTraceContextPropagator creates a new text format propagator that propagates SpanContext
// in W3C TraceContext format.
func HttpTraceContextPropagator() apipropagation.TextFormatPropagator {
return httpTraceContextPropagator{}
}

View File

@ -0,0 +1,286 @@
// 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 (
"context"
"net/http"
"testing"
"go.opentelemetry.io/api/trace"
"github.com/google/go-cmp/cmp"
"go.opentelemetry.io/api/core"
mocktrace "go.opentelemetry.io/internal/trace"
"go.opentelemetry.io/propagation"
)
var (
traceID = core.TraceID{High: 0x4bf92f3577b34da6, Low: 0xa3ce929d0e0e4736}
spanID = uint64(0x00f067aa0ba902b7)
)
func TestExtractValidTraceContextFromHTTPReq(t *testing.T) {
trace.SetGlobalTracer(&mocktrace.MockTracer{})
propagator := propagation.HttpTraceContextPropagator()
tests := []struct {
name string
header string
wantSc core.SpanContext
}{
{
name: "valid header",
header: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00",
wantSc: core.SpanContext{
TraceID: traceID,
SpanID: spanID,
},
},
{
name: "valid header and sampled",
header: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
wantSc: core.SpanContext{
TraceID: traceID,
SpanID: spanID,
TraceOptions: core.TraceOptionSampled,
},
},
{
name: "future version",
header: "02-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
wantSc: core.SpanContext{
TraceID: traceID,
SpanID: spanID,
TraceOptions: core.TraceOptionSampled,
},
},
{
name: "future options with sampled bit set",
header: "02-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-09",
wantSc: core.SpanContext{
TraceID: traceID,
SpanID: spanID,
TraceOptions: core.TraceOptionSampled,
},
},
{
name: "future options with sampled bit cleared",
header: "02-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-08",
wantSc: core.SpanContext{
TraceID: traceID,
SpanID: spanID,
},
},
{
name: "future additional data",
header: "02-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-09-XYZxsf09",
wantSc: core.SpanContext{
TraceID: traceID,
SpanID: spanID,
TraceOptions: core.TraceOptionSampled,
},
},
{
name: "valid header ending in dash",
header: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01-",
wantSc: core.SpanContext{
TraceID: traceID,
SpanID: spanID,
TraceOptions: core.TraceOptionSampled,
},
},
{
name: "future valid header ending in dash",
header: "01-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-09-",
wantSc: core.SpanContext{
TraceID: traceID,
SpanID: spanID,
TraceOptions: core.TraceOptionSampled,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req, _ := http.NewRequest("GET", "http://example.com", nil)
req.Header.Set("traceparent", tt.header)
ctx := context.Background()
gotSc := propagator.Extract(ctx, req.Header)
if diff := cmp.Diff(gotSc, tt.wantSc); diff != "" {
t.Errorf("Extract Tracecontext: %s: -got +want %s", tt.name, diff)
}
})
}
}
func TestExtractInvalidTraceContextFromHTTPReq(t *testing.T) {
trace.SetGlobalTracer(&mocktrace.MockTracer{})
propagator := propagation.HttpTraceContextPropagator()
wantSc := core.EmptySpanContext()
tests := []struct {
name string
header string
}{
{
name: "wrong version length",
header: "0000-00000000000000000000000000000000-0000000000000000-01",
},
{
name: "wrong trace ID length",
header: "00-ab00000000000000000000000000000000-cd00000000000000-01",
},
{
name: "wrong span ID length",
header: "00-ab000000000000000000000000000000-cd0000000000000000-01",
},
{
name: "wrong trace flag length",
header: "00-ab000000000000000000000000000000-cd00000000000000-0100",
},
{
name: "bogus version",
header: "qw-00000000000000000000000000000000-0000000000000000-01",
},
{
name: "bogus trace ID",
header: "00-qw000000000000000000000000000000-cd00000000000000-01",
},
{
name: "bogus span ID",
header: "00-ab000000000000000000000000000000-qw00000000000000-01",
},
{
name: "bogus trace flag",
header: "00-ab000000000000000000000000000000-cd00000000000000-qw",
},
{
name: "upper case version",
header: "A0-00000000000000000000000000000000-0000000000000000-01",
},
{
name: "upper case trace ID",
header: "00-AB000000000000000000000000000000-cd00000000000000-01",
},
{
name: "upper case span ID",
header: "00-ab000000000000000000000000000000-CD00000000000000-01",
},
{
name: "upper case trace flag",
header: "00-ab000000000000000000000000000000-cd00000000000000-A1",
},
{
name: "zero trace ID and span ID",
header: "00-00000000000000000000000000000000-0000000000000000-01",
},
{
name: "trace-flag unused bits set",
header: "00-ab000000000000000000000000000000-cd00000000000000-09",
},
{
name: "missing options",
header: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7",
},
{
name: "empty options",
header: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req, _ := http.NewRequest("GET", "http://example.com", nil)
req.Header.Set("traceparent", tt.header)
ctx := context.Background()
gotSc := propagator.Extract(ctx, req.Header)
if diff := cmp.Diff(gotSc, wantSc); diff != "" {
t.Errorf("Extract Tracecontext: %s: -got +want %s", tt.name, diff)
}
})
}
}
func TestInjectTraceContextToHTTPReq(t *testing.T) {
var id uint64
mockTracer := &mocktrace.MockTracer{
Sampled: false,
StartSpanId: &id,
}
propagator := propagation.HttpTraceContextPropagator()
tests := []struct {
name string
sc core.SpanContext
wantHeader string
}{
{
name: "valid spancontext, sampled",
sc: core.SpanContext{
TraceID: traceID,
SpanID: spanID,
TraceOptions: core.TraceOptionSampled,
},
wantHeader: "00-4bf92f3577b34da6a3ce929d0e0e4736-0000000000000001-01",
},
{
name: "valid spancontext, not sampled",
sc: core.SpanContext{
TraceID: traceID,
SpanID: spanID,
},
wantHeader: "00-4bf92f3577b34da6a3ce929d0e0e4736-0000000000000002-00",
},
{
name: "valid spancontext, with unsupported bit set in traceoption",
sc: core.SpanContext{
TraceID: traceID,
SpanID: spanID,
TraceOptions: 0xff,
},
wantHeader: "00-4bf92f3577b34da6a3ce929d0e0e4736-0000000000000003-01",
},
{
name: "invalid spancontext",
sc: core.EmptySpanContext(),
wantHeader: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req, _ := http.NewRequest("GET", "http://example.com", nil)
ctx := context.Background()
if tt.sc.IsValid() {
ctx, _ = mockTracer.Start(ctx, "inject", trace.ChildOf(tt.sc))
}
propagator.Inject(ctx, req.Header)
gotHeader := req.Header.Get("traceparent")
if diff := cmp.Diff(gotHeader, tt.wantHeader); diff != "" {
t.Errorf("Extract Tracecontext: %s: -got +want %s", tt.name, diff)
}
})
}
}
func TestHttpTraceContextPropagator_GetAllKeys(t *testing.T) {
propagator := propagation.HttpTraceContextPropagator()
want := []string{"traceparent"}
got := propagator.GetAllKeys()
if diff := cmp.Diff(got, want); diff != "" {
t.Errorf("GetAllKeys: -got +want %s", diff)
}
}

View File

@ -18,7 +18,6 @@ import (
"context" "context"
"go.opentelemetry.io/api/core" "go.opentelemetry.io/api/core"
"go.opentelemetry.io/api/tag"
apitrace "go.opentelemetry.io/api/trace" apitrace "go.opentelemetry.io/api/trace"
) )
@ -102,7 +101,3 @@ func (tr *tracer) WithComponent(component string) apitrace.Tracer {
tr.component = component tr.component = component
return tr return tr
} }
func (tr *tracer) Inject(ctx context.Context, span apitrace.Span, injector apitrace.Injector) {
injector.Inject(span.SpanContext(), tag.NewEmptyMap())
}