1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2024-11-28 08:38:51 +02:00

Trace sdk (#65)

* trace sdk initial commit.

* fix imports and comments.

* remove tracestate

* split trace.go

* add attribute over limit test.

* add comments and restructure span.go and tracer.go

* refactor MessageEvent

* defer unlock

* some more cleanup in span.go

* rename *MessageEvent* to *Event*

* cleanup comments in trace_test.go

* fix typos.

* return full string ID for traceID and spanID.
This commit is contained in:
rghetia 2019-08-02 13:52:55 -07:00 committed by GitHub
parent ed3b26b6c8
commit 0f32efcdaa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 2082 additions and 10 deletions

View File

@ -53,14 +53,13 @@ func (sc SpanContext) HasSpanID() bool {
}
func (sc SpanContext) SpanIDString() string {
p := fmt.Sprintf("%.16x", sc.SpanID)
return p[0:3] + ".." + p[13:16]
return fmt.Sprintf("%.16x", sc.SpanID)
}
func (sc SpanContext) TraceIDString() string {
p1 := fmt.Sprintf("%.16x", sc.TraceID.High)
p2 := fmt.Sprintf("%.16x", sc.TraceID.Low)
return p1[0:3] + ".." + p2[13:16]
return p1 + p2
}
func (sc SpanContext) IsSampled() bool {

View File

@ -88,11 +88,11 @@ func TestSpanIDString(t *testing.T) {
{
name: "fourtytwo",
sc: SpanContext{SpanID: uint64(42)},
want: `000..02a`,
want: `000000000000002a`,
}, {
name: "empty",
sc: SpanContext{},
want: `000..000`,
want: `0000000000000000`,
},
} {
t.Run(testcase.name, func(t *testing.T) {
@ -119,11 +119,11 @@ func TestTraceIDString(t *testing.T) {
Low: uint64(42),
},
},
want: `000..02a`,
want: `000000000000002a000000000000002a`,
}, {
name: "empty",
sc: SpanContext{TraceID: TraceID{}},
want: `000..000`,
want: `00000000000000000000000000000000`,
},
} {
t.Run(testcase.name, func(t *testing.T) {

7
go.mod
View File

@ -5,9 +5,10 @@ go 1.12
require (
github.com/gogo/protobuf v1.2.1 // indirect
github.com/golang/mock v1.2.0 // indirect
github.com/golang/protobuf v1.3.1 // indirect
github.com/golang/protobuf v1.3.1
github.com/golangci/golangci-lint v1.17.1
github.com/google/go-cmp v0.3.0
github.com/hashicorp/golang-lru v0.5.1
github.com/lightstep/tracecontext.go v0.0.0-20181129014701-1757c391b1ac
github.com/onsi/ginkgo v1.8.0 // indirect
github.com/onsi/gomega v1.5.0 // indirect
@ -15,10 +16,10 @@ require (
github.com/sirupsen/logrus v1.2.0 // indirect
github.com/spf13/cobra v0.0.5 // indirect
github.com/stretchr/testify v1.3.0 // indirect
go.opencensus.io v0.22.0
golang.org/x/crypto v0.0.0-20190424203555-c05e17bb3b2d // indirect
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 // indirect
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980
golang.org/x/sys v0.0.0-20190614160838-b47fdc937951 // indirect
golang.org/x/text v0.3.2 // indirect
golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd
google.golang.org/appengine v1.4.0 // indirect
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873 // indirect

10
go.sum
View File

@ -112,6 +112,8 @@ github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3 h1:JVnpOZS+qxli+rgVl98ILOXVNbW+kb5wcxeGx8ShUIw=
github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce h1:xdsDDbiBDQTKASoGEZ+pEmF1OnWuu8AQ9I8iNbHNeno=
github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
@ -233,6 +235,10 @@ github.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk
github.com/valyala/quicktemplate v1.1.1/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opentelemetry.io v0.0.0-20190715214921-eed647d11f57 h1:5uH+S26G3/t2aU82KLk7vvytwkFoH7ynSIrSQo5CxSI=
go.opentelemetry.io v0.0.0-20190715214921-eed647d11f57/go.mod h1:7pfucu8MX8izd4pjPRY2kCQQMtFi4DWT/IV7KA3sjOQ=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@ -262,6 +268,7 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190313220215-9f648a60d977 h1:actzWV6iWn3GLqN8dZjzsB+CLt+gaV2+wsxroxiQI8I=
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 h1:dfGZHvZk057jK2MCeWus/TowKpJ8y4AmooUzdBSR9GU=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -283,6 +290,7 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190312061237-fead79001313 h1:pczuHS43Cp2ktBEEmLwScxgjWsBSzdaQiKzUyf3DTTc=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190614160838-b47fdc937951 h1:ZUgGZ7PSkne6oY+VgAvayrB16owfm9/DKAtgWubzgzU=
golang.org/x/sys v0.0.0-20190614160838-b47fdc937951/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170915090833-1cbadb444a80/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -310,9 +318,11 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873 h1:nfPFGzJkUDX6uBmpN/pSw7MbOAWegH5QDQuoXFHedLg=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
gopkg.in/airbrake/gobrake.v2 v2.0.9 h1:7z2uVWwn7oVeeugY1DtlPAy5H+KYgB1KeKTnqjNatLo=

0
sdk/README.md Normal file
View File

37
sdk/internal/internal.go Normal file
View File

@ -0,0 +1,37 @@
// 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 internal // import "go.opentelemetry.io/sdk/internal"
import (
"fmt"
"time"
opentelemetry "go.opentelemetry.io/sdk"
)
// UserAgent is the user agent to be added to the outgoing
// requests from the exporters.
var UserAgent = fmt.Sprintf("opentelemetry-go/%s", opentelemetry.Version())
// MonotonicEndTime returns the end time at present
// but offset from start, monotonically.
//
// The monotonic clock is used in subtractions hence
// the duration since start added back to start gives
// end as a monotonic time.
// See https://golang.org/pkg/time/#hdr-Monotonic_Clocks
func MonotonicEndTime(start time.Time) time.Time {
return start.Add(time.Since(start))
}

50
sdk/internal/sanitize.go Normal file
View File

@ -0,0 +1,50 @@
// 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 internal
import (
"strings"
"unicode"
)
const labelKeySizeLimit = 100
// Sanitize returns a string that is trunacated to 100 characters if it's too
// long, and replaces non-alphanumeric characters to underscores.
func Sanitize(s string) string {
if len(s) == 0 {
return s
}
if len(s) > labelKeySizeLimit {
s = s[:labelKeySizeLimit]
}
s = strings.Map(sanitizeRune, s)
if unicode.IsDigit(rune(s[0])) {
s = "key_" + s
}
if s[0] == '_' {
s = "key" + s
}
return s
}
// converts anything that is not a letter or digit to an underscore
func sanitizeRune(r rune) rune {
if unicode.IsLetter(r) || unicode.IsDigit(r) {
return r
}
// Everything else turns into an underscore
return '_'
}

View File

@ -0,0 +1,67 @@
// 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 internal
import (
"strings"
"testing"
)
func TestSanitize(t *testing.T) {
tests := []struct {
name string
input string
want string
}{
{
name: "trunacate long string",
input: strings.Repeat("a", 101),
want: strings.Repeat("a", 100),
},
{
name: "replace character",
input: "test/key-1",
want: "test_key_1",
},
{
name: "add prefix if starting with digit",
input: "0123456789",
want: "key_0123456789",
},
{
name: "add prefix if starting with _",
input: "_0123456789",
want: "key_0123456789",
},
{
name: "starts with _ after sanitization",
input: "/0123456789",
want: "key_0123456789",
},
{
name: "valid input",
input: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_0123456789",
want: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_0123456789",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got, want := Sanitize(tt.input), tt.want; got != want {
t.Errorf("sanitize() = %q; want %q", got, want)
}
})
}
}

21
sdk/opentelemetry.go Normal file
View File

@ -0,0 +1,21 @@
// 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 opentelemetry contains Go support for OpenTelemetry.
package opentelemetry // import "go.opentelemetry.io/sdk"
// Version is the current release version of OpenTelemetry in use.
func Version() string {
return "0.1.0"
}

40
sdk/trace/basetypes.go Normal file
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 trace
import (
"time"
"go.opentelemetry.io/api/core"
apievent "go.opentelemetry.io/api/event"
)
// event is used to describe an event with a message string and set of
// attributes.
type event struct {
msg string
attributes []core.KeyValue
time time.Time
}
var _ apievent.Event = &event{}
func (me *event) Message() string {
return me.msg
}
func (me *event) Attributes() []core.KeyValue {
return me.attributes
}

182
sdk/trace/benchmark_test.go Normal file
View File

@ -0,0 +1,182 @@
// 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"
"testing"
"go.opentelemetry.io/api/core"
"go.opentelemetry.io/api/key"
)
func BenchmarkStartEndSpan(b *testing.B) {
t := tracer{}
traceBenchmark(b, func(b *testing.B) {
ctx := context.Background()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, span := t.Start(ctx, "/foo")
span.Finish()
}
})
}
func BenchmarkSpanWithAttributes_4(b *testing.B) {
t := tracer{}
traceBenchmark(b, func(b *testing.B) {
ctx := context.Background()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, span := t.Start(ctx, "/foo")
span.SetAttributes(
key.New("key1").Bool(false),
key.New("key2").String("hello"),
key.New("key3").Uint64(123),
key.New("key4").Float64(123.456),
)
span.Finish()
}
})
}
func BenchmarkSpanWithAttributes_8(b *testing.B) {
t := tracer{}
traceBenchmark(b, func(b *testing.B) {
ctx := context.Background()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, span := t.Start(ctx, "/foo")
span.SetAttributes(
key.New("key1").Bool(false),
key.New("key2").String("hello"),
key.New("key3").Uint64(123),
key.New("key4").Float64(123.456),
key.New("key21").Bool(false),
key.New("key22").String("hello"),
key.New("key23").Uint64(123),
key.New("key24").Float64(123.456),
)
span.Finish()
}
})
}
func BenchmarkSpanWithAttributes_all(b *testing.B) {
t := tracer{}
traceBenchmark(b, func(b *testing.B) {
ctx := context.Background()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, span := t.Start(ctx, "/foo")
span.SetAttributes(
key.New("key1").Bool(false),
key.New("key2").String("hello"),
key.New("key3").Int64(123),
key.New("key4").Uint64(123),
key.New("key5").Int32(123),
key.New("key6").Uint32(123),
key.New("key7").Float64(123.456),
key.New("key8").Float32(123.456),
key.New("key9").Bytes([]byte{1, 2, 3, 4}),
key.New("key10").Int(123),
key.New("key11").Uint(123),
)
span.Finish()
}
})
}
func BenchmarkSpanWithAttributes_all_2x(b *testing.B) {
t := tracer{}
traceBenchmark(b, func(b *testing.B) {
ctx := context.Background()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, span := t.Start(ctx, "/foo")
span.SetAttributes(
key.New("key1").Bool(false),
key.New("key2").String("hello"),
key.New("key3").Int64(123),
key.New("key4").Uint64(123),
key.New("key5").Int32(123),
key.New("key6").Uint32(123),
key.New("key7").Float64(123.456),
key.New("key8").Float32(123.456),
key.New("key9").Bytes([]byte{1, 2, 3, 4}),
key.New("key10").Int(123),
key.New("key11").Uint(123),
key.New("key21").Bool(false),
key.New("key22").String("hello"),
key.New("key23").Int64(123),
key.New("key24").Uint64(123),
key.New("key25").Int32(123),
key.New("key26").Uint32(123),
key.New("key27").Float64(123.456),
key.New("key28").Float32(123.456),
key.New("key29").Bytes([]byte{1, 2, 3, 4}),
key.New("key210").Int(123),
key.New("key211").Uint(123),
)
span.Finish()
}
})
}
func BenchmarkTraceID_DotString(b *testing.B) {
traceBenchmark(b, func(b *testing.B) {
sc := core.SpanContext{TraceID: core.TraceID{High: 1, Low: 0x2a}}
want := "0000000000000001000000000000002a"
for i := 0; i < b.N; i++ {
if got := sc.TraceIDString(); got != want {
b.Fatalf("got = %q want = %q", got, want)
}
}
})
}
func BenchmarkSpanID_DotString(b *testing.B) {
traceBenchmark(b, func(b *testing.B) {
sc := core.SpanContext{SpanID: 1}
want := "0000000000000001"
for i := 0; i < b.N; i++ {
if got := sc.SpanIDString(); got != want {
b.Fatalf("got = %q want = %q", got, want)
}
}
})
}
func traceBenchmark(b *testing.B, fn func(*testing.B)) {
b.Run("AlwaysSample", func(b *testing.B) {
b.ReportAllocs()
ApplyConfig(Config{DefaultSampler: AlwaysSample()})
fn(b)
})
b.Run("NeverSample", func(b *testing.B) {
b.ReportAllocs()
ApplyConfig(Config{DefaultSampler: NeverSample()})
fn(b)
})
}

