├── .gitattributes ├── .github └── workflows │ ├── ci.yml │ └── security.yml ├── .gitignore ├── .golangci.yml ├── LICENSE ├── Makefile ├── README.md ├── SECURITY.md ├── bench_test.go ├── context.go ├── context_test.go ├── doc.go ├── endpoint.go ├── endpoint_test.go ├── example_test.go ├── examples └── httpserver_test.go ├── go.mod ├── go.sum ├── idgenerator ├── idgenerator.go └── idgenerator_test.go ├── middleware ├── baggage.go ├── grpc │ ├── baggage_test.go │ ├── client.go │ ├── client_test.go │ ├── doc.go │ ├── grpc_suite_test.go │ ├── server.go │ ├── server_test.go │ └── shared.go └── http │ ├── baggage_test.go │ ├── client.go │ ├── client_test.go │ ├── doc.go │ ├── request_sampler.go │ ├── server.go │ ├── server_test.go │ ├── spancloser.go │ ├── spantrace.go │ ├── transport.go │ └── transport_test.go ├── model ├── annotation.go ├── annotation_test.go ├── doc.go ├── endpoint.go ├── endpoint_test.go ├── kind.go ├── span.go ├── span_id.go ├── span_test.go ├── traceid.go └── traceid_test.go ├── noop.go ├── noop_test.go ├── propagation ├── b3 │ ├── doc.go │ ├── grpc.go │ ├── grpc_test.go │ ├── http.go │ ├── http_test.go │ ├── map.go │ ├── map_test.go │ ├── shared.go │ ├── spancontext.go │ └── spancontext_test.go ├── baggage │ ├── baggage.go │ └── baggage_test.go └── propagation.go ├── proto ├── testing │ ├── baggage.pb.go │ ├── baggage.proto │ ├── baggage_grpc.pb.go │ ├── service.pb.go │ ├── service.proto │ └── service_grpc.pb.go └── zipkin_proto3 │ ├── decode_proto.go │ ├── decode_proto_test.go │ ├── encode_proto.go │ ├── encode_proto_test.go │ ├── zipkin.pb.go │ └── zipkin.proto ├── reporter ├── amqp │ ├── amqp.go │ └── amqp_test.go ├── http │ ├── http.go │ └── http_test.go ├── kafka │ ├── kafka.go │ └── kafka_test.go ├── log │ └── log.go ├── recorder │ ├── recorder.go │ └── recorder_test.go ├── reporter.go └── serializer.go ├── sample.go ├── sample_test.go ├── span.go ├── span_implementation.go ├── span_options.go ├── span_test.go ├── tags.go ├── tracer.go ├── tracer_options.go └── tracer_test.go /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: gha 2 | on: 3 | push: 4 | branches: 5 | - master 6 | paths-ignore: 7 | - "**/*.md" 8 | - "LICENSE" 9 | pull_request: 10 | 11 | jobs: 12 | "CI": 13 | runs-on: ${{ matrix.os }} 14 | strategy: 15 | matrix: # Use versions consistent with zipkin-go's Go support policy. 16 | os: [macos-latest, windows-latest, ubuntu-latest] 17 | go: ["1.22"] # Current Go version 18 | include: 19 | - os: ubuntu-latest 20 | go: "1.21" 21 | - os: ubuntu-latest 22 | go: "1.20" # Floor Go version of zipkin-go (current - 2) 23 | steps: 24 | # Set fetch-depth: 0 to fetch commit history and tags for use in version calculation 25 | - name: Check out code 26 | uses: actions/checkout@v4 27 | with: 28 | fetch-depth: 0 29 | 30 | - name: Setup go 31 | uses: actions/setup-go@v5 32 | with: 33 | go-version: ${{ matrix.go }} 34 | 35 | - name: Lint files 36 | uses: golangci/golangci-lint-action@v4 37 | with: 38 | # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. 39 | version: latest 40 | 41 | - name: Run tests 42 | run: go test -coverprofile coverage.txt -v ./... 43 | env: 44 | CGO_ENABLED: 1 45 | 46 | - name: Upload coverage to Codecov 47 | uses: codecov/codecov-action@v4 48 | env: 49 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 50 | with: 51 | name: zipkin-go test reports 52 | fail_ci_if_error: false 53 | files: ./coverage.txt 54 | -------------------------------------------------------------------------------- /.github/workflows/security.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: security 3 | 4 | # We don't scan documentation-only commits. 5 | on: # yamllint disable-line rule:truthy 6 | push: # non-tagged pushes to master 7 | branches: 8 | - master 9 | tags-ignore: 10 | - '*' 11 | paths-ignore: 12 | - '**/*.md' 13 | pull_request: # pull requests targeted at the master branch. 14 | branches: 15 | - master 16 | paths-ignore: 17 | - '**/*.md' 18 | 19 | jobs: 20 | security: 21 | name: security 22 | runs-on: ubuntu-22.04 # newest available distribution, aka jellyfish 23 | steps: 24 | - name: Checkout Repository 25 | uses: actions/checkout@v4 26 | - uses: actions/cache@v4 27 | name: Cache Trivy Database 28 | with: 29 | path: .trivy 30 | key: ${{ runner.os }}-trivy 31 | restore-keys: ${{ runner.os }}-trivy 32 | - name: Run Trivy vulnerability and secret scanner 33 | uses: aquasecurity/trivy-action@master 34 | id: trivy 35 | with: 36 | scan-type: 'fs' 37 | scan-ref: '.' # scan the entire repository 38 | scanners: vuln,secret 39 | exit-code: '1' 40 | severity: HIGH,CRITICAL 41 | output: trivy-report.md 42 | cache-dir: .trivy 43 | - name: Set Summary 44 | shell: bash 45 | if: ${{ failure() && steps.trivy.conclusion == 'failure' }} 46 | # Add the Trivy report to the summary 47 | # 48 | # Note: This will cause a workflow error if trivy-report.md > the step 49 | # limit 1MiB. If this was due to too many CVEs, consider fixing them ;) 50 | run: cat trivy-report.md >> $GITHUB_STEP_SUMMARY 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | 26 | .idea 27 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | timeout: 5m 3 | 4 | issues: 5 | exclude-dirs: 6 | - zipkin_proto3 7 | 8 | linters: 9 | disable-all: true 10 | enable: 11 | - dupl 12 | - goconst 13 | - gocyclo 14 | - gofmt 15 | - revive 16 | - govet 17 | - ineffassign 18 | - lll 19 | - misspell 20 | - nakedret 21 | - revive 22 | - unparam 23 | - unused 24 | 25 | linters-settings: 26 | dupl: 27 | threshold: 400 28 | lll: 29 | line-length: 170 30 | gocyclo: 31 | min-complexity: 20 32 | revive: 33 | confidence: 0.85 34 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The OpenZipkin Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | .DEFAULT_GOAL := test 16 | 17 | .PHONY: test 18 | test: 19 | # MallocNanoZone env var avoids problems in macOS Monterey: golang/go#49138 20 | MallocNanoZone=0 go test -v -race -cover ./... 21 | 22 | .PHONY: bench 23 | bench: 24 | go test -v -run - -bench . -benchmem ./... 25 | 26 | .PHONY: protoc 27 | protoc: 28 | protoc --go_out=module=github.com/openzipkin/zipkin-go:. proto/zipkin_proto3/zipkin.proto 29 | protoc --go_out=module=github.com/openzipkin/zipkin-go:. proto/testing/*.proto 30 | protoc --go-grpc_out=module=github.com/openzipkin/zipkin-go:. proto/testing/*.proto 31 | 32 | .PHONY: lint 33 | lint: 34 | # Ignore grep's exit code since no match returns 1. 35 | echo 'linting...' ; golint ./... 36 | 37 | .PHONY: vet 38 | vet: 39 | go vet ./... 40 | 41 | .PHONY: all 42 | all: vet lint test bench 43 | 44 | .PHONY: example 45 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # OpenZipkin Security Process 2 | 3 | This document outlines the process for handling security concerns in OpenZipkin projects. 4 | 5 | Any vulnerability or misconfiguration detected in our [security workflow](.github/workflows/security.yml) 6 | should be addressed as a normal pull request. 7 | 8 | OpenZipkin is a volunteer community and does not have a dedicated security team. There may be 9 | periods where no volunteer is able to address a security concern. There is no SLA or warranty 10 | offered by volunteers. If you are a security researcher, please consider this before escalating. 11 | 12 | For security concerns that are sensitive or otherwise outside the scope of public issues, please 13 | contact zipkin-admin@googlegroups.com. 14 | -------------------------------------------------------------------------------- /bench_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package zipkin_test 16 | 17 | import ( 18 | "fmt" 19 | "net/http" 20 | "sync/atomic" 21 | "testing" 22 | "time" 23 | 24 | zipkin "github.com/openzipkin/zipkin-go" 25 | "github.com/openzipkin/zipkin-go/idgenerator" 26 | "github.com/openzipkin/zipkin-go/model" 27 | "github.com/openzipkin/zipkin-go/propagation" 28 | "github.com/openzipkin/zipkin-go/propagation/b3" 29 | "google.golang.org/grpc/metadata" 30 | ) 31 | 32 | const ( 33 | b3HTTP = "b3-http" 34 | b3GRPC = "b3-grpc" 35 | ) 36 | 37 | var tags []string 38 | 39 | func init() { 40 | var ( 41 | traceID model.TraceID 42 | gen = idgenerator.NewRandom64() 43 | ) 44 | 45 | tags = make([]string, 1000) 46 | for j := 0; j < len(tags); j++ { 47 | tags[j] = fmt.Sprintf("%d", gen.SpanID(traceID)) 48 | } 49 | 50 | } 51 | 52 | func addAnnotationsAndTags(sp zipkin.Span, numAnnotation, numTag int) { 53 | for j := 0; j < numAnnotation; j++ { 54 | sp.Annotate(time.Now(), "event") 55 | } 56 | 57 | for j := 0; j < numTag; j++ { 58 | sp.Tag(tags[j], "") 59 | } 60 | } 61 | 62 | func benchmarkWithOps(b *testing.B, numAnnotation, numTag int) { 63 | var ( 64 | r countingRecorder 65 | t, _ = zipkin.NewTracer(&r) 66 | ) 67 | 68 | b.ResetTimer() 69 | 70 | for i := 0; i < b.N; i++ { 71 | sp := t.StartSpan("test") 72 | addAnnotationsAndTags(sp, numAnnotation, numTag) 73 | sp.Finish() 74 | } 75 | 76 | b.StopTimer() 77 | 78 | if int(r) != b.N { 79 | b.Fatalf("missing traces: want %d, have %d", b.N, r) 80 | } 81 | } 82 | 83 | func BenchmarkSpan_Empty(b *testing.B) { 84 | benchmarkWithOps(b, 0, 0) 85 | } 86 | 87 | func BenchmarkSpan_100Annotations(b *testing.B) { 88 | benchmarkWithOps(b, 100, 0) 89 | } 90 | 91 | func BenchmarkSpan_1000Annotations(b *testing.B) { 92 | benchmarkWithOps(b, 1000, 0) 93 | } 94 | 95 | func BenchmarkSpan_100Tags(b *testing.B) { 96 | benchmarkWithOps(b, 0, 100) 97 | } 98 | 99 | func BenchmarkSpan_1000Tags(b *testing.B) { 100 | benchmarkWithOps(b, 0, 1000) 101 | } 102 | 103 | func benchmarkInject(b *testing.B, propagationType string) { 104 | var ( 105 | r countingRecorder 106 | injector propagation.Injector 107 | tracer, _ = zipkin.NewTracer(&r) 108 | ) 109 | 110 | switch propagationType { 111 | case b3HTTP: 112 | req, _ := http.NewRequest("GET", "/", nil) 113 | injector = b3.InjectHTTP(req) 114 | case b3GRPC: 115 | md := metadata.MD{} 116 | injector = b3.InjectGRPC(&md) 117 | default: 118 | b.Fatalf("unknown injector: %s", propagationType) 119 | } 120 | 121 | sp := tracer.StartSpan("testing") 122 | addAnnotationsAndTags(sp, 0, 0) 123 | 124 | b.ResetTimer() 125 | for i := 0; i < b.N; i++ { 126 | if err := injector(sp.Context()); err != nil { 127 | b.Fatal(err) 128 | } 129 | } 130 | } 131 | 132 | func benchmarkExtract(b *testing.B, propagationType string) { 133 | var ( 134 | r countingRecorder 135 | tracer, _ = zipkin.NewTracer(&r) 136 | ) 137 | 138 | sp := tracer.StartSpan("testing") 139 | 140 | switch propagationType { 141 | case b3HTTP: 142 | req, _ := http.NewRequest("GET", "/", nil) 143 | b3.InjectHTTP(req)(sp.Context()) 144 | 145 | b.ResetTimer() 146 | for i := 0; i < b.N; i++ { 147 | _ = b3.ExtractHTTP(copyRequest(req)) 148 | } 149 | 150 | case b3GRPC: 151 | md := metadata.MD{} 152 | b3.InjectGRPC(&md)(sp.Context()) 153 | 154 | b.ResetTimer() 155 | for i := 0; i < b.N; i++ { 156 | md2 := md.Copy() 157 | if _, err := b3.ExtractGRPC(&md2)(); err != nil { 158 | b.Fatal(err) 159 | } 160 | } 161 | default: 162 | b.Fatalf("unknown propagation type: %s", propagationType) 163 | } 164 | } 165 | 166 | func BenchmarkInject_B3_HTTP_Empty(b *testing.B) { 167 | benchmarkInject(b, b3HTTP) 168 | } 169 | 170 | func BenchmarkInject_B3_GRPC_Empty(b *testing.B) { 171 | benchmarkInject(b, b3GRPC) 172 | } 173 | 174 | func BenchmarkExtract_B3_HTTP_Empty(b *testing.B) { 175 | benchmarkExtract(b, b3HTTP) 176 | } 177 | 178 | func BenchmarkExtract_B3_GRPC_Empty(b *testing.B) { 179 | benchmarkExtract(b, b3GRPC) 180 | } 181 | 182 | type countingRecorder int32 183 | 184 | func (c *countingRecorder) Send(_ model.SpanModel) { 185 | atomic.AddInt32((*int32)(c), 1) 186 | } 187 | 188 | func (c *countingRecorder) Close() error { return nil } 189 | 190 | func copyRequest(req *http.Request) *http.Request { 191 | r, _ := http.NewRequest("GET", "/", nil) 192 | for k, v := range req.Header { 193 | r.Header[k] = v 194 | } 195 | return r 196 | } 197 | -------------------------------------------------------------------------------- /context.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package zipkin 16 | 17 | import ( 18 | "context" 19 | 20 | "github.com/openzipkin/zipkin-go/model" 21 | ) 22 | 23 | var defaultNoopSpan = &noopSpan{} 24 | 25 | // SpanFromContext retrieves a Zipkin Span from Go's context propagation 26 | // mechanism if found. If not found, returns nil. 27 | func SpanFromContext(ctx context.Context) Span { 28 | if s, ok := ctx.Value(spanKey).(Span); ok { 29 | return s 30 | } 31 | return nil 32 | } 33 | 34 | // SpanOrNoopFromContext retrieves a Zipkin Span from Go's context propagation 35 | // mechanism if found. If not found, returns a noopSpan. 36 | // This function typically is used for modules that want to provide existing 37 | // Zipkin spans with additional data, but can't guarantee that spans are 38 | // properly propagated. It is preferred to use SpanFromContext() and test for 39 | // Nil instead of using this function. 40 | func SpanOrNoopFromContext(ctx context.Context) Span { 41 | if s, ok := ctx.Value(spanKey).(Span); ok { 42 | return s 43 | } 44 | return defaultNoopSpan 45 | } 46 | 47 | // NewContext stores a Zipkin Span into Go's context propagation mechanism. 48 | func NewContext(ctx context.Context, s Span) context.Context { 49 | return context.WithValue(ctx, spanKey, s) 50 | } 51 | 52 | // BaggageFromContext takes a context and returns access to BaggageFields if 53 | // available. Returns nil if there are no BaggageFields found in context. 54 | func BaggageFromContext(ctx context.Context) model.BaggageFields { 55 | if span := SpanFromContext(ctx); span != nil { 56 | return span.Context().Baggage 57 | } 58 | return nil 59 | } 60 | 61 | type ctxKey struct{} 62 | 63 | var spanKey = ctxKey{} 64 | -------------------------------------------------------------------------------- /context_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package zipkin 16 | 17 | import ( 18 | "context" 19 | "testing" 20 | ) 21 | 22 | func TestSpanOrNoopFromContext(t *testing.T) { 23 | var ( 24 | ctx = context.Background() 25 | tr, _ = NewTracer(nil, WithLocalEndpoint(nil)) 26 | span = tr.StartSpan("test") 27 | ) 28 | 29 | if want, have := defaultNoopSpan, SpanOrNoopFromContext(ctx); want != have { 30 | t.Errorf("Invalid response want %+v, have %+v", want, have) 31 | } 32 | 33 | ctx = NewContext(ctx, span) 34 | 35 | if want, have := span, SpanOrNoopFromContext(ctx); want != have { 36 | t.Errorf("Invalid response want %+v, have %+v", want, have) 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | Package zipkin implements a native Zipkin instrumentation library for Go. 17 | 18 | See https://zipkin.io for more information about Zipkin. 19 | */ 20 | package zipkin 21 | -------------------------------------------------------------------------------- /endpoint.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package zipkin 16 | 17 | import ( 18 | "fmt" 19 | "net" 20 | "strconv" 21 | "strings" 22 | 23 | "github.com/openzipkin/zipkin-go/model" 24 | ) 25 | 26 | // NewEndpoint creates a new endpoint given the provided serviceName and 27 | // hostPort. 28 | func NewEndpoint(serviceName string, hostPort string) (*model.Endpoint, error) { 29 | e := &model.Endpoint{ 30 | ServiceName: serviceName, 31 | } 32 | 33 | if hostPort == "" || hostPort == ":0" { 34 | if serviceName == "" { 35 | // if all properties are empty we should not have an Endpoint object. 36 | return nil, nil 37 | } 38 | return e, nil 39 | } 40 | 41 | if strings.IndexByte(hostPort, ':') < 0 { 42 | hostPort += ":0" 43 | } 44 | 45 | host, port, err := net.SplitHostPort(hostPort) 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | p, err := strconv.ParseUint(port, 10, 16) 51 | if err != nil { 52 | return nil, err 53 | } 54 | e.Port = uint16(p) 55 | 56 | addrs, err := net.LookupIP(host) 57 | if err != nil { 58 | return nil, fmt.Errorf("host lookup failure: %w", err) 59 | } 60 | 61 | for i := range addrs { 62 | addr := addrs[i].To4() 63 | if addr == nil { 64 | // IPv6 - 16 bytes 65 | if e.IPv6 == nil { 66 | e.IPv6 = addrs[i].To16() 67 | } 68 | } else { 69 | // IPv4 - 4 bytes 70 | if e.IPv4 == nil { 71 | e.IPv4 = addr 72 | } 73 | } 74 | if e.IPv4 != nil && e.IPv6 != nil { 75 | // Both IPv4 & IPv6 have been set, done... 76 | break 77 | } 78 | } 79 | 80 | return e, nil 81 | } 82 | -------------------------------------------------------------------------------- /endpoint_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package zipkin_test 16 | 17 | import ( 18 | "fmt" 19 | "net" 20 | "reflect" 21 | "strings" 22 | "testing" 23 | 24 | "github.com/openzipkin/zipkin-go" 25 | "github.com/openzipkin/zipkin-go/model" 26 | ) 27 | 28 | const ( 29 | serviceName = "my_service" 30 | onlyHost = "localhost" 31 | defaultPort = 0 32 | port = 8081 33 | invalidNegativePort = "localhost:-8081" 34 | invalidOutOfRangePort = "localhost:65536" 35 | invalidHostPort = "::1:8081" 36 | unreachableHostPort = "nosuchhost:8081" 37 | ) 38 | 39 | var ( 40 | ipv4HostPort = "127.0.0.1:" + fmt.Sprintf("%d", port) 41 | ipv6HostPort = "[2001:db8::68]:" + fmt.Sprintf("%d", port) 42 | ipv4ForHostPort = net.IPv4(127, 0, 0, 1) 43 | ipv6ForHostPort = net.ParseIP("2001:db8::68") 44 | ) 45 | 46 | func TestEmptyEndpoint(t *testing.T) { 47 | ep, err := zipkin.NewEndpoint("", "") 48 | if err != nil { 49 | t.Errorf("unexpected error: %s", err.Error()) 50 | } 51 | if ep != nil { 52 | t.Errorf("endpoint want nil, have: %+v", ep) 53 | } 54 | } 55 | 56 | func TestServiceNameOnlyEndpoint(t *testing.T) { 57 | have, err := zipkin.NewEndpoint(serviceName, "") 58 | if err != nil { 59 | t.Errorf("unexpected error: %s", err.Error()) 60 | } 61 | want := &model.Endpoint{ServiceName: serviceName} 62 | if !reflect.DeepEqual(want, have) { 63 | t.Errorf("endpoint want %+v, have: %+v", want, have) 64 | } 65 | } 66 | 67 | func TestInvalidHostPort(t *testing.T) { 68 | _, err := zipkin.NewEndpoint(serviceName, invalidHostPort) 69 | 70 | if err == nil { 71 | t.Fatal("expected error") 72 | } 73 | 74 | if !strings.Contains(err.Error(), "too many colons in address") { 75 | t.Fatalf("expected too many colons in address error, got: %s", err.Error()) 76 | } 77 | } 78 | 79 | func TestNewEndpointFailsDueToOutOfRangePort(t *testing.T) { 80 | _, err := zipkin.NewEndpoint(serviceName, invalidOutOfRangePort) 81 | 82 | if err == nil { 83 | t.Fatal("expected error") 84 | } 85 | 86 | if !strings.Contains(err.Error(), "value out of range") { 87 | t.Fatalf("expected out of range error, got: %s", err.Error()) 88 | } 89 | } 90 | 91 | func TestNewEndpointFailsDueToNegativePort(t *testing.T) { 92 | _, err := zipkin.NewEndpoint(serviceName, invalidNegativePort) 93 | 94 | if err == nil { 95 | t.Fatal("expected error") 96 | } 97 | 98 | if !strings.Contains(err.Error(), "invalid syntax") { 99 | t.Fatalf("expected invalid syntax error, got: %s", err.Error()) 100 | } 101 | } 102 | 103 | func TestNewEndpointFailsDueToLookupIP(t *testing.T) { 104 | _, err := zipkin.NewEndpoint(serviceName, unreachableHostPort) 105 | 106 | if err == nil { 107 | t.Fatal("expected error") 108 | } 109 | 110 | if !strings.Contains(err.Error(), "host lookup failure") { 111 | t.Fatalf("expected no such host error, got: %s", err.Error()) 112 | } 113 | } 114 | 115 | func TestNewEndpointDefaultsPortToZeroWhenMissing(t *testing.T) { 116 | endpoint, err := zipkin.NewEndpoint(serviceName, onlyHost) 117 | 118 | if err != nil { 119 | t.Fatalf("unexpected error: %s", err.Error()) 120 | } 121 | 122 | if endpoint.Port != defaultPort { 123 | t.Fatalf("expected port %d, got %d", defaultPort, endpoint.Port) 124 | } 125 | } 126 | 127 | func TestNewEndpointIpv4Success(t *testing.T) { 128 | endpoint, err := zipkin.NewEndpoint(serviceName, ipv4HostPort) 129 | 130 | if err != nil { 131 | t.Fatalf("unexpected error: %s", err.Error()) 132 | } 133 | 134 | if serviceName != endpoint.ServiceName { 135 | t.Fatalf("expected service name %s, got %s", serviceName, endpoint.ServiceName) 136 | } 137 | 138 | if !ipv4ForHostPort.Equal(endpoint.IPv4) { 139 | t.Fatalf("expected IPv4 %s, got %s", ipv4ForHostPort.String(), endpoint.IPv4.String()) 140 | } 141 | 142 | if port != endpoint.Port { 143 | t.Fatalf("expected port %d, got %d", port, endpoint.Port) 144 | } 145 | 146 | if endpoint.IPv6 != nil { 147 | t.Fatalf("expected empty IPv6 got %+v", endpoint.IPv6) 148 | } 149 | } 150 | 151 | func TestNewEndpointIpv6Success(t *testing.T) { 152 | endpoint, err := zipkin.NewEndpoint(serviceName, ipv6HostPort) 153 | 154 | if err != nil { 155 | t.Fatalf("unexpected error: %s", err.Error()) 156 | } 157 | 158 | if serviceName != endpoint.ServiceName { 159 | t.Fatalf("expected service name %s, got %s", serviceName, endpoint.ServiceName) 160 | } 161 | 162 | if !ipv6ForHostPort.Equal(endpoint.IPv6) { 163 | t.Fatalf("expected IPv6 %s, got %s", ipv6ForHostPort.String(), endpoint.IPv6.String()) 164 | } 165 | 166 | if port != endpoint.Port { 167 | t.Fatalf("expected port %d, got %d", port, endpoint.Port) 168 | } 169 | 170 | if endpoint.IPv4 != nil { 171 | t.Fatalf("expected empty IPv4 got %+v", endpoint.IPv4) 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package zipkin_test 16 | 17 | import ( 18 | "context" 19 | "log" 20 | "time" 21 | 22 | zipkin "github.com/openzipkin/zipkin-go" 23 | "github.com/openzipkin/zipkin-go/model" 24 | "github.com/openzipkin/zipkin-go/reporter" 25 | httpreporter "github.com/openzipkin/zipkin-go/reporter/http" 26 | ) 27 | 28 | func doSomeWork(context.Context) {} 29 | 30 | func ExampleNewTracer() { 31 | // create a reporter to be used by the tracer 32 | reporter := httpreporter.NewReporter("http://localhost:9411/api/v2/spans") 33 | defer reporter.Close() 34 | 35 | // set-up the local endpoint for our service 36 | endpoint, err := zipkin.NewEndpoint("demoService", "172.20.23.100:80") 37 | if err != nil { 38 | log.Fatalf("unable to create local endpoint: %+v\n", err) 39 | } 40 | 41 | // set-up our sampling strategy 42 | sampler, err := zipkin.NewBoundarySampler(0.01, time.Now().UnixNano()) 43 | if err != nil { 44 | log.Fatalf("unable to create sampler: %+v\n", err) 45 | } 46 | 47 | // initialize the tracer 48 | tracer, err := zipkin.NewTracer( 49 | reporter, 50 | zipkin.WithLocalEndpoint(endpoint), 51 | zipkin.WithSampler(sampler), 52 | ) 53 | if err != nil { 54 | log.Fatalf("unable to create tracer: %+v\n", err) 55 | } 56 | 57 | // tracer can now be used to create spans. 58 | span := tracer.StartSpan("some_operation") 59 | // ... do some work ... 60 | span.Finish() 61 | 62 | // Output: 63 | } 64 | 65 | func ExampleTracerOption() { 66 | // initialize the tracer and use the WithNoopSpan TracerOption 67 | tracer, _ := zipkin.NewTracer( 68 | reporter.NewNoopReporter(), 69 | zipkin.WithNoopSpan(true), 70 | ) 71 | 72 | // tracer can now be used to create spans 73 | span := tracer.StartSpan("some_operation") 74 | // ... do some work ... 75 | span.Finish() 76 | 77 | // Output: 78 | } 79 | 80 | func ExampleNewContext() { 81 | var ( 82 | tracer, _ = zipkin.NewTracer(reporter.NewNoopReporter()) 83 | ctx = context.Background() 84 | ) 85 | 86 | // span for this function 87 | span := tracer.StartSpan("ExampleNewContext") 88 | defer span.Finish() 89 | 90 | // add span to Context 91 | ctx = zipkin.NewContext(ctx, span) 92 | 93 | // pass along Context which holds the span to another function 94 | doSomeWork(ctx) 95 | 96 | // Output: 97 | } 98 | 99 | func ExampleSpanOption() { 100 | tracer, _ := zipkin.NewTracer(reporter.NewNoopReporter()) 101 | 102 | // set-up the remote endpoint for the service we're about to call 103 | endpoint, err := zipkin.NewEndpoint("otherService", "172.20.23.101:80") 104 | if err != nil { 105 | log.Fatalf("unable to create remote endpoint: %+v\n", err) 106 | } 107 | 108 | // start a client side RPC span and use RemoteEndpoint SpanOption 109 | span := tracer.StartSpan( 110 | "some-operation", 111 | zipkin.RemoteEndpoint(endpoint), 112 | zipkin.Kind(model.Client), 113 | ) 114 | // ... call other service ... 115 | span.Finish() 116 | 117 | // Output: 118 | } 119 | -------------------------------------------------------------------------------- /examples/httpserver_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package zipkin_test 16 | 17 | import ( 18 | "log" 19 | "net/http" 20 | "net/http/httptest" 21 | "os" 22 | "time" 23 | 24 | "github.com/openzipkin/zipkin-go" 25 | zipkinhttp "github.com/openzipkin/zipkin-go/middleware/http" 26 | logreporter "github.com/openzipkin/zipkin-go/reporter/log" 27 | ) 28 | 29 | func Example() { 30 | // set up a span reporter 31 | reporter := logreporter.NewReporter(log.New(os.Stderr, "", log.LstdFlags)) 32 | defer func() { 33 | _ = reporter.Close() 34 | }() 35 | 36 | // create our local service endpoint 37 | endpoint, err := zipkin.NewEndpoint("myService", "localhost:0") 38 | if err != nil { 39 | log.Fatalf("unable to create local endpoint: %+v\n", err) 40 | } 41 | 42 | // initialize our tracer 43 | tracer, err := zipkin.NewTracer(reporter, zipkin.WithLocalEndpoint(endpoint)) 44 | if err != nil { 45 | log.Fatalf("unable to create tracer: %+v\n", err) 46 | } 47 | 48 | // create global zipkin http server middleware 49 | serverMiddleware := zipkinhttp.NewServerMiddleware( 50 | tracer, zipkinhttp.TagResponseSize(true), 51 | ) 52 | 53 | // create global zipkin traced http client 54 | client, err := zipkinhttp.NewClient(tracer, zipkinhttp.ClientTrace(true)) 55 | if err != nil { 56 | log.Fatalf("unable to create client: %+v\n", err) 57 | } 58 | 59 | // initialize router 60 | router := http.NewServeMux() 61 | 62 | // start web service with zipkin http server middleware 63 | ts := httptest.NewServer(serverMiddleware(router)) 64 | defer ts.Close() 65 | 66 | // set-up handlers 67 | router.HandleFunc("/some_function", someFunc(client, ts.URL)) 68 | router.HandleFunc("/other_function", otherFunc(client)) 69 | 70 | // initiate a call to some_func 71 | var req *http.Request 72 | req, err = http.NewRequest("GET", ts.URL+"/some_function", nil) 73 | if err != nil { 74 | log.Fatalf("unable to create http request: %+v\n", err) 75 | } 76 | 77 | var res *http.Response 78 | res, err = client.DoWithAppSpan(req, "some_function") 79 | if err != nil { 80 | log.Fatalf("unable to do http request: %+v\n", err) 81 | } 82 | _ = res.Body.Close() 83 | 84 | // Output: 85 | } 86 | 87 | func someFunc(client *zipkinhttp.Client, url string) http.HandlerFunc { 88 | return func(w http.ResponseWriter, r *http.Request) { 89 | log.Printf("some_function called with method: %s\n", r.Method) 90 | 91 | // retrieve span from context (created by server middleware) 92 | span := zipkin.SpanFromContext(r.Context()) 93 | span.Tag("custom_key", "some value") 94 | 95 | // doing some expensive calculations.... 96 | time.Sleep(25 * time.Millisecond) 97 | span.Annotate(time.Now(), "expensive_calc_done") 98 | 99 | newRequest, err := http.NewRequest("POST", url+"/other_function", nil) 100 | if err != nil { 101 | log.Printf("unable to create client: %+v\n", err) 102 | http.Error(w, err.Error(), 500) 103 | return 104 | } 105 | 106 | ctx := zipkin.NewContext(newRequest.Context(), span) 107 | 108 | newRequest = newRequest.WithContext(ctx) 109 | 110 | var res *http.Response 111 | res, err = client.DoWithAppSpan(newRequest, "other_function") 112 | if err != nil { 113 | log.Printf("call to other_function returned error: %+v\n", err) 114 | http.Error(w, err.Error(), 500) 115 | return 116 | } 117 | _ = res.Body.Close() 118 | } 119 | } 120 | 121 | func otherFunc(_ *zipkinhttp.Client) http.HandlerFunc { 122 | return func(w http.ResponseWriter, r *http.Request) { 123 | log.Printf("other_function called with method: %s\n", r.Method) 124 | time.Sleep(50 * time.Millisecond) 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/openzipkin/zipkin-go 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/IBM/sarama v1.43.1 7 | github.com/onsi/ginkgo/v2 v2.11.0 8 | github.com/onsi/gomega v1.27.10 9 | github.com/rabbitmq/amqp091-go v1.9.0 10 | google.golang.org/grpc v1.63.2 11 | google.golang.org/protobuf v1.33.0 12 | ) 13 | 14 | require ( 15 | github.com/davecgh/go-spew v1.1.1 // indirect 16 | github.com/eapache/go-resiliency v1.6.0 // indirect 17 | github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 // indirect 18 | github.com/eapache/queue v1.1.0 // indirect 19 | github.com/go-logr/logr v1.2.4 // indirect 20 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect 21 | github.com/golang/snappy v0.0.4 // indirect 22 | github.com/google/go-cmp v0.6.0 // indirect 23 | github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect 24 | github.com/hashicorp/errwrap v1.1.0 // indirect 25 | github.com/hashicorp/go-multierror v1.1.1 // indirect 26 | github.com/hashicorp/go-uuid v1.0.3 // indirect 27 | github.com/jcmturner/aescts/v2 v2.0.0 // indirect 28 | github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect 29 | github.com/jcmturner/gofork v1.7.6 // indirect 30 | github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect 31 | github.com/jcmturner/rpc/v2 v2.0.3 // indirect 32 | github.com/klauspost/compress v1.17.8 // indirect 33 | github.com/kr/text v0.2.0 // indirect 34 | github.com/pierrec/lz4/v4 v4.1.21 // indirect 35 | github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect 36 | github.com/rogpeppe/go-internal v1.9.0 // indirect 37 | golang.org/x/crypto v0.22.0 // indirect 38 | golang.org/x/net v0.24.0 // indirect 39 | golang.org/x/sys v0.19.0 // indirect 40 | golang.org/x/text v0.14.0 // indirect 41 | golang.org/x/tools v0.9.3 // indirect 42 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be // indirect 43 | gopkg.in/yaml.v3 v3.0.1 // indirect 44 | ) 45 | -------------------------------------------------------------------------------- /idgenerator/idgenerator.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | Package idgenerator contains several Span and Trace ID generators which can be 17 | used by the Zipkin tracer. Additional third party generators can be plugged in 18 | if they adhere to the IDGenerator interface. 19 | */ 20 | package idgenerator 21 | 22 | import ( 23 | "math/rand" 24 | "sync" 25 | "time" 26 | 27 | "github.com/openzipkin/zipkin-go/model" 28 | ) 29 | 30 | var ( 31 | seededIDGen = rand.New(rand.NewSource(time.Now().UnixNano())) 32 | // NewSource returns a new pseudo-random Source seeded with the given value. 33 | // Unlike the default Source used by top-level functions, this source is not 34 | // safe for concurrent use by multiple goroutines. Hence the need for a mutex. 35 | seededIDLock sync.Mutex 36 | ) 37 | 38 | // IDGenerator interface can be used to provide the Zipkin Tracer with custom 39 | // implementations to generate Span and Trace IDs. 40 | type IDGenerator interface { 41 | SpanID(traceID model.TraceID) model.ID // Generates a new Span ID 42 | TraceID() model.TraceID // Generates a new Trace ID 43 | } 44 | 45 | // NewRandom64 returns an ID Generator which can generate 64 bit trace and span 46 | // id's 47 | func NewRandom64() IDGenerator { 48 | return &randomID64{} 49 | } 50 | 51 | // NewRandom128 returns an ID Generator which can generate 128 bit trace and 64 52 | // bit span id's 53 | func NewRandom128() IDGenerator { 54 | return &randomID128{} 55 | } 56 | 57 | // NewRandomTimestamped generates 128 bit time sortable traceid's and 64 bit 58 | // spanid's. 59 | func NewRandomTimestamped() IDGenerator { 60 | return &randomTimestamped{} 61 | } 62 | 63 | // randomID64 can generate 64 bit traceid's and 64 bit spanid's. 64 | type randomID64 struct{} 65 | 66 | func (r *randomID64) TraceID() (id model.TraceID) { 67 | seededIDLock.Lock() 68 | id = model.TraceID{ 69 | Low: uint64(seededIDGen.Int63()), 70 | } 71 | seededIDLock.Unlock() 72 | return 73 | } 74 | 75 | func (r *randomID64) SpanID(traceID model.TraceID) (id model.ID) { 76 | if !traceID.Empty() { 77 | return model.ID(traceID.Low) 78 | } 79 | seededIDLock.Lock() 80 | id = model.ID(seededIDGen.Int63()) 81 | seededIDLock.Unlock() 82 | return 83 | } 84 | 85 | // randomID128 can generate 128 bit traceid's and 64 bit spanid's. 86 | type randomID128 struct{} 87 | 88 | func (r *randomID128) TraceID() (id model.TraceID) { 89 | seededIDLock.Lock() 90 | id = model.TraceID{ 91 | High: uint64(seededIDGen.Int63()), 92 | Low: uint64(seededIDGen.Int63()), 93 | } 94 | seededIDLock.Unlock() 95 | return 96 | } 97 | 98 | func (r *randomID128) SpanID(traceID model.TraceID) (id model.ID) { 99 | if !traceID.Empty() { 100 | return model.ID(traceID.Low) 101 | } 102 | seededIDLock.Lock() 103 | id = model.ID(seededIDGen.Int63()) 104 | seededIDLock.Unlock() 105 | return 106 | } 107 | 108 | // randomTimestamped can generate 128 bit time sortable traceid's compatible 109 | // with AWS X-Ray and 64 bit spanid's. 110 | type randomTimestamped struct{} 111 | 112 | func (t *randomTimestamped) TraceID() (id model.TraceID) { 113 | seededIDLock.Lock() 114 | id = model.TraceID{ 115 | High: uint64(time.Now().Unix()<<32) + uint64(seededIDGen.Int31()), 116 | Low: uint64(seededIDGen.Int63()), 117 | } 118 | seededIDLock.Unlock() 119 | return 120 | } 121 | 122 | func (t *randomTimestamped) SpanID(traceID model.TraceID) (id model.ID) { 123 | if !traceID.Empty() { 124 | return model.ID(traceID.Low) 125 | } 126 | seededIDLock.Lock() 127 | id = model.ID(seededIDGen.Int63()) 128 | seededIDLock.Unlock() 129 | return 130 | } 131 | -------------------------------------------------------------------------------- /idgenerator/idgenerator_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package idgenerator_test 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/openzipkin/zipkin-go/idgenerator" 21 | "github.com/openzipkin/zipkin-go/model" 22 | ) 23 | 24 | func TestRandom64(t *testing.T) { 25 | var ( 26 | spanID model.ID 27 | gen = idgenerator.NewRandom64() 28 | traceID = gen.TraceID() 29 | ) 30 | 31 | if traceID.Empty() { 32 | t.Errorf("Expected valid TraceID, got: %+v", traceID) 33 | } 34 | 35 | if want, have := uint64(0), traceID.High; want != have { 36 | t.Errorf("Expected TraceID.High to be 0, got %d", have) 37 | } 38 | 39 | spanID = gen.SpanID(traceID) 40 | 41 | if want, have := model.ID(traceID.Low), spanID; want != have { 42 | t.Errorf("Expected root span to have span ID %d, got %d", want, have) 43 | } 44 | 45 | spanID = gen.SpanID(model.TraceID{}) 46 | 47 | if spanID == 0 { 48 | t.Errorf("Expected child span to have a valid span ID, got 0") 49 | } 50 | } 51 | 52 | func TestRandom128(t *testing.T) { 53 | var ( 54 | spanID model.ID 55 | gen = idgenerator.NewRandom128() 56 | traceID = gen.TraceID() 57 | ) 58 | 59 | if traceID.Empty() { 60 | t.Errorf("Expected valid TraceID, got: %+v", traceID) 61 | } 62 | 63 | if traceID.Low == 0 { 64 | t.Error("Expected TraceID.Low to have value, got 0") 65 | } 66 | 67 | if traceID.High == 0 { 68 | t.Error("Expected TraceID.High to have value, got 0") 69 | } 70 | 71 | spanID = gen.SpanID(traceID) 72 | 73 | if want, have := model.ID(traceID.Low), spanID; want != have { 74 | t.Errorf("Expected root span to have span ID %d, got %d", want, have) 75 | } 76 | 77 | spanID = gen.SpanID(model.TraceID{}) 78 | 79 | if spanID == 0 { 80 | t.Errorf("Expected child span to have a valid span ID, got 0") 81 | } 82 | } 83 | 84 | func TestRandomTimeStamped(t *testing.T) { 85 | var ( 86 | spanID model.ID 87 | gen = idgenerator.NewRandomTimestamped() 88 | traceID = gen.TraceID() 89 | ) 90 | 91 | if traceID.Empty() { 92 | t.Errorf("Expected valid TraceID, got: %+v", traceID) 93 | } 94 | 95 | if traceID.Low == 0 { 96 | t.Error("Expected TraceID.Low to have value, got 0") 97 | } 98 | 99 | if traceID.High == 0 { 100 | t.Error("Expected TraceID.High to have value, got 0") 101 | } 102 | 103 | spanID = gen.SpanID(traceID) 104 | 105 | if want, have := model.ID(traceID.Low), spanID; want != have { 106 | t.Errorf("Expected root span to have span ID %d, got %d", want, have) 107 | } 108 | 109 | spanID = gen.SpanID(model.TraceID{}) 110 | 111 | if spanID == 0 { 112 | t.Errorf("Expected child span to have a valid span ID, got 0") 113 | } 114 | 115 | // test chronological order 116 | var ids []model.TraceID 117 | 118 | for i := 0; i < 1000; i++ { 119 | ids = append(ids, gen.TraceID()) 120 | } 121 | 122 | var latestTS uint64 123 | for idx, traceID := range ids { 124 | if newVal, oldVal := traceID.High>>32, latestTS; newVal < oldVal { 125 | t.Errorf("[%d] expected a higher timestamp part in traceid but got: old: %d new: %d", idx, oldVal, newVal) 126 | } 127 | latestTS = traceID.High >> 32 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /middleware/baggage.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package middleware 16 | 17 | import "github.com/openzipkin/zipkin-go/model" 18 | 19 | // BaggageHandler holds the interface for server and client middlewares 20 | // interacting with baggage context propagation implementations. 21 | // A reference implementation can be found in package: 22 | // github.com/openzipkin/zipkin-go/propagation/baggage 23 | type BaggageHandler interface { 24 | // New returns a fresh BaggageFields implementation primed for usage in a 25 | // request lifecycle. 26 | // This method needs to be called by incoming transport middlewares. See 27 | // middlewares/grpc/server.go and middlewares/http/server.go 28 | New() model.BaggageFields 29 | } 30 | -------------------------------------------------------------------------------- /middleware/grpc/baggage_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package grpc_test 16 | 17 | import ( 18 | "context" 19 | "net" 20 | "testing" 21 | 22 | "github.com/openzipkin/zipkin-go/middleware" 23 | "google.golang.org/grpc" 24 | "google.golang.org/grpc/metadata" 25 | "google.golang.org/protobuf/types/known/emptypb" 26 | 27 | "github.com/openzipkin/zipkin-go" 28 | zgrpc "github.com/openzipkin/zipkin-go/middleware/grpc" 29 | "github.com/openzipkin/zipkin-go/propagation/baggage" 30 | service "github.com/openzipkin/zipkin-go/proto/testing" 31 | ) 32 | 33 | const ( 34 | reqID = "x-request-id" 35 | reqIDValue = "5a3553a7-4088-4ae0-8845-8314ebd59ddb" 36 | customField = "custom-field" 37 | customFieldValue = "custom-value" 38 | ) 39 | 40 | var tracer *zipkin.Tracer 41 | 42 | // TestGRPCBaggage tests baggage propagation through actual gRPC client - 43 | // server connections. It creates a client which will inject an x-request-id 44 | // header which will trigger the receiving server to retrieve the incoming value 45 | // on the handler1 endpoint, propagate and use it in an outgoing call to the 46 | // handler2 endpoint, which should also retrieve the incoming value. 47 | // By doing this we test: 48 | // - outgoing baggage on client side (stand-alone client) 49 | // - incoming baggage on server side (handler1 endpoint) 50 | // - in process baggage propagation on server side (handler1 implementation) 51 | // - add additional header in handler1 implementation 52 | // - incoming baggage on server side (handler2 endpoint) 53 | func TestGRPCBaggage(t *testing.T) { 54 | tracer, _ = zipkin.NewTracer(nil) 55 | 56 | var bagHandler = baggage.New(reqID, customField) 57 | 58 | // create listener for server to use 59 | ln, err := net.Listen("tcp", ":0") 60 | if err != nil { 61 | t.Fatalf("unable to create listener for grpc server: %+v", err) 62 | } 63 | defer func() { 64 | _ = ln.Close() 65 | }() 66 | 67 | // create gRPC client 68 | client := newClient(t, ln.Addr().String()) 69 | 70 | // start gRPC server using the provided listener, gRPC client and baggage 71 | // handler 72 | bSrv := runServer(ln, client, bagHandler) 73 | 74 | // set x-request-id using a UUID as value 75 | md := metadata.New(nil) 76 | md.Set(reqID, reqIDValue) 77 | 78 | ctx := metadata.NewOutgoingContext(context.Background(), md) 79 | 80 | // call gRPC server Handler1 method 81 | if _, err = client.Handler1(ctx, &emptypb.Empty{}); err != nil { 82 | t.Fatalf("unexpected grpc request error: %+v", err) 83 | } 84 | 85 | // check server inspection variables for correct baggage field propagation 86 | if bSrv.resultHandler1 != reqIDValue { 87 | t.Errorf("resultHandler1 expected propagated %s: want %s, have: %s", 88 | reqID, reqIDValue, bSrv.resultHandler1) 89 | } 90 | if bSrv.result1Handler2 != reqIDValue { 91 | t.Errorf("result1Handler2 expected propagated %s: want %s, have: %s", 92 | reqID, reqIDValue, bSrv.result1Handler2) 93 | } 94 | if bSrv.result2Handler2 != customFieldValue { 95 | t.Errorf("result2Handler2 expected propagated %s: want %s, have: %s", 96 | customField, customFieldValue, bSrv.result2Handler2) 97 | } 98 | } 99 | 100 | func runServer( 101 | ln net.Listener, // listener to use 102 | client service.BaggageServiceClient, // the server can call itself 103 | bagHandler middleware.BaggageHandler, // baggage handler to use 104 | ) *baggageServer { 105 | var ( 106 | zHnd = zgrpc.NewServerHandler(tracer, zgrpc.EnableBaggage(bagHandler)) 107 | gSrv = grpc.NewServer(grpc.StatsHandler(zHnd)) 108 | bSrv = &baggageServer{client: client} 109 | ) 110 | service.RegisterBaggageServiceServer(gSrv, bSrv) 111 | go func() { 112 | _ = gSrv.Serve(ln) 113 | }() 114 | return bSrv 115 | } 116 | 117 | func newClient(t *testing.T, serverAddr string) service.BaggageServiceClient { 118 | zHnd := zgrpc.NewClientHandler(tracer) 119 | cc, err := grpc.Dial( 120 | serverAddr, 121 | grpc.WithInsecure(), 122 | grpc.WithStatsHandler(zHnd), 123 | ) 124 | if err != nil { 125 | t.Fatalf("unable to create connection for grpc client: %+v", err) 126 | } 127 | return service.NewBaggageServiceClient(cc) 128 | } 129 | 130 | type baggageServer struct { 131 | service.UnimplementedBaggageServiceServer 132 | client service.BaggageServiceClient 133 | resultHandler1 string 134 | result1Handler2 string 135 | result2Handler2 string 136 | } 137 | 138 | func (b *baggageServer) Handler1(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) { 139 | // retrieve received value from incoming context 140 | if md, ok := metadata.FromIncomingContext(ctx); ok { 141 | if values := md.Get(reqID); len(values) > 0 { 142 | b.resultHandler1 = values[0] 143 | } 144 | } 145 | // add additional baggage field 146 | if span := zipkin.SpanFromContext(ctx); span != nil { 147 | span.Context().Baggage.Add(customField, customFieldValue) 148 | } 149 | // outgoing call from client uses baggage found in context 150 | if _, err := b.client.Handler2(ctx, &emptypb.Empty{}); err != nil { 151 | return nil, err 152 | } 153 | return &emptypb.Empty{}, nil 154 | } 155 | 156 | func (b *baggageServer) Handler2(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) { 157 | // retrieve received value from incoming context 158 | if md, ok := metadata.FromIncomingContext(ctx); ok { 159 | if values := md.Get(reqID); len(values) > 0 { 160 | b.result1Handler2 = values[0] 161 | } 162 | if values := md.Get(customField); len(values) > 0 { 163 | b.result2Handler2 = values[0] 164 | } 165 | } 166 | return &emptypb.Empty{}, nil 167 | } 168 | 169 | var _ service.BaggageServiceServer = (*baggageServer)(nil) 170 | -------------------------------------------------------------------------------- /middleware/grpc/client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package grpc 16 | 17 | import ( 18 | "context" 19 | 20 | "google.golang.org/grpc/metadata" 21 | "google.golang.org/grpc/stats" 22 | 23 | "github.com/openzipkin/zipkin-go" 24 | "github.com/openzipkin/zipkin-go/model" 25 | "github.com/openzipkin/zipkin-go/propagation/b3" 26 | ) 27 | 28 | type clientHandler struct { 29 | tracer *zipkin.Tracer 30 | remoteServiceName string 31 | } 32 | 33 | // A ClientOption can be passed to NewClientHandler to customize the returned handler. 34 | type ClientOption func(*clientHandler) 35 | 36 | // WithRemoteServiceName will set the value for the remote endpoint's service name on 37 | // all spans. 38 | func WithRemoteServiceName(name string) ClientOption { 39 | return func(c *clientHandler) { 40 | c.remoteServiceName = name 41 | } 42 | } 43 | 44 | // NewClientHandler returns a stats.Handler which can be used with grpc.WithStatsHandler to add 45 | // tracing to a gRPC client. The gRPC method name is used as the span name and by default the only 46 | // tags are the gRPC status code if the call fails. 47 | func NewClientHandler(tracer *zipkin.Tracer, options ...ClientOption) stats.Handler { 48 | c := &clientHandler{ 49 | tracer: tracer, 50 | } 51 | for _, option := range options { 52 | option(c) 53 | } 54 | return c 55 | } 56 | 57 | // HandleConn exists to satisfy gRPC stats.Handler. 58 | func (c *clientHandler) HandleConn(_ context.Context, _ stats.ConnStats) { 59 | // no-op 60 | } 61 | 62 | // TagConn exists to satisfy gRPC stats.Handler. 63 | func (c *clientHandler) TagConn(ctx context.Context, _ *stats.ConnTagInfo) context.Context { 64 | // no-op 65 | return ctx 66 | } 67 | 68 | // HandleRPC implements per-RPC tracing and stats instrumentation. 69 | func (c *clientHandler) HandleRPC(ctx context.Context, rs stats.RPCStats) { 70 | handleRPC(ctx, rs) 71 | } 72 | 73 | // TagRPC implements per-RPC context management. 74 | func (c *clientHandler) TagRPC(ctx context.Context, rti *stats.RPCTagInfo) context.Context { 75 | var span zipkin.Span 76 | 77 | ep := remoteEndpointFromContext(ctx, c.remoteServiceName) 78 | 79 | name := spanName(rti) 80 | span, ctx = c.tracer.StartSpanFromContext(ctx, name, zipkin.Kind(model.Client), zipkin.RemoteEndpoint(ep)) 81 | 82 | md, ok := metadata.FromOutgoingContext(ctx) 83 | if ok { 84 | md = md.Copy() 85 | } else { 86 | md = metadata.New(nil) 87 | } 88 | _ = b3.InjectGRPC(&md)(span.Context()) 89 | 90 | // inject baggage fields from span context into the outgoing gRPC request metadata 91 | if span.Context().Baggage != nil { 92 | span.Context().Baggage.Iterate(func(key string, values []string) { 93 | md.Set(key, values...) 94 | }) 95 | } 96 | 97 | ctx = metadata.NewOutgoingContext(ctx, md) 98 | return ctx 99 | } 100 | -------------------------------------------------------------------------------- /middleware/grpc/client_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package grpc_test 16 | 17 | import ( 18 | "context" 19 | 20 | "github.com/onsi/ginkgo/v2" 21 | "github.com/onsi/gomega" 22 | "google.golang.org/grpc" 23 | "google.golang.org/grpc/metadata" 24 | 25 | "github.com/openzipkin/zipkin-go" 26 | zipkingrpc "github.com/openzipkin/zipkin-go/middleware/grpc" 27 | "github.com/openzipkin/zipkin-go/model" 28 | "github.com/openzipkin/zipkin-go/propagation/b3" 29 | service "github.com/openzipkin/zipkin-go/proto/testing" 30 | "github.com/openzipkin/zipkin-go/reporter/recorder" 31 | ) 32 | 33 | var _ = ginkgo.Describe("gRPC Client", func() { 34 | var ( 35 | reporter *recorder.ReporterRecorder 36 | tracer *zipkin.Tracer 37 | conn *grpc.ClientConn 38 | client service.HelloServiceClient 39 | ) 40 | 41 | ginkgo.BeforeEach(func() { 42 | var err error 43 | 44 | reporter = recorder.NewReporter() 45 | ep, _ := zipkin.NewEndpoint("grpcClient", "") 46 | tracer, err = zipkin.NewTracer( 47 | reporter, zipkin.WithLocalEndpoint(ep), zipkin.WithIDGenerator(newSequentialIDGenerator(1))) 48 | gomega.Expect(tracer, err).ToNot(gomega.BeNil()) 49 | }) 50 | 51 | ginkgo.AfterEach(func() { 52 | _ = reporter.Close() 53 | _ = conn.Close() 54 | }) 55 | 56 | ginkgo.Context("with defaults", func() { 57 | ginkgo.BeforeEach(func() { 58 | var err error 59 | 60 | conn, err = grpc.Dial(serverAddr, grpc.WithInsecure(), grpc.WithStatsHandler(zipkingrpc.NewClientHandler(tracer))) 61 | gomega.Expect(conn, err).ToNot(gomega.BeNil()) 62 | client = service.NewHelloServiceClient(conn) 63 | }) 64 | 65 | ginkgo.It("creates a span", func() { 66 | resp, err := client.Hello(context.Background(), &service.HelloRequest{Payload: "Hello"}) 67 | gomega.Expect(resp, err).ToNot(gomega.BeNil()) 68 | 69 | spans := reporter.Flush() 70 | gomega.Expect(spans).To(gomega.HaveLen(1)) 71 | 72 | span := spans[0] 73 | gomega.Expect(span.Kind).To(gomega.Equal(model.Client)) 74 | gomega.Expect(span.Name).To(gomega.Equal("zipkin.testing.HelloService.Hello")) 75 | gomega.Expect(span.RemoteEndpoint).To(gomega.BeNil()) 76 | gomega.Expect(span.Tags).To(gomega.BeEmpty()) 77 | }) 78 | 79 | ginkgo.It("propagates trace context", func() { 80 | resp, err := client.Hello(context.Background(), &service.HelloRequest{Payload: "Hello"}) 81 | gomega.Expect(resp.GetMetadata(), err).To(gomega.HaveKeyWithValue(b3.TraceID, "0000000000000001")) 82 | gomega.Expect(resp.GetMetadata(), err).To(gomega.HaveKeyWithValue(b3.SpanID, "0000000000000001")) 83 | gomega.Expect(resp.GetMetadata(), err).ToNot(gomega.HaveKey(b3.ParentSpanID)) 84 | }) 85 | 86 | ginkgo.It("propagates parent span", func() { 87 | _, ctx := tracer.StartSpanFromContext(context.Background(), "parent") 88 | resp, err := client.Hello(ctx, &service.HelloRequest{Payload: "Hello"}) 89 | gomega.Expect(resp.GetMetadata(), err).To(gomega.HaveKeyWithValue(b3.TraceID, "0000000000000001")) 90 | gomega.Expect(resp.GetMetadata(), err).To(gomega.HaveKeyWithValue(b3.SpanID, "0000000000000002")) 91 | gomega.Expect(resp.GetMetadata(), err).To(gomega.HaveKeyWithValue(b3.ParentSpanID, "0000000000000001")) 92 | }) 93 | 94 | ginkgo.It("tags with error code", func() { 95 | _, err := client.Hello(context.Background(), &service.HelloRequest{Payload: "fail"}) 96 | gomega.Expect(err).To(gomega.HaveOccurred()) 97 | 98 | spans := reporter.Flush() 99 | gomega.Expect(spans).To(gomega.HaveLen(1)) 100 | gomega.Expect(spans[0].Tags).To(gomega.HaveLen(2)) 101 | gomega.Expect(spans[0].Tags).To(gomega.HaveKeyWithValue("grpc.status_code", "ABORTED")) 102 | gomega.Expect(spans[0].Tags).To(gomega.HaveKeyWithValue(string(zipkin.TagError), "ABORTED")) 103 | }) 104 | 105 | ginkgo.It("copies existing metadata", func() { 106 | ctx := metadata.AppendToOutgoingContext(context.Background(), "existing", "metadata") 107 | resp, err := client.Hello(ctx, &service.HelloRequest{Payload: "Hello"}) 108 | 109 | gomega.Expect(resp.GetMetadata(), err).To(gomega.HaveKeyWithValue("existing", "metadata")) 110 | }) 111 | }) 112 | 113 | ginkgo.Context("with remote service name", func() { 114 | ginkgo.BeforeEach(func() { 115 | var err error 116 | 117 | conn, err = grpc.Dial( 118 | serverAddr, 119 | grpc.WithInsecure(), 120 | grpc.WithStatsHandler(zipkingrpc.NewClientHandler( 121 | tracer, 122 | zipkingrpc.WithRemoteServiceName("remoteService")))) 123 | gomega.Expect(conn, err).ToNot(gomega.BeNil()) 124 | client = service.NewHelloServiceClient(conn) 125 | }) 126 | 127 | ginkgo.It("has remote service name", func() { 128 | resp, err := client.Hello(context.Background(), &service.HelloRequest{Payload: "Hello"}) 129 | gomega.Expect(resp, err).ToNot(gomega.BeNil()) 130 | 131 | spans := reporter.Flush() 132 | gomega.Expect(spans).To(gomega.HaveLen(1)) 133 | gomega.Expect(spans[0].RemoteEndpoint.ServiceName).To(gomega.Equal("remoteService")) 134 | }) 135 | }) 136 | }) 137 | -------------------------------------------------------------------------------- /middleware/grpc/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | Package grpc contains several gRPC handlers which can be used for instrumenting calls with Zipkin. 17 | */ 18 | package grpc 19 | -------------------------------------------------------------------------------- /middleware/grpc/grpc_suite_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package grpc_test 16 | 17 | import ( 18 | "context" 19 | "errors" 20 | "net" 21 | "testing" 22 | 23 | "github.com/onsi/ginkgo/v2" 24 | "github.com/onsi/gomega" 25 | "google.golang.org/grpc" 26 | "google.golang.org/grpc/codes" 27 | "google.golang.org/grpc/metadata" 28 | "google.golang.org/grpc/status" 29 | 30 | "github.com/openzipkin/zipkin-go" 31 | zipkingrpc "github.com/openzipkin/zipkin-go/middleware/grpc" 32 | "github.com/openzipkin/zipkin-go/model" 33 | "github.com/openzipkin/zipkin-go/propagation/b3" 34 | service "github.com/openzipkin/zipkin-go/proto/testing" 35 | "github.com/openzipkin/zipkin-go/reporter/recorder" 36 | ) 37 | 38 | var ( 39 | serverIDGenerator *sequentialIDGenerator 40 | serverReporter *recorder.ReporterRecorder 41 | 42 | server *grpc.Server 43 | serverAddr string 44 | 45 | customServer *grpc.Server 46 | customServerAddr string 47 | ) 48 | 49 | func TestGrpc(t *testing.T) { 50 | gomega.RegisterFailHandler(ginkgo.Fail) 51 | ginkgo.RunSpecs(t, "Grpc Suite") 52 | } 53 | 54 | var _ = ginkgo.BeforeSuite(func() { 55 | var ( 56 | err error 57 | tracer *zipkin.Tracer 58 | lis net.Listener 59 | customLis net.Listener 60 | ) 61 | 62 | serverReporter = recorder.NewReporter() 63 | ep, _ := zipkin.NewEndpoint("grpcServer", "") 64 | serverIDGenerator = newSequentialIDGenerator(0x1000000) 65 | 66 | tracer, err = zipkin.NewTracer( 67 | serverReporter, zipkin.WithLocalEndpoint(ep), 68 | zipkin.WithIDGenerator(serverIDGenerator), 69 | zipkin.WithSharedSpans(false), 70 | ) 71 | gomega.Expect(tracer, err).ToNot(gomega.BeNil(), "failed to create Zipkin tracer") 72 | 73 | lis, err = net.Listen("tcp", ":0") 74 | gomega.Expect(lis, err).ToNot(gomega.BeNil(), "failed to listen to tcp port") 75 | 76 | server = grpc.NewServer(grpc.StatsHandler(zipkingrpc.NewServerHandler(tracer))) 77 | service.RegisterHelloServiceServer(server, &TestHelloService{}) 78 | go func() { 79 | _ = server.Serve(lis) 80 | }() 81 | serverAddr = lis.Addr().String() 82 | 83 | customLis, err = net.Listen("tcp", ":0") 84 | gomega.Expect(customLis, err).ToNot(gomega.BeNil(), "failed to listen to tcp port") 85 | 86 | tracer, err = zipkin.NewTracer( 87 | serverReporter, 88 | zipkin.WithLocalEndpoint(ep), 89 | zipkin.WithIDGenerator(serverIDGenerator), 90 | zipkin.WithSharedSpans(true), 91 | ) 92 | gomega.Expect(tracer, err).ToNot(gomega.BeNil(), "failed to create Zipkin tracer") 93 | 94 | customServer = grpc.NewServer( 95 | grpc.StatsHandler( 96 | zipkingrpc.NewServerHandler( 97 | tracer, zipkingrpc.ServerTags(map[string]string{"default": "tag"}), 98 | ), 99 | ), 100 | ) 101 | service.RegisterHelloServiceServer(customServer, &TestHelloService{}) 102 | go func() { 103 | _ = customServer.Serve(customLis) 104 | }() 105 | customServerAddr = customLis.Addr().String() 106 | }) 107 | 108 | var _ = ginkgo.AfterSuite(func() { 109 | server.Stop() 110 | customServer.Stop() 111 | _ = serverReporter.Close() 112 | }) 113 | 114 | type sequentialIDGenerator struct { 115 | nextTraceID uint64 116 | nextSpanID uint64 117 | start uint64 118 | } 119 | 120 | func newSequentialIDGenerator(start uint64) *sequentialIDGenerator { 121 | return &sequentialIDGenerator{start, start, start} 122 | } 123 | 124 | func (g *sequentialIDGenerator) SpanID(_ model.TraceID) model.ID { 125 | id := model.ID(g.nextSpanID) 126 | g.nextSpanID++ 127 | return id 128 | } 129 | 130 | func (g *sequentialIDGenerator) TraceID() model.TraceID { 131 | id := model.TraceID{ 132 | High: 0, 133 | Low: g.nextTraceID, 134 | } 135 | g.nextTraceID++ 136 | return id 137 | } 138 | 139 | func (g *sequentialIDGenerator) reset() { 140 | g.nextTraceID = g.start 141 | g.nextSpanID = g.start 142 | } 143 | 144 | type TestHelloService struct { 145 | service.UnimplementedHelloServiceServer 146 | } 147 | 148 | func (s *TestHelloService) Hello(ctx context.Context, req *service.HelloRequest) (*service.HelloResponse, error) { 149 | if req.Payload == "fail" { 150 | return nil, status.Error(codes.Aborted, "fail") 151 | } 152 | 153 | resp := &service.HelloResponse{ 154 | Payload: "World", 155 | Metadata: map[string]string{}, 156 | SpanContext: map[string]string{}, 157 | } 158 | 159 | md, ok := metadata.FromIncomingContext(ctx) 160 | if !ok { 161 | return nil, errors.New("could not parse incoming metadata") 162 | } 163 | 164 | for k := range md { 165 | // Just append the first value for a key for simplicity since we don't use multi-value headers. 166 | resp.GetMetadata()[k] = md[k][0] 167 | } 168 | 169 | span := zipkin.SpanFromContext(ctx) 170 | if span != nil { 171 | spanCtx := span.Context() 172 | resp.GetSpanContext()[b3.SpanID] = spanCtx.ID.String() 173 | resp.GetSpanContext()[b3.TraceID] = spanCtx.TraceID.String() 174 | if spanCtx.ParentID != nil { 175 | resp.GetSpanContext()[b3.ParentSpanID] = spanCtx.ParentID.String() 176 | } 177 | } 178 | 179 | return resp, nil 180 | } 181 | -------------------------------------------------------------------------------- /middleware/grpc/server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package grpc 16 | 17 | import ( 18 | "context" 19 | 20 | "github.com/openzipkin/zipkin-go/middleware" 21 | "google.golang.org/grpc/metadata" 22 | "google.golang.org/grpc/stats" 23 | 24 | "github.com/openzipkin/zipkin-go" 25 | "github.com/openzipkin/zipkin-go/model" 26 | "github.com/openzipkin/zipkin-go/propagation/b3" 27 | ) 28 | 29 | type serverHandler struct { 30 | tracer *zipkin.Tracer 31 | defaultTags map[string]string 32 | baggage middleware.BaggageHandler 33 | } 34 | 35 | // A ServerOption can be passed to NewServerHandler to customize the returned handler. 36 | type ServerOption func(*serverHandler) 37 | 38 | // ServerTags adds default Tags to inject into server spans. 39 | func ServerTags(tags map[string]string) ServerOption { 40 | return func(h *serverHandler) { 41 | h.defaultTags = tags 42 | } 43 | } 44 | 45 | // EnableBaggage can be passed to NewServerHandler to enable propagation of 46 | // registered fields through the SpanContext object. 47 | func EnableBaggage(b middleware.BaggageHandler) ServerOption { 48 | return func(h *serverHandler) { 49 | h.baggage = b 50 | } 51 | } 52 | 53 | // NewServerHandler returns a stats.Handler which can be used with grpc.WithStatsHandler to add 54 | // tracing to a gRPC server. The gRPC method name is used as the span name and by default the only 55 | // tags are the gRPC status code if the call fails. Use ServerTags to add additional tags that 56 | // should be applied to all spans. 57 | func NewServerHandler(tracer *zipkin.Tracer, options ...ServerOption) stats.Handler { 58 | c := &serverHandler{ 59 | tracer: tracer, 60 | } 61 | for _, option := range options { 62 | option(c) 63 | } 64 | return c 65 | } 66 | 67 | // HandleConn exists to satisfy gRPC stats.Handler. 68 | func (s *serverHandler) HandleConn(_ context.Context, _ stats.ConnStats) { 69 | // no-op 70 | } 71 | 72 | // TagConn exists to satisfy gRPC stats.Handler. 73 | func (s *serverHandler) TagConn(ctx context.Context, _ *stats.ConnTagInfo) context.Context { 74 | // no-op 75 | return ctx 76 | } 77 | 78 | // HandleRPC implements per-RPC tracing and stats instrumentation. 79 | func (s *serverHandler) HandleRPC(ctx context.Context, rs stats.RPCStats) { 80 | handleRPC(ctx, rs) 81 | } 82 | 83 | // TagRPC implements per-RPC context management. 84 | func (s *serverHandler) TagRPC(ctx context.Context, rti *stats.RPCTagInfo) context.Context { 85 | md, ok := metadata.FromIncomingContext(ctx) 86 | // In practice, ok never seems to be false but add a defensive check. 87 | if !ok { 88 | md = metadata.New(nil) 89 | } 90 | 91 | name := spanName(rti) 92 | 93 | spanContext := s.tracer.Extract(b3.ExtractGRPC(&md)) 94 | 95 | // store registered baggage fields to be propagated in spanContext 96 | if s.baggage != nil { 97 | spanContext.Baggage = s.baggage.New() 98 | for key, values := range md { 99 | spanContext.Baggage.Add(key, values...) 100 | } 101 | } 102 | 103 | span := s.tracer.StartSpan( 104 | name, 105 | zipkin.Kind(model.Server), 106 | zipkin.Parent(spanContext), 107 | zipkin.RemoteEndpoint(remoteEndpointFromContext(ctx, "")), 108 | ) 109 | 110 | if !zipkin.IsNoop(span) { 111 | for k, v := range s.defaultTags { 112 | span.Tag(k, v) 113 | } 114 | } 115 | 116 | return zipkin.NewContext(ctx, span) 117 | } 118 | -------------------------------------------------------------------------------- /middleware/grpc/shared.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package grpc 16 | 17 | import ( 18 | "context" 19 | "strings" 20 | 21 | "google.golang.org/grpc/codes" 22 | "google.golang.org/grpc/peer" 23 | "google.golang.org/grpc/stats" 24 | "google.golang.org/grpc/status" 25 | 26 | "github.com/openzipkin/zipkin-go" 27 | "github.com/openzipkin/zipkin-go/model" 28 | ) 29 | 30 | // A RPCHandler can be registered using WithClientRPCHandler or WithServerRPCHandler to intercept calls to HandleRPC of 31 | // a handler for additional span customization. 32 | type RPCHandler func(span zipkin.Span, rpcStats stats.RPCStats) 33 | 34 | func spanName(rti *stats.RPCTagInfo) string { 35 | name := strings.TrimPrefix(rti.FullMethodName, "/") 36 | name = strings.Replace(name, "/", ".", -1) 37 | return name 38 | } 39 | 40 | func handleRPC(ctx context.Context, rs stats.RPCStats) { 41 | span := zipkin.SpanFromContext(ctx) 42 | if zipkin.IsNoop(span) { 43 | return 44 | } 45 | 46 | switch rs := rs.(type) { 47 | case *stats.End: 48 | s, ok := status.FromError(rs.Error) 49 | // rs.Error should always be convertable to a status, this is just a defensive check. 50 | if ok { 51 | if s.Code() != codes.OK { 52 | // Uppercase for consistency with Brave 53 | c := strings.ToUpper(s.Code().String()) 54 | span.Tag("grpc.status_code", c) 55 | zipkin.TagError.Set(span, c) 56 | } 57 | } else { 58 | zipkin.TagError.Set(span, rs.Error.Error()) 59 | } 60 | span.Finish() 61 | } 62 | } 63 | 64 | func remoteEndpointFromContext(ctx context.Context, name string) *model.Endpoint { 65 | remoteAddr := "" 66 | 67 | p, ok := peer.FromContext(ctx) 68 | if ok { 69 | remoteAddr = p.Addr.String() 70 | } 71 | 72 | ep, _ := zipkin.NewEndpoint(name, remoteAddr) 73 | return ep 74 | } 75 | -------------------------------------------------------------------------------- /middleware/http/baggage_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package http_test 16 | 17 | import ( 18 | "net" 19 | "net/http" 20 | "testing" 21 | 22 | "github.com/openzipkin/zipkin-go" 23 | zipkinhttp "github.com/openzipkin/zipkin-go/middleware/http" 24 | "github.com/openzipkin/zipkin-go/propagation/baggage" 25 | ) 26 | 27 | const ( 28 | reqID = "X-Request-Id" 29 | reqIDValue = "5a3553a7-4088-4ae0-8845-8314ebd59ddb" 30 | customField = "custom-field" 31 | customFieldValue = "custom-value" 32 | ) 33 | 34 | func TestHTTPBaggage(t *testing.T) { 35 | var ( 36 | tracer, _ = zipkin.NewTracer(nil) 37 | tr, _ = zipkinhttp.NewTransport(tracer) 38 | cli = &http.Client{Transport: tr} 39 | srv = newServer(cli) 40 | bagHandler = baggage.New(reqID, customField) 41 | ) 42 | 43 | // attach server middleware to http server 44 | srv.s.Handler = zipkinhttp.NewServerMiddleware( 45 | tracer, 46 | zipkinhttp.EnableBaggage(bagHandler), 47 | )(srv.s.Handler) 48 | 49 | // create listener 50 | ln, err := net.Listen("tcp", ":0") 51 | if err != nil { 52 | t.Fatalf("unable to create listener for http server: %+v", err) 53 | } 54 | defer func() { 55 | _ = ln.Close() 56 | }() 57 | 58 | // start http server 59 | go func() { 60 | srv.s.Addr = ln.Addr().String() 61 | _ = srv.s.Serve(ln) 62 | }() 63 | 64 | // generate request to handler1 with X-Request-Id set 65 | req, err := http.NewRequest("GET", "http://"+ln.Addr().String()+"/handler1", nil) 66 | if err != nil { 67 | t.Fatalf("unable to create initial http request: %+v", err) 68 | } 69 | req.Header.Add(reqID, reqIDValue) 70 | 71 | // send client request 72 | if _, err = cli.Do(req); err != nil { 73 | t.Errorf("unexpected http request error: %+v", err) 74 | } 75 | 76 | // check server inspection variables for correct baggage field propagation 77 | if srv.resultHandler1 != reqIDValue { 78 | t.Errorf("resultHandler1 expected propagated %s: want %s, have: %s", 79 | reqID, reqIDValue, srv.resultHandler1) 80 | } 81 | if srv.result1Handler2 != reqIDValue { 82 | t.Errorf("result1Handler2 expected propagated %s: want %s, have: %s", 83 | reqID, reqIDValue, srv.result1Handler2) 84 | } 85 | if srv.result2Handler2 != customFieldValue { 86 | t.Errorf("result2Handler2 expected propagated %s: want %s, have: %s", 87 | customField, customFieldValue, srv.result2Handler2) 88 | } 89 | 90 | } 91 | 92 | type server struct { 93 | s *http.Server 94 | c *http.Client 95 | resultHandler1 string 96 | result1Handler2 string 97 | result2Handler2 string 98 | } 99 | 100 | func newServer(client *http.Client) *server { 101 | mux := http.NewServeMux() 102 | s := &server{ 103 | c: client, 104 | s: &http.Server{Handler: mux}, 105 | } 106 | mux.HandleFunc("/handler1", s.handler1) 107 | mux.HandleFunc("/handler2", s.handler2) 108 | return s 109 | } 110 | 111 | func (s *server) handler1(w http.ResponseWriter, h *http.Request) { 112 | // store received request id for inspection 113 | s.resultHandler1 = h.Header.Get(reqID) 114 | // add additional baggage field 115 | ctx := h.Context() 116 | if span := zipkin.SpanFromContext(ctx); span != nil { 117 | span.Context().Baggage.Add(customField, customFieldValue) 118 | } 119 | // call handler2 120 | req, _ := http.NewRequestWithContext(ctx, "GET", "http://"+s.s.Addr+"/handler2", nil) 121 | if _, err := s.c.Do(req); err != nil { 122 | http.Error(w, http.StatusText(500), 500) 123 | return 124 | } 125 | w.WriteHeader(201) 126 | } 127 | 128 | func (s *server) handler2(w http.ResponseWriter, h *http.Request) { 129 | // store received request id for inspection 130 | s.result1Handler2 = h.Header.Get(reqID) 131 | s.result2Handler2 = h.Header.Get(customField) 132 | w.WriteHeader(201) 133 | } 134 | -------------------------------------------------------------------------------- /middleware/http/client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package http 16 | 17 | import ( 18 | "errors" 19 | "net/http" 20 | "strconv" 21 | "time" 22 | 23 | "github.com/openzipkin/zipkin-go" 24 | "github.com/openzipkin/zipkin-go/model" 25 | ) 26 | 27 | // ErrValidTracerRequired error 28 | var ErrValidTracerRequired = errors.New("valid tracer required") 29 | 30 | // Client holds a Zipkin instrumented HTTP Client. 31 | type Client struct { 32 | *http.Client 33 | tracer *zipkin.Tracer 34 | httpTrace bool 35 | defaultTags map[string]string 36 | transportOptions []TransportOption 37 | remoteEndpoint *model.Endpoint 38 | } 39 | 40 | // ClientOption allows optional configuration of Client. 41 | type ClientOption func(*Client) 42 | 43 | // WithClient allows one to add a custom configured http.Client to use. 44 | func WithClient(client *http.Client) ClientOption { 45 | return func(c *Client) { 46 | if client == nil { 47 | client = &http.Client{} 48 | } 49 | c.Client = client 50 | } 51 | } 52 | 53 | // ClientTrace allows one to enable Go's net/http/httptrace. 54 | func ClientTrace(enabled bool) ClientOption { 55 | return func(c *Client) { 56 | c.httpTrace = enabled 57 | } 58 | } 59 | 60 | // ClientTags adds default Tags to inject into client application spans. 61 | func ClientTags(tags map[string]string) ClientOption { 62 | return func(c *Client) { 63 | c.defaultTags = tags 64 | } 65 | } 66 | 67 | // TransportOptions passes optional Transport configuration to the internal 68 | // transport used by Client. 69 | func TransportOptions(options ...TransportOption) ClientOption { 70 | return func(c *Client) { 71 | c.transportOptions = options 72 | } 73 | } 74 | 75 | // WithRemoteEndpoint will set the remote endpoint for all spans. 76 | func WithRemoteEndpoint(remoteEndpoint *model.Endpoint) ClientOption { 77 | return func(c *Client) { 78 | c.remoteEndpoint = remoteEndpoint 79 | } 80 | } 81 | 82 | // NewClient returns an HTTP Client adding Zipkin instrumentation around an 83 | // embedded standard Go http.Client. 84 | func NewClient(tracer *zipkin.Tracer, options ...ClientOption) (*Client, error) { 85 | if tracer == nil { 86 | return nil, ErrValidTracerRequired 87 | } 88 | 89 | c := &Client{tracer: tracer, Client: &http.Client{}} 90 | for _, option := range options { 91 | option(c) 92 | } 93 | 94 | c.transportOptions = append( 95 | c.transportOptions, 96 | // the following Client settings override provided transport settings. 97 | RoundTripper(c.Client.Transport), 98 | TransportTrace(c.httpTrace), 99 | TransportRemoteEndpoint(c.remoteEndpoint), 100 | ) 101 | tr, err := NewTransport(tracer, c.transportOptions...) 102 | if err != nil { 103 | return nil, err 104 | } 105 | c.Client.Transport = tr 106 | 107 | return c, nil 108 | } 109 | 110 | // DoWithAppSpan wraps http.Client's Do with tracing using an application span. 111 | func (c *Client) DoWithAppSpan(req *http.Request, name string) (*http.Response, error) { 112 | var parentContext model.SpanContext 113 | 114 | if span := zipkin.SpanFromContext(req.Context()); span != nil { 115 | parentContext = span.Context() 116 | } 117 | 118 | appSpan := c.tracer.StartSpan(name, zipkin.Parent(parentContext), zipkin.RemoteEndpoint(c.remoteEndpoint)) 119 | 120 | zipkin.TagHTTPMethod.Set(appSpan, req.Method) 121 | zipkin.TagHTTPPath.Set(appSpan, req.URL.Path) 122 | 123 | res, err := c.Do( 124 | req.WithContext(zipkin.NewContext(req.Context(), appSpan)), 125 | ) 126 | if err != nil { 127 | zipkin.TagError.Set(appSpan, err.Error()) 128 | appSpan.Finish() 129 | return res, err 130 | } 131 | 132 | if c.httpTrace { 133 | appSpan.Annotate(time.Now(), "wr") 134 | } 135 | 136 | if res.ContentLength > 0 { 137 | zipkin.TagHTTPResponseSize.Set(appSpan, strconv.FormatInt(res.ContentLength, 10)) 138 | } 139 | if res.StatusCode < 200 || res.StatusCode > 299 { 140 | statusCode := strconv.FormatInt(int64(res.StatusCode), 10) 141 | zipkin.TagHTTPStatusCode.Set(appSpan, statusCode) 142 | if res.StatusCode > 399 { 143 | zipkin.TagError.Set(appSpan, statusCode) 144 | } 145 | } 146 | 147 | res.Body = &spanCloser{ 148 | ReadCloser: res.Body, 149 | sp: appSpan, 150 | traceEnabled: c.httpTrace, 151 | } 152 | return res, err 153 | } 154 | -------------------------------------------------------------------------------- /middleware/http/client_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package http_test 16 | 17 | import ( 18 | "net/http" 19 | "testing" 20 | 21 | "github.com/openzipkin/zipkin-go" 22 | httpclient "github.com/openzipkin/zipkin-go/middleware/http" 23 | "github.com/openzipkin/zipkin-go/reporter/recorder" 24 | ) 25 | 26 | func TestHTTPClient(t *testing.T) { 27 | reporter := recorder.NewReporter() 28 | defer func() { 29 | _ = reporter.Close() 30 | }() 31 | 32 | ep, _ := zipkin.NewEndpoint("httpClient", "") 33 | tracer, err := zipkin.NewTracer(reporter, zipkin.WithLocalEndpoint(ep)) 34 | if err != nil { 35 | t.Fatalf("unable to create tracer: %+v", err) 36 | } 37 | 38 | clientTags := map[string]string{ 39 | "client": "testClient", 40 | } 41 | 42 | transportTags := map[string]string{ 43 | "conf.timeout": "default", 44 | } 45 | 46 | remoteEndpoint, _ := zipkin.NewEndpoint("google-service", "1.2.3.4:80") 47 | client, err := httpclient.NewClient( 48 | tracer, 49 | httpclient.WithClient(&http.Client{}), 50 | httpclient.ClientTrace(true), 51 | httpclient.ClientTags(clientTags), 52 | httpclient.TransportOptions(httpclient.TransportTags(transportTags)), 53 | httpclient.WithRemoteEndpoint(remoteEndpoint), 54 | ) 55 | if err != nil { 56 | t.Fatalf("unable to create http client: %+v", err) 57 | } 58 | 59 | req, _ := http.NewRequest("GET", "https://www.google.com", nil) 60 | 61 | res, err := client.DoWithAppSpan(req, "Get Google") 62 | if err != nil { 63 | t.Fatalf("unable to execute client request: %+v", err) 64 | } 65 | _ = res.Body.Close() 66 | 67 | spans := reporter.Flush() 68 | if len(spans) < 2 { 69 | t.Errorf("Span Count want 2+, have %d", len(spans)) 70 | } 71 | 72 | rep := spans[0].RemoteEndpoint 73 | if rep == nil { 74 | t.Errorf("Span remoteEndpoint must not nil") 75 | } 76 | if rep.ServiceName != remoteEndpoint.ServiceName { 77 | t.Errorf("Span remoteEndpoint ServiceName want %s, have %s", remoteEndpoint.ServiceName, rep.ServiceName) 78 | } 79 | 80 | req, _ = http.NewRequest("GET", "https://www.google.com", nil) 81 | 82 | res, err = client.Do(req) 83 | if err != nil { 84 | t.Fatalf("unable to execute client request: %+v", err) 85 | } 86 | _ = res.Body.Close() 87 | 88 | spans = reporter.Flush() 89 | if len(spans) == 0 { 90 | t.Errorf("Span Count want 1+, have 0") 91 | } 92 | 93 | span := tracer.StartSpan("ParentSpan") 94 | 95 | req, _ = http.NewRequest("GET", "http://www.google.com", nil) 96 | 97 | ctx := zipkin.NewContext(req.Context(), span) 98 | 99 | req = req.WithContext(ctx) 100 | 101 | res, err = client.DoWithAppSpan(req, "ChildSpan") 102 | if err != nil { 103 | t.Fatalf("unable to execute client request: %+v", err) 104 | } 105 | _ = res.Body.Close() 106 | } 107 | -------------------------------------------------------------------------------- /middleware/http/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | Package http contains several http middlewares which can be used for 17 | instrumenting calls with Zipkin. 18 | */ 19 | package http 20 | -------------------------------------------------------------------------------- /middleware/http/request_sampler.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package http 16 | 17 | import "net/http" 18 | 19 | // RequestSamplerFunc can be implemented for client and/or server side sampling decisions that can override the existing 20 | // upstream sampling decision. If the implementation returns nil, the existing sampling decision stays as is. 21 | type RequestSamplerFunc func(r *http.Request) *bool 22 | 23 | // Sample is a convenience function that returns a pointer to a boolean true. Use this for RequestSamplerFuncs when 24 | // wanting the RequestSampler to override the sampling decision to yes. 25 | func Sample() *bool { 26 | sample := true 27 | return &sample 28 | } 29 | 30 | // Discard is a convenience function that returns a pointer to a boolean false. Use this for RequestSamplerFuncs when 31 | // wanting the RequestSampler to override the sampling decision to no. 32 | func Discard() *bool { 33 | sample := false 34 | return &sample 35 | } 36 | -------------------------------------------------------------------------------- /middleware/http/spancloser.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package http 16 | 17 | import ( 18 | "io" 19 | "time" 20 | 21 | zipkin "github.com/openzipkin/zipkin-go" 22 | ) 23 | 24 | type spanCloser struct { 25 | io.ReadCloser 26 | sp zipkin.Span 27 | traceEnabled bool 28 | } 29 | 30 | func (s *spanCloser) Close() (err error) { 31 | if s.traceEnabled { 32 | s.sp.Annotate(time.Now(), "Body Close") 33 | } 34 | err = s.ReadCloser.Close() 35 | s.sp.Finish() 36 | return 37 | } 38 | -------------------------------------------------------------------------------- /middleware/http/spantrace.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package http 16 | 17 | import ( 18 | "crypto/tls" 19 | "net/http/httptrace" 20 | "strconv" 21 | "strings" 22 | "time" 23 | 24 | zipkin "github.com/openzipkin/zipkin-go" 25 | ) 26 | 27 | type spanTrace struct { 28 | zipkin.Span 29 | c *httptrace.ClientTrace 30 | } 31 | 32 | func (s *spanTrace) getConn(hostPort string) { 33 | s.Annotate(time.Now(), "Connecting") 34 | s.Tag("httptrace.get_connection.host_port", hostPort) 35 | } 36 | 37 | func (s *spanTrace) gotConn(info httptrace.GotConnInfo) { 38 | s.Annotate(time.Now(), "Connected") 39 | s.Tag("httptrace.got_connection.reused", strconv.FormatBool(info.Reused)) 40 | s.Tag("httptrace.got_connection.was_idle", strconv.FormatBool(info.WasIdle)) 41 | if info.WasIdle { 42 | s.Tag("httptrace.got_connection.idle_time", info.IdleTime.String()) 43 | } 44 | } 45 | 46 | func (s *spanTrace) putIdleConn(err error) { 47 | s.Annotate(time.Now(), "Put Idle Connection") 48 | if err != nil { 49 | s.Tag("httptrace.put_idle_connection.error", err.Error()) 50 | } 51 | } 52 | 53 | func (s *spanTrace) gotFirstResponseByte() { 54 | s.Annotate(time.Now(), "First Response Byte") 55 | } 56 | 57 | func (s *spanTrace) got100Continue() { 58 | s.Annotate(time.Now(), "Got 100 Continue") 59 | } 60 | 61 | func (s *spanTrace) dnsStart(info httptrace.DNSStartInfo) { 62 | s.Annotate(time.Now(), "DNS Start") 63 | s.Tag("httptrace.dns_start.host", info.Host) 64 | } 65 | 66 | func (s *spanTrace) dnsDone(info httptrace.DNSDoneInfo) { 67 | s.Annotate(time.Now(), "DNS Done") 68 | var addrs []string 69 | for _, addr := range info.Addrs { 70 | addrs = append(addrs, addr.String()) 71 | } 72 | s.Tag("httptrace.dns_done.addrs", strings.Join(addrs, " , ")) 73 | if info.Err != nil { 74 | s.Tag("httptrace.dns_done.error", info.Err.Error()) 75 | } 76 | } 77 | 78 | func (s *spanTrace) connectStart(network, addr string) { 79 | s.Annotate(time.Now(), "Connect Start") 80 | s.Tag("httptrace.connect_start.network", network) 81 | s.Tag("httptrace.connect_start.addr", addr) 82 | } 83 | 84 | func (s *spanTrace) connectDone(network, addr string, err error) { 85 | s.Annotate(time.Now(), "Connect Done") 86 | s.Tag("httptrace.connect_done.network", network) 87 | s.Tag("httptrace.connect_done.addr", addr) 88 | if err != nil { 89 | s.Tag("httptrace.connect_done.error", err.Error()) 90 | } 91 | } 92 | 93 | func (s *spanTrace) tlsHandshakeStart() { 94 | s.Annotate(time.Now(), "TLS Handshake Start") 95 | } 96 | 97 | func (s *spanTrace) tlsHandshakeDone(_ tls.ConnectionState, err error) { 98 | s.Annotate(time.Now(), "TLS Handshake Done") 99 | if err != nil { 100 | s.Tag("httptrace.tls_handshake_done.error", err.Error()) 101 | } 102 | } 103 | 104 | func (s *spanTrace) wroteHeaders() { 105 | s.Annotate(time.Now(), "Wrote Headers") 106 | } 107 | 108 | func (s *spanTrace) wait100Continue() { 109 | s.Annotate(time.Now(), "Wait 100 Continue") 110 | } 111 | 112 | func (s *spanTrace) wroteRequest(info httptrace.WroteRequestInfo) { 113 | s.Annotate(time.Now(), "Wrote Request") 114 | if info.Err != nil { 115 | s.Tag("httptrace.wrote_request.error", info.Err.Error()) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /model/annotation.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package model 16 | 17 | import ( 18 | "encoding/json" 19 | "errors" 20 | "time" 21 | ) 22 | 23 | // ErrValidTimestampRequired error 24 | var ErrValidTimestampRequired = errors.New("valid annotation timestamp required") 25 | 26 | // Annotation associates an event that explains latency with a timestamp. 27 | type Annotation struct { 28 | Timestamp time.Time 29 | Value string 30 | } 31 | 32 | // MarshalJSON implements custom JSON encoding 33 | func (a *Annotation) MarshalJSON() ([]byte, error) { 34 | return json.Marshal(&struct { 35 | Timestamp int64 `json:"timestamp"` 36 | Value string `json:"value"` 37 | }{ 38 | Timestamp: a.Timestamp.Round(time.Microsecond).UnixNano() / 1e3, 39 | Value: a.Value, 40 | }) 41 | } 42 | 43 | // UnmarshalJSON implements custom JSON decoding 44 | func (a *Annotation) UnmarshalJSON(b []byte) error { 45 | type Alias Annotation 46 | annotation := &struct { 47 | TimeStamp uint64 `json:"timestamp"` 48 | *Alias 49 | }{ 50 | Alias: (*Alias)(a), 51 | } 52 | if err := json.Unmarshal(b, &annotation); err != nil { 53 | return err 54 | } 55 | if annotation.TimeStamp < 1 { 56 | return ErrValidTimestampRequired 57 | } 58 | a.Timestamp = time.Unix(0, int64(annotation.TimeStamp)*1e3) 59 | return nil 60 | } 61 | -------------------------------------------------------------------------------- /model/annotation_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package model 16 | 17 | import ( 18 | "encoding/json" 19 | "testing" 20 | ) 21 | 22 | func TestAnnotationNegativeTimestamp(t *testing.T) { 23 | var ( 24 | span SpanModel 25 | b1 = []byte(`{"annotations":[{"timestamp":-1}]}`) 26 | b2 = []byte(`{"annotations":[{"timestamp":0}]}`) 27 | ) 28 | 29 | if err := json.Unmarshal(b1, &span); err == nil { 30 | t.Errorf("Unmarshal should have failed with error, have: %+v", span) 31 | } 32 | 33 | if err := json.Unmarshal(b2, &span); err == nil { 34 | t.Errorf("Unmarshal should have failed with error, have: %+v", span) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /model/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | Package model contains the Zipkin V2 model which is used by the Zipkin Go 17 | tracer implementation. 18 | 19 | Third party instrumentation libraries can use the model and transport packages 20 | found in this Zipkin Go library to directly interface with the Zipkin Server or 21 | Zipkin Collectors without the need to use the tracer implementation itself. 22 | */ 23 | package model 24 | -------------------------------------------------------------------------------- /model/endpoint.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package model 16 | 17 | import ( 18 | "encoding/json" 19 | "net" 20 | "strings" 21 | ) 22 | 23 | // Endpoint holds the network context of a node in the service graph. 24 | type Endpoint struct { 25 | ServiceName string 26 | IPv4 net.IP 27 | IPv6 net.IP 28 | Port uint16 29 | } 30 | 31 | // MarshalJSON exports our Endpoint into the correct format for the Zipkin V2 API. 32 | func (e Endpoint) MarshalJSON() ([]byte, error) { 33 | return json.Marshal(&struct { 34 | ServiceName string `json:"serviceName,omitempty"` 35 | IPv4 net.IP `json:"ipv4,omitempty"` 36 | IPv6 net.IP `json:"ipv6,omitempty"` 37 | Port uint16 `json:"port,omitempty"` 38 | }{ 39 | strings.ToLower(e.ServiceName), 40 | e.IPv4, 41 | e.IPv6, 42 | e.Port, 43 | }) 44 | } 45 | 46 | // Empty returns if all Endpoint properties are empty / unspecified. 47 | func (e *Endpoint) Empty() bool { 48 | return e == nil || 49 | (e.ServiceName == "" && e.Port == 0 && len(e.IPv4) == 0 && len(e.IPv6) == 0) 50 | } 51 | -------------------------------------------------------------------------------- /model/endpoint_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package model_test 16 | 17 | import ( 18 | "net" 19 | "testing" 20 | 21 | "github.com/openzipkin/zipkin-go/model" 22 | ) 23 | 24 | func TestEmptyEndpoint(t *testing.T) { 25 | var e *model.Endpoint 26 | 27 | if want, have := true, e.Empty(); want != have { 28 | t.Errorf("Endpoint want %t, have %t", want, have) 29 | } 30 | 31 | e = &model.Endpoint{} 32 | 33 | if want, have := true, e.Empty(); want != have { 34 | t.Errorf("Endpoint want %t, have %t", want, have) 35 | } 36 | 37 | e = &model.Endpoint{ 38 | IPv4: net.IPv4zero, 39 | } 40 | 41 | if want, have := false, e.Empty(); want != have { 42 | t.Errorf("Endpoint want %t, have %t", want, have) 43 | } 44 | 45 | e = &model.Endpoint{ 46 | IPv6: net.IPv6zero, 47 | } 48 | 49 | if want, have := false, e.Empty(); want != have { 50 | t.Errorf("Endpoint want %t, have %t", want, have) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /model/kind.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package model 16 | 17 | // Kind clarifies context of timestamp, duration and remoteEndpoint in a span. 18 | type Kind string 19 | 20 | // Available Kind values 21 | const ( 22 | Undetermined Kind = "" 23 | Client Kind = "CLIENT" 24 | Server Kind = "SERVER" 25 | Producer Kind = "PRODUCER" 26 | Consumer Kind = "CONSUMER" 27 | ) 28 | -------------------------------------------------------------------------------- /model/span.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package model 16 | 17 | import ( 18 | "encoding/json" 19 | "errors" 20 | "strings" 21 | "time" 22 | ) 23 | 24 | // unmarshal errors 25 | var ( 26 | ErrValidTraceIDRequired = errors.New("valid traceId required") 27 | ErrValidIDRequired = errors.New("valid span id required") 28 | ErrValidDurationRequired = errors.New("valid duration required") 29 | ) 30 | 31 | // BaggageFields holds the interface for consumers needing to interact with 32 | // the fields in application logic. 33 | type BaggageFields interface { 34 | // Get returns the values for a field identified by its key. 35 | Get(key string) []string 36 | // Add adds the provided values to a header designated by key. If not 37 | // accepted by the baggage implementation, it will return false. 38 | Add(key string, value ...string) bool 39 | // Set sets the provided values to a header designated by key. If not 40 | // accepted by the baggage implementation, it will return false. 41 | Set(key string, value ...string) bool 42 | // Delete removes the field data designated by key. If not accepted by the 43 | // baggage implementation, it will return false. 44 | Delete(key string) bool 45 | // Iterate will iterate over the available fields and for each one it will 46 | // trigger the callback function. 47 | Iterate(f func(key string, values []string)) 48 | } 49 | 50 | // SpanContext holds the context of a Span. 51 | type SpanContext struct { 52 | TraceID TraceID `json:"traceId"` 53 | ID ID `json:"id"` 54 | ParentID *ID `json:"parentId,omitempty"` 55 | Debug bool `json:"debug,omitempty"` 56 | Sampled *bool `json:"-"` 57 | Err error `json:"-"` 58 | Baggage BaggageFields `json:"-"` 59 | } 60 | 61 | // SpanModel structure. 62 | // 63 | // If using this library to instrument your application you will not need to 64 | // directly access or modify this representation. The SpanModel is exported for 65 | // use cases involving 3rd party Go instrumentation libraries desiring to 66 | // export data to a Zipkin server using the Zipkin V2 Span model. 67 | type SpanModel struct { 68 | SpanContext 69 | Name string `json:"name,omitempty"` 70 | Kind Kind `json:"kind,omitempty"` 71 | Timestamp time.Time `json:"-"` 72 | Duration time.Duration `json:"-"` 73 | Shared bool `json:"shared,omitempty"` 74 | LocalEndpoint *Endpoint `json:"localEndpoint,omitempty"` 75 | RemoteEndpoint *Endpoint `json:"remoteEndpoint,omitempty"` 76 | Annotations []Annotation `json:"annotations,omitempty"` 77 | Tags map[string]string `json:"tags,omitempty"` 78 | } 79 | 80 | // MarshalJSON exports our Model into the correct format for the Zipkin V2 API. 81 | func (s SpanModel) MarshalJSON() ([]byte, error) { 82 | type Alias SpanModel 83 | 84 | var timestamp int64 85 | if !s.Timestamp.IsZero() { 86 | if s.Timestamp.Unix() < 1 { 87 | // Zipkin does not allow Timestamps before Unix epoch 88 | return nil, ErrValidTimestampRequired 89 | } 90 | timestamp = s.Timestamp.Round(time.Microsecond).UnixNano() / 1e3 91 | } 92 | 93 | if s.Duration < time.Microsecond { 94 | if s.Duration < 0 { 95 | // negative duration is not allowed and signals a timing logic error 96 | return nil, ErrValidDurationRequired 97 | } else if s.Duration > 0 { 98 | // sub microsecond durations are reported as 1 microsecond 99 | s.Duration = 1 * time.Microsecond 100 | } 101 | } else { 102 | // Duration will be rounded to nearest microsecond representation. 103 | // 104 | // NOTE: Duration.Round() is not available in Go 1.8 which we still support. 105 | // To handle microsecond resolution rounding we'll add 500 nanoseconds to 106 | // the duration. When truncated to microseconds in the call to marshal, it 107 | // will be naturally rounded. See TestSpanDurationRounding in span_test.go 108 | s.Duration += 500 * time.Nanosecond 109 | } 110 | 111 | s.Name = strings.ToLower(s.Name) 112 | 113 | if s.LocalEndpoint.Empty() { 114 | s.LocalEndpoint = nil 115 | } 116 | 117 | if s.RemoteEndpoint.Empty() { 118 | s.RemoteEndpoint = nil 119 | } 120 | 121 | return json.Marshal(&struct { 122 | T int64 `json:"timestamp,omitempty"` 123 | D int64 `json:"duration,omitempty"` 124 | Alias 125 | }{ 126 | T: timestamp, 127 | D: s.Duration.Nanoseconds() / 1e3, 128 | Alias: (Alias)(s), 129 | }) 130 | } 131 | 132 | // UnmarshalJSON imports our Model from a Zipkin V2 API compatible span 133 | // representation. 134 | func (s *SpanModel) UnmarshalJSON(b []byte) error { 135 | type Alias SpanModel 136 | span := &struct { 137 | T uint64 `json:"timestamp,omitempty"` 138 | D uint64 `json:"duration,omitempty"` 139 | *Alias 140 | }{ 141 | Alias: (*Alias)(s), 142 | } 143 | if err := json.Unmarshal(b, &span); err != nil { 144 | return err 145 | } 146 | if s.ID < 1 { 147 | return ErrValidIDRequired 148 | } 149 | if span.T > 0 { 150 | s.Timestamp = time.Unix(0, int64(span.T)*1e3) 151 | } 152 | s.Duration = time.Duration(span.D*1e3) * time.Nanosecond 153 | if s.LocalEndpoint.Empty() { 154 | s.LocalEndpoint = nil 155 | } 156 | 157 | if s.RemoteEndpoint.Empty() { 158 | s.RemoteEndpoint = nil 159 | } 160 | return nil 161 | } 162 | -------------------------------------------------------------------------------- /model/span_id.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package model 16 | 17 | import ( 18 | "fmt" 19 | "strconv" 20 | ) 21 | 22 | // ID type 23 | type ID uint64 24 | 25 | // String outputs the 64-bit ID as hex string. 26 | func (i ID) String() string { 27 | return fmt.Sprintf("%016x", uint64(i)) 28 | } 29 | 30 | // MarshalJSON serializes an ID type (SpanID, ParentSpanID) to HEX. 31 | func (i ID) MarshalJSON() ([]byte, error) { 32 | return []byte(fmt.Sprintf("%q", i.String())), nil 33 | } 34 | 35 | // UnmarshalJSON deserializes an ID type (SpanID, ParentSpanID) from HEX. 36 | func (i *ID) UnmarshalJSON(b []byte) (err error) { 37 | var id uint64 38 | if len(b) < 3 { 39 | return nil 40 | } 41 | id, err = strconv.ParseUint(string(b[1:len(b)-1]), 16, 64) 42 | *i = ID(id) 43 | return err 44 | } 45 | -------------------------------------------------------------------------------- /model/traceid.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package model 16 | 17 | import ( 18 | "fmt" 19 | "strconv" 20 | ) 21 | 22 | // TraceID is a 128 bit number internally stored as 2x uint64 (high & low). 23 | // In case of 64 bit traceIDs, the value can be found in Low. 24 | type TraceID struct { 25 | High uint64 26 | Low uint64 27 | } 28 | 29 | // Empty returns if TraceID has zero value. 30 | func (t TraceID) Empty() bool { 31 | return t.Low == 0 && t.High == 0 32 | } 33 | 34 | // String outputs the 128-bit traceID as hex string. 35 | func (t TraceID) String() string { 36 | if t.High == 0 { 37 | return fmt.Sprintf("%016x", t.Low) 38 | } 39 | return fmt.Sprintf("%016x%016x", t.High, t.Low) 40 | } 41 | 42 | // TraceIDFromHex returns the TraceID from a hex string. 43 | func TraceIDFromHex(h string) (t TraceID, err error) { 44 | if len(h) > 16 { 45 | if t.High, err = strconv.ParseUint(h[0:len(h)-16], 16, 64); err != nil { 46 | return 47 | } 48 | t.Low, err = strconv.ParseUint(h[len(h)-16:], 16, 64) 49 | return 50 | } 51 | t.Low, err = strconv.ParseUint(h, 16, 64) 52 | return 53 | } 54 | 55 | // MarshalJSON custom JSON serializer to export the TraceID in the required 56 | // zero padded hex representation. 57 | func (t TraceID) MarshalJSON() ([]byte, error) { 58 | return []byte(fmt.Sprintf("%q", t.String())), nil 59 | } 60 | 61 | // UnmarshalJSON custom JSON deserializer to retrieve the traceID from the hex 62 | // encoded representation. 63 | func (t *TraceID) UnmarshalJSON(traceID []byte) error { 64 | if len(traceID) < 3 { 65 | return ErrValidTraceIDRequired 66 | } 67 | // A valid JSON string is encoded wrapped in double quotes. We need to trim 68 | // these before converting the hex payload. 69 | tID, err := TraceIDFromHex(string(traceID[1 : len(traceID)-1])) 70 | if err != nil { 71 | return err 72 | } 73 | *t = tID 74 | return nil 75 | } 76 | -------------------------------------------------------------------------------- /model/traceid_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package model 16 | 17 | import ( 18 | "encoding/json" 19 | "testing" 20 | ) 21 | 22 | func TestTraceID(t *testing.T) { 23 | traceID := TraceID{High: 1, Low: 2} 24 | if len(traceID.String()) != 32 { 25 | t.Errorf("Expected zero-padded TraceID to have 32 characters") 26 | } 27 | 28 | b, err := json.Marshal(traceID) 29 | if err != nil { 30 | t.Fatalf("Expected successful json serialization, got error: %+v", err) 31 | } 32 | 33 | if want, have := string(b), `"00000000000000010000000000000002"`; want != have { 34 | t.Fatalf("Expected json serialization, want %q, have %q", want, have) 35 | } 36 | 37 | var traceID2 TraceID 38 | if err = json.Unmarshal(b, &traceID2); err != nil { 39 | t.Fatalf("Expected successful json deserialization, got error: %+v", err) 40 | } 41 | 42 | if traceID2.High != traceID.High || traceID2.Low != traceID.Low { 43 | t.Fatalf("Unexpected traceID2, want: %#v, have %#v", traceID, traceID2) 44 | } 45 | 46 | have, err := TraceIDFromHex(traceID.String()) 47 | if err != nil { 48 | t.Fatalf("Expected traceID got error: %+v", err) 49 | } 50 | if traceID.High != have.High || traceID.Low != have.Low { 51 | t.Errorf("Expected %+v, got %+v", traceID, have) 52 | } 53 | 54 | traceID = TraceID{High: 0, Low: 2} 55 | 56 | if len(traceID.String()) != 16 { 57 | t.Errorf("Expected zero-padded TraceID to have 16 characters, got %d", len(traceID.String())) 58 | } 59 | 60 | have, err = TraceIDFromHex(traceID.String()) 61 | if err != nil { 62 | t.Fatalf("Expected traceID got error: %+v", err) 63 | } 64 | if traceID.High != have.High || traceID.Low != have.Low { 65 | t.Errorf("Expected %+v, got %+v", traceID, have) 66 | } 67 | 68 | traceID = TraceID{High: 0, Low: 0} 69 | 70 | if !traceID.Empty() { 71 | t.Errorf("Expected TraceID to be empty") 72 | } 73 | 74 | if _, err = TraceIDFromHex("12345678901234zz12345678901234zz"); err == nil { 75 | t.Errorf("Expected error got nil") 76 | } 77 | 78 | if err = json.Unmarshal([]byte(`"12345678901234zz12345678901234zz"`), &traceID); err == nil { 79 | t.Errorf("Expected error got nil") 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /noop.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package zipkin 16 | 17 | import ( 18 | "time" 19 | 20 | "github.com/openzipkin/zipkin-go/model" 21 | ) 22 | 23 | type noopSpan struct { 24 | model.SpanContext 25 | } 26 | 27 | func (n *noopSpan) Context() model.SpanContext { return n.SpanContext } 28 | 29 | func (n *noopSpan) SetName(string) {} 30 | 31 | func (*noopSpan) SetRemoteEndpoint(*model.Endpoint) {} 32 | 33 | func (*noopSpan) Annotate(time.Time, string) {} 34 | 35 | func (*noopSpan) Tag(string, string) {} 36 | 37 | func (*noopSpan) Finish() {} 38 | 39 | func (*noopSpan) FinishedWithDuration(_ time.Duration) {} 40 | 41 | func (*noopSpan) Flush() {} 42 | 43 | // IsNoop tells whether the span is noop or not. Usually used to avoid resource misusage 44 | // when customizing a span as data won't be recorded 45 | func IsNoop(s Span) bool { 46 | _, ok := s.(*noopSpan) 47 | return ok 48 | } 49 | -------------------------------------------------------------------------------- /noop_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package zipkin 16 | 17 | import ( 18 | "reflect" 19 | "testing" 20 | "time" 21 | 22 | "github.com/openzipkin/zipkin-go/model" 23 | "github.com/openzipkin/zipkin-go/reporter" 24 | ) 25 | 26 | func TestNoopContext(t *testing.T) { 27 | var ( 28 | span Span 29 | sc model.SpanContext 30 | parentID = model.ID(3) 31 | tr, _ = NewTracer( 32 | reporter.NewNoopReporter(), 33 | WithNoopSpan(true), 34 | WithSampler(NeverSample), 35 | WithSharedSpans(true), 36 | ) 37 | ) 38 | 39 | sc = model.SpanContext{ 40 | TraceID: model.TraceID{High: 1, Low: 2}, 41 | ID: model.ID(4), 42 | ParentID: &parentID, 43 | Debug: false, // debug must be false 44 | Sampled: new(bool), // bool must be pointer to false 45 | } 46 | 47 | span = tr.StartSpan("testNoop", Parent(sc), Kind(model.Server)) 48 | 49 | noop, ok := span.(*noopSpan) 50 | if !ok { 51 | t.Fatalf("Span type want %s, have %s", reflect.TypeOf(&spanImpl{}), reflect.TypeOf(span)) 52 | } 53 | 54 | if have := noop.Context(); !reflect.DeepEqual(sc, have) { 55 | t.Errorf("Context want %+v, have %+v", sc, have) 56 | } 57 | 58 | span.Tag("dummy", "dummy") 59 | span.Annotate(time.Now(), "dummy") 60 | span.SetName("dummy") 61 | span.SetRemoteEndpoint(nil) 62 | span.Flush() 63 | } 64 | 65 | func TestIsNoop(t *testing.T) { 66 | sc := model.SpanContext{ 67 | TraceID: model.TraceID{High: 1, Low: 2}, 68 | ID: model.ID(3), 69 | Sampled: new(bool), 70 | } 71 | 72 | ns := &noopSpan{sc} 73 | 74 | if want, have := true, IsNoop(ns); want != have { 75 | t.Error("unexpected noop") 76 | } 77 | 78 | span := &spanImpl{SpanModel: model.SpanModel{SpanContext: sc}} 79 | 80 | if want, have := false, IsNoop(span); want != have { 81 | t.Error("expected noop") 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /propagation/b3/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | Package b3 implements serialization and deserialization logic for Zipkin 17 | B3 Headers. 18 | */ 19 | package b3 20 | -------------------------------------------------------------------------------- /propagation/b3/grpc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package b3 16 | 17 | import ( 18 | "google.golang.org/grpc/metadata" 19 | 20 | "github.com/openzipkin/zipkin-go/model" 21 | "github.com/openzipkin/zipkin-go/propagation" 22 | ) 23 | 24 | // ExtractGRPC will extract a span.Context from the gRPC Request metadata if 25 | // found in B3 header format. 26 | func ExtractGRPC(md *metadata.MD) propagation.Extractor { 27 | return func() (*model.SpanContext, error) { 28 | var ( 29 | traceIDHeader = GetGRPCHeader(md, TraceID) 30 | spanIDHeader = GetGRPCHeader(md, SpanID) 31 | parentSpanIDHeader = GetGRPCHeader(md, ParentSpanID) 32 | sampledHeader = GetGRPCHeader(md, Sampled) 33 | flagsHeader = GetGRPCHeader(md, Flags) 34 | ) 35 | 36 | return ParseHeaders( 37 | traceIDHeader, spanIDHeader, parentSpanIDHeader, sampledHeader, 38 | flagsHeader, 39 | ) 40 | } 41 | } 42 | 43 | // InjectGRPC will inject a span.Context into gRPC metadata. 44 | func InjectGRPC(md *metadata.MD) propagation.Injector { 45 | return func(sc model.SpanContext) error { 46 | if (model.SpanContext{}) == sc { 47 | return ErrEmptyContext 48 | } 49 | 50 | if sc.Debug { 51 | setGRPCHeader(md, Flags, "1") 52 | } else if sc.Sampled != nil { 53 | // Debug is encoded as X-B3-Flags: 1. Since Debug implies Sampled, 54 | // we don't send "X-B3-Sampled" if Debug is set. 55 | if *sc.Sampled { 56 | setGRPCHeader(md, Sampled, "1") 57 | } else { 58 | setGRPCHeader(md, Sampled, "0") 59 | } 60 | } 61 | 62 | if !sc.TraceID.Empty() && sc.ID > 0 { 63 | // set identifiers 64 | setGRPCHeader(md, TraceID, sc.TraceID.String()) 65 | setGRPCHeader(md, SpanID, sc.ID.String()) 66 | if sc.ParentID != nil { 67 | setGRPCHeader(md, ParentSpanID, sc.ParentID.String()) 68 | } 69 | } 70 | 71 | return nil 72 | } 73 | } 74 | 75 | // GetGRPCHeader retrieves the last value found for a particular key. If key is 76 | // not found it returns an empty string. 77 | func GetGRPCHeader(md *metadata.MD, key string) string { 78 | v := (*md)[key] 79 | if len(v) < 1 { 80 | return "" 81 | } 82 | return v[len(v)-1] 83 | } 84 | 85 | func setGRPCHeader(md *metadata.MD, key, value string) { 86 | (*md)[key] = append((*md)[key], value) 87 | } 88 | -------------------------------------------------------------------------------- /propagation/b3/http.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package b3 16 | 17 | import ( 18 | "net/http" 19 | 20 | "github.com/openzipkin/zipkin-go/model" 21 | "github.com/openzipkin/zipkin-go/propagation" 22 | ) 23 | 24 | // InjectOption provides functional option handler type. 25 | type InjectOption func(opts *InjectOptions) 26 | 27 | // InjectOptions provides the available functional options. 28 | type InjectOptions struct { 29 | shouldInjectSingleHeader bool 30 | shouldInjectMultiHeader bool 31 | } 32 | 33 | // WithSingleAndMultiHeader allows to include both single and multiple 34 | // headers in the context injection 35 | func WithSingleAndMultiHeader() InjectOption { 36 | return func(opts *InjectOptions) { 37 | opts.shouldInjectSingleHeader = true 38 | opts.shouldInjectMultiHeader = true 39 | } 40 | } 41 | 42 | // WithSingleHeaderOnly allows to include only single header in the context 43 | // injection 44 | func WithSingleHeaderOnly() InjectOption { 45 | return func(opts *InjectOptions) { 46 | opts.shouldInjectSingleHeader = true 47 | opts.shouldInjectMultiHeader = false 48 | } 49 | } 50 | 51 | // ExtractHTTP will extract a span.Context from the HTTP Request if found in 52 | // B3 header format. 53 | func ExtractHTTP(r *http.Request) propagation.Extractor { 54 | return func() (*model.SpanContext, error) { 55 | var ( 56 | traceIDHeader = r.Header.Get(TraceID) 57 | spanIDHeader = r.Header.Get(SpanID) 58 | parentSpanIDHeader = r.Header.Get(ParentSpanID) 59 | sampledHeader = r.Header.Get(Sampled) 60 | flagsHeader = r.Header.Get(Flags) 61 | singleHeader = r.Header.Get(Context) 62 | ) 63 | 64 | var ( 65 | sc *model.SpanContext 66 | sErr error 67 | mErr error 68 | ) 69 | if singleHeader != "" { 70 | sc, sErr = ParseSingleHeader(singleHeader) 71 | if sErr == nil { 72 | return sc, nil 73 | } 74 | } 75 | 76 | sc, mErr = ParseHeaders( 77 | traceIDHeader, spanIDHeader, parentSpanIDHeader, 78 | sampledHeader, flagsHeader, 79 | ) 80 | 81 | if mErr != nil && sErr != nil { 82 | return nil, sErr 83 | } 84 | 85 | return sc, mErr 86 | } 87 | } 88 | 89 | // InjectHTTP will inject a span.Context into a HTTP Request 90 | func InjectHTTP(r *http.Request, opts ...InjectOption) propagation.Injector { 91 | options := InjectOptions{shouldInjectMultiHeader: true} 92 | for _, opt := range opts { 93 | opt(&options) 94 | } 95 | 96 | return func(sc model.SpanContext) error { 97 | if (model.SpanContext{}) == sc { 98 | return ErrEmptyContext 99 | } 100 | 101 | if options.shouldInjectMultiHeader { 102 | if sc.Debug { 103 | r.Header.Set(Flags, "1") 104 | } else if sc.Sampled != nil { 105 | // Debug is encoded as X-B3-Flags: 1. Since Debug implies Sampled, 106 | // so don't also send "X-B3-Sampled: 1". 107 | if *sc.Sampled { 108 | r.Header.Set(Sampled, "1") 109 | } else { 110 | r.Header.Set(Sampled, "0") 111 | } 112 | } 113 | 114 | if !sc.TraceID.Empty() && sc.ID > 0 { 115 | r.Header.Set(TraceID, sc.TraceID.String()) 116 | r.Header.Set(SpanID, sc.ID.String()) 117 | if sc.ParentID != nil { 118 | r.Header.Set(ParentSpanID, sc.ParentID.String()) 119 | } 120 | } 121 | } 122 | 123 | if options.shouldInjectSingleHeader { 124 | r.Header.Set(Context, BuildSingleHeader(sc)) 125 | } 126 | 127 | return nil 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /propagation/b3/map.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package b3 16 | 17 | import ( 18 | "github.com/openzipkin/zipkin-go/model" 19 | "github.com/openzipkin/zipkin-go/propagation" 20 | ) 21 | 22 | // Map allows serialization and deserialization of SpanContext into a standard Go map. 23 | type Map map[string]string 24 | 25 | // Extract implements Extractor 26 | func (m *Map) Extract() (*model.SpanContext, error) { 27 | var ( 28 | traceIDHeader = (*m)[TraceID] 29 | spanIDHeader = (*m)[SpanID] 30 | parentSpanIDHeader = (*m)[ParentSpanID] 31 | sampledHeader = (*m)[Sampled] 32 | flagsHeader = (*m)[Flags] 33 | singleHeader = (*m)[Context] 34 | ) 35 | 36 | var ( 37 | sc *model.SpanContext 38 | sErr error 39 | mErr error 40 | ) 41 | if singleHeader != "" { 42 | sc, sErr = ParseSingleHeader(singleHeader) 43 | if sErr == nil { 44 | return sc, nil 45 | } 46 | } 47 | 48 | sc, mErr = ParseHeaders( 49 | traceIDHeader, spanIDHeader, parentSpanIDHeader, 50 | sampledHeader, flagsHeader, 51 | ) 52 | 53 | if mErr != nil && sErr != nil { 54 | return nil, sErr 55 | } 56 | 57 | return sc, mErr 58 | 59 | } 60 | 61 | // Inject implements Injector 62 | func (m *Map) Inject(opts ...InjectOption) propagation.Injector { 63 | options := InjectOptions{shouldInjectMultiHeader: true} 64 | for _, opt := range opts { 65 | opt(&options) 66 | } 67 | 68 | return func(sc model.SpanContext) error { 69 | if (model.SpanContext{}) == sc { 70 | return ErrEmptyContext 71 | } 72 | 73 | if options.shouldInjectMultiHeader { 74 | if sc.Debug { 75 | (*m)[Flags] = "1" 76 | } else if sc.Sampled != nil { 77 | // Debug is encoded as X-B3-Flags: 1. Since Debug implies Sampled, 78 | // so don't also send "X-B3-Sampled: 1". 79 | if *sc.Sampled { 80 | (*m)[Sampled] = "1" 81 | } else { 82 | (*m)[Sampled] = "0" 83 | } 84 | } 85 | 86 | if !sc.TraceID.Empty() && sc.ID > 0 { 87 | (*m)[TraceID] = sc.TraceID.String() 88 | (*m)[SpanID] = sc.ID.String() 89 | if sc.ParentID != nil { 90 | (*m)[ParentSpanID] = sc.ParentID.String() 91 | } 92 | } 93 | } 94 | 95 | if options.shouldInjectSingleHeader { 96 | (*m)[Context] = BuildSingleHeader(sc) 97 | } 98 | 99 | return nil 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /propagation/b3/shared.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package b3 16 | 17 | import "errors" 18 | 19 | // Common Header Extraction / Injection errors 20 | var ( 21 | ErrInvalidSampledByte = errors.New("invalid B3 Sampled found") 22 | ErrInvalidSampledHeader = errors.New("invalid B3 Sampled header found") 23 | ErrInvalidFlagsHeader = errors.New("invalid B3 Flags header found") 24 | ErrInvalidTraceIDHeader = errors.New("invalid B3 TraceID header found") 25 | ErrInvalidSpanIDHeader = errors.New("invalid B3 SpanID header found") 26 | ErrInvalidParentSpanIDHeader = errors.New("invalid B3 ParentSpanID header found") 27 | ErrInvalidScope = errors.New("require either both TraceID and SpanID or none") 28 | ErrInvalidScopeParent = errors.New("ParentSpanID requires both TraceID and SpanID to be available") 29 | ErrInvalidScopeParentSingle = errors.New("ParentSpanID requires TraceID, SpanID and Sampled to be available") 30 | ErrEmptyContext = errors.New("empty request context") 31 | ErrInvalidTraceIDValue = errors.New("invalid B3 TraceID value found") 32 | ErrInvalidSpanIDValue = errors.New("invalid B3 SpanID value found") 33 | ErrInvalidParentSpanIDValue = errors.New("invalid B3 ParentSpanID value found") 34 | ) 35 | 36 | // Default B3 Header keys 37 | const ( 38 | TraceID = "x-b3-traceid" 39 | SpanID = "x-b3-spanid" 40 | ParentSpanID = "x-b3-parentspanid" 41 | Sampled = "x-b3-sampled" 42 | Flags = "x-b3-flags" 43 | Context = "b3" 44 | ) 45 | -------------------------------------------------------------------------------- /propagation/b3/spancontext.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package b3 16 | 17 | import ( 18 | "strconv" 19 | "strings" 20 | 21 | "github.com/openzipkin/zipkin-go/model" 22 | ) 23 | 24 | // ParseHeaders takes values found from B3 Headers and tries to reconstruct a 25 | // SpanContext. 26 | func ParseHeaders( 27 | hdrTraceID, hdrSpanID, hdrParentSpanID, hdrSampled, hdrFlags string, 28 | ) (*model.SpanContext, error) { 29 | var ( 30 | err error 31 | spanID uint64 32 | requiredCount int 33 | sc = &model.SpanContext{} 34 | ) 35 | 36 | // correct values for an existing sampled header are "0" and "1". 37 | // For legacy support and being lenient to other tracing implementations we 38 | // allow "true" and "false" as inputs for interop purposes. 39 | switch strings.ToLower(hdrSampled) { 40 | case "0", "false": 41 | sampled := false 42 | sc.Sampled = &sampled 43 | case "1", "true": 44 | sampled := true 45 | sc.Sampled = &sampled 46 | case "": 47 | // sc.Sampled = nil 48 | default: 49 | return nil, ErrInvalidSampledHeader 50 | } 51 | 52 | // The only accepted value for Flags is "1". This will set Debug to true. All 53 | // other values and omission of header will be ignored. 54 | if hdrFlags == "1" { 55 | sc.Debug = true 56 | sc.Sampled = nil 57 | } 58 | 59 | if hdrTraceID != "" { 60 | requiredCount++ 61 | if sc.TraceID, err = model.TraceIDFromHex(hdrTraceID); err != nil { 62 | return nil, ErrInvalidTraceIDHeader 63 | } 64 | } 65 | 66 | if hdrSpanID != "" { 67 | requiredCount++ 68 | if spanID, err = strconv.ParseUint(hdrSpanID, 16, 64); err != nil { 69 | return nil, ErrInvalidSpanIDHeader 70 | } 71 | sc.ID = model.ID(spanID) 72 | } 73 | 74 | if requiredCount != 0 && requiredCount != 2 { 75 | return nil, ErrInvalidScope 76 | } 77 | 78 | if hdrParentSpanID != "" { 79 | if requiredCount == 0 { 80 | return nil, ErrInvalidScopeParent 81 | } 82 | if spanID, err = strconv.ParseUint(hdrParentSpanID, 16, 64); err != nil { 83 | return nil, ErrInvalidParentSpanIDHeader 84 | } 85 | parentSpanID := model.ID(spanID) 86 | sc.ParentID = &parentSpanID 87 | } 88 | 89 | return sc, nil 90 | } 91 | 92 | // ParseSingleHeader takes values found from B3 Single Header and tries to reconstruct a 93 | // SpanContext. 94 | func ParseSingleHeader(contextHeader string) (*model.SpanContext, error) { 95 | if contextHeader == "" { 96 | return nil, ErrEmptyContext 97 | } 98 | 99 | var ( 100 | sc = model.SpanContext{} 101 | sampling string 102 | ) 103 | 104 | headerLen := len(contextHeader) 105 | 106 | if headerLen == 1 { 107 | sampling = contextHeader 108 | } else if headerLen == 16 || headerLen == 32 { 109 | return nil, ErrInvalidScope 110 | } else if headerLen >= 16+16+1 { 111 | var high, low uint64 112 | pos := 0 113 | if string(contextHeader[16]) != "-" { 114 | // traceID must be 128 bits 115 | var err error 116 | high, err = strconv.ParseUint(contextHeader[0:16], 16, 64) 117 | if err != nil { 118 | return nil, ErrInvalidTraceIDValue 119 | } 120 | pos = 16 121 | } 122 | 123 | low, err := strconv.ParseUint(contextHeader[pos:pos+16], 16, 64) 124 | if err != nil { 125 | return nil, ErrInvalidTraceIDValue 126 | } 127 | 128 | sc.TraceID = model.TraceID{High: high, Low: low} 129 | 130 | rawID, err := strconv.ParseUint(contextHeader[pos+16+1:pos+16+1+16], 16, 64) 131 | if err != nil { 132 | return nil, ErrInvalidSpanIDValue 133 | } 134 | 135 | sc.ID = model.ID(rawID) 136 | 137 | if headerLen > pos+16+1+16 { 138 | if headerLen == pos+16+1+16+1 { 139 | return nil, ErrInvalidSampledByte 140 | } 141 | 142 | if headerLen == pos+16+1+16+1+1 { 143 | sampling = string(contextHeader[pos+16+1+16+1]) 144 | } else if headerLen == pos+16+1+16+1+16 { 145 | return nil, ErrInvalidScopeParentSingle 146 | } else if headerLen == pos+16+1+16+1+1+1+16 { 147 | sampling = string(contextHeader[pos+16+1+16+1]) 148 | 149 | var rawParentID uint64 150 | rawParentID, err = strconv.ParseUint(contextHeader[pos+16+1+16+1+1+1:], 16, 64) 151 | if err != nil { 152 | return nil, ErrInvalidParentSpanIDValue 153 | } 154 | 155 | parentID := model.ID(rawParentID) 156 | sc.ParentID = &parentID 157 | } else { 158 | return nil, ErrInvalidParentSpanIDValue 159 | } 160 | } 161 | } else { 162 | return nil, ErrInvalidTraceIDValue 163 | } 164 | switch sampling { 165 | case "d": 166 | sc.Debug = true 167 | case "1": 168 | trueVal := true 169 | sc.Sampled = &trueVal 170 | case "0": 171 | falseVal := false 172 | sc.Sampled = &falseVal 173 | case "": 174 | default: 175 | return nil, ErrInvalidSampledByte 176 | } 177 | 178 | return &sc, nil 179 | } 180 | 181 | // BuildSingleHeader takes the values from the SpanContext and builds the B3 header 182 | func BuildSingleHeader(sc model.SpanContext) string { 183 | var header []string 184 | if !sc.TraceID.Empty() && sc.ID > 0 { 185 | header = append(header, sc.TraceID.String(), sc.ID.String()) 186 | } 187 | 188 | if sc.Debug { 189 | header = append(header, "d") 190 | } else if sc.Sampled != nil { 191 | if *sc.Sampled { 192 | header = append(header, "1") 193 | } else { 194 | header = append(header, "0") 195 | } 196 | } 197 | 198 | if sc.ParentID != nil { 199 | header = append(header, sc.ParentID.String()) 200 | } 201 | 202 | return strings.Join(header, "-") 203 | } 204 | -------------------------------------------------------------------------------- /propagation/baggage/baggage.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package baggage holds a Baggage propagation implementation based on 16 | // explicit allowList semantics. 17 | package baggage 18 | 19 | import ( 20 | "strings" 21 | 22 | "github.com/openzipkin/zipkin-go/middleware" 23 | "github.com/openzipkin/zipkin-go/model" 24 | ) 25 | 26 | var ( 27 | _ middleware.BaggageHandler = (*baggage)(nil) 28 | _ model.BaggageFields = (*baggage)(nil) 29 | ) 30 | 31 | type baggage struct { 32 | // registry holds our registry of allowed fields to propagate 33 | registry map[string]struct{} 34 | // fields holds the retrieved key-values pairs to propagate 35 | fields map[string][]string 36 | } 37 | 38 | // New returns a new Baggage interface which is configured to propagate the 39 | // registered fields. 40 | func New(keys ...string) middleware.BaggageHandler { 41 | b := &baggage{ 42 | registry: make(map[string]struct{}), 43 | } 44 | for _, key := range keys { 45 | b.registry[strings.ToLower(key)] = struct{}{} 46 | } 47 | return b 48 | } 49 | 50 | // New is called by server middlewares and returns a fresh initialized 51 | // baggage implementation. 52 | func (b *baggage) New() model.BaggageFields { 53 | return &baggage{ 54 | registry: b.registry, 55 | fields: make(map[string][]string), 56 | } 57 | } 58 | 59 | func (b *baggage) Get(key string) []string { 60 | return b.fields[strings.ToLower(key)] 61 | } 62 | 63 | func (b *baggage) Add(key string, values ...string) bool { 64 | if len(values) == 0 { 65 | return false 66 | } 67 | key = strings.ToLower(key) 68 | if _, ok := b.registry[key]; !ok { 69 | return false 70 | } 71 | // multiple values for a header is allowed 72 | b.fields[key] = append(b.fields[key], values...) 73 | 74 | return true 75 | } 76 | 77 | func (b *baggage) Set(key string, values ...string) bool { 78 | if len(values) == 0 { 79 | return false 80 | } 81 | key = strings.ToLower(key) 82 | if _, ok := b.registry[key]; !ok { 83 | return false 84 | } 85 | b.fields[key] = values 86 | 87 | return true 88 | } 89 | 90 | func (b *baggage) Delete(key string) bool { 91 | key = strings.ToLower(key) 92 | if _, ok := b.registry[key]; !ok { 93 | return false 94 | } 95 | for k := range b.fields { 96 | if key == k { 97 | delete(b.fields, k) 98 | } 99 | } 100 | return true 101 | } 102 | 103 | func (b *baggage) Iterate(f func(key string, values []string)) { 104 | for key, v := range b.fields { 105 | values := make([]string, len(v)) 106 | copy(values, v) 107 | f(key, values) 108 | } 109 | } 110 | 111 | func (b *baggage) IterateKeys(f func(key string)) { 112 | for key := range b.registry { 113 | f(key) 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /propagation/baggage/baggage_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package baggage 16 | 17 | import ( 18 | "strings" 19 | "testing" 20 | ) 21 | 22 | func TestBaggageRegistry(t *testing.T) { 23 | baggageHandler := New("X-Request-Id", "some-header", "x-request-id") 24 | 25 | var items int 26 | baggageHandler.(*baggage).IterateKeys(func(key string) { 27 | if key != "some-header" && key != "x-request-id" { 28 | t.Errorf("Unexpected registry item: %s", key) 29 | } 30 | items++ 31 | }) 32 | 33 | if items != 2 { 34 | t.Errorf("Unexpected registration count: want %d, have %d", 2, items) 35 | } 36 | } 37 | 38 | func TestBaggageValues(t *testing.T) { 39 | // register the baggage fields we'll propagate 40 | baggageHandler := New("X-Request-Id", "Some-Header") 41 | 42 | // initialize fresh BaggageFields container 43 | baggage := baggageHandler.New() 44 | 45 | t.Run("AddHeader", func(t *testing.T) { 46 | if baggage.Add("Invalid-Key", "Invalid-Key-Value") { 47 | t.Errorf("expected Invalid-Key to return false") 48 | } 49 | if !baggage.Add("X-Request-Id", "X-Request-Id-Value") { 50 | t.Errorf("expected X-Request-Id to return true") 51 | } 52 | if !baggage.Add("Some-Header", "Some-Header-Value1", "Some-Header-Value2") { 53 | t.Errorf("expected Some-Header to return true") 54 | } 55 | if !baggage.Add("Some-Header", "Some-Header-Value3") { 56 | t.Errorf("expected Some-Header to return true") 57 | } 58 | }) 59 | 60 | baggageHandler.New().Iterate(func(key string, values []string) { 61 | t.Errorf("expected no header data to exist, have: key=%s values=%v", key, values) 62 | }) 63 | 64 | t.Run("IterateHeaders", func(t *testing.T) { 65 | baggage.Iterate(func(key string, have []string) { 66 | if strings.EqualFold(key, "x-request-id") { 67 | want := 1 68 | if len(have) != want { 69 | t.Errorf("expected different value count: want %d, have %d", want, len(have)) 70 | } 71 | if have[0] != "X-Request-Id-Value" { 72 | t.Errorf("expected different value: want %s, have %s", "X-Request-Id-Value", have[0]) 73 | } 74 | return 75 | } 76 | if strings.EqualFold(key, "some-header") { 77 | want := 3 78 | if len(have) != want { 79 | t.Errorf("expected different value count: want %d, have %d", want, len(have)) 80 | } 81 | wantVal := "Some-Header-Value1" 82 | if have[0] != wantVal { 83 | t.Errorf("expected different value: want %s, have %s", wantVal, have[0]) 84 | } 85 | wantVal = "Some-Header-Value2" 86 | if have[1] != wantVal { 87 | t.Errorf("expected different value: want %s, have %s", wantVal, have[1]) 88 | } 89 | wantVal = "Some-Header-Value3" 90 | if have[2] != wantVal { 91 | t.Errorf("expected different value: want %s, have %s", wantVal, have[2]) 92 | } 93 | return 94 | } 95 | t.Errorf("unexpected header key: %s", key) 96 | }) 97 | }) 98 | 99 | t.Run("DeleteHeader", func(t *testing.T) { 100 | if baggage.Delete("Invalid-Key") { 101 | t.Errorf("expected Invalid-Key to return false") 102 | } 103 | 104 | if !baggage.Delete("some-header") { 105 | t.Errorf("expected some-header to return true") 106 | } 107 | 108 | baggage.Iterate(func(key string, have []string) { 109 | if strings.EqualFold(key, "x-request-id") { 110 | want := 1 111 | if len(have) != want { 112 | t.Errorf("expected different value count: want %d, have %d", want, len(have)) 113 | } 114 | if have[0] != "X-Request-Id-Value" { 115 | t.Errorf("expected different value: want %s, have %s", "X-Request-Id-Value", have[0]) 116 | } 117 | return 118 | } 119 | t.Errorf("unexpected header key: %s", key) 120 | }) 121 | }) 122 | 123 | } 124 | -------------------------------------------------------------------------------- /propagation/propagation.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | Package propagation holds the required function signatures for Injection and 17 | Extraction as used by the Zipkin Tracer. 18 | 19 | Subpackages of this package contain officially supported standard propagation 20 | implementations. 21 | */ 22 | package propagation 23 | 24 | import "github.com/openzipkin/zipkin-go/model" 25 | 26 | // Extractor function signature 27 | type Extractor func() (*model.SpanContext, error) 28 | 29 | // Injector function signature 30 | type Injector func(model.SpanContext) error 31 | -------------------------------------------------------------------------------- /proto/testing/baggage.pb.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by protoc-gen-go. DO NOT EDIT. 16 | // versions: 17 | // protoc-gen-go v1.28.1 18 | // protoc v3.19.0 19 | // source: proto/testing/baggage.proto 20 | 21 | package testing 22 | 23 | import ( 24 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 25 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 26 | emptypb "google.golang.org/protobuf/types/known/emptypb" 27 | reflect "reflect" 28 | ) 29 | 30 | const ( 31 | // Verify that this generated code is sufficiently up-to-date. 32 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 33 | // Verify that runtime/protoimpl is sufficiently up-to-date. 34 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 35 | ) 36 | 37 | var File_proto_testing_baggage_proto protoreflect.FileDescriptor 38 | 39 | var file_proto_testing_baggage_proto_rawDesc = []byte{ 40 | 0x0a, 0x1b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2f, 41 | 0x62, 0x61, 0x67, 0x67, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0e, 0x7a, 42 | 0x69, 0x70, 0x6b, 0x69, 0x6e, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x1a, 0x1b, 0x67, 43 | 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 44 | 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x88, 0x01, 0x0a, 0x0e, 0x42, 45 | 0x61, 0x67, 0x67, 0x61, 0x67, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3a, 0x0a, 46 | 0x08, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x31, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 47 | 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 48 | 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 49 | 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3a, 0x0a, 0x08, 0x48, 0x61, 0x6e, 50 | 0x64, 0x6c, 0x65, 0x72, 0x32, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 51 | 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 52 | 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 53 | 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x2f, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 54 | 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x7a, 0x69, 0x70, 0x6b, 0x69, 0x6e, 0x2f, 0x7a, 55 | 0x69, 0x70, 0x6b, 0x69, 0x6e, 0x2d, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x74, 56 | 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 57 | } 58 | 59 | var file_proto_testing_baggage_proto_goTypes = []interface{}{ 60 | (*emptypb.Empty)(nil), // 0: google.protobuf.Empty 61 | } 62 | var file_proto_testing_baggage_proto_depIdxs = []int32{ 63 | 0, // 0: zipkin.testing.BaggageService.Handler1:input_type -> google.protobuf.Empty 64 | 0, // 1: zipkin.testing.BaggageService.Handler2:input_type -> google.protobuf.Empty 65 | 0, // 2: zipkin.testing.BaggageService.Handler1:output_type -> google.protobuf.Empty 66 | 0, // 3: zipkin.testing.BaggageService.Handler2:output_type -> google.protobuf.Empty 67 | 2, // [2:4] is the sub-list for method output_type 68 | 0, // [0:2] is the sub-list for method input_type 69 | 0, // [0:0] is the sub-list for extension type_name 70 | 0, // [0:0] is the sub-list for extension extendee 71 | 0, // [0:0] is the sub-list for field type_name 72 | } 73 | 74 | func init() { file_proto_testing_baggage_proto_init() } 75 | func file_proto_testing_baggage_proto_init() { 76 | if File_proto_testing_baggage_proto != nil { 77 | return 78 | } 79 | type x struct{} 80 | out := protoimpl.TypeBuilder{ 81 | File: protoimpl.DescBuilder{ 82 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 83 | RawDescriptor: file_proto_testing_baggage_proto_rawDesc, 84 | NumEnums: 0, 85 | NumMessages: 0, 86 | NumExtensions: 0, 87 | NumServices: 1, 88 | }, 89 | GoTypes: file_proto_testing_baggage_proto_goTypes, 90 | DependencyIndexes: file_proto_testing_baggage_proto_depIdxs, 91 | }.Build() 92 | File_proto_testing_baggage_proto = out.File 93 | file_proto_testing_baggage_proto_rawDesc = nil 94 | file_proto_testing_baggage_proto_goTypes = nil 95 | file_proto_testing_baggage_proto_depIdxs = nil 96 | } 97 | -------------------------------------------------------------------------------- /proto/testing/baggage.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | import "google/protobuf/empty.proto"; 18 | 19 | package zipkin.testing; 20 | 21 | option go_package = "github.com/openzipkin/zipkin-go/proto/testing"; 22 | 23 | service BaggageService { 24 | rpc Handler1(google.protobuf.Empty) returns (google.protobuf.Empty); 25 | rpc Handler2(google.protobuf.Empty) returns (google.protobuf.Empty); 26 | } 27 | -------------------------------------------------------------------------------- /proto/testing/baggage_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | // versions: 3 | // - protoc-gen-go-grpc v1.2.0 4 | // - protoc v3.19.0 5 | // source: proto/testing/baggage.proto 6 | 7 | package testing 8 | 9 | import ( 10 | context "context" 11 | grpc "google.golang.org/grpc" 12 | codes "google.golang.org/grpc/codes" 13 | status "google.golang.org/grpc/status" 14 | emptypb "google.golang.org/protobuf/types/known/emptypb" 15 | ) 16 | 17 | // This is a compile-time assertion to ensure that this generated file 18 | // is compatible with the grpc package it is being compiled against. 19 | // Requires gRPC-Go v1.32.0 or later. 20 | const _ = grpc.SupportPackageIsVersion7 21 | 22 | // BaggageServiceClient is the client API for BaggageService service. 23 | // 24 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 25 | type BaggageServiceClient interface { 26 | Handler1(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) 27 | Handler2(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) 28 | } 29 | 30 | type baggageServiceClient struct { 31 | cc grpc.ClientConnInterface 32 | } 33 | 34 | func NewBaggageServiceClient(cc grpc.ClientConnInterface) BaggageServiceClient { 35 | return &baggageServiceClient{cc} 36 | } 37 | 38 | func (c *baggageServiceClient) Handler1(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) { 39 | out := new(emptypb.Empty) 40 | err := c.cc.Invoke(ctx, "/zipkin.testing.BaggageService/Handler1", in, out, opts...) 41 | if err != nil { 42 | return nil, err 43 | } 44 | return out, nil 45 | } 46 | 47 | func (c *baggageServiceClient) Handler2(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) { 48 | out := new(emptypb.Empty) 49 | err := c.cc.Invoke(ctx, "/zipkin.testing.BaggageService/Handler2", in, out, opts...) 50 | if err != nil { 51 | return nil, err 52 | } 53 | return out, nil 54 | } 55 | 56 | // BaggageServiceServer is the server API for BaggageService service. 57 | // All implementations must embed UnimplementedBaggageServiceServer 58 | // for forward compatibility 59 | type BaggageServiceServer interface { 60 | Handler1(context.Context, *emptypb.Empty) (*emptypb.Empty, error) 61 | Handler2(context.Context, *emptypb.Empty) (*emptypb.Empty, error) 62 | mustEmbedUnimplementedBaggageServiceServer() 63 | } 64 | 65 | // UnimplementedBaggageServiceServer must be embedded to have forward compatible implementations. 66 | type UnimplementedBaggageServiceServer struct { 67 | } 68 | 69 | func (UnimplementedBaggageServiceServer) Handler1(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { 70 | return nil, status.Errorf(codes.Unimplemented, "method Handler1 not implemented") 71 | } 72 | func (UnimplementedBaggageServiceServer) Handler2(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { 73 | return nil, status.Errorf(codes.Unimplemented, "method Handler2 not implemented") 74 | } 75 | func (UnimplementedBaggageServiceServer) mustEmbedUnimplementedBaggageServiceServer() {} 76 | 77 | // UnsafeBaggageServiceServer may be embedded to opt out of forward compatibility for this service. 78 | // Use of this interface is not recommended, as added methods to BaggageServiceServer will 79 | // result in compilation errors. 80 | type UnsafeBaggageServiceServer interface { 81 | mustEmbedUnimplementedBaggageServiceServer() 82 | } 83 | 84 | func RegisterBaggageServiceServer(s grpc.ServiceRegistrar, srv BaggageServiceServer) { 85 | s.RegisterService(&BaggageService_ServiceDesc, srv) 86 | } 87 | 88 | func _BaggageService_Handler1_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 89 | in := new(emptypb.Empty) 90 | if err := dec(in); err != nil { 91 | return nil, err 92 | } 93 | if interceptor == nil { 94 | return srv.(BaggageServiceServer).Handler1(ctx, in) 95 | } 96 | info := &grpc.UnaryServerInfo{ 97 | Server: srv, 98 | FullMethod: "/zipkin.testing.BaggageService/Handler1", 99 | } 100 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 101 | return srv.(BaggageServiceServer).Handler1(ctx, req.(*emptypb.Empty)) 102 | } 103 | return interceptor(ctx, in, info, handler) 104 | } 105 | 106 | func _BaggageService_Handler2_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 107 | in := new(emptypb.Empty) 108 | if err := dec(in); err != nil { 109 | return nil, err 110 | } 111 | if interceptor == nil { 112 | return srv.(BaggageServiceServer).Handler2(ctx, in) 113 | } 114 | info := &grpc.UnaryServerInfo{ 115 | Server: srv, 116 | FullMethod: "/zipkin.testing.BaggageService/Handler2", 117 | } 118 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 119 | return srv.(BaggageServiceServer).Handler2(ctx, req.(*emptypb.Empty)) 120 | } 121 | return interceptor(ctx, in, info, handler) 122 | } 123 | 124 | // BaggageService_ServiceDesc is the grpc.ServiceDesc for BaggageService service. 125 | // It's only intended for direct use with grpc.RegisterService, 126 | // and not to be introspected or modified (even as a copy) 127 | var BaggageService_ServiceDesc = grpc.ServiceDesc{ 128 | ServiceName: "zipkin.testing.BaggageService", 129 | HandlerType: (*BaggageServiceServer)(nil), 130 | Methods: []grpc.MethodDesc{ 131 | { 132 | MethodName: "Handler1", 133 | Handler: _BaggageService_Handler1_Handler, 134 | }, 135 | { 136 | MethodName: "Handler2", 137 | Handler: _BaggageService_Handler2_Handler, 138 | }, 139 | }, 140 | Streams: []grpc.StreamDesc{}, 141 | Metadata: "proto/testing/baggage.proto", 142 | } 143 | -------------------------------------------------------------------------------- /proto/testing/service.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package zipkin.testing; 18 | 19 | option go_package = "github.com/openzipkin/zipkin-go/proto/testing"; 20 | 21 | message HelloRequest { 22 | string payload = 1; 23 | } 24 | 25 | message HelloResponse { 26 | string payload = 1; 27 | 28 | map metadata = 2; 29 | 30 | map span_context = 3; 31 | } 32 | 33 | service HelloService { 34 | rpc Hello (HelloRequest) returns (HelloResponse); 35 | } 36 | -------------------------------------------------------------------------------- /proto/testing/service_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | // versions: 3 | // - protoc-gen-go-grpc v1.2.0 4 | // - protoc v3.19.0 5 | // source: proto/testing/service.proto 6 | 7 | package testing 8 | 9 | import ( 10 | context "context" 11 | grpc "google.golang.org/grpc" 12 | codes "google.golang.org/grpc/codes" 13 | status "google.golang.org/grpc/status" 14 | ) 15 | 16 | // This is a compile-time assertion to ensure that this generated file 17 | // is compatible with the grpc package it is being compiled against. 18 | // Requires gRPC-Go v1.32.0 or later. 19 | const _ = grpc.SupportPackageIsVersion7 20 | 21 | // HelloServiceClient is the client API for HelloService service. 22 | // 23 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 24 | type HelloServiceClient interface { 25 | Hello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error) 26 | } 27 | 28 | type helloServiceClient struct { 29 | cc grpc.ClientConnInterface 30 | } 31 | 32 | func NewHelloServiceClient(cc grpc.ClientConnInterface) HelloServiceClient { 33 | return &helloServiceClient{cc} 34 | } 35 | 36 | func (c *helloServiceClient) Hello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error) { 37 | out := new(HelloResponse) 38 | err := c.cc.Invoke(ctx, "/zipkin.testing.HelloService/Hello", in, out, opts...) 39 | if err != nil { 40 | return nil, err 41 | } 42 | return out, nil 43 | } 44 | 45 | // HelloServiceServer is the server API for HelloService service. 46 | // All implementations must embed UnimplementedHelloServiceServer 47 | // for forward compatibility 48 | type HelloServiceServer interface { 49 | Hello(context.Context, *HelloRequest) (*HelloResponse, error) 50 | mustEmbedUnimplementedHelloServiceServer() 51 | } 52 | 53 | // UnimplementedHelloServiceServer must be embedded to have forward compatible implementations. 54 | type UnimplementedHelloServiceServer struct { 55 | } 56 | 57 | func (UnimplementedHelloServiceServer) Hello(context.Context, *HelloRequest) (*HelloResponse, error) { 58 | return nil, status.Errorf(codes.Unimplemented, "method Hello not implemented") 59 | } 60 | func (UnimplementedHelloServiceServer) mustEmbedUnimplementedHelloServiceServer() {} 61 | 62 | // UnsafeHelloServiceServer may be embedded to opt out of forward compatibility for this service. 63 | // Use of this interface is not recommended, as added methods to HelloServiceServer will 64 | // result in compilation errors. 65 | type UnsafeHelloServiceServer interface { 66 | mustEmbedUnimplementedHelloServiceServer() 67 | } 68 | 69 | func RegisterHelloServiceServer(s grpc.ServiceRegistrar, srv HelloServiceServer) { 70 | s.RegisterService(&HelloService_ServiceDesc, srv) 71 | } 72 | 73 | func _HelloService_Hello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 74 | in := new(HelloRequest) 75 | if err := dec(in); err != nil { 76 | return nil, err 77 | } 78 | if interceptor == nil { 79 | return srv.(HelloServiceServer).Hello(ctx, in) 80 | } 81 | info := &grpc.UnaryServerInfo{ 82 | Server: srv, 83 | FullMethod: "/zipkin.testing.HelloService/Hello", 84 | } 85 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 86 | return srv.(HelloServiceServer).Hello(ctx, req.(*HelloRequest)) 87 | } 88 | return interceptor(ctx, in, info, handler) 89 | } 90 | 91 | // HelloService_ServiceDesc is the grpc.ServiceDesc for HelloService service. 92 | // It's only intended for direct use with grpc.RegisterService, 93 | // and not to be introspected or modified (even as a copy) 94 | var HelloService_ServiceDesc = grpc.ServiceDesc{ 95 | ServiceName: "zipkin.testing.HelloService", 96 | HandlerType: (*HelloServiceServer)(nil), 97 | Methods: []grpc.MethodDesc{ 98 | { 99 | MethodName: "Hello", 100 | Handler: _HelloService_Hello_Handler, 101 | }, 102 | }, 103 | Streams: []grpc.StreamDesc{}, 104 | Metadata: "proto/testing/service.proto", 105 | } 106 | -------------------------------------------------------------------------------- /proto/zipkin_proto3/decode_proto.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | Package zipkin_proto3 adds support for the Zipkin protobuf definition to allow 17 | Go applications to consume model.SpanModel from protobuf serialized data. 18 | */ 19 | package zipkin_proto3 20 | 21 | import ( 22 | "encoding/binary" 23 | "errors" 24 | "fmt" 25 | "net" 26 | "time" 27 | 28 | "google.golang.org/protobuf/proto" 29 | 30 | zipkinmodel "github.com/openzipkin/zipkin-go/model" 31 | ) 32 | 33 | // ParseSpans parses zipkinmodel.SpanModel values from data serialized by Protobuf3. 34 | // debugWasSet is a boolean that toggles the Debug field of each Span. Its value 35 | // is usually retrieved from the transport headers when the "X-B3-Flags" header has a value of 1. 36 | func ParseSpans(protoBlob []byte, debugWasSet bool) (zss []*zipkinmodel.SpanModel, err error) { 37 | var listOfSpans ListOfSpans 38 | if err := proto.Unmarshal(protoBlob, &listOfSpans); err != nil { 39 | return nil, err 40 | } 41 | for _, zps := range listOfSpans.Spans { 42 | zms, err := protoSpanToModelSpan(zps, debugWasSet) 43 | if err != nil { 44 | return zss, err 45 | } 46 | zss = append(zss, zms) 47 | } 48 | return zss, nil 49 | } 50 | 51 | var errNilZipkinSpan = errors.New("expecting a non-nil Span") 52 | 53 | func protoSpanToModelSpan(s *Span, debugWasSet bool) (*zipkinmodel.SpanModel, error) { 54 | if s == nil { 55 | return nil, errNilZipkinSpan 56 | } 57 | if len(s.TraceId) != 16 { 58 | return nil, fmt.Errorf("invalid TraceID: has length %d yet wanted length 16", len(s.TraceId)) 59 | } 60 | traceID, err := zipkinmodel.TraceIDFromHex(fmt.Sprintf("%x", s.TraceId)) 61 | if err != nil { 62 | return nil, fmt.Errorf("invalid TraceID: %v", err) 63 | } 64 | 65 | parentSpanID, _, err := protoSpanIDToModelSpanID(s.ParentId) 66 | if err != nil { 67 | return nil, fmt.Errorf("invalid ParentID: %v", err) 68 | } 69 | spanIDPtr, spanIDBlank, err := protoSpanIDToModelSpanID(s.Id) 70 | if err != nil { 71 | return nil, fmt.Errorf("invalid SpanID: %v", err) 72 | } 73 | if spanIDBlank || spanIDPtr == nil { 74 | // This is a logical error 75 | return nil, errors.New("expected a non-nil SpanID") 76 | } 77 | 78 | zmsc := zipkinmodel.SpanContext{ 79 | TraceID: traceID, 80 | ID: *spanIDPtr, 81 | ParentID: parentSpanID, 82 | Debug: debugWasSet, 83 | } 84 | zms := &zipkinmodel.SpanModel{ 85 | SpanContext: zmsc, 86 | Name: s.Name, 87 | Kind: zipkinmodel.Kind(s.Kind.String()), 88 | Timestamp: microsToTime(s.Timestamp), 89 | Tags: s.Tags, 90 | Duration: microsToDuration(s.Duration), 91 | LocalEndpoint: protoEndpointToModelEndpoint(s.LocalEndpoint), 92 | RemoteEndpoint: protoEndpointToModelEndpoint(s.RemoteEndpoint), 93 | Shared: s.Shared, 94 | Annotations: protoAnnotationsToModelAnnotations(s.Annotations), 95 | } 96 | 97 | return zms, nil 98 | } 99 | 100 | func microsToDuration(us uint64) time.Duration { 101 | // us to ns; ns are the units of Duration 102 | return time.Duration(us * 1e3) 103 | } 104 | 105 | func protoEndpointToModelEndpoint(zpe *Endpoint) *zipkinmodel.Endpoint { 106 | if zpe == nil { 107 | return nil 108 | } 109 | return &zipkinmodel.Endpoint{ 110 | ServiceName: zpe.ServiceName, 111 | IPv4: net.IP(zpe.Ipv4), 112 | IPv6: net.IP(zpe.Ipv6), 113 | Port: uint16(zpe.Port), 114 | } 115 | } 116 | 117 | func protoSpanIDToModelSpanID(spanId []byte) (zid *zipkinmodel.ID, blank bool, err error) { 118 | if len(spanId) == 0 { 119 | return nil, true, nil 120 | } 121 | if len(spanId) != 8 { 122 | return nil, true, fmt.Errorf("has length %d yet wanted length 8", len(spanId)) 123 | } 124 | 125 | // Converting [8]byte --> uint64 126 | u64 := binary.BigEndian.Uint64(spanId) 127 | zid_ := zipkinmodel.ID(u64) 128 | return &zid_, false, nil 129 | } 130 | 131 | func protoAnnotationsToModelAnnotations(zpa []*Annotation) (zma []zipkinmodel.Annotation) { 132 | for _, za := range zpa { 133 | if za != nil { 134 | zma = append(zma, zipkinmodel.Annotation{ 135 | Timestamp: microsToTime(za.Timestamp), 136 | Value: za.Value, 137 | }) 138 | } 139 | } 140 | 141 | if len(zma) == 0 { 142 | return nil 143 | } 144 | return zma 145 | } 146 | 147 | func microsToTime(us uint64) time.Time { 148 | return time.Unix(0, int64(us*1e3)).UTC() 149 | } 150 | -------------------------------------------------------------------------------- /proto/zipkin_proto3/encode_proto.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package zipkin_proto3 16 | 17 | import ( 18 | "encoding/binary" 19 | "errors" 20 | "time" 21 | 22 | zipkinmodel "github.com/openzipkin/zipkin-go/model" 23 | "google.golang.org/protobuf/proto" 24 | ) 25 | 26 | var errNilProtoSpan = errors.New("expecting a non-nil Span") 27 | 28 | // SpanSerializer implements http.SpanSerializer 29 | type SpanSerializer struct{} 30 | 31 | // Serialize takes an array of zipkin SpanModel objects and serializes it to a protobuf blob. 32 | func (SpanSerializer) Serialize(sms []*zipkinmodel.SpanModel) (protoBlob []byte, err error) { 33 | var listOfSpans ListOfSpans 34 | 35 | for _, sm := range sms { 36 | sp, err := modelSpanToProtoSpan(sm) 37 | if err != nil { 38 | return nil, err 39 | } 40 | listOfSpans.Spans = append(listOfSpans.Spans, sp) 41 | } 42 | 43 | return proto.Marshal(&listOfSpans) 44 | } 45 | 46 | // ContentType returns the ContentType needed for this encoding. 47 | func (SpanSerializer) ContentType() string { 48 | return "application/x-protobuf" 49 | } 50 | 51 | func modelSpanToProtoSpan(sm *zipkinmodel.SpanModel) (*Span, error) { 52 | if sm == nil { 53 | return nil, errNilProtoSpan 54 | } 55 | 56 | traceID := make([]byte, 16) 57 | binary.BigEndian.PutUint64(traceID[0:8], uint64(sm.TraceID.High)) 58 | binary.BigEndian.PutUint64(traceID[8:16], uint64(sm.TraceID.Low)) 59 | 60 | parentID := make([]byte, 8) 61 | if sm.ParentID != nil { 62 | binary.BigEndian.PutUint64(parentID, uint64(*sm.ParentID)) 63 | } 64 | 65 | id := make([]byte, 8) 66 | binary.BigEndian.PutUint64(id, uint64(sm.ID)) 67 | 68 | var timeStamp uint64 69 | if !sm.Timestamp.IsZero() { 70 | timeStamp = uint64(sm.Timestamp.Round(time.Microsecond).UnixNano() / 1e3) 71 | } 72 | 73 | return &Span{ 74 | TraceId: traceID, 75 | ParentId: parentID, 76 | Id: id, 77 | Debug: sm.Debug, 78 | Kind: Span_Kind(Span_Kind_value[string(sm.Kind)]), 79 | Name: sm.Name, 80 | Timestamp: timeStamp, 81 | Tags: sm.Tags, 82 | Duration: uint64(sm.Duration.Nanoseconds() / 1e3), 83 | LocalEndpoint: modelEndpointToProtoEndpoint(sm.LocalEndpoint), 84 | RemoteEndpoint: modelEndpointToProtoEndpoint(sm.RemoteEndpoint), 85 | Shared: sm.Shared, 86 | Annotations: modelAnnotationsToProtoAnnotations(sm.Annotations), 87 | }, nil 88 | } 89 | 90 | func durationToMicros(d time.Duration) (uint64, error) { 91 | if d < time.Microsecond { 92 | if d < 0 { 93 | return 0, zipkinmodel.ErrValidDurationRequired 94 | } else if d > 0 { 95 | d = 1 * time.Microsecond 96 | } 97 | } else { 98 | d += 500 * time.Nanosecond 99 | } 100 | return uint64(d.Nanoseconds() / 1e3), nil 101 | } 102 | 103 | func modelEndpointToProtoEndpoint(ep *zipkinmodel.Endpoint) *Endpoint { 104 | if ep == nil { 105 | return nil 106 | } 107 | return &Endpoint{ 108 | ServiceName: ep.ServiceName, 109 | Ipv4: []byte(ep.IPv4), 110 | Ipv6: []byte(ep.IPv6), 111 | Port: int32(ep.Port), 112 | } 113 | } 114 | 115 | func modelAnnotationsToProtoAnnotations(mas []zipkinmodel.Annotation) (pas []*Annotation) { 116 | for _, ma := range mas { 117 | pas = append(pas, &Annotation{ 118 | Timestamp: timeToMicros(ma.Timestamp), 119 | Value: ma.Value, 120 | }) 121 | } 122 | return 123 | } 124 | 125 | func timeToMicros(t time.Time) uint64 { 126 | if t.IsZero() { 127 | return 0 128 | } 129 | return uint64(t.UnixNano()) / 1e3 130 | } 131 | -------------------------------------------------------------------------------- /proto/zipkin_proto3/encode_proto_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package zipkin_proto3_test 16 | 17 | import ( 18 | "encoding/json" 19 | "net" 20 | "reflect" 21 | "testing" 22 | "time" 23 | 24 | zipkinmodel "github.com/openzipkin/zipkin-go/model" 25 | "github.com/openzipkin/zipkin-go/proto/zipkin_proto3" 26 | ) 27 | 28 | func TestExportSpans(t *testing.T) { 29 | want := []*zipkinmodel.SpanModel{ 30 | { 31 | SpanContext: zipkinmodel.SpanContext{ 32 | TraceID: zipkinmodel.TraceID{ 33 | High: 0x7F6F5F4F3F2F1F0F, 34 | Low: 0xF7F6F5F4F3F2F1F0, 35 | }, 36 | ID: 0xF7F6F5F4F3F2F1F0, 37 | ParentID: idPtr(0xF7F6F5F4F3F2F1F0), 38 | Debug: true, 39 | }, 40 | Name: "ProtoSpan1", 41 | Timestamp: now, 42 | Duration: 12 * time.Second, 43 | Shared: false, 44 | Kind: zipkinmodel.Consumer, 45 | LocalEndpoint: &zipkinmodel.Endpoint{ 46 | ServiceName: "svc-1", 47 | IPv4: net.IP{0xC0, 0xA8, 0x00, 0x01}, 48 | Port: 8009, 49 | }, 50 | RemoteEndpoint: &zipkinmodel.Endpoint{ 51 | ServiceName: "memcached", 52 | IPv6: net.IP{0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x53, 0xa7, 0x7c, 0xda, 0x4d, 0xd2, 0x1b}, 53 | Port: 11211, 54 | }, 55 | }, 56 | { 57 | SpanContext: zipkinmodel.SpanContext{ 58 | TraceID: zipkinmodel.TraceID{ 59 | High: 0x7A6A5A4A3A2A1A0A, 60 | Low: 0xC7C6C5C4C3C2C1C0, 61 | }, 62 | ID: 0x6766656463626160, 63 | ParentID: idPtr(0x1716151413121110), 64 | Debug: true, 65 | }, 66 | Name: "CacheWarmUp", 67 | Timestamp: minus10hr5ms, 68 | Kind: zipkinmodel.Producer, 69 | Duration: 7 * time.Second, 70 | LocalEndpoint: &zipkinmodel.Endpoint{ 71 | ServiceName: "search", 72 | IPv4: net.IP{0x0A, 0x00, 0x00, 0x0D}, 73 | Port: 8009, 74 | }, 75 | RemoteEndpoint: &zipkinmodel.Endpoint{ 76 | ServiceName: "redis", 77 | IPv6: net.IP{0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x53, 0xa7, 0x7c, 0xda, 0x4d, 0xd2, 0x1b}, 78 | Port: 6379, 79 | }, 80 | Annotations: []zipkinmodel.Annotation{ 81 | { 82 | Timestamp: minus10hr5ms, 83 | Value: "DB reset", 84 | }, 85 | { 86 | Timestamp: minus10hr5ms, 87 | Value: "GC Cycle 39", 88 | }, 89 | }, 90 | }, 91 | } 92 | 93 | protoBlob, err := zipkin_proto3.SpanSerializer{}.Serialize(want) 94 | if err != nil { 95 | t.Fatalf("Failed to parse spans from protobuf blob: %v", err) 96 | } 97 | 98 | if got, _ := zipkin_proto3.ParseSpans(protoBlob, true); !reflect.DeepEqual(want, got) { 99 | w, _ := json.Marshal(want) 100 | g, _ := json.Marshal(got) 101 | t.Errorf("conversion error!\nWANT:\n%s\n\nGOT:\n%s\n", w, g) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /reporter/amqp/amqp.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | Package amqp implements a RabbitMq reporter to send spans to a Rabbit server/cluster. 17 | */ 18 | package amqp 19 | 20 | import ( 21 | "encoding/json" 22 | "fmt" 23 | "log" 24 | "os" 25 | 26 | amqp "github.com/rabbitmq/amqp091-go" 27 | 28 | "github.com/openzipkin/zipkin-go/model" 29 | "github.com/openzipkin/zipkin-go/reporter" 30 | ) 31 | 32 | // defaultRmqRoutingKey/Exchange/Kind sets the standard RabbitMQ queue our Reporter will publish on. 33 | const ( 34 | defaultRmqRoutingKey = "zipkin" 35 | defaultRmqExchange = "zipkin" 36 | defaultExchangeKind = "direct" 37 | ) 38 | 39 | // rmqReporter implements Reporter by publishing spans to a RabbitMQ exchange 40 | type rmqReporter struct { 41 | e chan error 42 | channel *amqp.Channel 43 | conn *amqp.Connection 44 | exchange string 45 | queue string 46 | logger *log.Logger 47 | } 48 | 49 | // ReporterOption sets a parameter for the rmqReporter 50 | type ReporterOption func(c *rmqReporter) 51 | 52 | // Logger sets the logger used to report errors in the collection 53 | // process. 54 | func Logger(logger *log.Logger) ReporterOption { 55 | return func(c *rmqReporter) { 56 | c.logger = logger 57 | } 58 | } 59 | 60 | // Exchange sets the Exchange used to send messages ( 61 | // see https://github.com/openzipkin/zipkin/tree/master/zipkin-collector/rabbitmq 62 | // if want to change default routing key or exchange 63 | func Exchange(exchange string) ReporterOption { 64 | return func(c *rmqReporter) { 65 | c.exchange = exchange 66 | } 67 | } 68 | 69 | // Queue sets the Queue used to send messages 70 | func Queue(queue string) ReporterOption { 71 | return func(c *rmqReporter) { 72 | c.queue = queue 73 | } 74 | } 75 | 76 | // Channel sets the Channel used to send messages 77 | func Channel(ch *amqp.Channel) ReporterOption { 78 | return func(c *rmqReporter) { 79 | c.channel = ch 80 | } 81 | } 82 | 83 | // Connection sets the Connection used to send messages 84 | func Connection(conn *amqp.Connection) ReporterOption { 85 | return func(c *rmqReporter) { 86 | c.conn = conn 87 | } 88 | } 89 | 90 | // NewReporter returns a new RabbitMq-backed Reporter. address should be as described here: https://www.rabbitmq.com/uri-spec.html 91 | func NewReporter(address string, options ...ReporterOption) (reporter.Reporter, error) { 92 | r := &rmqReporter{ 93 | logger: log.New(os.Stderr, "", log.LstdFlags), 94 | queue: defaultRmqRoutingKey, 95 | exchange: defaultRmqExchange, 96 | e: make(chan error), 97 | } 98 | 99 | for _, option := range options { 100 | option(r) 101 | } 102 | 103 | checks := []func() error{ 104 | r.queueVerify, 105 | r.exchangeVerify, 106 | r.queueBindVerify, 107 | } 108 | 109 | var err error 110 | 111 | if r.conn == nil { 112 | r.conn, err = amqp.Dial(address) 113 | if err != nil { 114 | return nil, err 115 | } 116 | } 117 | 118 | if r.channel == nil { 119 | r.channel, err = r.conn.Channel() 120 | if err != nil { 121 | return nil, err 122 | } 123 | } 124 | 125 | for i := 0; i < len(checks); i++ { 126 | if err := checks[i](); err != nil { 127 | return nil, err 128 | } 129 | } 130 | 131 | go r.logErrors() 132 | 133 | return r, nil 134 | } 135 | 136 | func (r *rmqReporter) logErrors() { 137 | for err := range r.e { 138 | r.logger.Print("msg", err.Error()) 139 | } 140 | } 141 | 142 | func (r *rmqReporter) Send(s model.SpanModel) { 143 | // Zipkin expects the message to be wrapped in an array 144 | ss := []model.SpanModel{s} 145 | m, err := json.Marshal(ss) 146 | if err != nil { 147 | r.e <- fmt.Errorf("failed when marshalling the span: %s", err.Error()) 148 | return 149 | } 150 | 151 | msg := amqp.Publishing{ 152 | Body: m, 153 | } 154 | 155 | err = r.channel.Publish(defaultRmqExchange, defaultRmqRoutingKey, false, false, msg) 156 | if err != nil { 157 | r.e <- fmt.Errorf("failed when publishing the span: %s", err.Error()) 158 | } 159 | } 160 | 161 | func (r *rmqReporter) queueBindVerify() error { 162 | return r.channel.QueueBind( 163 | defaultRmqRoutingKey, 164 | defaultRmqRoutingKey, 165 | defaultRmqExchange, 166 | false, 167 | nil) 168 | } 169 | 170 | func (r *rmqReporter) exchangeVerify() error { 171 | err := r.channel.ExchangeDeclare( 172 | defaultRmqExchange, 173 | defaultExchangeKind, 174 | true, 175 | false, 176 | false, 177 | false, 178 | nil, 179 | ) 180 | 181 | if err != nil { 182 | return err 183 | } 184 | 185 | return nil 186 | } 187 | 188 | func (r *rmqReporter) queueVerify() error { 189 | _, err := r.channel.QueueDeclare( 190 | defaultRmqExchange, 191 | true, 192 | false, 193 | false, 194 | false, 195 | nil, 196 | ) 197 | if err != nil { 198 | return err 199 | } 200 | 201 | return nil 202 | } 203 | 204 | func (r *rmqReporter) Close() error { 205 | err := r.channel.Close() 206 | if err != nil { 207 | return err 208 | } 209 | 210 | err = r.conn.Close() 211 | if err != nil { 212 | return err 213 | } 214 | return nil 215 | } 216 | -------------------------------------------------------------------------------- /reporter/amqp/amqp_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !windows 16 | // +build !windows 17 | 18 | package amqp_test 19 | 20 | import ( 21 | "encoding/json" 22 | "os" 23 | "testing" 24 | "time" 25 | 26 | "github.com/openzipkin/zipkin-go/model" 27 | zipkinamqp "github.com/openzipkin/zipkin-go/reporter/amqp" 28 | amqp "github.com/rabbitmq/amqp091-go" 29 | ) 30 | 31 | var spans = []*model.SpanModel{ 32 | makeNewSpan("avg", 123, 456, 0, true), 33 | makeNewSpan("sum", 123, 789, 456, true), 34 | makeNewSpan("div", 123, 101112, 456, true), 35 | } 36 | 37 | func TestRabbitProduce(t *testing.T) { 38 | address := os.Getenv("AMQP_ADDR") 39 | if address == "" { 40 | t.Skip("AMQP_ADDR not set, skipping test...") 41 | } 42 | _, ch, closeFunc := setupRabbit(t, address) 43 | defer closeFunc() 44 | 45 | c, err := zipkinamqp.NewReporter(address, zipkinamqp.Channel(ch)) 46 | if err != nil { 47 | t.Fatal(err) 48 | } 49 | 50 | msgs := setupConsume(t, ch) 51 | 52 | for _, s := range spans { 53 | c.Send(*s) 54 | } 55 | 56 | for _, s := range spans { 57 | msg := <-msgs 58 | ds := deserializeSpan(t, msg.Body) 59 | testEqual(t, s, ds) 60 | } 61 | } 62 | 63 | func TestRabbitClose(t *testing.T) { 64 | address := os.Getenv("AMQP_ADDR") 65 | if address == "" { 66 | t.Skip("AMQP_ADDR not set, skipping test...") 67 | } 68 | conn, ch, closeFunc := setupRabbit(t, address) 69 | defer closeFunc() 70 | 71 | r, err := zipkinamqp.NewReporter(address, zipkinamqp.Channel(ch), zipkinamqp.Connection(conn)) 72 | if err != nil { 73 | t.Fatal(err) 74 | } 75 | if err = r.Close(); err != nil { 76 | t.Fatal(err) 77 | } 78 | } 79 | 80 | func setupRabbit(t *testing.T, address string) (*amqp.Connection, *amqp.Channel, func()) { 81 | var err error 82 | conn, err := amqp.Dial(address) 83 | failOnError(t, err, "Failed to connect to RabbitMQ") 84 | 85 | ch, err := conn.Channel() 86 | failOnError(t, err, "Failed to open a channel") 87 | 88 | return conn, ch, func() { conn.Close(); ch.Close() } 89 | } 90 | 91 | func setupConsume(t *testing.T, ch *amqp.Channel) <-chan amqp.Delivery { 92 | csm, err := ch.Consume( 93 | "zipkin", 94 | "", 95 | true, 96 | false, 97 | false, 98 | false, 99 | nil, 100 | ) 101 | failOnError(t, err, "Failed to register a consumer") 102 | return csm 103 | } 104 | 105 | func deserializeSpan(t *testing.T, data []byte) *model.SpanModel { 106 | var receivedSpans []model.SpanModel 107 | err := json.Unmarshal(data, &receivedSpans) 108 | if err != nil { 109 | t.Fatal(err) 110 | } 111 | return &receivedSpans[0] 112 | } 113 | 114 | func failOnError(t *testing.T, err error, msg string) { 115 | if err != nil { 116 | t.Fatalf("%s: %s", msg, err) 117 | } 118 | } 119 | 120 | func testEqual(t *testing.T, want *model.SpanModel, have *model.SpanModel) { 121 | if have.TraceID != want.TraceID { 122 | t.Errorf("incorrect trace_id. have %d, want %d", have.TraceID, want.TraceID) 123 | } 124 | if have.ID != want.ID { 125 | t.Errorf("incorrect id. have %d, want %d", have.ID, want.ID) 126 | } 127 | if have.ParentID == nil { 128 | if want.ParentID != nil { 129 | t.Errorf("incorrect parent_id. have %d, want %d", have.ParentID, want.ParentID) 130 | } 131 | } else if *have.ParentID != *want.ParentID { 132 | t.Errorf("incorrect parent_id. have %d, want %d", have.ParentID, want.ParentID) 133 | } 134 | } 135 | 136 | func makeNewSpan(methodName string, traceID, spanID, parentSpanID uint64, debug bool) *model.SpanModel { 137 | timestamp := time.Now() 138 | parentID := new(model.ID) 139 | if parentSpanID != 0 { 140 | *parentID = model.ID(parentSpanID) 141 | } 142 | 143 | return &model.SpanModel{ 144 | SpanContext: model.SpanContext{ 145 | TraceID: model.TraceID{Low: traceID}, 146 | ID: model.ID(spanID), 147 | ParentID: parentID, 148 | Debug: debug, 149 | }, 150 | Name: methodName, 151 | Timestamp: timestamp, 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /reporter/kafka/kafka.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | Package kafka implements a Kafka reporter to send spans to a Kafka server/cluster. 17 | */ 18 | package kafka 19 | 20 | import ( 21 | "log" 22 | "os" 23 | 24 | "github.com/IBM/sarama" 25 | 26 | "github.com/openzipkin/zipkin-go/model" 27 | "github.com/openzipkin/zipkin-go/reporter" 28 | ) 29 | 30 | // defaultKafkaTopic sets the standard Kafka topic our Reporter will publish 31 | // on. The default topic for zipkin-receiver-kafka is "zipkin", see: 32 | // https://github.com/openzipkin/zipkin/tree/master/zipkin-receiver-kafka 33 | const defaultKafkaTopic = "zipkin" 34 | 35 | // kafkaReporter implements Reporter by publishing spans to a Kafka 36 | // broker. 37 | type kafkaReporter struct { 38 | producer sarama.AsyncProducer 39 | logger *log.Logger 40 | topic string 41 | serializer reporter.SpanSerializer 42 | } 43 | 44 | // ReporterOption sets a parameter for the kafkaReporter 45 | type ReporterOption func(c *kafkaReporter) 46 | 47 | // Logger sets the logger used to report errors in the collection 48 | // process. 49 | func Logger(logger *log.Logger) ReporterOption { 50 | return func(c *kafkaReporter) { 51 | c.logger = logger 52 | } 53 | } 54 | 55 | // Producer sets the producer used to produce to Kafka. For tweaking 56 | // the reporting settings (e.g. reporting timeout or authentication) 57 | // check the sarama.Config struct. 58 | func Producer(p sarama.AsyncProducer) ReporterOption { 59 | return func(c *kafkaReporter) { 60 | c.producer = p 61 | } 62 | } 63 | 64 | // Topic sets the kafka topic to attach the reporter producer on. 65 | func Topic(t string) ReporterOption { 66 | return func(c *kafkaReporter) { 67 | c.topic = t 68 | } 69 | } 70 | 71 | // Serializer sets the serialization function to use for sending span data to 72 | // Zipkin. 73 | func Serializer(serializer reporter.SpanSerializer) ReporterOption { 74 | return func(c *kafkaReporter) { 75 | if serializer != nil { 76 | c.serializer = serializer 77 | } 78 | } 79 | } 80 | 81 | // NewReporter returns a new Kafka-backed Reporter. address should be a slice of 82 | // TCP endpoints of the form "host:port". 83 | func NewReporter(address []string, options ...ReporterOption) (reporter.Reporter, error) { 84 | r := &kafkaReporter{ 85 | logger: log.New(os.Stderr, "", log.LstdFlags), 86 | topic: defaultKafkaTopic, 87 | serializer: reporter.JSONSerializer{}, 88 | } 89 | 90 | for _, option := range options { 91 | option(r) 92 | } 93 | if r.producer == nil { 94 | p, err := sarama.NewAsyncProducer(address, nil) 95 | if err != nil { 96 | return nil, err 97 | } 98 | r.producer = p 99 | } 100 | 101 | go r.logErrors() 102 | 103 | return r, nil 104 | } 105 | 106 | func (r *kafkaReporter) logErrors() { 107 | for pe := range r.producer.Errors() { 108 | r.logger.Print("msg", pe.Msg, "err", pe.Err, "result", "failed to produce msg") 109 | } 110 | } 111 | 112 | func (r *kafkaReporter) Send(s model.SpanModel) { 113 | // Zipkin expects the message to be wrapped in an array 114 | ss := []*model.SpanModel{&s} 115 | m, err := r.serializer.Serialize(ss) 116 | if err != nil { 117 | r.logger.Printf("failed when marshalling the span: %s\n", err.Error()) 118 | return 119 | } 120 | 121 | r.producer.Input() <- &sarama.ProducerMessage{ 122 | Topic: r.topic, 123 | Key: nil, 124 | Value: sarama.ByteEncoder(m), 125 | } 126 | } 127 | 128 | func (r *kafkaReporter) Close() error { 129 | return r.producer.Close() 130 | } 131 | -------------------------------------------------------------------------------- /reporter/log/log.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | Package log implements a reporter to send spans in V2 JSON format to the Go 17 | standard Logger. 18 | */ 19 | package log 20 | 21 | import ( 22 | "encoding/json" 23 | "log" 24 | "os" 25 | "time" 26 | 27 | "github.com/openzipkin/zipkin-go/model" 28 | "github.com/openzipkin/zipkin-go/reporter" 29 | ) 30 | 31 | // logReporter will send spans to the default Go Logger. 32 | type logReporter struct { 33 | logger *log.Logger 34 | } 35 | 36 | // NewReporter returns a new log reporter. 37 | func NewReporter(l *log.Logger) reporter.Reporter { 38 | if l == nil { 39 | // use standard type of log setup 40 | l = log.New(os.Stderr, "", log.LstdFlags) 41 | } 42 | return &logReporter{ 43 | logger: l, 44 | } 45 | } 46 | 47 | // Send outputs a span to the Go logger. 48 | func (r *logReporter) Send(s model.SpanModel) { 49 | if b, err := json.MarshalIndent(s, "", " "); err == nil { 50 | r.logger.Printf("%s:\n%s\n\n", time.Now(), string(b)) 51 | } 52 | } 53 | 54 | // Close closes the reporter 55 | func (*logReporter) Close() error { return nil } 56 | -------------------------------------------------------------------------------- /reporter/recorder/recorder.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | Package recorder implements a reporter to record spans in v2 format. 17 | */ 18 | package recorder 19 | 20 | import ( 21 | "sync" 22 | 23 | "github.com/openzipkin/zipkin-go/model" 24 | ) 25 | 26 | // ReporterRecorder records Zipkin spans. 27 | type ReporterRecorder struct { 28 | mtx sync.Mutex 29 | spans []model.SpanModel 30 | } 31 | 32 | // NewReporter returns a new recording reporter. 33 | func NewReporter() *ReporterRecorder { 34 | return &ReporterRecorder{} 35 | } 36 | 37 | // Send adds the provided span to the span list held by the recorder. 38 | func (r *ReporterRecorder) Send(span model.SpanModel) { 39 | r.mtx.Lock() 40 | r.spans = append(r.spans, span) 41 | r.mtx.Unlock() 42 | } 43 | 44 | // Flush returns all recorded spans and clears its internal span storage 45 | func (r *ReporterRecorder) Flush() []model.SpanModel { 46 | r.mtx.Lock() 47 | spans := r.spans 48 | r.spans = nil 49 | r.mtx.Unlock() 50 | return spans 51 | } 52 | 53 | // Close flushes the reporter 54 | func (r *ReporterRecorder) Close() error { 55 | r.Flush() 56 | return nil 57 | } 58 | -------------------------------------------------------------------------------- /reporter/recorder/recorder_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package recorder 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/openzipkin/zipkin-go/model" 21 | ) 22 | 23 | func TestFlushInRecorderSuccess(t *testing.T) { 24 | rec := NewReporter() 25 | 26 | span := model.SpanModel{} 27 | rec.Send(span) 28 | 29 | if len(rec.spans) != 1 { 30 | t.Fatalf("Span Count want 1, have %d", len(rec.spans)) 31 | } 32 | 33 | rec.Flush() 34 | 35 | if len(rec.spans) != 0 { 36 | t.Fatalf("Span Count want 0, have %d", len(rec.spans)) 37 | } 38 | } 39 | 40 | func TestCloseInRecorderSuccess(t *testing.T) { 41 | rec := NewReporter() 42 | 43 | span := model.SpanModel{} 44 | rec.Send(span) 45 | 46 | if len(rec.spans) != 1 { 47 | t.Fatalf("Span Count want 1, have %d", len(rec.spans)) 48 | } 49 | 50 | rec.Close() 51 | 52 | if len(rec.spans) != 0 { 53 | t.Fatalf("Span Count want 0, have %d", len(rec.spans)) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /reporter/reporter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | Package reporter holds the Reporter interface which is used by the Zipkin 17 | Tracer to send finished spans. 18 | 19 | Subpackages of package reporter contain officially supported standard 20 | reporter implementations. 21 | */ 22 | package reporter 23 | 24 | import "github.com/openzipkin/zipkin-go/model" 25 | 26 | // Reporter interface can be used to provide the Zipkin Tracer with custom 27 | // implementations to publish Zipkin Span data. 28 | type Reporter interface { 29 | Send(model.SpanModel) // Send Span data to the reporter 30 | Close() error // Close the reporter 31 | } 32 | 33 | type noopReporter struct{} 34 | 35 | func (r *noopReporter) Send(model.SpanModel) {} 36 | func (r *noopReporter) Close() error { return nil } 37 | 38 | // NewNoopReporter returns a no-op Reporter implementation. 39 | func NewNoopReporter() Reporter { 40 | return &noopReporter{} 41 | } 42 | -------------------------------------------------------------------------------- /reporter/serializer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package reporter 16 | 17 | import ( 18 | "encoding/json" 19 | 20 | "github.com/openzipkin/zipkin-go/model" 21 | ) 22 | 23 | // SpanSerializer describes the methods needed for allowing to set Span encoding 24 | // type for the various Zipkin transports. 25 | type SpanSerializer interface { 26 | Serialize([]*model.SpanModel) ([]byte, error) 27 | ContentType() string 28 | } 29 | 30 | // JSONSerializer implements the default JSON encoding SpanSerializer. 31 | type JSONSerializer struct{} 32 | 33 | // Serialize takes an array of Zipkin SpanModel objects and returns a JSON 34 | // encoding of it. 35 | func (JSONSerializer) Serialize(spans []*model.SpanModel) ([]byte, error) { 36 | return json.Marshal(spans) 37 | } 38 | 39 | // ContentType returns the ContentType needed for this encoding. 40 | func (JSONSerializer) ContentType() string { 41 | return "application/json" 42 | } 43 | -------------------------------------------------------------------------------- /sample.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package zipkin 16 | 17 | import ( 18 | "fmt" 19 | "math" 20 | "math/rand" 21 | "sync" 22 | "time" 23 | ) 24 | 25 | // Sampler functions return if a Zipkin span should be sampled, based on its 26 | // traceID. 27 | type Sampler func(id uint64) bool 28 | 29 | // NeverSample will always return false. If used by a service it will not allow 30 | // the service to start traces but will still allow the service to participate 31 | // in traces started upstream. 32 | func NeverSample(_ uint64) bool { return false } 33 | 34 | // AlwaysSample will always return true. If used by a service it will always start 35 | // traces if no upstream trace has been propagated. If an incoming upstream trace 36 | // is not sampled the service will adhere to this and only propagate the context. 37 | func AlwaysSample(_ uint64) bool { return true } 38 | 39 | // NewModuloSampler provides a generic type Sampler. 40 | func NewModuloSampler(mod uint64) Sampler { 41 | if mod < 2 { 42 | return AlwaysSample 43 | } 44 | return func(id uint64) bool { 45 | return (id % mod) == 0 46 | } 47 | } 48 | 49 | // NewBoundarySampler is appropriate for high-traffic instrumentation who 50 | // provision random trace ids, and make the sampling decision only once. 51 | // It defends against nodes in the cluster selecting exactly the same ids. 52 | func NewBoundarySampler(rate float64, salt int64) (Sampler, error) { 53 | if rate == 0.0 { 54 | return NeverSample, nil 55 | } 56 | if rate == 1.0 { 57 | return AlwaysSample, nil 58 | } 59 | if rate < 0.0001 || rate > 1 { 60 | return nil, fmt.Errorf("rate should be 0.0 or between 0.0001 and 1: was %f", rate) 61 | } 62 | 63 | var ( 64 | // convert rate into a proportional boundary where values below it are sampled 65 | // (e.g., 1% rate ≈ first 1% of space) 66 | boundary = uint64(rate * (1 << 63)) 67 | usalt = uint64(salt) 68 | ) 69 | return func(id uint64) bool { 70 | // XOR with salt provides deterministic randomization 71 | // right shift ensures uniform distribution across [0, 2^63) 72 | return ((id ^ usalt) >> 1) < boundary 73 | }, nil 74 | } 75 | 76 | // NewCountingSampler is appropriate for low-traffic instrumentation or 77 | // those who do not provision random trace ids. It is not appropriate for 78 | // collectors as the sampling decision isn't idempotent (consistent based 79 | // on trace id). 80 | func NewCountingSampler(rate float64) (Sampler, error) { 81 | if rate == 0.0 { 82 | return NeverSample, nil 83 | } 84 | if rate == 1.0 { 85 | return AlwaysSample, nil 86 | } 87 | if rate < 0.01 || rate > 1 { 88 | return nil, fmt.Errorf("rate should be 0.0 or between 0.01 and 1: was %f", rate) 89 | } 90 | var ( 91 | i = 0 92 | outOf100 = int(rate*100 + math.Copysign(0.5, rate*100)) // for rounding float to int conversion instead of truncation 93 | decisions = randomBitSet(100, outOf100, rand.New(rand.NewSource(time.Now().UnixNano()))) 94 | mtx = &sync.Mutex{} 95 | ) 96 | 97 | return func(_ uint64) bool { 98 | mtx.Lock() 99 | result := decisions[i] 100 | i++ 101 | if i == 100 { 102 | i = 0 103 | } 104 | mtx.Unlock() 105 | return result 106 | }, nil 107 | } 108 | 109 | /** 110 | * Reservoir sampling algorithm borrowed from Stack Overflow. 111 | * 112 | * http://stackoverflow.com/questions/12817946/generate-a-random-bitset-with-n-1s 113 | */ 114 | func randomBitSet(size int, cardinality int, rnd *rand.Rand) []bool { 115 | result := make([]bool, size) 116 | chosen := make([]int, cardinality) 117 | var i int 118 | for i = 0; i < cardinality; i++ { 119 | chosen[i] = i 120 | result[i] = true 121 | } 122 | for ; i < size; i++ { 123 | j := rnd.Intn(i + 1) 124 | if j < cardinality { 125 | result[chosen[j]] = false 126 | result[i] = true 127 | chosen[j] = i 128 | } 129 | } 130 | return result 131 | } 132 | -------------------------------------------------------------------------------- /sample_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package zipkin_test 16 | 17 | import ( 18 | "fmt" 19 | "math/rand" 20 | "testing" 21 | "time" 22 | 23 | zipkin "github.com/openzipkin/zipkin-go" 24 | ) 25 | 26 | func TestBoundarySampler(t *testing.T) { 27 | type triple struct { 28 | id uint64 29 | salt int64 30 | rate float64 31 | hasError bool 32 | } 33 | for input, sampled := range map[triple]bool{ 34 | {123, 456, 1.0, false}: true, 35 | {123, 456, 999, true}: true, 36 | {123, 456, 0.0, false}: false, 37 | {123, 456, -42, true}: false, 38 | {0xffffffffffffffff, 0, 0.01, false}: false, 39 | {0xa000000000000000, 0, 0.01, false}: false, 40 | {0x028f5c28f5c28f5f, 0, 0.01, false}: true, 41 | {0x028f5c28f5c28f60, 0, 0.01, false}: false, 42 | {1, 0xfffffffffffffff, 0.01, false}: false, 43 | {999, 0, 0.99, false}: true, 44 | } { 45 | sampler, err := zipkin.NewBoundarySampler(input.rate, input.salt) 46 | if want, have := input.hasError, (err != nil); want != have { 47 | t.Fatalf("%#+v: want error %t, have error %t", input, want, have) 48 | } 49 | if input.hasError { 50 | want := fmt.Errorf("rate should be 0.0 or between 0.0001 and 1: was %f", input.rate) 51 | if have := err; have == nil || want.Error() != have.Error() { 52 | t.Fatalf("%#+v: want error %+v, have error %+v", input, want, have) 53 | } 54 | continue 55 | } 56 | if want, have := sampled, sampler(input.id); want != have { 57 | t.Errorf("%#+v: want %v, have %v", input, want, have) 58 | } 59 | 60 | } 61 | } 62 | 63 | func TestBoundarySamplerProducesSamplingDecisionsTrueToTheRate(t *testing.T) { 64 | rand.Uint64() 65 | c := 0 66 | sampler, _ := zipkin.NewBoundarySampler(0.01, 0) 67 | n := 10000 68 | for i := 0; i < n; i++ { 69 | id := rand.Uint64() 70 | if sampler(id) { 71 | c++ 72 | } 73 | } 74 | if !(c > 50 && c < 150) { 75 | t.Error("should sample at 1%, should be in vicinity of 100") 76 | } 77 | } 78 | 79 | func TestCountingSampler(t *testing.T) { 80 | { 81 | _, have := zipkin.NewCountingSampler(0.009) 82 | want := fmt.Errorf("rate should be 0.0 or between 0.01 and 1: was %f", 0.009) 83 | if have == nil || want.Error() != have.Error() { 84 | t.Errorf("rate 0.009, want error %+v, got %+v", want, have) 85 | } 86 | } 87 | { 88 | _, have := zipkin.NewCountingSampler(1.001) 89 | want := fmt.Errorf("rate should be 0.0 or between 0.01 and 1: was %f", 1.001) 90 | if have == nil || want.Error() != have.Error() { 91 | t.Errorf("rate 1.001, want error %+v, got %+v", want, have) 92 | } 93 | } 94 | for n := 0; n <= 100; n++ { 95 | var ( 96 | rate = float64(n) / 100 97 | sampler, _ = zipkin.NewCountingSampler(rate) 98 | found = 0 99 | ) 100 | for i := 0; i < 1000; i++ { 101 | if sampler(1) { 102 | found++ 103 | } 104 | } 105 | if found != n*10 { 106 | t.Errorf("rate %f, want %d, have %d", rate, n, found) 107 | } 108 | } 109 | } 110 | 111 | func TestModuleSampler(t *testing.T) { 112 | rand.Seed(time.Now().Unix()) 113 | 114 | for mod := uint64(1); mod <= 100; mod++ { 115 | var ( 116 | sampler = zipkin.NewModuloSampler(mod) 117 | want = uint64(rand.Intn(1000)) 118 | max = mod * want 119 | found = uint64(0) 120 | ) 121 | 122 | for i := uint64(0); i < max; i++ { 123 | if sampler(i) { 124 | found++ 125 | } 126 | } 127 | 128 | if want, have := max/mod, found; want != have { 129 | t.Errorf("expected %d samples, got %d", want, have) 130 | } 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /span.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package zipkin 16 | 17 | import ( 18 | "time" 19 | 20 | "github.com/openzipkin/zipkin-go/model" 21 | ) 22 | 23 | // Span interface as returned by Tracer.StartSpan() 24 | type Span interface { 25 | // Context returns the Span's SpanContext. 26 | Context() model.SpanContext 27 | 28 | // SetName updates the Span's name. 29 | SetName(string) 30 | 31 | // SetRemoteEndpoint updates the Span's Remote Endpoint. 32 | SetRemoteEndpoint(*model.Endpoint) 33 | 34 | // Annotate adds a timed event to the Span. 35 | Annotate(time.Time, string) 36 | 37 | // Tag sets Tag with given key and value to the Span. If key already exists in 38 | // the Span the value will be overridden except for error tags where the first 39 | // value is persisted. 40 | Tag(string, string) 41 | 42 | // Finish the Span and send to Reporter. If DelaySend option was used at 43 | // Span creation time, Finish will not send the Span to the Reporter. It then 44 | // becomes the user's responsibility to get the Span reported (by using 45 | // span.Flush). 46 | Finish() 47 | 48 | // Finish the Span with duration and send to Reporter. If DelaySend option was used at 49 | // Span creation time, FinishedWithDuration will not send the Span to the Reporter. It then 50 | // becomes the user's responsibility to get the Span reported (by using 51 | // span.Flush). 52 | FinishedWithDuration(duration time.Duration) 53 | 54 | // Flush the Span to the Reporter (regardless of being finished or not). 55 | // This can be used if the DelaySend SpanOption was set or when dealing with 56 | // one-way RPC tracing where duration might not be measured. 57 | Flush() 58 | } 59 | -------------------------------------------------------------------------------- /span_implementation.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package zipkin 16 | 17 | import ( 18 | "sync" 19 | "sync/atomic" 20 | "time" 21 | 22 | "github.com/openzipkin/zipkin-go/model" 23 | ) 24 | 25 | type spanImpl struct { 26 | mtx sync.RWMutex 27 | model.SpanModel 28 | tracer *Tracer 29 | mustCollect int32 // used as atomic bool (1 = true, 0 = false) 30 | flushOnFinish bool 31 | } 32 | 33 | func (s *spanImpl) Context() model.SpanContext { 34 | return s.SpanContext 35 | } 36 | 37 | func (s *spanImpl) SetName(name string) { 38 | s.mtx.Lock() 39 | s.Name = name 40 | s.mtx.Unlock() 41 | } 42 | 43 | func (s *spanImpl) SetRemoteEndpoint(e *model.Endpoint) { 44 | s.mtx.Lock() 45 | if e == nil { 46 | s.RemoteEndpoint = nil 47 | } else { 48 | s.RemoteEndpoint = &model.Endpoint{} 49 | *s.RemoteEndpoint = *e 50 | } 51 | s.mtx.Unlock() 52 | } 53 | 54 | func (s *spanImpl) Annotate(t time.Time, value string) { 55 | a := model.Annotation{ 56 | Timestamp: t, 57 | Value: value, 58 | } 59 | 60 | s.mtx.Lock() 61 | s.Annotations = append(s.Annotations, a) 62 | s.mtx.Unlock() 63 | } 64 | 65 | func (s *spanImpl) Tag(key, value string) { 66 | s.mtx.Lock() 67 | 68 | if key == string(TagError) { 69 | if _, found := s.Tags[key]; found { 70 | s.mtx.Unlock() 71 | return 72 | } 73 | } 74 | 75 | s.Tags[key] = value 76 | s.mtx.Unlock() 77 | } 78 | 79 | func (s *spanImpl) Finish() { 80 | if atomic.CompareAndSwapInt32(&s.mustCollect, 1, 0) { 81 | s.Duration = time.Since(s.Timestamp) 82 | if s.flushOnFinish { 83 | s.mtx.RLock() 84 | s.tracer.reporter.Send(s.SpanModel) 85 | s.mtx.RUnlock() 86 | } 87 | } 88 | } 89 | 90 | func (s *spanImpl) FinishedWithDuration(d time.Duration) { 91 | if atomic.CompareAndSwapInt32(&s.mustCollect, 1, 0) { 92 | s.Duration = d 93 | if s.flushOnFinish { 94 | s.mtx.RLock() 95 | s.tracer.reporter.Send(s.SpanModel) 96 | s.mtx.RUnlock() 97 | } 98 | } 99 | } 100 | 101 | func (s *spanImpl) Flush() { 102 | if s.SpanModel.Debug || (s.SpanModel.Sampled != nil && *s.SpanModel.Sampled) { 103 | s.mtx.RLock() 104 | s.tracer.reporter.Send(s.SpanModel) 105 | s.mtx.RUnlock() 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /span_options.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package zipkin 16 | 17 | import ( 18 | "time" 19 | 20 | "github.com/openzipkin/zipkin-go/model" 21 | ) 22 | 23 | // SpanOption allows for functional options to adjust behavior and payload of 24 | // the Span to be created with tracer.StartSpan(). 25 | type SpanOption func(t *Tracer, s *spanImpl) 26 | 27 | // Kind sets the kind of the span being created. 28 | func Kind(kind model.Kind) SpanOption { 29 | return func(_ *Tracer, s *spanImpl) { 30 | s.Kind = kind 31 | } 32 | } 33 | 34 | // Parent will use provided SpanContext as parent to the span being created. 35 | func Parent(sc model.SpanContext) SpanOption { 36 | return func(t *Tracer, s *spanImpl) { 37 | if sc.Err != nil { 38 | // encountered an extraction error 39 | switch t.extractFailurePolicy { 40 | case ExtractFailurePolicyRestart: 41 | case ExtractFailurePolicyError: 42 | panic(s.SpanContext.Err) 43 | case ExtractFailurePolicyTagAndRestart: 44 | s.Tags["error.extract"] = sc.Err.Error() 45 | default: 46 | panic(ErrInvalidExtractFailurePolicy) 47 | } 48 | /* don't use provided SpanContext, but restart trace */ 49 | return 50 | } 51 | s.SpanContext = sc 52 | } 53 | } 54 | 55 | // StartTime uses a given start time for the span being created. 56 | func StartTime(start time.Time) SpanOption { 57 | return func(_ *Tracer, s *spanImpl) { 58 | s.Timestamp = start 59 | } 60 | } 61 | 62 | // RemoteEndpoint sets the remote endpoint of the span being created. 63 | func RemoteEndpoint(e *model.Endpoint) SpanOption { 64 | return func(_ *Tracer, s *spanImpl) { 65 | s.RemoteEndpoint = e 66 | } 67 | } 68 | 69 | // Tags sets initial tags for the span being created. If default tracer tags 70 | // are present they will be overwritten on key collisions. 71 | func Tags(tags map[string]string) SpanOption { 72 | return func(_ *Tracer, s *spanImpl) { 73 | for k, v := range tags { 74 | s.Tags[k] = v 75 | } 76 | } 77 | } 78 | 79 | // FlushOnFinish when set to false will disable span.Finish() to send the Span 80 | // to the Reporter automatically (which is the default behavior). If set to 81 | // false, having the Span be reported becomes the responsibility of the user. 82 | // This is available if late tag data is expected to be only available after the 83 | // required finish time of the Span. 84 | func FlushOnFinish(b bool) SpanOption { 85 | return func(_ *Tracer, s *spanImpl) { 86 | s.flushOnFinish = b 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /span_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package zipkin 16 | 17 | import ( 18 | "reflect" 19 | "testing" 20 | "time" 21 | 22 | "github.com/openzipkin/zipkin-go/reporter" 23 | "github.com/openzipkin/zipkin-go/reporter/recorder" 24 | ) 25 | 26 | func TestSpanNameUpdate(t *testing.T) { 27 | var ( 28 | oldName = "oldName" 29 | newName = "newName" 30 | ) 31 | 32 | tracer, _ := NewTracer(reporter.NewNoopReporter()) 33 | 34 | span := tracer.StartSpan(oldName) 35 | 36 | if want, have := oldName, span.(*spanImpl).Name; want != have { 37 | t.Errorf("Name want %q, have %q", want, have) 38 | } 39 | 40 | span.SetName(newName) 41 | 42 | if want, have := newName, span.(*spanImpl).Name; want != have { 43 | t.Errorf("Name want %q, have %q", want, have) 44 | } 45 | } 46 | 47 | func TestRemoteEndpoint(t *testing.T) { 48 | tracer, err := NewTracer(reporter.NewNoopReporter()) 49 | if err != nil { 50 | t.Fatalf("expected valid tracer, got error: %+v", err) 51 | } 52 | 53 | ep1, err := NewEndpoint("myService", "www.google.com:80") 54 | 55 | if err != nil { 56 | t.Fatalf("expected valid endpoint, got error: %+v", err) 57 | } 58 | 59 | span := tracer.StartSpan("test", RemoteEndpoint(ep1)) 60 | 61 | if !reflect.DeepEqual(span.(*spanImpl).RemoteEndpoint, ep1) { 62 | t.Errorf("RemoteEndpoint want %+v, have %+v", ep1, span.(*spanImpl).RemoteEndpoint) 63 | } 64 | 65 | ep2, err := NewEndpoint("otherService", "www.microsoft.com:443") 66 | 67 | if err != nil { 68 | t.Fatalf("expected valid endpoint, got error: %+v", err) 69 | } 70 | 71 | span.SetRemoteEndpoint(ep2) 72 | 73 | if !reflect.DeepEqual(span.(*spanImpl).RemoteEndpoint, ep2) { 74 | t.Errorf("RemoteEndpoint want %+v, have %+v", ep1, span.(*spanImpl).RemoteEndpoint) 75 | } 76 | 77 | span.SetRemoteEndpoint(nil) 78 | 79 | if have := span.(*spanImpl).RemoteEndpoint; have != nil { 80 | t.Errorf("RemoteEndpoint want nil, have %+v", have) 81 | } 82 | } 83 | 84 | func TestTagsSpanOption(t *testing.T) { 85 | tracerTags := map[string]string{ 86 | "key1": "value1", 87 | "key2": "will_be_overwritten", 88 | } 89 | tracer, err := NewTracer(reporter.NewNoopReporter(), WithTags(tracerTags)) 90 | if err != nil { 91 | t.Fatalf("expected valid tracer, got error: %+v", err) 92 | } 93 | 94 | spanTags := map[string]string{ 95 | "key2": "value2", 96 | "key3": "value3", 97 | } 98 | span := tracer.StartSpan("test", Tags(spanTags)) 99 | defer span.Finish() 100 | 101 | allTags := map[string]string{ 102 | "key1": "value1", 103 | "key2": "value2", 104 | "key3": "value3", 105 | } 106 | 107 | if want, have := allTags, span.(*spanImpl).Tags; !reflect.DeepEqual(want, have) { 108 | t.Errorf("Tags don't match:\nwant: %+v\nhave: %+v", want, have) 109 | } 110 | } 111 | 112 | func TestFlushOnFinishSpanOption(t *testing.T) { 113 | rec := recorder.NewReporter() 114 | defer rec.Close() 115 | 116 | tracer, _ := NewTracer(rec) 117 | 118 | span := tracer.StartSpan("test") 119 | time.Sleep(5 * time.Millisecond) 120 | span.Finish() 121 | 122 | spans := rec.Flush() 123 | 124 | if want, have := 1, len(spans); want != have { 125 | t.Errorf("Spans want: %d, have %d", want, have) 126 | } 127 | 128 | span = tracer.StartSpan("test", FlushOnFinish(false)) 129 | time.Sleep(5 * time.Millisecond) 130 | span.Finish() 131 | 132 | spans = rec.Flush() 133 | 134 | if want, have := 0, len(spans); want != have { 135 | t.Errorf("Spans want: %d, have %d", want, have) 136 | } 137 | 138 | span.Tag("post", "finish") 139 | span.Flush() 140 | 141 | spans = rec.Flush() 142 | 143 | if want, have := 1, len(spans); want != have { 144 | t.Errorf("Spans want: %d, have %d", want, have) 145 | } 146 | 147 | if want, have := map[string]string{"post": "finish"}, spans[0].Tags; !reflect.DeepEqual(want, have) { 148 | t.Errorf("Tags want: %+v, have: %+v", want, have) 149 | } 150 | 151 | } 152 | -------------------------------------------------------------------------------- /tags.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package zipkin 16 | 17 | // Tag holds available types 18 | type Tag string 19 | 20 | // Common Tag values 21 | const ( 22 | TagHTTPMethod Tag = "http.method" 23 | TagHTTPPath Tag = "http.path" 24 | TagHTTPUrl Tag = "http.url" 25 | TagHTTPRoute Tag = "http.route" 26 | TagHTTPStatusCode Tag = "http.status_code" 27 | TagHTTPRequestSize Tag = "http.request.size" 28 | TagHTTPResponseSize Tag = "http.response.size" 29 | TagGRPCStatusCode Tag = "grpc.status_code" 30 | TagSQLQuery Tag = "sql.query" 31 | TagError Tag = "error" 32 | ) 33 | 34 | // Set a standard Tag with a payload on provided Span. 35 | func (t Tag) Set(s Span, value string) { 36 | s.Tag(string(t), value) 37 | } 38 | -------------------------------------------------------------------------------- /tracer_options.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The OpenZipkin Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package zipkin 16 | 17 | import ( 18 | "errors" 19 | 20 | "github.com/openzipkin/zipkin-go/idgenerator" 21 | "github.com/openzipkin/zipkin-go/model" 22 | ) 23 | 24 | // Tracer Option Errors 25 | var ( 26 | ErrInvalidEndpoint = errors.New("requires valid local endpoint") 27 | ErrInvalidExtractFailurePolicy = errors.New("invalid extract failure policy provided") 28 | ) 29 | 30 | // ExtractFailurePolicy deals with Extraction errors 31 | type ExtractFailurePolicy int 32 | 33 | // ExtractFailurePolicyOptions 34 | const ( 35 | ExtractFailurePolicyRestart ExtractFailurePolicy = iota 36 | ExtractFailurePolicyError 37 | ExtractFailurePolicyTagAndRestart 38 | ) 39 | 40 | // TracerOption allows for functional options to adjust behavior of the Tracer 41 | // to be created with NewTracer(). 42 | type TracerOption func(o *Tracer) error 43 | 44 | // WithLocalEndpoint sets the local endpoint of the tracer. 45 | func WithLocalEndpoint(e *model.Endpoint) TracerOption { 46 | return func(o *Tracer) error { 47 | if e == nil { 48 | o.localEndpoint = nil 49 | return nil 50 | } 51 | ep := *e 52 | o.localEndpoint = &ep 53 | return nil 54 | } 55 | } 56 | 57 | // WithExtractFailurePolicy allows one to set the ExtractFailurePolicy. 58 | func WithExtractFailurePolicy(p ExtractFailurePolicy) TracerOption { 59 | return func(o *Tracer) error { 60 | if p < 0 || p > ExtractFailurePolicyTagAndRestart { 61 | return ErrInvalidExtractFailurePolicy 62 | } 63 | o.extractFailurePolicy = p 64 | return nil 65 | } 66 | } 67 | 68 | // WithNoopSpan if set to true will switch to a NoopSpan implementation 69 | // if the trace is not sampled. 70 | func WithNoopSpan(unsampledNoop bool) TracerOption { 71 | return func(o *Tracer) error { 72 | o.unsampledNoop = unsampledNoop 73 | return nil 74 | } 75 | } 76 | 77 | // WithSharedSpans allows to place client-side and server-side annotations 78 | // for a RPC call in the same span (Zipkin V1 behavior) or different spans 79 | // (more in line with other tracing solutions). By default this Tracer 80 | // uses shared host spans (so client-side and server-side in the same span). 81 | func WithSharedSpans(val bool) TracerOption { 82 | return func(o *Tracer) error { 83 | o.sharedSpans = val 84 | return nil 85 | } 86 | } 87 | 88 | // WithSampler allows one to set a Sampler function 89 | func WithSampler(sampler Sampler) TracerOption { 90 | return func(o *Tracer) error { 91 | o.sampler = sampler 92 | return nil 93 | } 94 | } 95 | 96 | // WithTraceID128Bit if set to true will instruct the Tracer to start traces 97 | // with 128 bit TraceID's. If set to false the Tracer will start traces with 98 | // 64 bits. 99 | func WithTraceID128Bit(val bool) TracerOption { 100 | return func(o *Tracer) error { 101 | if val { 102 | o.generate = idgenerator.NewRandom128() 103 | } else { 104 | o.generate = idgenerator.NewRandom64() 105 | } 106 | return nil 107 | } 108 | } 109 | 110 | // WithIDGenerator allows one to set a custom ID Generator 111 | func WithIDGenerator(generator idgenerator.IDGenerator) TracerOption { 112 | return func(o *Tracer) error { 113 | o.generate = generator 114 | return nil 115 | } 116 | } 117 | 118 | // WithTags allows one to set default tags to be added to each created span 119 | func WithTags(tags map[string]string) TracerOption { 120 | return func(o *Tracer) error { 121 | for k, v := range tags { 122 | o.defaultTags[k] = v 123 | } 124 | return nil 125 | } 126 | } 127 | 128 | // WithNoopTracer allows one to start the Tracer as Noop implementation. 129 | func WithNoopTracer(tracerNoop bool) TracerOption { 130 | return func(o *Tracer) error { 131 | if tracerNoop { 132 | o.noop = 1 133 | } else { 134 | o.noop = 0 135 | } 136 | return nil 137 | } 138 | } 139 | --------------------------------------------------------------------------------