77
sdk/trace/config.go Normal file
View File

@ -0,0 +1,77 @@
// 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 (
"sync"
"go.opentelemetry.io/sdk/trace/internal"
)
// Config represents the global tracing configuration.
type Config struct {
// DefaultSampler is the default sampler used when creating new spans.
DefaultSampler Sampler
// IDGenerator is for internal use only.
IDGenerator internal.IDGenerator
// MaxEventsPerSpan is max number of message events per span
MaxEventsPerSpan int
// MaxAnnotationEventsPerSpan is max number of attributes per span
MaxAttributesPerSpan int
// MaxLinksPerSpan is max number of links per span
MaxLinksPerSpan int
}
var configWriteMu sync.Mutex
const (
// DefaultMaxEventsPerSpan is default max number of message events per span
DefaultMaxEventsPerSpan = 128
// DefaultMaxAttributesPerSpan is default max number of attributes per span
DefaultMaxAttributesPerSpan = 32
// DefaultMaxLinksPerSpan is default max number of links per span
DefaultMaxLinksPerSpan = 32
)
// ApplyConfig applies changes to the global tracing configuration.
//
// Fields not provided in the given config are going to be preserved.
func ApplyConfig(cfg Config) {
configWriteMu.Lock()
defer configWriteMu.Unlock()
c := *config.Load().(*Config)
if cfg.DefaultSampler != nil {
c.DefaultSampler = cfg.DefaultSampler
}
if cfg.IDGenerator != nil {
c.IDGenerator = cfg.IDGenerator
}
if cfg.MaxEventsPerSpan > 0 {
c.MaxEventsPerSpan = cfg.MaxEventsPerSpan
}
if cfg.MaxAttributesPerSpan > 0 {
c.MaxAttributesPerSpan = cfg.MaxAttributesPerSpan
}
if cfg.MaxLinksPerSpan > 0 {
c.MaxLinksPerSpan = cfg.MaxLinksPerSpan
}
config.Store(&c)
}

81
sdk/trace/config_test.go Normal file
View File

@ -0,0 +1,81 @@
// 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 (
"reflect"
"testing"
)
func TestApplyConfig(t *testing.T) {
testCfgs := []Config{
{},
{
MaxAttributesPerSpan: 1,
MaxEventsPerSpan: 3,
MaxLinksPerSpan: 4,
},
{
MaxAttributesPerSpan: -1,
MaxEventsPerSpan: -3,
MaxLinksPerSpan: 5,
}}
cfg := config.Load().(*Config)
wantCfgs := []Config{
{
DefaultSampler: cfg.DefaultSampler,
IDGenerator: cfg.IDGenerator,
MaxAttributesPerSpan: DefaultMaxAttributesPerSpan,
MaxEventsPerSpan: DefaultMaxEventsPerSpan,
MaxLinksPerSpan: DefaultMaxLinksPerSpan,
},
{
DefaultSampler: cfg.DefaultSampler,
IDGenerator: cfg.IDGenerator,
MaxAttributesPerSpan: 1,
MaxEventsPerSpan: 3,
MaxLinksPerSpan: 4,
},
{
DefaultSampler: cfg.DefaultSampler,
IDGenerator: cfg.IDGenerator,
MaxAttributesPerSpan: 1,
MaxEventsPerSpan: 3,
MaxLinksPerSpan: 5,
}}
for i, newCfg := range testCfgs {
ApplyConfig(newCfg)
gotCfg := config.Load().(*Config)
wantCfg := wantCfgs[i]
if got, want := reflect.ValueOf(gotCfg.DefaultSampler).Pointer(), reflect.ValueOf(wantCfg.DefaultSampler).Pointer(); got != want {
t.Fatalf("testId = %d config.DefaultSampler = %#v; want %#v", i, got, want)
}
if got, want := gotCfg.IDGenerator, wantCfg.IDGenerator; got != want {
t.Fatalf("testId = %d config.IDGenerator = %#v; want %#v", i, got, want)
}
if got, want := gotCfg.MaxAttributesPerSpan, wantCfg.MaxAttributesPerSpan; got != want {
t.Fatalf("testId = %d config.MaxAttributesPerSpan = %#v; want %#v", i, got, want)
}
if got, want := gotCfg.MaxLinksPerSpan, wantCfg.MaxLinksPerSpan; got != want {
t.Fatalf("testId = %d config.MaxLinksPerSpan = %#v; want %#v", i, got, want)
}
if got, want := gotCfg.MaxEventsPerSpan, wantCfg.MaxEventsPerSpan; got != want {
t.Fatalf("testId = %d config.MaxEventsPerSpan = %#v; want %#v", i, got, want)
}
}
}

21
sdk/trace/doc.go Normal file
View File

@ -0,0 +1,21 @@
// 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 contains support for OpenTelemetry distributed tracing.
The following assumes a basic familiarity with OpenTelemetry concepts.
See http://opentelemetry.io
*/
package trace // import "go.opentelemetry.io/sdk/trace"

38
sdk/trace/evictedqueue.go Normal file
View File

@ -0,0 +1,38 @@
// 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
type evictedQueue struct {
queue []interface{}
capacity int
droppedCount int
}
func newEvictedQueue(capacity int) *evictedQueue {
eq := &evictedQueue{
capacity: capacity,
queue: make([]interface{}, 0),
}
return eq
}
func (eq *evictedQueue) add(value interface{}) {
if len(eq.queue) == eq.capacity {
eq.queue = eq.queue[1:]
eq.droppedCount++
}
eq.queue = append(eq.queue, value)
}

View File

@ -0,0 +1,65 @@
// 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 (
"reflect"
"testing"
)
func init() {
}
func TestAdd(t *testing.T) {
q := newEvictedQueue(3)
q.add("value1")
q.add("value2")
if wantLen, gotLen := 2, len(q.queue); wantLen != gotLen {
t.Errorf("got queue length %d want %d", gotLen, wantLen)
}
}
func (eq *evictedQueue) queueToArray() []string {
arr := make([]string, 0)
for _, value := range eq.queue {
arr = append(arr, value.(string))
}
return arr
}
func TestDropCount(t *testing.T) {
q := newEvictedQueue(3)
q.add("value1")
q.add("value2")
q.add("value3")
q.add("value1")
q.add("value4")
if wantLen, gotLen := 3, len(q.queue); wantLen != gotLen {
t.Errorf("got queue length %d want %d", gotLen, wantLen)
}
if wantDropCount, gotDropCount := 2, q.droppedCount; wantDropCount != gotDropCount {
t.Errorf("got drop count %d want %d", gotDropCount, wantDropCount)
}
wantArr := []string{"value3", "value1", "value4"}
gotArr := q.queueToArray()
if wantLen, gotLen := len(wantArr), len(gotArr); gotLen != wantLen {
t.Errorf("got array len %d want %d", gotLen, wantLen)
}
if !reflect.DeepEqual(gotArr, wantArr) {
t.Errorf("got array = %#v; want %#v", gotArr, wantArr)
}
}

View File

@ -0,0 +1,41 @@
// 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_test
import (
"context"
"fmt"
"go.opentelemetry.io/api/trace"
)
// This example shows how to use trace.Start and (*Span).End to capture
// a function execution in a Span. It assumes that the function
// has a context.Context argument.
func ExampleStart() {
printEvens := func(ctx context.Context) {
_, span := trace.GlobalTracer().Start(ctx, "my/package.Function")
defer span.Finish()
for i := 0; i < 10; i++ {
if i%2 == 0 {
fmt.Printf("Even!\n")
}
}
}
ctx := context.Background()
printEvens(ctx)
}

97
sdk/trace/export.go Normal file
View File

@ -0,0 +1,97 @@
// 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 (
"sync"
"sync/atomic"
"time"
"go.opentelemetry.io/api/core"
"google.golang.org/grpc/codes"
)
// Exporter is a type for functions that receive sampled trace spans.
//
// The ExportSpan method should be safe for concurrent use and should return
// quickly; if an Exporter takes a significant amount of time to process a
// SpanData, that work should be done on another goroutine.
//
// The SpanData should not be modified, but a pointer to it can be kept.
type Exporter interface {
ExportSpan(s *SpanData)
}
type exportersMap map[Exporter]struct{}
var (
exporterMu sync.Mutex
exporters atomic.Value
)
// RegisterExporter adds to the list of Exporters that will receive sampled
// trace spans.
//
// Binaries can register exporters, libraries shouldn't register exporters.
func RegisterExporter(e Exporter) {
exporterMu.Lock()
defer exporterMu.Unlock()
new := make(exportersMap)
if old, ok := exporters.Load().(exportersMap); ok {
for k, v := range old {
new[k] = v
}
}
new[e] = struct{}{}
exporters.Store(new)
}
// UnregisterExporter removes from the list of Exporters the Exporter that was
// registered with the given name.
func UnregisterExporter(e Exporter) {
exporterMu.Lock()
defer exporterMu.Unlock()
new := make(exportersMap)
if old, ok := exporters.Load().(exportersMap); ok {
for k, v := range old {
new[k] = v
}
}
delete(new, e)
exporters.Store(new)
}
// SpanData contains all the information collected by a span.
type SpanData struct {
SpanContext core.SpanContext
ParentSpanID uint64
SpanKind int
Name string
StartTime time.Time
// The wall clock time of EndTime will be adjusted to always be offset
// from StartTime by the duration of the span.
EndTime time.Time
// The values of Attributes each have type string, bool, or int64.
Attributes map[string]interface{}
MessageEvents []event
Status codes.Code
HasRemoteParent bool
DroppedAttributeCount int
DroppedMessageEventCount int
DroppedLinkCount int
// ChildSpanCount holds the number of child span created for this span.
ChildSpanCount int
}

67
sdk/trace/id_generator.go Normal file
View File

@ -0,0 +1,67 @@
// 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 (
"math/rand"
"sync"
"sync/atomic"
"go.opentelemetry.io/api/core"
"go.opentelemetry.io/sdk/trace/internal"
)
type defaultIDGenerator struct {
sync.Mutex
// Please keep these as the first fields
// so that these 8 byte fields will be aligned on addresses
// divisible by 8, on both 32-bit and 64-bit machines when
// performing atomic increments and accesses.
// See:
// * https://github.com/census-instrumentation/opencensus-go/issues/587
// * https://github.com/census-instrumentation/opencensus-go/issues/865
// * https://golang.org/pkg/sync/atomic/#pkg-note-BUG
nextSpanID uint64
spanIDInc uint64
traceIDAdd [2]uint64
traceIDRand *rand.Rand
}
var _ internal.IDGenerator = &defaultIDGenerator{}
// NewSpanID returns a non-zero span ID from a randomly-chosen sequence.
func (gen *defaultIDGenerator) NewSpanID() uint64 {
var id uint64
for id == 0 {
id = atomic.AddUint64(&gen.nextSpanID, gen.spanIDInc)
}
return id
}
// NewTraceID returns a non-zero trace ID from a randomly-chosen sequence.
// mu should be held while this function is called.
func (gen *defaultIDGenerator) NewTraceID() core.TraceID {
gen.Lock()
defer gen.Unlock()
// Construct the trace ID from two outputs of traceIDRand, with a constant
// added to each half for additional entropy.
tid := core.TraceID{
High: gen.traceIDRand.Uint64() + gen.traceIDAdd[0],
Low: gen.traceIDRand.Uint64() + gen.traceIDAdd[1],
}
return tid
}

View File

@ -0,0 +1,24 @@
// 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 internal provides trace internals.
package internal
import "go.opentelemetry.io/api/core"
// IDGenerator allows custom generators for TraceId and SpanId.
type IDGenerator interface {
NewTraceID() core.TraceID
NewSpanID() uint64
}

37
sdk/trace/lrumap.go Normal file
View File

@ -0,0 +1,37 @@
// 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 (
"github.com/hashicorp/golang-lru/simplelru"
)
type lruMap struct {
simpleLruMap *simplelru.LRU
droppedCount int
}
func newLruMap(size int) *lruMap {
lm := &lruMap{}
lm.simpleLruMap, _ = simplelru.NewLRU(size, nil)
return lm
}
func (lm *lruMap) add(key, value interface{}) {
evicted := lm.simpleLruMap.Add(key, value)
if evicted {
lm.droppedCount++
}
}

75
sdk/trace/sampling.go Normal file
View File

@ -0,0 +1,75 @@
// 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 (
"go.opentelemetry.io/api/core"
)
const defaultSamplingProbability = 1e-4
// Sampler decides whether a trace should be sampled and exported.
type Sampler func(SamplingParameters) SamplingDecision
// SamplingParameters contains the values passed to a Sampler.
type SamplingParameters struct {
ParentContext core.SpanContext
TraceID core.TraceID
SpanID uint64
Name string
HasRemoteParent bool
}
// SamplingDecision is the value returned by a Sampler.
type SamplingDecision struct {
Sample bool
}
// ProbabilitySampler returns a Sampler that samples a given fraction of traces.
//
// It also samples spans whose parents are sampled.
func ProbabilitySampler(fraction float64) Sampler {
if !(fraction >= 0) {
fraction = 0
} else if fraction >= 1 {
return AlwaysSample()
}
traceIDUpperBound := uint64(fraction * (1 << 63))
return Sampler(func(p SamplingParameters) SamplingDecision {
if p.ParentContext.IsSampled() {
return SamplingDecision{Sample: true}
}
x := p.TraceID.High >> 1
return SamplingDecision{Sample: x < traceIDUpperBound}
})
}
// AlwaysSample returns a Sampler that samples every trace.
// Be careful about using this sampler in a production application with
// significant traffic: a new trace will be started and exported for every
// request.
func AlwaysSample() Sampler {
return func(p SamplingParameters) SamplingDecision {
return SamplingDecision{Sample: true}
}
}
// NeverSample returns a Sampler that samples no traces.
func NeverSample() Sampler {
return func(p SamplingParameters) SamplingDecision {
return SamplingDecision{Sample: false}
}
}

288
sdk/trace/span.go Normal file
View File

@ -0,0 +1,288 @@
// 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"
"sync"
"time"
"go.opentelemetry.io/api/core"
apievent "go.opentelemetry.io/api/event"
apitag "go.opentelemetry.io/api/tag"
apitrace "go.opentelemetry.io/api/trace"
"go.opentelemetry.io/sdk/internal"
"google.golang.org/grpc/codes"
)
// span implements apitrace.Span interface.
type span struct {
// data contains information recorded about the span.
//
// It will be non-nil if we are exporting the span or recording events for it.
// Otherwise, data is nil, and the span is simply a carrier for the
// SpanContext, so that the trace ID is propagated.
data *SpanData
mu sync.Mutex // protects the contents of *data (but not the pointer value.)
spanContext core.SpanContext
// lruAttributes are capped at configured limit. When the capacity is reached an oldest entry
// is removed to create room for a new entry.
lruAttributes *lruMap
// messageEvents are stored in FIFO queue capped by configured limit.
messageEvents *evictedQueue
// links are stored in FIFO queue capped by configured limit.
links *evictedQueue
// spanStore is the spanStore this span belongs to, if any, otherwise it is nil.
//*spanStore
endOnce sync.Once
executionTracerTaskEnd func() // ends the execution tracer span
tracer apitrace.Tracer // tracer used to create span.
}
var _ apitrace.Span = &span{}
func (s *span) SpanContext() core.SpanContext {
if s == nil {
return core.INVALID_SPAN_CONTEXT
}
return s.spanContext
}
func (s *span) IsRecordingEvents() bool {
if s == nil {
return false
}
return s.data != nil
}
func (s *span) SetStatus(status codes.Code) {
if s == nil {
return
}
if !s.IsRecordingEvents() {
return
}
s.mu.Lock()
s.data.Status = status
s.mu.Unlock()
}
func (s *span) SetAttribute(attribute core.KeyValue) {
if !s.IsRecordingEvents() {
return
}
s.copyToCappedAttributes(attribute)
}
func (s *span) SetAttributes(attributes ...core.KeyValue) {
if !s.IsRecordingEvents() {
return
}
s.copyToCappedAttributes(attributes...)
}
// ModifyAttribute does nothing.
func (s *span) ModifyAttribute(mutator apitag.Mutator) {
}
// ModifyAttributes does nothing.
func (s *span) ModifyAttributes(mutators ...apitag.Mutator) {
}
func (s *span) Finish() {
if s == nil {
return
}
if s.executionTracerTaskEnd != nil {
s.executionTracerTaskEnd()
}
if !s.IsRecordingEvents() {
return
}
s.endOnce.Do(func() {
exp, _ := exporters.Load().(exportersMap)
mustExport := s.spanContext.IsSampled() && len(exp) > 0
//if s.spanStore != nil || mustExport {
if mustExport {
sd := s.makeSpanData()
sd.EndTime = internal.MonotonicEndTime(sd.StartTime)
//if s.spanStore != nil {
// s.spanStore.finished(s, sd)
//}
if mustExport {
for e := range exp {
e.ExportSpan(sd)
}
}
}
})
}
func (s *span) Tracer() apitrace.Tracer {
return s.tracer
}
func (s *span) AddEvent(ctx context.Context, event apievent.Event) {
if !s.IsRecordingEvents() {
return
}
s.mu.Lock()
defer s.mu.Unlock()
s.messageEvents.add(event)
}
func (s *span) Event(ctx context.Context, msg string, attrs ...core.KeyValue) {
if !s.IsRecordingEvents() {
return
}
now := time.Now()
s.mu.Lock()
s.messageEvents.add(event{
msg: msg,
attributes: attrs,
time: now,
})
s.mu.Unlock()
}
// makeSpanData produces a SpanData representing the current state of the span.
// It requires that s.data is non-nil.
func (s *span) makeSpanData() *SpanData {
var sd SpanData
s.mu.Lock()
defer s.mu.Unlock()
sd = *s.data
if s.lruAttributes.simpleLruMap.Len() > 0 {
sd.Attributes = s.lruAttributesToAttributeMap()
sd.DroppedAttributeCount = s.lruAttributes.droppedCount
}
if len(s.messageEvents.queue) > 0 {
sd.MessageEvents = s.interfaceArrayToMessageEventArray()
sd.DroppedMessageEventCount = s.messageEvents.droppedCount
}
return &sd
}
func (s *span) interfaceArrayToMessageEventArray() []event {
messageEventArr := make([]event, 0)
for _, value := range s.messageEvents.queue {
messageEventArr = append(messageEventArr, value.(event))
}
return messageEventArr
}
func (s *span) lruAttributesToAttributeMap() map[string]interface{} {
attributes := make(map[string]interface{})
for _, key := range s.lruAttributes.simpleLruMap.Keys() {
value, ok := s.lruAttributes.simpleLruMap.Get(key)
if ok {
key := key.(core.Key)
attributes[key.Variable.Name] = value
}
}
return attributes
}
func (s *span) copyToCappedAttributes(attributes ...core.KeyValue) {
s.mu.Lock()
defer s.mu.Unlock()
for _, a := range attributes {
s.lruAttributes.add(a.Key, a.Value)
}
}
func (s *span) addChild() {
if !s.IsRecordingEvents() {
return
}
s.mu.Lock()
s.data.ChildSpanCount++
s.mu.Unlock()
}
func startSpanInternal(name string, parent core.SpanContext, remoteParent bool, o apitrace.SpanOptions) *span {
var noParent bool
span := &span{}
span.spanContext = parent
cfg := config.Load().(*Config)
if parent == core.INVALID_SPAN_CONTEXT {
span.spanContext.TraceID = cfg.IDGenerator.NewTraceID()
noParent = true
}
span.spanContext.SpanID = cfg.IDGenerator.NewSpanID()
sampler := cfg.DefaultSampler
// TODO: [rghetia] fix sampler
//if !hasParent || remoteParent || o.Sampler != nil {
if noParent || remoteParent {
// If this span is the child of a local span and no Sampler is set in the
// options, keep the parent's TraceOptions.
//
// Otherwise, consult the Sampler in the options if it is non-nil, otherwise
// the default sampler.
//if o.Sampler != nil {
// sampler = o.Sampler
//}
sampled := sampler(SamplingParameters{
ParentContext: parent,
TraceID: span.spanContext.TraceID,
SpanID: span.spanContext.SpanID,
Name: name,
HasRemoteParent: remoteParent}).Sample
if sampled {
span.spanContext.TraceOptions = core.TraceOptionSampled
}
}
// TODO: [rghetia] restore when spanstore is added.
// if !internal.LocalSpanStoreEnabled && !span.spanContext.IsSampled() && !o.RecordEvent {
if !span.spanContext.IsSampled() && !o.RecordEvent {
return span
}
span.data = &SpanData{
SpanContext: span.spanContext,
StartTime: time.Now(),
// TODO;[rghetia] : fix spanKind
//SpanKind: o.SpanKind,
Name: name,
HasRemoteParent: remoteParent,
}
span.lruAttributes = newLruMap(cfg.MaxAttributesPerSpan)
span.messageEvents = newEvictedQueue(cfg.MaxEventsPerSpan)
span.links = newEvictedQueue(cfg.MaxLinksPerSpan)
if !noParent {
span.data.ParentSpanID = parent.SpanID
}
// TODO: [rghetia] restore when spanstore is added.
//if internal.LocalSpanStoreEnabled {
// ss := spanStoreForNameCreateIfNew(name)
// if ss != nil {
// span.spanStore = ss
// ss.add(span)
// }
//}
return span
}

75
sdk/trace/trace.go Normal file
View File

@ -0,0 +1,75 @@
// 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"
crand "crypto/rand"
"encoding/binary"
"math/rand"
"sync"
"sync/atomic"
apitrace "go.opentelemetry.io/api/trace"
)
var config atomic.Value // access atomically
func init() {
gen := &defaultIDGenerator{}
// initialize traceID and spanID generators.
var rngSeed int64
for _, p := range []interface{}{
&rngSeed, &gen.traceIDAdd, &gen.nextSpanID, &gen.spanIDInc,
} {
_ = binary.Read(crand.Reader, binary.LittleEndian, p)
}
gen.traceIDRand = rand.New(rand.NewSource(rngSeed))
gen.spanIDInc |= 1
config.Store(&Config{
DefaultSampler: ProbabilitySampler(defaultSamplingProbability),
IDGenerator: gen,
MaxAttributesPerSpan: DefaultMaxAttributesPerSpan,
MaxEventsPerSpan: DefaultMaxEventsPerSpan,
MaxLinksPerSpan: DefaultMaxLinksPerSpan,
})
}
var tr *tracer
var registerOnce sync.Once
// Register registers tracer implementation as default Tracer.
// It creates single instance of tracer and registers it once.
// Recommended use is to call Register in main() of an
// application before calling any tracing api.
func Register() apitrace.Tracer {
registerOnce.Do(func() {
tr = &tracer{}
apitrace.SetGlobalTracer(tr)
})
return tr
}
type contextKey struct{}
func fromContext(ctx context.Context) *span {
s, _ := ctx.Value(contextKey{}).(*span)
return s
}
func newContext(parent context.Context, s *span) context.Context {
return context.WithValue(parent, contextKey{}, s)
}

32
sdk/trace/trace_go11.go Normal file
View File

@ -0,0 +1,32 @@
// 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.
// +build go1.11
package trace
import (
"context"
rt "runtime/trace"
)
func startExecutionTracerTask(ctx context.Context, name string) (context.Context, func()) {
if !rt.IsEnabled() {
// Avoid additional overhead if
// runtime/trace is not enabled.
return ctx, func() {}
}
nctx, task := rt.NewTask(ctx, name)
return nctx, task.End
}

View File

@ -0,0 +1,25 @@
// 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.
// +build !go1.11
package trace
import (
"context"
)
func startExecutionTracerTask(ctx context.Context, name string) (context.Context, func()) {
return ctx, func() {}
}

524
sdk/trace/trace_test.go Normal file
View File

@ -0,0 +1,524 @@
// 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"
"fmt"
"sync/atomic"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"go.opentelemetry.io/api/core"
"go.opentelemetry.io/api/key"
apitrace "go.opentelemetry.io/api/trace"
"google.golang.org/grpc/codes"
)
var (
tid = core.TraceID{High: 0x0102030405060708, Low: 0x0102040810203040}
sid = uint64(0x0102040810203040)
)
func init() {
Register()
// no random sampling, but sample children of sampled spans.
ApplyConfig(Config{DefaultSampler: ProbabilitySampler(0)})
}
type testExporter struct {
spans []*SpanData
}
func (t *testExporter) ExportSpan(s *SpanData) {
t.spans = append(t.spans, s)
}
func TestStartSpan(t *testing.T) {
_, span := apitrace.GlobalTracer().Start(context.Background(), "StartSpan")
defer span.Finish()
if span == nil {
t.Errorf("span not started")
}
}
func TestRecordingIsOff(t *testing.T) {
_, span := apitrace.GlobalTracer().Start(context.Background(), "StartSpan")
defer span.Finish()
if span.IsRecordingEvents() == true {
t.Error("new span is recording events")
}
}
// TODO: [rghetia] enable sampling test when Sampling is working.
func TestStartSpanWithChildOf(t *testing.T) {
sc1 := core.SpanContext{
TraceID: tid,
SpanID: sid,
TraceOptions: 0x0,
}
_, s1 := apitrace.GlobalTracer().Start(context.Background(), "span1-unsampled-parent1", apitrace.ChildOf(sc1))
if err := checkChild(sc1, s1); err != nil {
t.Error(err)
}
_, s2 := apitrace.GlobalTracer().Start(context.Background(), "span2-unsampled-parent1", apitrace.ChildOf(sc1))
if err := checkChild(sc1, s2); err != nil {
t.Error(err)
}
sc2 := core.SpanContext{
TraceID: tid,
SpanID: sid,
TraceOptions: 0x1,
//Tracestate: testTracestate,
}
_, s3 := apitrace.GlobalTracer().Start(context.Background(), "span3-sampled-parent2", apitrace.ChildOf(sc2))
if err := checkChild(sc2, s3); err != nil {
t.Error(err)
}
ctx, s4 := apitrace.GlobalTracer().Start(context.Background(), "span4-sampled-parent2", apitrace.ChildOf(sc2))
if err := checkChild(sc2, s4); err != nil {
t.Error(err)
}
s4Sc := s4.SpanContext()
_, s5 := apitrace.GlobalTracer().Start(ctx, "span5-implicit-childof-span4")
if err := checkChild(s4Sc, s5); err != nil {
t.Error(err)
}
}
// TODO: [rghetia] Equivalent of SpanKind Test.
func TestSetSpanAttributes(t *testing.T) {
span := startSpan()
span.SetAttribute(key.New("key1").String("value1"))
got, err := endSpan(span)
if err != nil {
t.Fatal(err)
}
want := &SpanData{
SpanContext: core.SpanContext{
TraceID: tid,
TraceOptions: 0x1,
},
ParentSpanID: sid,
Name: "span0",
Attributes: map[string]interface{}{"key1": core.Value{Type: core.STRING, String: "value1"}},
HasRemoteParent: true,
}
if diff := cmp.Diff(got, want); diff != "" {
t.Errorf("SetSpanAttributes: -got +want %s", diff)
}
}
func TestSetSpanAttributesOverLimit(t *testing.T) {
cfg := Config{MaxAttributesPerSpan: 2}
ApplyConfig(cfg)
span := startSpan()
span.SetAttribute(key.New("key1").String("value1"))
span.SetAttribute(key.New("key2").String("value2"))
span.SetAttribute(key.New("key1").String("value3")) // Replace key1.
span.SetAttribute(key.New("key4").String("value4")) // Remove key2 and add key4
got, err := endSpan(span)
if err != nil {
t.Fatal(err)
}
want := &SpanData{
SpanContext: core.SpanContext{
TraceID: tid,
TraceOptions: 0x1,
},
ParentSpanID: sid,
Name: "span0",
Attributes: map[string]interface{}{
"key1": core.Value{Type: core.STRING, String: "value3"},
"key4": core.Value{Type: core.STRING, String: "value4"}},
HasRemoteParent: true,
DroppedAttributeCount: 1,
}
if diff := cmp.Diff(got, want); diff != "" {
t.Errorf("SetSpanAttributesOverLimit: -got +want %s", diff)
}
}
func TestEvents(t *testing.T) {
span := startSpan()
k1v1 := key.New("key1").String("value1")
k2v2 := key.New("key2").String("value2")
k3v3 := key.New("key3").String("value3")
span.Event(context.Background(), "foo", key.New("key1").String("value1"))
span.Event(context.Background(), "bar",
key.New("key2").String("value2"),
key.New("key3").String("value3"),
)
got, err := endSpan(span)
if err != nil {
t.Fatal(err)
}
for i := range got.MessageEvents {
if !checkTime(&got.MessageEvents[i].time) {
t.Error("exporting span: expected nonzero event Time")
}
}
want := &SpanData{
SpanContext: core.SpanContext{
TraceID: tid,
TraceOptions: 0x1,
},
ParentSpanID: sid,
Name: "span0",
HasRemoteParent: true,
MessageEvents: []event{
{msg: "foo", attributes: []core.KeyValue{k1v1}},
{msg: "bar", attributes: []core.KeyValue{k2v2, k3v3}},
},
}
if diff := cmp.Diff(got, want, cmp.AllowUnexported(event{})); diff != "" {
t.Errorf("Message Events: -got +want %s", diff)
}
}
func TestEventsOverLimit(t *testing.T) {
cfg := Config{MaxEventsPerSpan: 2}
ApplyConfig(cfg)
span := startSpan()
k1v1 := key.New("key1").String("value1")
k2v2 := key.New("key2").String("value2")
k3v3 := key.New("key3").String("value3")
span.Event(context.Background(), "fooDrop", key.New("key1").String("value1"))
span.Event(context.Background(), "barDrop",
key.New("key2").String("value2"),
key.New("key3").String("value3"),
)
span.Event(context.Background(), "foo", key.New("key1").String("value1"))
span.Event(context.Background(), "bar",
key.New("key2").String("value2"),
key.New("key3").String("value3"),
)
got, err := endSpan(span)
if err != nil {
t.Fatal(err)
}
for i := range got.MessageEvents {
if !checkTime(&got.MessageEvents[i].time) {
t.Error("exporting span: expected nonzero event Time")
}
}
want := &SpanData{
SpanContext: core.SpanContext{
TraceID: tid,
TraceOptions: 0x1,
},
ParentSpanID: sid,
Name: "span0",
MessageEvents: []event{
{msg: "foo", attributes: []core.KeyValue{k1v1}},
{msg: "bar", attributes: []core.KeyValue{k2v2, k3v3}},
},
DroppedMessageEventCount: 2,
HasRemoteParent: true,
}
if diff := cmp.Diff(got, want, cmp.AllowUnexported(event{})); diff != "" {
t.Errorf("Message Event over limit: -got +want %s", diff)
}
}
func TestSetSpanName(t *testing.T) {
want := "SpanName-1"
_, span := apitrace.GlobalTracer().Start(context.Background(), want,
apitrace.ChildOf(core.SpanContext{
TraceID: tid,
SpanID: sid,
TraceOptions: 1,
}),
)
got, err := endSpan(span)
if err != nil {
t.Fatal(err)
}
if got.Name != want {
t.Errorf("span.Name: got %q; want %q", got.Name, want)
}
}
func TestSetSpanStatus(t *testing.T) {
span := startSpan()
span.SetStatus(codes.Canceled)
got, err := endSpan(span)
if err != nil {
t.Fatal(err)
}
want := &SpanData{
SpanContext: core.SpanContext{
TraceID: tid,
TraceOptions: 0x1,
},
ParentSpanID: sid,
Name: "span0",
Status: codes.Canceled,
HasRemoteParent: true,
}
if diff := cmp.Diff(got, want); diff != "" {
t.Errorf("SetSpanStatus: -got +want %s", diff)
}
}
func TestUnregisterExporter(t *testing.T) {
var te testExporter
RegisterExporter(&te)
UnregisterExporter(&te)
ctx := startSpan()
_, _ = endSpan(ctx)
if len(te.spans) != 0 {
t.Error("unregistered Exporter was called")
}
}
func remoteSpanContext() core.SpanContext {
return core.SpanContext{
TraceID: tid,
SpanID: sid,
TraceOptions: 1,
}
}
// checkChild is test utility function that tests that c has fields set appropriately,
// given that it is a child span of p.
func checkChild(p core.SpanContext, apiSpan apitrace.Span) error {
s := apiSpan.(*span)
if s == nil {
return fmt.Errorf("got nil child span, want non-nil")
}
if got, want := s.spanContext.TraceIDString(), p.TraceIDString(); got != want {
return fmt.Errorf("got child trace ID %s, want %s", got, want)
}
if childID, parentID := s.spanContext.SpanIDString(), p.SpanIDString(); childID == parentID {
return fmt.Errorf("got child span ID %s, parent span ID %s; want unequal IDs", childID, parentID)
}
if got, want := s.spanContext.TraceOptions, p.TraceOptions; got != want {
return fmt.Errorf("got child trace options %d, want %d", got, want)
}
// TODO [rgheita] : Fix tracestate test
//if got, want := c.spanContext.Tracestate, p.Tracestate; got != want {
// return fmt.Errorf("got child tracestate %v, want %v", got, want)
//}
return nil
}
// startSpan is a test utility func that starts a span with ChildOf option.
// remote span context contains traceoption with sampled bit set. This allows
// the span to be automatically sampled.
func startSpan() apitrace.Span {
_, span := apitrace.GlobalTracer().Start(
context.Background(),
"span0",
apitrace.ChildOf(remoteSpanContext()),
apitrace.WithRecordEvents(),
)
return span
}
// endSpan is a test utility function that ends the span in the context and
// returns the exported SpanData.
// It requires that span be sampled using one of these methods
// 1. Passing parent span context using ChildOf option
// 2. Use WithSampler(AlwaysSample())
// 3. Configuring AlwaysSample() as default sampler
//
// It also does some basic tests on the span.
// It also clears spanID in the SpanData to make the comparison easier.
func endSpan(span apitrace.Span) (*SpanData, error) {
if !span.IsRecordingEvents() {
return nil, fmt.Errorf("IsRecordingEvents: got false, want true")
}
if !span.SpanContext().IsSampled() {
return nil, fmt.Errorf("IsSampled: got false, want true")
}
var te testExporter
RegisterExporter(&te)
span.Finish()
UnregisterExporter(&te)
if len(te.spans) != 1 {
return nil, fmt.Errorf("got exported spans %#v, want one span", te.spans)
}
got := te.spans[0]
if got.SpanContext.SpanID == 0 {
return nil, fmt.Errorf("exporting span: expected nonzero SpanID")
}
got.SpanContext.SpanID = 0
if !checkTime(&got.StartTime) {
return nil, fmt.Errorf("exporting span: expected nonzero StartTime")
}
if !checkTime(&got.EndTime) {
return nil, fmt.Errorf("exporting span: expected nonzero EndTime")
}
return got, nil
}
// checkTime checks that a nonzero time was set in x, then clears it.
func checkTime(x *time.Time) bool {
if x.IsZero() {
return false
}
*x = time.Time{}
return true
}
type exporter map[string]*SpanData
func (e exporter) ExportSpan(s *SpanData) {
e[s.Name] = s
}
func TestEndSpanTwice(t *testing.T) {
spans := make(exporter)
RegisterExporter(&spans)
defer UnregisterExporter(&spans)
span := startSpan()
span.Finish()
span.Finish()
UnregisterExporter(&spans)
if len(spans) != 1 {
t.Fatalf("expected only a single span, got %#v", spans)
}
}
func TestStartSpanAfterEnd(t *testing.T) {
spans := make(exporter)
RegisterExporter(&spans)
defer UnregisterExporter(&spans)
ctx, span0 := apitrace.GlobalTracer().Start(context.Background(), "parent", apitrace.ChildOf(remoteSpanContext()))
ctx1, span1 := apitrace.GlobalTracer().Start(ctx, "span-1")
span1.Finish()
// Start a new span with the context containing span-1
// even though span-1 is ended, we still add this as a new child of span-1
_, span2 := apitrace.GlobalTracer().Start(ctx1, "span-2")
span2.Finish()
span0.Finish()
UnregisterExporter(&spans)
if got, want := len(spans), 3; got != want {
t.Fatalf("len(%#v) = %d; want %d", spans, got, want)
}
if got, want := spans["span-1"].SpanContext.TraceID, spans["parent"].SpanContext.TraceID; got != want {
t.Errorf("span-1.TraceID=%q; want %q", got, want)
}
if got, want := spans["span-2"].SpanContext.TraceID, spans["parent"].SpanContext.TraceID; got != want {
t.Errorf("span-2.TraceID=%q; want %q", got, want)
}
if got, want := spans["span-1"].ParentSpanID, spans["parent"].SpanContext.SpanID; got != want {
t.Errorf("span-1.ParentSpanID=%q; want %q (parent.SpanID)", got, want)
}
if got, want := spans["span-2"].ParentSpanID, spans["span-1"].SpanContext.SpanID; got != want {
t.Errorf("span-2.ParentSpanID=%q; want %q (span1.SpanID)", got, want)
}
}
func TestChildSpanCount(t *testing.T) {
ApplyConfig(Config{DefaultSampler: AlwaysSample()})
spans := make(exporter)
RegisterExporter(&spans)
defer UnregisterExporter(&spans)
ctx, span0 := apitrace.GlobalTracer().Start(context.Background(), "parent")
ctx1, span1 := apitrace.GlobalTracer().Start(ctx, "span-1")
_, span2 := apitrace.GlobalTracer().Start(ctx1, "span-2")
span2.Finish()
span1.Finish()
_, span3 := apitrace.GlobalTracer().Start(ctx, "span-3")
span3.Finish()
span0.Finish()
UnregisterExporter(&spans)
if got, want := len(spans), 4; got != want {
t.Fatalf("len(%#v) = %d; want %d", spans, got, want)
}
if got, want := spans["span-3"].ChildSpanCount, 0; got != want {
t.Errorf("span-3.ChildSpanCount=%q; want %q", got, want)
}
if got, want := spans["span-2"].ChildSpanCount, 0; got != want {
t.Errorf("span-2.ChildSpanCount=%q; want %q", got, want)
}
if got, want := spans["span-1"].ChildSpanCount, 1; got != want {
t.Errorf("span-1.ChildSpanCount=%q; want %q", got, want)
}
if got, want := spans["parent"].ChildSpanCount, 2; got != want {
t.Errorf("parent.ChildSpanCount=%q; want %q", got, want)
}
}
func TestNilSpanFinish(t *testing.T) {
var span *span
span.Finish()
}
func TestExecutionTracerTaskEnd(t *testing.T) {
var n uint64
ApplyConfig(Config{DefaultSampler: NeverSample()})
executionTracerTaskEnd := func() {
atomic.AddUint64(&n, 1)
}
var spans []*span
_, apiSpan := apitrace.GlobalTracer().Start(context.Background(), "foo")
s := apiSpan.(*span)
s.executionTracerTaskEnd = executionTracerTaskEnd
spans = append(spans, s) // never sample
_, apiSpan = apitrace.GlobalTracer().Start(
context.Background(),
"foo",
apitrace.ChildOf(
core.SpanContext{
TraceID: core.TraceID{High: 0x0102030405060708, Low: 0x090a0b0c0d0e0f},
SpanID: uint64(0x0001020304050607),
TraceOptions: 0,
},
),
)
s = apiSpan.(*span)
s.executionTracerTaskEnd = executionTracerTaskEnd
spans = append(spans, s) // parent not sampled
ApplyConfig(Config{DefaultSampler: AlwaysSample()})
_, apiSpan = apitrace.GlobalTracer().Start(context.Background(), "foo")
s = apiSpan.(*span)
s.executionTracerTaskEnd = executionTracerTaskEnd
spans = append(spans, s) // always sample
for _, span := range spans {
span.Finish()
}
if got, want := n, uint64(len(spans)); got != want {
t.Fatalf("Execution tracer task ended for %v spans; want %v", got, want)
}
}

98
sdk/trace/tracer.go Normal file
View File

@ -0,0 +1,98 @@
// 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"
"go.opentelemetry.io/api/core"
apitrace "go.opentelemetry.io/api/trace"
)
type tracer struct {
name string
component string
resources []core.KeyValue
}
var _ apitrace.Tracer = &tracer{}
func (tr *tracer) Start(ctx context.Context, name string, o ...apitrace.SpanOption) (context.Context, apitrace.Span) {
var opts apitrace.SpanOptions
var parent core.SpanContext
var remoteParent bool
//TODO [rghetia] : Add new option for parent. If parent is configured then use that parent.
for _, op := range o {
op(&opts)
}
// TODO: [rghetia] ChildOfRelationship is used to indicate that the parent is remote
// and its context is received as part of a request. There are two possibilities
// 1. Remote is trusted. So continue using same trace.
// tracer.Start(ctx, "some name", ChildOf(remote_span_context))
// 2. Remote is not trusted. In this case create a root span and then add the remote as link
// span := tracer.Start(ctx, "some name")
// span.Link(remote_span_context, ChildOfRelationship)
if opts.Reference.SpanContext != core.INVALID_SPAN_CONTEXT &&
opts.Reference.RelationshipType == apitrace.ChildOfRelationship {
parent = opts.Reference.SpanContext
remoteParent = true
} else {
if p := fromContext(ctx); p != nil {
p.addChild()
parent = p.spanContext
}
}
span := startSpanInternal(name, parent, remoteParent, opts)
span.tracer = tr
ctx, end := startExecutionTracerTask(ctx, name)
span.executionTracerTaskEnd = end
return newContext(ctx, span), span
}
func (tr *tracer) WithSpan(ctx context.Context, name string, body func(ctx context.Context) error) error {
ctx, span := tr.Start(ctx, name)
defer span.Finish()
if err := body(ctx); err != nil {
// TODO: set event with boolean attribute for error.
return err
}
return nil
}
func (tr *tracer) WithService(name string) apitrace.Tracer {
tr.name = name
return tr
}
// WithResources does nothing and returns noop implementation of apitrace.Tracer.
func (tr *tracer) WithResources(res ...core.KeyValue) apitrace.Tracer {
tr.resources = res
return tr
}
// WithComponent does nothing and returns noop implementation of apitrace.Tracer.
func (tr *tracer) WithComponent(component string) apitrace.Tracer {
tr.component = component
return tr
}
func (tr *tracer) Inject(ctx context.Context, span apitrace.Span, injector apitrace.Injector) {
injector.Inject(span.SpanContext(), nil)
}