pax_global_header00006660000000000000000000000064146135626610014524gustar00rootroot0000000000000052 comment=e609ce4fbeb610ff0c19b342956a040b0e2f8c67 zipkin-go-0.4.3/000077500000000000000000000000001461356266100134375ustar00rootroot00000000000000zipkin-go-0.4.3/.gitattributes000066400000000000000000000000161461356266100163270ustar00rootroot00000000000000* text eol=lf zipkin-go-0.4.3/.github/000077500000000000000000000000001461356266100147775ustar00rootroot00000000000000zipkin-go-0.4.3/.github/workflows/000077500000000000000000000000001461356266100170345ustar00rootroot00000000000000zipkin-go-0.4.3/.github/workflows/ci.yml000066400000000000000000000027241461356266100201570ustar00rootroot00000000000000name: gha on: push: branches: - master paths-ignore: - "**/*.md" - "LICENSE" pull_request: jobs: "CI": runs-on: ${{ matrix.os }} strategy: matrix: # Use versions consistent with zipkin-go's Go support policy. os: [macos-latest, windows-latest, ubuntu-latest] go: ["1.22"] # Current Go version include: - os: ubuntu-latest go: "1.21" - os: ubuntu-latest go: "1.20" # Floor Go version of zipkin-go (current - 2) steps: # Set fetch-depth: 0 to fetch commit history and tags for use in version calculation - name: Check out code uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup go uses: actions/setup-go@v5 with: go-version: ${{ matrix.go }} - name: Lint files uses: golangci/golangci-lint-action@v4 with: # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. version: latest - name: Run tests run: go test -coverprofile coverage.txt -v ./... env: CGO_ENABLED: 1 - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: name: zipkin-go test reports fail_ci_if_error: false files: ./coverage.txt zipkin-go-0.4.3/.github/workflows/security.yml000066400000000000000000000027351461356266100214350ustar00rootroot00000000000000--- name: security # We don't scan documentation-only commits. on: # yamllint disable-line rule:truthy push: # non-tagged pushes to master branches: - master tags-ignore: - '*' paths-ignore: - '**/*.md' pull_request: # pull requests targeted at the master branch. branches: - master paths-ignore: - '**/*.md' jobs: security: name: security runs-on: ubuntu-22.04 # newest available distribution, aka jellyfish steps: - name: Checkout Repository uses: actions/checkout@v4 - uses: actions/cache@v4 name: Cache Trivy Database with: path: .trivy key: ${{ runner.os }}-trivy restore-keys: ${{ runner.os }}-trivy - name: Run Trivy vulnerability and secret scanner uses: aquasecurity/trivy-action@master id: trivy with: scan-type: 'fs' scan-ref: '.' # scan the entire repository scanners: vuln,secret exit-code: '1' severity: HIGH,CRITICAL output: trivy-report.md cache-dir: .trivy - name: Set Summary shell: bash if: ${{ failure() && steps.trivy.conclusion == 'failure' }} # Add the Trivy report to the summary # # Note: This will cause a workflow error if trivy-report.md > the step # limit 1MiB. If this was due to too many CVEs, consider fixing them ;) run: cat trivy-report.md >> $GITHUB_STEP_SUMMARY zipkin-go-0.4.3/.gitignore000066400000000000000000000004211461356266100154240ustar00rootroot00000000000000# Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.prof .idea zipkin-go-0.4.3/.golangci.yml000066400000000000000000000006411461356266100160240ustar00rootroot00000000000000run: timeout: 5m issues: exclude-dirs: - zipkin_proto3 linters: disable-all: true enable: - dupl - goconst - gocyclo - gofmt - revive - govet - ineffassign - lll - misspell - nakedret - revive - unparam - unused linters-settings: dupl: threshold: 400 lll: line-length: 170 gocyclo: min-complexity: 20 revive: confidence: 0.85 zipkin-go-0.4.3/LICENSE000066400000000000000000000240121461356266100144430ustar00rootroot00000000000000Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2017 The OpenZipkin Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. zipkin-go-0.4.3/Makefile000066400000000000000000000024161461356266100151020ustar00rootroot00000000000000# Copyright 2022 The OpenZipkin Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. .DEFAULT_GOAL := test .PHONY: test test: # MallocNanoZone env var avoids problems in macOS Monterey: golang/go#49138 MallocNanoZone=0 go test -v -race -cover ./... .PHONY: bench bench: go test -v -run - -bench . -benchmem ./... .PHONY: protoc protoc: protoc --go_out=module=github.com/openzipkin/zipkin-go:. proto/zipkin_proto3/zipkin.proto protoc --go_out=module=github.com/openzipkin/zipkin-go:. proto/testing/*.proto protoc --go-grpc_out=module=github.com/openzipkin/zipkin-go:. proto/testing/*.proto .PHONY: lint lint: # Ignore grep's exit code since no match returns 1. echo 'linting...' ; golint ./... .PHONY: vet vet: go vet ./... .PHONY: all all: vet lint test bench .PHONY: example zipkin-go-0.4.3/README.md000066400000000000000000000132131461356266100147160ustar00rootroot00000000000000 # Zipkin Library for Go [![GHA](https://github.com/openzipkin/zipkin-go/actions/workflows/ci.yml/badge.svg?event=push)](https://github.com/openzipkin/zipkin-go/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/openzipkin/zipkin-go/branch/master/graph/badge.svg?token=gXdWofFlsq)](https://codecov.io/gh/openzipkin/zipkin-go) [![Go Report Card](https://goreportcard.com/badge/github.com/openzipkin/zipkin-go)](https://goreportcard.com/report/github.com/openzipkin/zipkin-go) [![GoDoc](https://godoc.org/github.com/openzipkin/zipkin-go?status.svg)](https://godoc.org/github.com/openzipkin/zipkin-go) [![Gitter chat](https://badges.gitter.im/openzipkin/zipkin.svg)](https://gitter.im/openzipkin/zipkin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Sourcegraph](https://sourcegraph.com/github.com/openzipkin/zipkin-go/-/badge.svg)](https://sourcegraph.com/github.com/openzipkin/zipkin-go?badge) Zipkin Go is the official Go Tracer / Tracing implementation for Zipkin, supported by the OpenZipkin community. ## package organization `zipkin-go` is built with interoperability in mind within the OpenZipkin community and even 3rd parties, the library consists of several packages. The main tracing implementation can be found in the root folder of this repository. Reusable parts not considered core implementation or deemed beneficiary for usage by others are placed in their own packages within this repository. ### model This library implements the Zipkin V2 Span Model which is available in the model package. It contains a Go data model compatible with the Zipkin V2 API and can automatically sanitize, parse and (de)serialize to and from the required JSON representation as used by the official Zipkin V2 Collectors. ### propagation The propagation package and B3 subpackage hold the logic for propagating SpanContext (span identifiers and sampling flags) between services participating in traces. Currently Zipkin B3 Propagation is supported for HTTP and GRPC. ### middleware The middleware subpackages contain officially supported middleware handlers and tracing wrappers. #### http An easy to use http.Handler middleware for tracing server side requests is provided. This allows one to use this middleware in applications using standard library servers as well as most available higher level frameworks. Some frameworks will have their own instrumentation and middleware that maps better for their ecosystem. For HTTP client operations `NewTransport` can return a `http.RoundTripper` implementation that can either wrap the standard http.Client's Transport or a custom provided one and add per request tracing. Since HTTP Requests can have one or multiple redirects it is advisable to always enclose HTTP Client calls with a `Span` either around the `*http.Client` call level or parent function level. For convenience `NewClient` is provided which returns a HTTP Client which embeds `*http.Client` and provides an `application span` around the HTTP calls when calling the `DoWithAppSpan()` method. #### grpc Easy to use grpc.StatsHandler middleware are provided for tracing gRPC server and client requests. For a server, pass `NewServerHandler` when calling `NewServer`, e.g., ```go import ( "google.golang.org/grpc" zipkingrpc "github.com/openzipkin/zipkin-go/middleware/grpc" ) server = grpc.NewServer(grpc.StatsHandler(zipkingrpc.NewServerHandler(tracer))) ``` For a client, pass `NewClientHandler` when calling `Dial`, e.g., ```go import ( "google.golang.org/grpc" zipkingrpc "github.com/openzipkin/zipkin-go/middleware/grpc" ) conn, err = grpc.Dial(addr, grpc.WithStatsHandler(zipkingrpc.NewClientHandler(tracer))) ``` ### reporter The reporter package holds the interface which the various Reporter implementations use. It is exported into its own package as it can be used by 3rd parties to use these Reporter packages in their own libraries for exporting to the Zipkin ecosystem. The `zipkin-go` tracer also uses the interface to accept 3rd party Reporter implementations. #### HTTP Reporter Most common Reporter type used by Zipkin users transporting Spans to the Zipkin server using JSON over HTTP. The reporter holds a buffer and reports to the backend asynchronously. #### Kafka Reporter High performance Reporter transporting Spans to the Zipkin server using a Kafka Producer digesting JSON V2 Spans. The reporter uses the [Sarama async producer](https://pkg.go.dev/github.com/IBM/sarama#AsyncProducer) underneath. ## Usage and Examples [HTTP Server Example](examples/httpserver_test.go) ## Go Support Policy zipkin-go follows the same version policy as Go's [Release Policy](https://go.dev/doc/devel/release): two versions. zipkin-go will ensure these versions work and bugs are valid if there's an issue with a current Go version. Additionally, zipkin-go intentionally delays usage of language or standard library features one additional version. For example, when Go 1.29 is released, zipkin-go can use language features or standard libraries added in 1.27. This is a convenience for embedders who have a slower version policy than Go. However, only supported Go versions may be used to raise support issues. zipkin-go-0.4.3/SECURITY.md000066400000000000000000000012711461356266100152310ustar00rootroot00000000000000# OpenZipkin Security Process This document outlines the process for handling security concerns in OpenZipkin projects. Any vulnerability or misconfiguration detected in our [security workflow](.github/workflows/security.yml) should be addressed as a normal pull request. OpenZipkin is a volunteer community and does not have a dedicated security team. There may be periods where no volunteer is able to address a security concern. There is no SLA or warranty offered by volunteers. If you are a security researcher, please consider this before escalating. For security concerns that are sensitive or otherwise outside the scope of public issues, please contact zipkin-admin@googlegroups.com. zipkin-go-0.4.3/bench_test.go000066400000000000000000000100611461356266100161020ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package zipkin_test import ( "fmt" "net/http" "sync/atomic" "testing" "time" zipkin "github.com/openzipkin/zipkin-go" "github.com/openzipkin/zipkin-go/idgenerator" "github.com/openzipkin/zipkin-go/model" "github.com/openzipkin/zipkin-go/propagation" "github.com/openzipkin/zipkin-go/propagation/b3" "google.golang.org/grpc/metadata" ) const ( b3HTTP = "b3-http" b3GRPC = "b3-grpc" ) var tags []string func init() { var ( traceID model.TraceID gen = idgenerator.NewRandom64() ) tags = make([]string, 1000) for j := 0; j < len(tags); j++ { tags[j] = fmt.Sprintf("%d", gen.SpanID(traceID)) } } func addAnnotationsAndTags(sp zipkin.Span, numAnnotation, numTag int) { for j := 0; j < numAnnotation; j++ { sp.Annotate(time.Now(), "event") } for j := 0; j < numTag; j++ { sp.Tag(tags[j], "") } } func benchmarkWithOps(b *testing.B, numAnnotation, numTag int) { var ( r countingRecorder t, _ = zipkin.NewTracer(&r) ) b.ResetTimer() for i := 0; i < b.N; i++ { sp := t.StartSpan("test") addAnnotationsAndTags(sp, numAnnotation, numTag) sp.Finish() } b.StopTimer() if int(r) != b.N { b.Fatalf("missing traces: want %d, have %d", b.N, r) } } func BenchmarkSpan_Empty(b *testing.B) { benchmarkWithOps(b, 0, 0) } func BenchmarkSpan_100Annotations(b *testing.B) { benchmarkWithOps(b, 100, 0) } func BenchmarkSpan_1000Annotations(b *testing.B) { benchmarkWithOps(b, 1000, 0) } func BenchmarkSpan_100Tags(b *testing.B) { benchmarkWithOps(b, 0, 100) } func BenchmarkSpan_1000Tags(b *testing.B) { benchmarkWithOps(b, 0, 1000) } func benchmarkInject(b *testing.B, propagationType string) { var ( r countingRecorder injector propagation.Injector tracer, _ = zipkin.NewTracer(&r) ) switch propagationType { case b3HTTP: req, _ := http.NewRequest("GET", "/", nil) injector = b3.InjectHTTP(req) case b3GRPC: md := metadata.MD{} injector = b3.InjectGRPC(&md) default: b.Fatalf("unknown injector: %s", propagationType) } sp := tracer.StartSpan("testing") addAnnotationsAndTags(sp, 0, 0) b.ResetTimer() for i := 0; i < b.N; i++ { if err := injector(sp.Context()); err != nil { b.Fatal(err) } } } func benchmarkExtract(b *testing.B, propagationType string) { var ( r countingRecorder tracer, _ = zipkin.NewTracer(&r) ) sp := tracer.StartSpan("testing") switch propagationType { case b3HTTP: req, _ := http.NewRequest("GET", "/", nil) b3.InjectHTTP(req)(sp.Context()) b.ResetTimer() for i := 0; i < b.N; i++ { _ = b3.ExtractHTTP(copyRequest(req)) } case b3GRPC: md := metadata.MD{} b3.InjectGRPC(&md)(sp.Context()) b.ResetTimer() for i := 0; i < b.N; i++ { md2 := md.Copy() if _, err := b3.ExtractGRPC(&md2)(); err != nil { b.Fatal(err) } } default: b.Fatalf("unknown propagation type: %s", propagationType) } } func BenchmarkInject_B3_HTTP_Empty(b *testing.B) { benchmarkInject(b, b3HTTP) } func BenchmarkInject_B3_GRPC_Empty(b *testing.B) { benchmarkInject(b, b3GRPC) } func BenchmarkExtract_B3_HTTP_Empty(b *testing.B) { benchmarkExtract(b, b3HTTP) } func BenchmarkExtract_B3_GRPC_Empty(b *testing.B) { benchmarkExtract(b, b3GRPC) } type countingRecorder int32 func (c *countingRecorder) Send(_ model.SpanModel) { atomic.AddInt32((*int32)(c), 1) } func (c *countingRecorder) Close() error { return nil } func copyRequest(req *http.Request) *http.Request { r, _ := http.NewRequest("GET", "/", nil) for k, v := range req.Header { r.Header[k] = v } return r } zipkin-go-0.4.3/context.go000066400000000000000000000040041461356266100154500ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package zipkin import ( "context" "github.com/openzipkin/zipkin-go/model" ) var defaultNoopSpan = &noopSpan{} // SpanFromContext retrieves a Zipkin Span from Go's context propagation // mechanism if found. If not found, returns nil. func SpanFromContext(ctx context.Context) Span { if s, ok := ctx.Value(spanKey).(Span); ok { return s } return nil } // SpanOrNoopFromContext retrieves a Zipkin Span from Go's context propagation // mechanism if found. If not found, returns a noopSpan. // This function typically is used for modules that want to provide existing // Zipkin spans with additional data, but can't guarantee that spans are // properly propagated. It is preferred to use SpanFromContext() and test for // Nil instead of using this function. func SpanOrNoopFromContext(ctx context.Context) Span { if s, ok := ctx.Value(spanKey).(Span); ok { return s } return defaultNoopSpan } // NewContext stores a Zipkin Span into Go's context propagation mechanism. func NewContext(ctx context.Context, s Span) context.Context { return context.WithValue(ctx, spanKey, s) } // BaggageFromContext takes a context and returns access to BaggageFields if // available. Returns nil if there are no BaggageFields found in context. func BaggageFromContext(ctx context.Context) model.BaggageFields { if span := SpanFromContext(ctx); span != nil { return span.Context().Baggage } return nil } type ctxKey struct{} var spanKey = ctxKey{} zipkin-go-0.4.3/context_test.go000066400000000000000000000021501461356266100165070ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package zipkin import ( "context" "testing" ) func TestSpanOrNoopFromContext(t *testing.T) { var ( ctx = context.Background() tr, _ = NewTracer(nil, WithLocalEndpoint(nil)) span = tr.StartSpan("test") ) if want, have := defaultNoopSpan, SpanOrNoopFromContext(ctx); want != have { t.Errorf("Invalid response want %+v, have %+v", want, have) } ctx = NewContext(ctx, span) if want, have := span, SpanOrNoopFromContext(ctx); want != have { t.Errorf("Invalid response want %+v, have %+v", want, have) } } zipkin-go-0.4.3/doc.go000066400000000000000000000013611461356266100145340ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /* Package zipkin implements a native Zipkin instrumentation library for Go. See https://zipkin.io for more information about Zipkin. */ package zipkin zipkin-go-0.4.3/endpoint.go000066400000000000000000000034361461356266100156140ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package zipkin import ( "fmt" "net" "strconv" "strings" "github.com/openzipkin/zipkin-go/model" ) // NewEndpoint creates a new endpoint given the provided serviceName and // hostPort. func NewEndpoint(serviceName string, hostPort string) (*model.Endpoint, error) { e := &model.Endpoint{ ServiceName: serviceName, } if hostPort == "" || hostPort == ":0" { if serviceName == "" { // if all properties are empty we should not have an Endpoint object. return nil, nil } return e, nil } if strings.IndexByte(hostPort, ':') < 0 { hostPort += ":0" } host, port, err := net.SplitHostPort(hostPort) if err != nil { return nil, err } p, err := strconv.ParseUint(port, 10, 16) if err != nil { return nil, err } e.Port = uint16(p) addrs, err := net.LookupIP(host) if err != nil { return nil, fmt.Errorf("host lookup failure: %w", err) } for i := range addrs { addr := addrs[i].To4() if addr == nil { // IPv6 - 16 bytes if e.IPv6 == nil { e.IPv6 = addrs[i].To16() } } else { // IPv4 - 4 bytes if e.IPv4 == nil { e.IPv4 = addr } } if e.IPv4 != nil && e.IPv6 != nil { // Both IPv4 & IPv6 have been set, done... break } } return e, nil } zipkin-go-0.4.3/endpoint_test.go000066400000000000000000000107471461356266100166560ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package zipkin_test import ( "fmt" "net" "reflect" "strings" "testing" "github.com/openzipkin/zipkin-go" "github.com/openzipkin/zipkin-go/model" ) const ( serviceName = "my_service" onlyHost = "localhost" defaultPort = 0 port = 8081 invalidNegativePort = "localhost:-8081" invalidOutOfRangePort = "localhost:65536" invalidHostPort = "::1:8081" unreachableHostPort = "nosuchhost:8081" ) var ( ipv4HostPort = "127.0.0.1:" + fmt.Sprintf("%d", port) ipv6HostPort = "[2001:db8::68]:" + fmt.Sprintf("%d", port) ipv4ForHostPort = net.IPv4(127, 0, 0, 1) ipv6ForHostPort = net.ParseIP("2001:db8::68") ) func TestEmptyEndpoint(t *testing.T) { ep, err := zipkin.NewEndpoint("", "") if err != nil { t.Errorf("unexpected error: %s", err.Error()) } if ep != nil { t.Errorf("endpoint want nil, have: %+v", ep) } } func TestServiceNameOnlyEndpoint(t *testing.T) { have, err := zipkin.NewEndpoint(serviceName, "") if err != nil { t.Errorf("unexpected error: %s", err.Error()) } want := &model.Endpoint{ServiceName: serviceName} if !reflect.DeepEqual(want, have) { t.Errorf("endpoint want %+v, have: %+v", want, have) } } func TestInvalidHostPort(t *testing.T) { _, err := zipkin.NewEndpoint(serviceName, invalidHostPort) if err == nil { t.Fatal("expected error") } if !strings.Contains(err.Error(), "too many colons in address") { t.Fatalf("expected too many colons in address error, got: %s", err.Error()) } } func TestNewEndpointFailsDueToOutOfRangePort(t *testing.T) { _, err := zipkin.NewEndpoint(serviceName, invalidOutOfRangePort) if err == nil { t.Fatal("expected error") } if !strings.Contains(err.Error(), "value out of range") { t.Fatalf("expected out of range error, got: %s", err.Error()) } } func TestNewEndpointFailsDueToNegativePort(t *testing.T) { _, err := zipkin.NewEndpoint(serviceName, invalidNegativePort) if err == nil { t.Fatal("expected error") } if !strings.Contains(err.Error(), "invalid syntax") { t.Fatalf("expected invalid syntax error, got: %s", err.Error()) } } func TestNewEndpointFailsDueToLookupIP(t *testing.T) { _, err := zipkin.NewEndpoint(serviceName, unreachableHostPort) if err == nil { t.Fatal("expected error") } if !strings.Contains(err.Error(), "host lookup failure") { t.Fatalf("expected no such host error, got: %s", err.Error()) } } func TestNewEndpointDefaultsPortToZeroWhenMissing(t *testing.T) { endpoint, err := zipkin.NewEndpoint(serviceName, onlyHost) if err != nil { t.Fatalf("unexpected error: %s", err.Error()) } if endpoint.Port != defaultPort { t.Fatalf("expected port %d, got %d", defaultPort, endpoint.Port) } } func TestNewEndpointIpv4Success(t *testing.T) { endpoint, err := zipkin.NewEndpoint(serviceName, ipv4HostPort) if err != nil { t.Fatalf("unexpected error: %s", err.Error()) } if serviceName != endpoint.ServiceName { t.Fatalf("expected service name %s, got %s", serviceName, endpoint.ServiceName) } if !ipv4ForHostPort.Equal(endpoint.IPv4) { t.Fatalf("expected IPv4 %s, got %s", ipv4ForHostPort.String(), endpoint.IPv4.String()) } if port != endpoint.Port { t.Fatalf("expected port %d, got %d", port, endpoint.Port) } if endpoint.IPv6 != nil { t.Fatalf("expected empty IPv6 got %+v", endpoint.IPv6) } } func TestNewEndpointIpv6Success(t *testing.T) { endpoint, err := zipkin.NewEndpoint(serviceName, ipv6HostPort) if err != nil { t.Fatalf("unexpected error: %s", err.Error()) } if serviceName != endpoint.ServiceName { t.Fatalf("expected service name %s, got %s", serviceName, endpoint.ServiceName) } if !ipv6ForHostPort.Equal(endpoint.IPv6) { t.Fatalf("expected IPv6 %s, got %s", ipv6ForHostPort.String(), endpoint.IPv6.String()) } if port != endpoint.Port { t.Fatalf("expected port %d, got %d", port, endpoint.Port) } if endpoint.IPv4 != nil { t.Fatalf("expected empty IPv4 got %+v", endpoint.IPv4) } } zipkin-go-0.4.3/example_test.go000066400000000000000000000057731461356266100164740ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package zipkin_test import ( "context" "log" "time" zipkin "github.com/openzipkin/zipkin-go" "github.com/openzipkin/zipkin-go/model" "github.com/openzipkin/zipkin-go/reporter" httpreporter "github.com/openzipkin/zipkin-go/reporter/http" ) func doSomeWork(context.Context) {} func ExampleNewTracer() { // create a reporter to be used by the tracer reporter := httpreporter.NewReporter("http://localhost:9411/api/v2/spans") defer reporter.Close() // set-up the local endpoint for our service endpoint, err := zipkin.NewEndpoint("demoService", "172.20.23.100:80") if err != nil { log.Fatalf("unable to create local endpoint: %+v\n", err) } // set-up our sampling strategy sampler, err := zipkin.NewBoundarySampler(0.01, time.Now().UnixNano()) if err != nil { log.Fatalf("unable to create sampler: %+v\n", err) } // initialize the tracer tracer, err := zipkin.NewTracer( reporter, zipkin.WithLocalEndpoint(endpoint), zipkin.WithSampler(sampler), ) if err != nil { log.Fatalf("unable to create tracer: %+v\n", err) } // tracer can now be used to create spans. span := tracer.StartSpan("some_operation") // ... do some work ... span.Finish() // Output: } func ExampleTracerOption() { // initialize the tracer and use the WithNoopSpan TracerOption tracer, _ := zipkin.NewTracer( reporter.NewNoopReporter(), zipkin.WithNoopSpan(true), ) // tracer can now be used to create spans span := tracer.StartSpan("some_operation") // ... do some work ... span.Finish() // Output: } func ExampleNewContext() { var ( tracer, _ = zipkin.NewTracer(reporter.NewNoopReporter()) ctx = context.Background() ) // span for this function span := tracer.StartSpan("ExampleNewContext") defer span.Finish() // add span to Context ctx = zipkin.NewContext(ctx, span) // pass along Context which holds the span to another function doSomeWork(ctx) // Output: } func ExampleSpanOption() { tracer, _ := zipkin.NewTracer(reporter.NewNoopReporter()) // set-up the remote endpoint for the service we're about to call endpoint, err := zipkin.NewEndpoint("otherService", "172.20.23.101:80") if err != nil { log.Fatalf("unable to create remote endpoint: %+v\n", err) } // start a client side RPC span and use RemoteEndpoint SpanOption span := tracer.StartSpan( "some-operation", zipkin.RemoteEndpoint(endpoint), zipkin.Kind(model.Client), ) // ... call other service ... span.Finish() // Output: } zipkin-go-0.4.3/examples/000077500000000000000000000000001461356266100152555ustar00rootroot00000000000000zipkin-go-0.4.3/examples/httpserver_test.go000066400000000000000000000071171461356266100210570ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package zipkin_test import ( "log" "net/http" "net/http/httptest" "os" "time" "github.com/openzipkin/zipkin-go" zipkinhttp "github.com/openzipkin/zipkin-go/middleware/http" logreporter "github.com/openzipkin/zipkin-go/reporter/log" ) func Example() { // set up a span reporter reporter := logreporter.NewReporter(log.New(os.Stderr, "", log.LstdFlags)) defer func() { _ = reporter.Close() }() // create our local service endpoint endpoint, err := zipkin.NewEndpoint("myService", "localhost:0") if err != nil { log.Fatalf("unable to create local endpoint: %+v\n", err) } // initialize our tracer tracer, err := zipkin.NewTracer(reporter, zipkin.WithLocalEndpoint(endpoint)) if err != nil { log.Fatalf("unable to create tracer: %+v\n", err) } // create global zipkin http server middleware serverMiddleware := zipkinhttp.NewServerMiddleware( tracer, zipkinhttp.TagResponseSize(true), ) // create global zipkin traced http client client, err := zipkinhttp.NewClient(tracer, zipkinhttp.ClientTrace(true)) if err != nil { log.Fatalf("unable to create client: %+v\n", err) } // initialize router router := http.NewServeMux() // start web service with zipkin http server middleware ts := httptest.NewServer(serverMiddleware(router)) defer ts.Close() // set-up handlers router.HandleFunc("/some_function", someFunc(client, ts.URL)) router.HandleFunc("/other_function", otherFunc(client)) // initiate a call to some_func var req *http.Request req, err = http.NewRequest("GET", ts.URL+"/some_function", nil) if err != nil { log.Fatalf("unable to create http request: %+v\n", err) } var res *http.Response res, err = client.DoWithAppSpan(req, "some_function") if err != nil { log.Fatalf("unable to do http request: %+v\n", err) } _ = res.Body.Close() // Output: } func someFunc(client *zipkinhttp.Client, url string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { log.Printf("some_function called with method: %s\n", r.Method) // retrieve span from context (created by server middleware) span := zipkin.SpanFromContext(r.Context()) span.Tag("custom_key", "some value") // doing some expensive calculations.... time.Sleep(25 * time.Millisecond) span.Annotate(time.Now(), "expensive_calc_done") newRequest, err := http.NewRequest("POST", url+"/other_function", nil) if err != nil { log.Printf("unable to create client: %+v\n", err) http.Error(w, err.Error(), 500) return } ctx := zipkin.NewContext(newRequest.Context(), span) newRequest = newRequest.WithContext(ctx) var res *http.Response res, err = client.DoWithAppSpan(newRequest, "other_function") if err != nil { log.Printf("call to other_function returned error: %+v\n", err) http.Error(w, err.Error(), 500) return } _ = res.Body.Close() } } func otherFunc(_ *zipkinhttp.Client) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { log.Printf("other_function called with method: %s\n", r.Method) time.Sleep(50 * time.Millisecond) } } zipkin-go-0.4.3/go.mod000066400000000000000000000033731461356266100145530ustar00rootroot00000000000000module github.com/openzipkin/zipkin-go go 1.20 require ( github.com/IBM/sarama v1.43.1 github.com/onsi/ginkgo/v2 v2.11.0 github.com/onsi/gomega v1.27.10 github.com/rabbitmq/amqp091-go v1.9.0 google.golang.org/grpc v1.63.2 google.golang.org/protobuf v1.33.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/eapache/go-resiliency v1.6.0 // indirect github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 // indirect github.com/eapache/queue v1.1.0 // indirect github.com/go-logr/logr v1.2.4 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/jcmturner/aescts/v2 v2.0.0 // indirect github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect github.com/jcmturner/gofork v1.7.6 // indirect github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect github.com/jcmturner/rpc/v2 v2.0.3 // indirect github.com/klauspost/compress v1.17.8 // indirect github.com/kr/text v0.2.0 // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect golang.org/x/crypto v0.22.0 // indirect golang.org/x/net v0.24.0 // indirect golang.org/x/sys v0.19.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.9.3 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) zipkin-go-0.4.3/go.sum000066400000000000000000000307131461356266100145760ustar00rootroot00000000000000github.com/IBM/sarama v1.43.1 h1:Z5uz65Px7f4DhI/jQqEm/tV9t8aU+JUdTyW/K/fCXpA= github.com/IBM/sarama v1.43.1/go.mod h1:GG5q1RURtDNPz8xxJs3mgX6Ytak8Z9eLhAkJPObe2xE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/eapache/go-resiliency v1.6.0 h1:CqGDTLtpwuWKn6Nj3uNUdflaq+/kIPsg0gfNzHton30= github.com/eapache/go-resiliency v1.6.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho= github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 h1:Oy0F4ALJ04o5Qqpdz8XLIpNA3WM/iSIXqxtqo7UGVws= github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0= github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8= github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rabbitmq/amqp091-go v1.9.0 h1:qrQtyzB4H8BQgEuJwhmVQqVHB9O4+MNDJCCAcpc3Aoo= github.com/rabbitmq/amqp091-go v1.9.0/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM= golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be h1:LG9vZxsWGOmUKieR8wPAUR3u3MpnYFQZROPIMaXh7/A= google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= zipkin-go-0.4.3/idgenerator/000077500000000000000000000000001461356266100157425ustar00rootroot00000000000000zipkin-go-0.4.3/idgenerator/idgenerator.go000066400000000000000000000070341461356266100206000ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /* Package idgenerator contains several Span and Trace ID generators which can be used by the Zipkin tracer. Additional third party generators can be plugged in if they adhere to the IDGenerator interface. */ package idgenerator import ( "math/rand" "sync" "time" "github.com/openzipkin/zipkin-go/model" ) var ( seededIDGen = rand.New(rand.NewSource(time.Now().UnixNano())) // NewSource returns a new pseudo-random Source seeded with the given value. // Unlike the default Source used by top-level functions, this source is not // safe for concurrent use by multiple goroutines. Hence the need for a mutex. seededIDLock sync.Mutex ) // IDGenerator interface can be used to provide the Zipkin Tracer with custom // implementations to generate Span and Trace IDs. type IDGenerator interface { SpanID(traceID model.TraceID) model.ID // Generates a new Span ID TraceID() model.TraceID // Generates a new Trace ID } // NewRandom64 returns an ID Generator which can generate 64 bit trace and span // id's func NewRandom64() IDGenerator { return &randomID64{} } // NewRandom128 returns an ID Generator which can generate 128 bit trace and 64 // bit span id's func NewRandom128() IDGenerator { return &randomID128{} } // NewRandomTimestamped generates 128 bit time sortable traceid's and 64 bit // spanid's. func NewRandomTimestamped() IDGenerator { return &randomTimestamped{} } // randomID64 can generate 64 bit traceid's and 64 bit spanid's. type randomID64 struct{} func (r *randomID64) TraceID() (id model.TraceID) { seededIDLock.Lock() id = model.TraceID{ Low: uint64(seededIDGen.Int63()), } seededIDLock.Unlock() return } func (r *randomID64) SpanID(traceID model.TraceID) (id model.ID) { if !traceID.Empty() { return model.ID(traceID.Low) } seededIDLock.Lock() id = model.ID(seededIDGen.Int63()) seededIDLock.Unlock() return } // randomID128 can generate 128 bit traceid's and 64 bit spanid's. type randomID128 struct{} func (r *randomID128) TraceID() (id model.TraceID) { seededIDLock.Lock() id = model.TraceID{ High: uint64(seededIDGen.Int63()), Low: uint64(seededIDGen.Int63()), } seededIDLock.Unlock() return } func (r *randomID128) SpanID(traceID model.TraceID) (id model.ID) { if !traceID.Empty() { return model.ID(traceID.Low) } seededIDLock.Lock() id = model.ID(seededIDGen.Int63()) seededIDLock.Unlock() return } // randomTimestamped can generate 128 bit time sortable traceid's compatible // with AWS X-Ray and 64 bit spanid's. type randomTimestamped struct{} func (t *randomTimestamped) TraceID() (id model.TraceID) { seededIDLock.Lock() id = model.TraceID{ High: uint64(time.Now().Unix()<<32) + uint64(seededIDGen.Int31()), Low: uint64(seededIDGen.Int63()), } seededIDLock.Unlock() return } func (t *randomTimestamped) SpanID(traceID model.TraceID) (id model.ID) { if !traceID.Empty() { return model.ID(traceID.Low) } seededIDLock.Lock() id = model.ID(seededIDGen.Int63()) seededIDLock.Unlock() return } zipkin-go-0.4.3/idgenerator/idgenerator_test.go000066400000000000000000000060741461356266100216420ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package idgenerator_test import ( "testing" "github.com/openzipkin/zipkin-go/idgenerator" "github.com/openzipkin/zipkin-go/model" ) func TestRandom64(t *testing.T) { var ( spanID model.ID gen = idgenerator.NewRandom64() traceID = gen.TraceID() ) if traceID.Empty() { t.Errorf("Expected valid TraceID, got: %+v", traceID) } if want, have := uint64(0), traceID.High; want != have { t.Errorf("Expected TraceID.High to be 0, got %d", have) } spanID = gen.SpanID(traceID) if want, have := model.ID(traceID.Low), spanID; want != have { t.Errorf("Expected root span to have span ID %d, got %d", want, have) } spanID = gen.SpanID(model.TraceID{}) if spanID == 0 { t.Errorf("Expected child span to have a valid span ID, got 0") } } func TestRandom128(t *testing.T) { var ( spanID model.ID gen = idgenerator.NewRandom128() traceID = gen.TraceID() ) if traceID.Empty() { t.Errorf("Expected valid TraceID, got: %+v", traceID) } if traceID.Low == 0 { t.Error("Expected TraceID.Low to have value, got 0") } if traceID.High == 0 { t.Error("Expected TraceID.High to have value, got 0") } spanID = gen.SpanID(traceID) if want, have := model.ID(traceID.Low), spanID; want != have { t.Errorf("Expected root span to have span ID %d, got %d", want, have) } spanID = gen.SpanID(model.TraceID{}) if spanID == 0 { t.Errorf("Expected child span to have a valid span ID, got 0") } } func TestRandomTimeStamped(t *testing.T) { var ( spanID model.ID gen = idgenerator.NewRandomTimestamped() traceID = gen.TraceID() ) if traceID.Empty() { t.Errorf("Expected valid TraceID, got: %+v", traceID) } if traceID.Low == 0 { t.Error("Expected TraceID.Low to have value, got 0") } if traceID.High == 0 { t.Error("Expected TraceID.High to have value, got 0") } spanID = gen.SpanID(traceID) if want, have := model.ID(traceID.Low), spanID; want != have { t.Errorf("Expected root span to have span ID %d, got %d", want, have) } spanID = gen.SpanID(model.TraceID{}) if spanID == 0 { t.Errorf("Expected child span to have a valid span ID, got 0") } // test chronological order var ids []model.TraceID for i := 0; i < 1000; i++ { ids = append(ids, gen.TraceID()) } var latestTS uint64 for idx, traceID := range ids { if newVal, oldVal := traceID.High>>32, latestTS; newVal < oldVal { t.Errorf("[%d] expected a higher timestamp part in traceid but got: old: %d new: %d", idx, oldVal, newVal) } latestTS = traceID.High >> 32 } } zipkin-go-0.4.3/middleware/000077500000000000000000000000001461356266100155545ustar00rootroot00000000000000zipkin-go-0.4.3/middleware/baggage.go000066400000000000000000000022721461356266100174630ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package middleware import "github.com/openzipkin/zipkin-go/model" // BaggageHandler holds the interface for server and client middlewares // interacting with baggage context propagation implementations. // A reference implementation can be found in package: // github.com/openzipkin/zipkin-go/propagation/baggage type BaggageHandler interface { // New returns a fresh BaggageFields implementation primed for usage in a // request lifecycle. // This method needs to be called by incoming transport middlewares. See // middlewares/grpc/server.go and middlewares/http/server.go New() model.BaggageFields } zipkin-go-0.4.3/middleware/grpc/000077500000000000000000000000001461356266100165075ustar00rootroot00000000000000zipkin-go-0.4.3/middleware/grpc/baggage_test.go000066400000000000000000000125321461356266100214550ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package grpc_test import ( "context" "net" "testing" "github.com/openzipkin/zipkin-go/middleware" "google.golang.org/grpc" "google.golang.org/grpc/metadata" "google.golang.org/protobuf/types/known/emptypb" "github.com/openzipkin/zipkin-go" zgrpc "github.com/openzipkin/zipkin-go/middleware/grpc" "github.com/openzipkin/zipkin-go/propagation/baggage" service "github.com/openzipkin/zipkin-go/proto/testing" ) const ( reqID = "x-request-id" reqIDValue = "5a3553a7-4088-4ae0-8845-8314ebd59ddb" customField = "custom-field" customFieldValue = "custom-value" ) var tracer *zipkin.Tracer // TestGRPCBaggage tests baggage propagation through actual gRPC client - // server connections. It creates a client which will inject an x-request-id // header which will trigger the receiving server to retrieve the incoming value // on the handler1 endpoint, propagate and use it in an outgoing call to the // handler2 endpoint, which should also retrieve the incoming value. // By doing this we test: // - outgoing baggage on client side (stand-alone client) // - incoming baggage on server side (handler1 endpoint) // - in process baggage propagation on server side (handler1 implementation) // - add additional header in handler1 implementation // - incoming baggage on server side (handler2 endpoint) func TestGRPCBaggage(t *testing.T) { tracer, _ = zipkin.NewTracer(nil) var bagHandler = baggage.New(reqID, customField) // create listener for server to use ln, err := net.Listen("tcp", ":0") if err != nil { t.Fatalf("unable to create listener for grpc server: %+v", err) } defer func() { _ = ln.Close() }() // create gRPC client client := newClient(t, ln.Addr().String()) // start gRPC server using the provided listener, gRPC client and baggage // handler bSrv := runServer(ln, client, bagHandler) // set x-request-id using a UUID as value md := metadata.New(nil) md.Set(reqID, reqIDValue) ctx := metadata.NewOutgoingContext(context.Background(), md) // call gRPC server Handler1 method if _, err = client.Handler1(ctx, &emptypb.Empty{}); err != nil { t.Fatalf("unexpected grpc request error: %+v", err) } // check server inspection variables for correct baggage field propagation if bSrv.resultHandler1 != reqIDValue { t.Errorf("resultHandler1 expected propagated %s: want %s, have: %s", reqID, reqIDValue, bSrv.resultHandler1) } if bSrv.result1Handler2 != reqIDValue { t.Errorf("result1Handler2 expected propagated %s: want %s, have: %s", reqID, reqIDValue, bSrv.result1Handler2) } if bSrv.result2Handler2 != customFieldValue { t.Errorf("result2Handler2 expected propagated %s: want %s, have: %s", customField, customFieldValue, bSrv.result2Handler2) } } func runServer( ln net.Listener, // listener to use client service.BaggageServiceClient, // the server can call itself bagHandler middleware.BaggageHandler, // baggage handler to use ) *baggageServer { var ( zHnd = zgrpc.NewServerHandler(tracer, zgrpc.EnableBaggage(bagHandler)) gSrv = grpc.NewServer(grpc.StatsHandler(zHnd)) bSrv = &baggageServer{client: client} ) service.RegisterBaggageServiceServer(gSrv, bSrv) go func() { _ = gSrv.Serve(ln) }() return bSrv } func newClient(t *testing.T, serverAddr string) service.BaggageServiceClient { zHnd := zgrpc.NewClientHandler(tracer) cc, err := grpc.Dial( serverAddr, grpc.WithInsecure(), grpc.WithStatsHandler(zHnd), ) if err != nil { t.Fatalf("unable to create connection for grpc client: %+v", err) } return service.NewBaggageServiceClient(cc) } type baggageServer struct { service.UnimplementedBaggageServiceServer client service.BaggageServiceClient resultHandler1 string result1Handler2 string result2Handler2 string } func (b *baggageServer) Handler1(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) { // retrieve received value from incoming context if md, ok := metadata.FromIncomingContext(ctx); ok { if values := md.Get(reqID); len(values) > 0 { b.resultHandler1 = values[0] } } // add additional baggage field if span := zipkin.SpanFromContext(ctx); span != nil { span.Context().Baggage.Add(customField, customFieldValue) } // outgoing call from client uses baggage found in context if _, err := b.client.Handler2(ctx, &emptypb.Empty{}); err != nil { return nil, err } return &emptypb.Empty{}, nil } func (b *baggageServer) Handler2(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) { // retrieve received value from incoming context if md, ok := metadata.FromIncomingContext(ctx); ok { if values := md.Get(reqID); len(values) > 0 { b.result1Handler2 = values[0] } if values := md.Get(customField); len(values) > 0 { b.result2Handler2 = values[0] } } return &emptypb.Empty{}, nil } var _ service.BaggageServiceServer = (*baggageServer)(nil) zipkin-go-0.4.3/middleware/grpc/client.go000066400000000000000000000056351461356266100203250ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package grpc import ( "context" "google.golang.org/grpc/metadata" "google.golang.org/grpc/stats" "github.com/openzipkin/zipkin-go" "github.com/openzipkin/zipkin-go/model" "github.com/openzipkin/zipkin-go/propagation/b3" ) type clientHandler struct { tracer *zipkin.Tracer remoteServiceName string } // A ClientOption can be passed to NewClientHandler to customize the returned handler. type ClientOption func(*clientHandler) // WithRemoteServiceName will set the value for the remote endpoint's service name on // all spans. func WithRemoteServiceName(name string) ClientOption { return func(c *clientHandler) { c.remoteServiceName = name } } // NewClientHandler returns a stats.Handler which can be used with grpc.WithStatsHandler to add // tracing to a gRPC client. The gRPC method name is used as the span name and by default the only // tags are the gRPC status code if the call fails. func NewClientHandler(tracer *zipkin.Tracer, options ...ClientOption) stats.Handler { c := &clientHandler{ tracer: tracer, } for _, option := range options { option(c) } return c } // HandleConn exists to satisfy gRPC stats.Handler. func (c *clientHandler) HandleConn(_ context.Context, _ stats.ConnStats) { // no-op } // TagConn exists to satisfy gRPC stats.Handler. func (c *clientHandler) TagConn(ctx context.Context, _ *stats.ConnTagInfo) context.Context { // no-op return ctx } // HandleRPC implements per-RPC tracing and stats instrumentation. func (c *clientHandler) HandleRPC(ctx context.Context, rs stats.RPCStats) { handleRPC(ctx, rs) } // TagRPC implements per-RPC context management. func (c *clientHandler) TagRPC(ctx context.Context, rti *stats.RPCTagInfo) context.Context { var span zipkin.Span ep := remoteEndpointFromContext(ctx, c.remoteServiceName) name := spanName(rti) span, ctx = c.tracer.StartSpanFromContext(ctx, name, zipkin.Kind(model.Client), zipkin.RemoteEndpoint(ep)) md, ok := metadata.FromOutgoingContext(ctx) if ok { md = md.Copy() } else { md = metadata.New(nil) } _ = b3.InjectGRPC(&md)(span.Context()) // inject baggage fields from span context into the outgoing gRPC request metadata if span.Context().Baggage != nil { span.Context().Baggage.Iterate(func(key string, values []string) { md.Set(key, values...) }) } ctx = metadata.NewOutgoingContext(ctx, md) return ctx } zipkin-go-0.4.3/middleware/grpc/client_test.go000066400000000000000000000116151461356266100213570ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package grpc_test import ( "context" "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" "google.golang.org/grpc" "google.golang.org/grpc/metadata" "github.com/openzipkin/zipkin-go" zipkingrpc "github.com/openzipkin/zipkin-go/middleware/grpc" "github.com/openzipkin/zipkin-go/model" "github.com/openzipkin/zipkin-go/propagation/b3" service "github.com/openzipkin/zipkin-go/proto/testing" "github.com/openzipkin/zipkin-go/reporter/recorder" ) var _ = ginkgo.Describe("gRPC Client", func() { var ( reporter *recorder.ReporterRecorder tracer *zipkin.Tracer conn *grpc.ClientConn client service.HelloServiceClient ) ginkgo.BeforeEach(func() { var err error reporter = recorder.NewReporter() ep, _ := zipkin.NewEndpoint("grpcClient", "") tracer, err = zipkin.NewTracer( reporter, zipkin.WithLocalEndpoint(ep), zipkin.WithIDGenerator(newSequentialIDGenerator(1))) gomega.Expect(tracer, err).ToNot(gomega.BeNil()) }) ginkgo.AfterEach(func() { _ = reporter.Close() _ = conn.Close() }) ginkgo.Context("with defaults", func() { ginkgo.BeforeEach(func() { var err error conn, err = grpc.Dial(serverAddr, grpc.WithInsecure(), grpc.WithStatsHandler(zipkingrpc.NewClientHandler(tracer))) gomega.Expect(conn, err).ToNot(gomega.BeNil()) client = service.NewHelloServiceClient(conn) }) ginkgo.It("creates a span", func() { resp, err := client.Hello(context.Background(), &service.HelloRequest{Payload: "Hello"}) gomega.Expect(resp, err).ToNot(gomega.BeNil()) spans := reporter.Flush() gomega.Expect(spans).To(gomega.HaveLen(1)) span := spans[0] gomega.Expect(span.Kind).To(gomega.Equal(model.Client)) gomega.Expect(span.Name).To(gomega.Equal("zipkin.testing.HelloService.Hello")) gomega.Expect(span.RemoteEndpoint).To(gomega.BeNil()) gomega.Expect(span.Tags).To(gomega.BeEmpty()) }) ginkgo.It("propagates trace context", func() { resp, err := client.Hello(context.Background(), &service.HelloRequest{Payload: "Hello"}) gomega.Expect(resp.GetMetadata(), err).To(gomega.HaveKeyWithValue(b3.TraceID, "0000000000000001")) gomega.Expect(resp.GetMetadata(), err).To(gomega.HaveKeyWithValue(b3.SpanID, "0000000000000001")) gomega.Expect(resp.GetMetadata(), err).ToNot(gomega.HaveKey(b3.ParentSpanID)) }) ginkgo.It("propagates parent span", func() { _, ctx := tracer.StartSpanFromContext(context.Background(), "parent") resp, err := client.Hello(ctx, &service.HelloRequest{Payload: "Hello"}) gomega.Expect(resp.GetMetadata(), err).To(gomega.HaveKeyWithValue(b3.TraceID, "0000000000000001")) gomega.Expect(resp.GetMetadata(), err).To(gomega.HaveKeyWithValue(b3.SpanID, "0000000000000002")) gomega.Expect(resp.GetMetadata(), err).To(gomega.HaveKeyWithValue(b3.ParentSpanID, "0000000000000001")) }) ginkgo.It("tags with error code", func() { _, err := client.Hello(context.Background(), &service.HelloRequest{Payload: "fail"}) gomega.Expect(err).To(gomega.HaveOccurred()) spans := reporter.Flush() gomega.Expect(spans).To(gomega.HaveLen(1)) gomega.Expect(spans[0].Tags).To(gomega.HaveLen(2)) gomega.Expect(spans[0].Tags).To(gomega.HaveKeyWithValue("grpc.status_code", "ABORTED")) gomega.Expect(spans[0].Tags).To(gomega.HaveKeyWithValue(string(zipkin.TagError), "ABORTED")) }) ginkgo.It("copies existing metadata", func() { ctx := metadata.AppendToOutgoingContext(context.Background(), "existing", "metadata") resp, err := client.Hello(ctx, &service.HelloRequest{Payload: "Hello"}) gomega.Expect(resp.GetMetadata(), err).To(gomega.HaveKeyWithValue("existing", "metadata")) }) }) ginkgo.Context("with remote service name", func() { ginkgo.BeforeEach(func() { var err error conn, err = grpc.Dial( serverAddr, grpc.WithInsecure(), grpc.WithStatsHandler(zipkingrpc.NewClientHandler( tracer, zipkingrpc.WithRemoteServiceName("remoteService")))) gomega.Expect(conn, err).ToNot(gomega.BeNil()) client = service.NewHelloServiceClient(conn) }) ginkgo.It("has remote service name", func() { resp, err := client.Hello(context.Background(), &service.HelloRequest{Payload: "Hello"}) gomega.Expect(resp, err).ToNot(gomega.BeNil()) spans := reporter.Flush() gomega.Expect(spans).To(gomega.HaveLen(1)) gomega.Expect(spans[0].RemoteEndpoint.ServiceName).To(gomega.Equal("remoteService")) }) }) }) zipkin-go-0.4.3/middleware/grpc/doc.go000066400000000000000000000013161461356266100176040ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /* Package grpc contains several gRPC handlers which can be used for instrumenting calls with Zipkin. */ package grpc zipkin-go-0.4.3/middleware/grpc/grpc_suite_test.go000066400000000000000000000112351461356266100222430ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package grpc_test import ( "context" "errors" "net" "testing" "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" "github.com/openzipkin/zipkin-go" zipkingrpc "github.com/openzipkin/zipkin-go/middleware/grpc" "github.com/openzipkin/zipkin-go/model" "github.com/openzipkin/zipkin-go/propagation/b3" service "github.com/openzipkin/zipkin-go/proto/testing" "github.com/openzipkin/zipkin-go/reporter/recorder" ) var ( serverIDGenerator *sequentialIDGenerator serverReporter *recorder.ReporterRecorder server *grpc.Server serverAddr string customServer *grpc.Server customServerAddr string ) func TestGrpc(t *testing.T) { gomega.RegisterFailHandler(ginkgo.Fail) ginkgo.RunSpecs(t, "Grpc Suite") } var _ = ginkgo.BeforeSuite(func() { var ( err error tracer *zipkin.Tracer lis net.Listener customLis net.Listener ) serverReporter = recorder.NewReporter() ep, _ := zipkin.NewEndpoint("grpcServer", "") serverIDGenerator = newSequentialIDGenerator(0x1000000) tracer, err = zipkin.NewTracer( serverReporter, zipkin.WithLocalEndpoint(ep), zipkin.WithIDGenerator(serverIDGenerator), zipkin.WithSharedSpans(false), ) gomega.Expect(tracer, err).ToNot(gomega.BeNil(), "failed to create Zipkin tracer") lis, err = net.Listen("tcp", ":0") gomega.Expect(lis, err).ToNot(gomega.BeNil(), "failed to listen to tcp port") server = grpc.NewServer(grpc.StatsHandler(zipkingrpc.NewServerHandler(tracer))) service.RegisterHelloServiceServer(server, &TestHelloService{}) go func() { _ = server.Serve(lis) }() serverAddr = lis.Addr().String() customLis, err = net.Listen("tcp", ":0") gomega.Expect(customLis, err).ToNot(gomega.BeNil(), "failed to listen to tcp port") tracer, err = zipkin.NewTracer( serverReporter, zipkin.WithLocalEndpoint(ep), zipkin.WithIDGenerator(serverIDGenerator), zipkin.WithSharedSpans(true), ) gomega.Expect(tracer, err).ToNot(gomega.BeNil(), "failed to create Zipkin tracer") customServer = grpc.NewServer( grpc.StatsHandler( zipkingrpc.NewServerHandler( tracer, zipkingrpc.ServerTags(map[string]string{"default": "tag"}), ), ), ) service.RegisterHelloServiceServer(customServer, &TestHelloService{}) go func() { _ = customServer.Serve(customLis) }() customServerAddr = customLis.Addr().String() }) var _ = ginkgo.AfterSuite(func() { server.Stop() customServer.Stop() _ = serverReporter.Close() }) type sequentialIDGenerator struct { nextTraceID uint64 nextSpanID uint64 start uint64 } func newSequentialIDGenerator(start uint64) *sequentialIDGenerator { return &sequentialIDGenerator{start, start, start} } func (g *sequentialIDGenerator) SpanID(_ model.TraceID) model.ID { id := model.ID(g.nextSpanID) g.nextSpanID++ return id } func (g *sequentialIDGenerator) TraceID() model.TraceID { id := model.TraceID{ High: 0, Low: g.nextTraceID, } g.nextTraceID++ return id } func (g *sequentialIDGenerator) reset() { g.nextTraceID = g.start g.nextSpanID = g.start } type TestHelloService struct { service.UnimplementedHelloServiceServer } func (s *TestHelloService) Hello(ctx context.Context, req *service.HelloRequest) (*service.HelloResponse, error) { if req.Payload == "fail" { return nil, status.Error(codes.Aborted, "fail") } resp := &service.HelloResponse{ Payload: "World", Metadata: map[string]string{}, SpanContext: map[string]string{}, } md, ok := metadata.FromIncomingContext(ctx) if !ok { return nil, errors.New("could not parse incoming metadata") } for k := range md { // Just append the first value for a key for simplicity since we don't use multi-value headers. resp.GetMetadata()[k] = md[k][0] } span := zipkin.SpanFromContext(ctx) if span != nil { spanCtx := span.Context() resp.GetSpanContext()[b3.SpanID] = spanCtx.ID.String() resp.GetSpanContext()[b3.TraceID] = spanCtx.TraceID.String() if spanCtx.ParentID != nil { resp.GetSpanContext()[b3.ParentSpanID] = spanCtx.ParentID.String() } } return resp, nil } zipkin-go-0.4.3/middleware/grpc/server.go000066400000000000000000000065431461356266100203540ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package grpc import ( "context" "github.com/openzipkin/zipkin-go/middleware" "google.golang.org/grpc/metadata" "google.golang.org/grpc/stats" "github.com/openzipkin/zipkin-go" "github.com/openzipkin/zipkin-go/model" "github.com/openzipkin/zipkin-go/propagation/b3" ) type serverHandler struct { tracer *zipkin.Tracer defaultTags map[string]string baggage middleware.BaggageHandler } // A ServerOption can be passed to NewServerHandler to customize the returned handler. type ServerOption func(*serverHandler) // ServerTags adds default Tags to inject into server spans. func ServerTags(tags map[string]string) ServerOption { return func(h *serverHandler) { h.defaultTags = tags } } // EnableBaggage can be passed to NewServerHandler to enable propagation of // registered fields through the SpanContext object. func EnableBaggage(b middleware.BaggageHandler) ServerOption { return func(h *serverHandler) { h.baggage = b } } // NewServerHandler returns a stats.Handler which can be used with grpc.WithStatsHandler to add // tracing to a gRPC server. The gRPC method name is used as the span name and by default the only // tags are the gRPC status code if the call fails. Use ServerTags to add additional tags that // should be applied to all spans. func NewServerHandler(tracer *zipkin.Tracer, options ...ServerOption) stats.Handler { c := &serverHandler{ tracer: tracer, } for _, option := range options { option(c) } return c } // HandleConn exists to satisfy gRPC stats.Handler. func (s *serverHandler) HandleConn(_ context.Context, _ stats.ConnStats) { // no-op } // TagConn exists to satisfy gRPC stats.Handler. func (s *serverHandler) TagConn(ctx context.Context, _ *stats.ConnTagInfo) context.Context { // no-op return ctx } // HandleRPC implements per-RPC tracing and stats instrumentation. func (s *serverHandler) HandleRPC(ctx context.Context, rs stats.RPCStats) { handleRPC(ctx, rs) } // TagRPC implements per-RPC context management. func (s *serverHandler) TagRPC(ctx context.Context, rti *stats.RPCTagInfo) context.Context { md, ok := metadata.FromIncomingContext(ctx) // In practice, ok never seems to be false but add a defensive check. if !ok { md = metadata.New(nil) } name := spanName(rti) spanContext := s.tracer.Extract(b3.ExtractGRPC(&md)) // store registered baggage fields to be propagated in spanContext if s.baggage != nil { spanContext.Baggage = s.baggage.New() for key, values := range md { spanContext.Baggage.Add(key, values...) } } span := s.tracer.StartSpan( name, zipkin.Kind(model.Server), zipkin.Parent(spanContext), zipkin.RemoteEndpoint(remoteEndpointFromContext(ctx, "")), ) if !zipkin.IsNoop(span) { for k, v := range s.defaultTags { span.Tag(k, v) } } return zipkin.NewContext(ctx, span) } zipkin-go-0.4.3/middleware/grpc/server_test.go000066400000000000000000000145171461356266100214130ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package grpc_test import ( "context" "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" "google.golang.org/grpc" "google.golang.org/grpc/metadata" "github.com/openzipkin/zipkin-go" "github.com/openzipkin/zipkin-go/model" "github.com/openzipkin/zipkin-go/propagation/b3" service "github.com/openzipkin/zipkin-go/proto/testing" "github.com/openzipkin/zipkin-go/reporter" ) var _ = ginkgo.Describe("gRPC Server", func() { var ( conn *grpc.ClientConn client service.HelloServiceClient ) ginkgo.BeforeEach(func() { serverIDGenerator.reset() serverReporter.Flush() }) ginkgo.AfterEach(func() { _ = conn.Close() }) ginkgo.Context("with defaults", func() { ginkgo.BeforeEach(func() { var err error conn, err = grpc.Dial(serverAddr, grpc.WithInsecure()) gomega.Expect(err).ToNot(gomega.HaveOccurred()) client = service.NewHelloServiceClient(conn) }) ginkgo.It("creates a span and context", func() { resp, err := client.Hello(context.Background(), &service.HelloRequest{Payload: "Hello"}) gomega.Expect(err).ToNot(gomega.HaveOccurred()) var spans []model.SpanModel gomega.Eventually(func() []model.SpanModel { spans = serverReporter.Flush() return spans }).Should(gomega.HaveLen(1)) span := spans[0] gomega.Expect(span.Kind).To(gomega.Equal(model.Server)) // Set to local host for tests, might be IPv4 or IPv6 not worth checking the actual address. gomega.Expect(span.RemoteEndpoint.Empty()).To(gomega.BeFalse()) gomega.Expect(span.Name).To(gomega.Equal("zipkin.testing.HelloService.Hello")) gomega.Expect(span.Tags).To(gomega.BeEmpty()) spanCtx := resp.GetSpanContext() gomega.Expect(spanCtx).To(gomega.HaveLen(2)) gomega.Expect(spanCtx).To(gomega.HaveKeyWithValue(b3.TraceID, "0000000001000000")) gomega.Expect(spanCtx).To(gomega.HaveKeyWithValue(b3.SpanID, "0000000001000000")) }) ginkgo.It("propagates parent", func() { // Manually create a client context tracer, err := zipkin.NewTracer( reporter.NewNoopReporter(), zipkin.WithIDGenerator(newSequentialIDGenerator(1))) gomega.Expect(tracer, err).ToNot(gomega.BeNil(), "failed to create Zipkin tracer") testSpan := tracer.StartSpan("test") md := metadata.New(nil) _ = b3.InjectGRPC(&md)(testSpan.Context()) ctx := metadata.NewOutgoingContext(context.Background(), md) resp, err := client.Hello(ctx, &service.HelloRequest{Payload: "Hello"}) gomega.Expect(err).ToNot(gomega.HaveOccurred()) var spans []model.SpanModel gomega.Eventually(func() []model.SpanModel { spans = serverReporter.Flush() return spans }).Should(gomega.HaveLen(1)) span := spans[0] gomega.Expect(span.Kind).To(gomega.Equal(model.Server)) gomega.Expect(span.RemoteEndpoint.Empty()).To(gomega.BeFalse()) gomega.Expect(span.Name).To(gomega.Equal("zipkin.testing.HelloService.Hello")) gomega.Expect(span.Tags).To(gomega.BeEmpty()) spanCtx := resp.GetSpanContext() gomega.Expect(spanCtx).To(gomega.HaveLen(3)) gomega.Expect(spanCtx).To(gomega.HaveKeyWithValue(b3.SpanID, "0000000001000000")) gomega.Expect(spanCtx).To(gomega.HaveKeyWithValue(b3.TraceID, "0000000000000001")) gomega.Expect(spanCtx).To(gomega.HaveKeyWithValue(b3.ParentSpanID, "0000000000000001")) }) ginkgo.It("tags with error code", func() { _, err := client.Hello(context.Background(), &service.HelloRequest{Payload: "fail"}) gomega.Expect(err).To(gomega.HaveOccurred()) var spans []model.SpanModel gomega.Eventually(func() []model.SpanModel { spans = serverReporter.Flush() return spans }).Should(gomega.HaveLen(1)) gomega.Expect(spans[0].Tags).To(gomega.HaveLen(2)) gomega.Expect(spans[0].Tags).To(gomega.HaveKeyWithValue("grpc.status_code", "ABORTED")) gomega.Expect(spans[0].Tags).To(gomega.HaveKeyWithValue(string(zipkin.TagError), "ABORTED")) }) }) ginkgo.Context("with joined spans and server tags", func() { ginkgo.BeforeEach(func() { var err error conn, err = grpc.Dial(customServerAddr, grpc.WithInsecure()) gomega.Expect(err).ToNot(gomega.HaveOccurred()) client = service.NewHelloServiceClient(conn) }) ginkgo.It("has server tags", func() { resp, err := client.Hello(context.Background(), &service.HelloRequest{Payload: "Hello"}) gomega.Expect(err).ToNot(gomega.HaveOccurred()) var spans []model.SpanModel gomega.Eventually(func() []model.SpanModel { spans = serverReporter.Flush() return spans }).Should(gomega.HaveLen(1)) span := spans[0] gomega.Expect(span.RemoteEndpoint.Empty()).To(gomega.BeFalse()) gomega.Expect(span.Tags).To(gomega.HaveLen(1)) gomega.Expect(span.Tags).To(gomega.HaveKeyWithValue("default", "tag")) spanCtx := resp.GetSpanContext() gomega.Expect(spanCtx).To(gomega.HaveLen(2)) gomega.Expect(spanCtx).To(gomega.HaveKeyWithValue(b3.TraceID, "0000000001000000")) gomega.Expect(spanCtx).To(gomega.HaveKeyWithValue(b3.SpanID, "0000000001000000")) }) ginkgo.It("joins with caller", func() { // Manually create a client context tracer, err := zipkin.NewTracer( reporter.NewNoopReporter(), zipkin.WithIDGenerator(newSequentialIDGenerator(1))) gomega.Expect(tracer, err).ToNot(gomega.BeNil(), "failed to create Zipkin tracer") testSpan := tracer.StartSpan("test") md := metadata.New(nil) _ = b3.InjectGRPC(&md)(testSpan.Context()) ctx := metadata.NewOutgoingContext(context.Background(), md) var resp *service.HelloResponse resp, err = client.Hello(ctx, &service.HelloRequest{Payload: "Hello"}) gomega.Expect(err).ToNot(gomega.HaveOccurred()) spanCtx := resp.GetSpanContext() gomega.Expect(spanCtx).To(gomega.HaveLen(2)) gomega.Expect(spanCtx).To(gomega.HaveKeyWithValue(b3.TraceID, "0000000000000001")) gomega.Expect(spanCtx).To(gomega.HaveKeyWithValue(b3.SpanID, "0000000000000001")) }) }) }) zipkin-go-0.4.3/middleware/grpc/shared.go000066400000000000000000000040301461356266100203010ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package grpc import ( "context" "strings" "google.golang.org/grpc/codes" "google.golang.org/grpc/peer" "google.golang.org/grpc/stats" "google.golang.org/grpc/status" "github.com/openzipkin/zipkin-go" "github.com/openzipkin/zipkin-go/model" ) // A RPCHandler can be registered using WithClientRPCHandler or WithServerRPCHandler to intercept calls to HandleRPC of // a handler for additional span customization. type RPCHandler func(span zipkin.Span, rpcStats stats.RPCStats) func spanName(rti *stats.RPCTagInfo) string { name := strings.TrimPrefix(rti.FullMethodName, "/") name = strings.Replace(name, "/", ".", -1) return name } func handleRPC(ctx context.Context, rs stats.RPCStats) { span := zipkin.SpanFromContext(ctx) if zipkin.IsNoop(span) { return } switch rs := rs.(type) { case *stats.End: s, ok := status.FromError(rs.Error) // rs.Error should always be convertable to a status, this is just a defensive check. if ok { if s.Code() != codes.OK { // Uppercase for consistency with Brave c := strings.ToUpper(s.Code().String()) span.Tag("grpc.status_code", c) zipkin.TagError.Set(span, c) } } else { zipkin.TagError.Set(span, rs.Error.Error()) } span.Finish() } } func remoteEndpointFromContext(ctx context.Context, name string) *model.Endpoint { remoteAddr := "" p, ok := peer.FromContext(ctx) if ok { remoteAddr = p.Addr.String() } ep, _ := zipkin.NewEndpoint(name, remoteAddr) return ep } zipkin-go-0.4.3/middleware/http/000077500000000000000000000000001461356266100165335ustar00rootroot00000000000000zipkin-go-0.4.3/middleware/http/baggage_test.go000066400000000000000000000073111461356266100215000ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package http_test import ( "net" "net/http" "testing" "github.com/openzipkin/zipkin-go" zipkinhttp "github.com/openzipkin/zipkin-go/middleware/http" "github.com/openzipkin/zipkin-go/propagation/baggage" ) const ( reqID = "X-Request-Id" reqIDValue = "5a3553a7-4088-4ae0-8845-8314ebd59ddb" customField = "custom-field" customFieldValue = "custom-value" ) func TestHTTPBaggage(t *testing.T) { var ( tracer, _ = zipkin.NewTracer(nil) tr, _ = zipkinhttp.NewTransport(tracer) cli = &http.Client{Transport: tr} srv = newServer(cli) bagHandler = baggage.New(reqID, customField) ) // attach server middleware to http server srv.s.Handler = zipkinhttp.NewServerMiddleware( tracer, zipkinhttp.EnableBaggage(bagHandler), )(srv.s.Handler) // create listener ln, err := net.Listen("tcp", ":0") if err != nil { t.Fatalf("unable to create listener for http server: %+v", err) } defer func() { _ = ln.Close() }() // start http server go func() { srv.s.Addr = ln.Addr().String() _ = srv.s.Serve(ln) }() // generate request to handler1 with X-Request-Id set req, err := http.NewRequest("GET", "http://"+ln.Addr().String()+"/handler1", nil) if err != nil { t.Fatalf("unable to create initial http request: %+v", err) } req.Header.Add(reqID, reqIDValue) // send client request if _, err = cli.Do(req); err != nil { t.Errorf("unexpected http request error: %+v", err) } // check server inspection variables for correct baggage field propagation if srv.resultHandler1 != reqIDValue { t.Errorf("resultHandler1 expected propagated %s: want %s, have: %s", reqID, reqIDValue, srv.resultHandler1) } if srv.result1Handler2 != reqIDValue { t.Errorf("result1Handler2 expected propagated %s: want %s, have: %s", reqID, reqIDValue, srv.result1Handler2) } if srv.result2Handler2 != customFieldValue { t.Errorf("result2Handler2 expected propagated %s: want %s, have: %s", customField, customFieldValue, srv.result2Handler2) } } type server struct { s *http.Server c *http.Client resultHandler1 string result1Handler2 string result2Handler2 string } func newServer(client *http.Client) *server { mux := http.NewServeMux() s := &server{ c: client, s: &http.Server{Handler: mux}, } mux.HandleFunc("/handler1", s.handler1) mux.HandleFunc("/handler2", s.handler2) return s } func (s *server) handler1(w http.ResponseWriter, h *http.Request) { // store received request id for inspection s.resultHandler1 = h.Header.Get(reqID) // add additional baggage field ctx := h.Context() if span := zipkin.SpanFromContext(ctx); span != nil { span.Context().Baggage.Add(customField, customFieldValue) } // call handler2 req, _ := http.NewRequestWithContext(ctx, "GET", "http://"+s.s.Addr+"/handler2", nil) if _, err := s.c.Do(req); err != nil { http.Error(w, http.StatusText(500), 500) return } w.WriteHeader(201) } func (s *server) handler2(w http.ResponseWriter, h *http.Request) { // store received request id for inspection s.result1Handler2 = h.Header.Get(reqID) s.result2Handler2 = h.Header.Get(customField) w.WriteHeader(201) } zipkin-go-0.4.3/middleware/http/client.go000066400000000000000000000101211461356266100203330ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package http import ( "errors" "net/http" "strconv" "time" "github.com/openzipkin/zipkin-go" "github.com/openzipkin/zipkin-go/model" ) // ErrValidTracerRequired error var ErrValidTracerRequired = errors.New("valid tracer required") // Client holds a Zipkin instrumented HTTP Client. type Client struct { *http.Client tracer *zipkin.Tracer httpTrace bool defaultTags map[string]string transportOptions []TransportOption remoteEndpoint *model.Endpoint } // ClientOption allows optional configuration of Client. type ClientOption func(*Client) // WithClient allows one to add a custom configured http.Client to use. func WithClient(client *http.Client) ClientOption { return func(c *Client) { if client == nil { client = &http.Client{} } c.Client = client } } // ClientTrace allows one to enable Go's net/http/httptrace. func ClientTrace(enabled bool) ClientOption { return func(c *Client) { c.httpTrace = enabled } } // ClientTags adds default Tags to inject into client application spans. func ClientTags(tags map[string]string) ClientOption { return func(c *Client) { c.defaultTags = tags } } // TransportOptions passes optional Transport configuration to the internal // transport used by Client. func TransportOptions(options ...TransportOption) ClientOption { return func(c *Client) { c.transportOptions = options } } // WithRemoteEndpoint will set the remote endpoint for all spans. func WithRemoteEndpoint(remoteEndpoint *model.Endpoint) ClientOption { return func(c *Client) { c.remoteEndpoint = remoteEndpoint } } // NewClient returns an HTTP Client adding Zipkin instrumentation around an // embedded standard Go http.Client. func NewClient(tracer *zipkin.Tracer, options ...ClientOption) (*Client, error) { if tracer == nil { return nil, ErrValidTracerRequired } c := &Client{tracer: tracer, Client: &http.Client{}} for _, option := range options { option(c) } c.transportOptions = append( c.transportOptions, // the following Client settings override provided transport settings. RoundTripper(c.Client.Transport), TransportTrace(c.httpTrace), TransportRemoteEndpoint(c.remoteEndpoint), ) tr, err := NewTransport(tracer, c.transportOptions...) if err != nil { return nil, err } c.Client.Transport = tr return c, nil } // DoWithAppSpan wraps http.Client's Do with tracing using an application span. func (c *Client) DoWithAppSpan(req *http.Request, name string) (*http.Response, error) { var parentContext model.SpanContext if span := zipkin.SpanFromContext(req.Context()); span != nil { parentContext = span.Context() } appSpan := c.tracer.StartSpan(name, zipkin.Parent(parentContext), zipkin.RemoteEndpoint(c.remoteEndpoint)) zipkin.TagHTTPMethod.Set(appSpan, req.Method) zipkin.TagHTTPPath.Set(appSpan, req.URL.Path) res, err := c.Do( req.WithContext(zipkin.NewContext(req.Context(), appSpan)), ) if err != nil { zipkin.TagError.Set(appSpan, err.Error()) appSpan.Finish() return res, err } if c.httpTrace { appSpan.Annotate(time.Now(), "wr") } if res.ContentLength > 0 { zipkin.TagHTTPResponseSize.Set(appSpan, strconv.FormatInt(res.ContentLength, 10)) } if res.StatusCode < 200 || res.StatusCode > 299 { statusCode := strconv.FormatInt(int64(res.StatusCode), 10) zipkin.TagHTTPStatusCode.Set(appSpan, statusCode) if res.StatusCode > 399 { zipkin.TagError.Set(appSpan, statusCode) } } res.Body = &spanCloser{ ReadCloser: res.Body, sp: appSpan, traceEnabled: c.httpTrace, } return res, err } zipkin-go-0.4.3/middleware/http/client_test.go000066400000000000000000000055011461356266100214000ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package http_test import ( "net/http" "testing" "github.com/openzipkin/zipkin-go" httpclient "github.com/openzipkin/zipkin-go/middleware/http" "github.com/openzipkin/zipkin-go/reporter/recorder" ) func TestHTTPClient(t *testing.T) { reporter := recorder.NewReporter() defer func() { _ = reporter.Close() }() ep, _ := zipkin.NewEndpoint("httpClient", "") tracer, err := zipkin.NewTracer(reporter, zipkin.WithLocalEndpoint(ep)) if err != nil { t.Fatalf("unable to create tracer: %+v", err) } clientTags := map[string]string{ "client": "testClient", } transportTags := map[string]string{ "conf.timeout": "default", } remoteEndpoint, _ := zipkin.NewEndpoint("google-service", "1.2.3.4:80") client, err := httpclient.NewClient( tracer, httpclient.WithClient(&http.Client{}), httpclient.ClientTrace(true), httpclient.ClientTags(clientTags), httpclient.TransportOptions(httpclient.TransportTags(transportTags)), httpclient.WithRemoteEndpoint(remoteEndpoint), ) if err != nil { t.Fatalf("unable to create http client: %+v", err) } req, _ := http.NewRequest("GET", "https://www.google.com", nil) res, err := client.DoWithAppSpan(req, "Get Google") if err != nil { t.Fatalf("unable to execute client request: %+v", err) } _ = res.Body.Close() spans := reporter.Flush() if len(spans) < 2 { t.Errorf("Span Count want 2+, have %d", len(spans)) } rep := spans[0].RemoteEndpoint if rep == nil { t.Errorf("Span remoteEndpoint must not nil") } if rep.ServiceName != remoteEndpoint.ServiceName { t.Errorf("Span remoteEndpoint ServiceName want %s, have %s", remoteEndpoint.ServiceName, rep.ServiceName) } req, _ = http.NewRequest("GET", "https://www.google.com", nil) res, err = client.Do(req) if err != nil { t.Fatalf("unable to execute client request: %+v", err) } _ = res.Body.Close() spans = reporter.Flush() if len(spans) == 0 { t.Errorf("Span Count want 1+, have 0") } span := tracer.StartSpan("ParentSpan") req, _ = http.NewRequest("GET", "http://www.google.com", nil) ctx := zipkin.NewContext(req.Context(), span) req = req.WithContext(ctx) res, err = client.DoWithAppSpan(req, "ChildSpan") if err != nil { t.Fatalf("unable to execute client request: %+v", err) } _ = res.Body.Close() } zipkin-go-0.4.3/middleware/http/doc.go000066400000000000000000000013211461356266100176240ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /* Package http contains several http middlewares which can be used for instrumenting calls with Zipkin. */ package http zipkin-go-0.4.3/middleware/http/request_sampler.go000066400000000000000000000026011461356266100222740ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package http import "net/http" // RequestSamplerFunc can be implemented for client and/or server side sampling decisions that can override the existing // upstream sampling decision. If the implementation returns nil, the existing sampling decision stays as is. type RequestSamplerFunc func(r *http.Request) *bool // Sample is a convenience function that returns a pointer to a boolean true. Use this for RequestSamplerFuncs when // wanting the RequestSampler to override the sampling decision to yes. func Sample() *bool { sample := true return &sample } // Discard is a convenience function that returns a pointer to a boolean false. Use this for RequestSamplerFuncs when // wanting the RequestSampler to override the sampling decision to no. func Discard() *bool { sample := false return &sample } zipkin-go-0.4.3/middleware/http/server.go000066400000000000000000000244541461356266100204010ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package http import ( "io" "net/http" "strconv" "sync/atomic" "github.com/openzipkin/zipkin-go" "github.com/openzipkin/zipkin-go/middleware" "github.com/openzipkin/zipkin-go/model" "github.com/openzipkin/zipkin-go/propagation/b3" ) type handler struct { tracer *zipkin.Tracer name string next http.Handler tagResponseSize bool defaultTags map[string]string requestSampler RequestSamplerFunc errHandler ErrHandler baggage middleware.BaggageHandler } // ServerOption allows Middleware to be optionally configured. type ServerOption func(*handler) // ServerTags adds default Tags to inject into server spans. func ServerTags(tags map[string]string) ServerOption { return func(h *handler) { h.defaultTags = tags } } // TagResponseSize will instruct the middleware to Tag the http response size // in the server side span. func TagResponseSize(enabled bool) ServerOption { return func(h *handler) { h.tagResponseSize = enabled } } // SpanName sets the name of the spans the middleware creates. Use this if // wrapping each endpoint with its own Middleware. // If omitting the SpanName option, the middleware will use the http request // method as span name. func SpanName(name string) ServerOption { return func(h *handler) { h.name = name } } // RequestSampler allows one to set the sampling decision based on the details // found in the http.Request. If wanting to keep the existing sampling decision // from upstream as is, this function should return nil. func RequestSampler(sampleFunc RequestSamplerFunc) ServerOption { return func(h *handler) { h.requestSampler = sampleFunc } } // ServerErrHandler allows to pass a custom error handler for the server response func ServerErrHandler(eh ErrHandler) ServerOption { return func(h *handler) { h.errHandler = eh } } // EnableBaggage can be passed to NewServerHandler to enable propagation of // registered fields through the SpanContext object. func EnableBaggage(b middleware.BaggageHandler) ServerOption { return func(h *handler) { h.baggage = b } } // NewServerMiddleware returns a http.Handler middleware with Zipkin tracing. func NewServerMiddleware(t *zipkin.Tracer, options ...ServerOption) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { h := &handler{ tracer: t, next: next, errHandler: defaultErrHandler, } for _, option := range options { option(h) } return h } } // ServeHTTP implements http.Handler. func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { var spanName string // try to extract B3 Headers from upstream spanContext := h.tracer.Extract(b3.ExtractHTTP(r)) // store registered headers to be propagated in spanContext if h.baggage != nil { spanContext.Baggage = h.baggage.New() for key, values := range r.Header { spanContext.Baggage.Add(key, values...) } } if h.requestSampler != nil { if sample := h.requestSampler(r); sample != nil { spanContext.Sampled = sample } } if len(h.name) == 0 { spanName = r.Method } else { spanName = h.name } // create Span using SpanContext if found sp := h.tracer.StartSpan( spanName, zipkin.Kind(model.Server), zipkin.Parent(spanContext), ) // add our span to context ctx := zipkin.NewContext(r.Context(), sp) if zipkin.IsNoop(sp) { // While the span is not being recorded, we still want to propagate the context. h.next.ServeHTTP(w, r.WithContext(ctx)) return } remoteEndpoint, _ := zipkin.NewEndpoint("", r.RemoteAddr) sp.SetRemoteEndpoint(remoteEndpoint) for k, v := range h.defaultTags { sp.Tag(k, v) } // tag typical HTTP request items zipkin.TagHTTPMethod.Set(sp, r.Method) zipkin.TagHTTPPath.Set(sp, r.URL.Path) if r.ContentLength > 0 { zipkin.TagHTTPRequestSize.Set(sp, strconv.FormatInt(r.ContentLength, 10)) } // create http.ResponseWriter interceptor for tracking response size and // status code. ri := &rwInterceptor{w: w, statusCode: 200} // tag found response size and status code on exit defer func() { code := ri.getStatusCode() sCode := strconv.Itoa(code) if code < 200 || code > 299 { zipkin.TagHTTPStatusCode.Set(sp, sCode) if code > 399 { h.errHandler(sp, nil, code) } } if h.tagResponseSize && atomic.LoadUint64(&ri.size) > 0 { zipkin.TagHTTPResponseSize.Set(sp, ri.getResponseSize()) } sp.Finish() }() // call next http Handler func using our updated context. h.next.ServeHTTP(ri.wrap(), r.WithContext(ctx)) } // rwInterceptor intercepts the ResponseWriter, so it can track response size // and returned status code. type rwInterceptor struct { w http.ResponseWriter size uint64 statusCode int } func (r *rwInterceptor) Header() http.Header { return r.w.Header() } func (r *rwInterceptor) Write(b []byte) (n int, err error) { n, err = r.w.Write(b) atomic.AddUint64(&r.size, uint64(n)) return } func (r *rwInterceptor) WriteHeader(i int) { r.statusCode = i r.w.WriteHeader(i) } func (r *rwInterceptor) getStatusCode() int { return r.statusCode } func (r *rwInterceptor) getResponseSize() string { return strconv.FormatUint(atomic.LoadUint64(&r.size), 10) } func (r *rwInterceptor) wrap() http.ResponseWriter { // nolint:gocyclo var ( hj, i0 = r.w.(http.Hijacker) cn, i1 = r.w.(http.CloseNotifier) pu, i2 = r.w.(http.Pusher) fl, i3 = r.w.(http.Flusher) rf, i4 = r.w.(io.ReaderFrom) ) switch { case !i0 && !i1 && !i2 && !i3 && !i4: return struct { http.ResponseWriter }{r} case !i0 && !i1 && !i2 && !i3 && i4: return struct { http.ResponseWriter io.ReaderFrom }{r, rf} case !i0 && !i1 && !i2 && i3 && !i4: return struct { http.ResponseWriter http.Flusher }{r, fl} case !i0 && !i1 && !i2 && i3 && i4: return struct { http.ResponseWriter http.Flusher io.ReaderFrom }{r, fl, rf} case !i0 && !i1 && i2 && !i3 && !i4: return struct { http.ResponseWriter http.Pusher }{r, pu} case !i0 && !i1 && i2 && !i3 && i4: return struct { http.ResponseWriter http.Pusher io.ReaderFrom }{r, pu, rf} case !i0 && !i1 && i2 && i3 && !i4: return struct { http.ResponseWriter http.Pusher http.Flusher }{r, pu, fl} case !i0 && !i1 && i2 && i3 && i4: return struct { http.ResponseWriter http.Pusher http.Flusher io.ReaderFrom }{r, pu, fl, rf} case !i0 && i1 && !i2 && !i3 && !i4: return struct { http.ResponseWriter http.CloseNotifier }{r, cn} case !i0 && i1 && !i2 && !i3 && i4: return struct { http.ResponseWriter http.CloseNotifier io.ReaderFrom }{r, cn, rf} case !i0 && i1 && !i2 && i3 && !i4: return struct { http.ResponseWriter http.CloseNotifier http.Flusher }{r, cn, fl} case !i0 && i1 && !i2 && i3 && i4: return struct { http.ResponseWriter http.CloseNotifier http.Flusher io.ReaderFrom }{r, cn, fl, rf} case !i0 && i1 && i2 && !i3 && !i4: return struct { http.ResponseWriter http.CloseNotifier http.Pusher }{r, cn, pu} case !i0 && i1 && i2 && !i3 && i4: return struct { http.ResponseWriter http.CloseNotifier http.Pusher io.ReaderFrom }{r, cn, pu, rf} case !i0 && i1 && i2 && i3 && !i4: return struct { http.ResponseWriter http.CloseNotifier http.Pusher http.Flusher }{r, cn, pu, fl} case !i0 && i1 && i2 && i3 && i4: return struct { http.ResponseWriter http.CloseNotifier http.Pusher http.Flusher io.ReaderFrom }{r, cn, pu, fl, rf} case i0 && !i1 && !i2 && !i3 && !i4: return struct { http.ResponseWriter http.Hijacker }{r, hj} case i0 && !i1 && !i2 && !i3 && i4: return struct { http.ResponseWriter http.Hijacker io.ReaderFrom }{r, hj, rf} case i0 && !i1 && !i2 && i3 && !i4: return struct { http.ResponseWriter http.Hijacker http.Flusher }{r, hj, fl} case i0 && !i1 && !i2 && i3 && i4: return struct { http.ResponseWriter http.Hijacker http.Flusher io.ReaderFrom }{r, hj, fl, rf} case i0 && !i1 && i2 && !i3 && !i4: return struct { http.ResponseWriter http.Hijacker http.Pusher }{r, hj, pu} case i0 && !i1 && i2 && !i3 && i4: return struct { http.ResponseWriter http.Hijacker http.Pusher io.ReaderFrom }{r, hj, pu, rf} case i0 && !i1 && i2 && i3 && !i4: return struct { http.ResponseWriter http.Hijacker http.Pusher http.Flusher }{r, hj, pu, fl} case i0 && !i1 && i2 && i3 && i4: return struct { http.ResponseWriter http.Hijacker http.Pusher http.Flusher io.ReaderFrom }{r, hj, pu, fl, rf} case i0 && i1 && !i2 && !i3 && !i4: return struct { http.ResponseWriter http.Hijacker http.CloseNotifier }{r, hj, cn} case i0 && i1 && !i2 && !i3 && i4: return struct { http.ResponseWriter http.Hijacker http.CloseNotifier io.ReaderFrom }{r, hj, cn, rf} case i0 && i1 && !i2 && i3 && !i4: return struct { http.ResponseWriter http.Hijacker http.CloseNotifier http.Flusher }{r, hj, cn, fl} case i0 && i1 && !i2 && i3 && i4: return struct { http.ResponseWriter http.Hijacker http.CloseNotifier http.Flusher io.ReaderFrom }{r, hj, cn, fl, rf} case i0 && i1 && i2 && !i3 && !i4: return struct { http.ResponseWriter http.Hijacker http.CloseNotifier http.Pusher }{r, hj, cn, pu} case i0 && i1 && i2 && !i3 && i4: return struct { http.ResponseWriter http.Hijacker http.CloseNotifier http.Pusher io.ReaderFrom }{r, hj, cn, pu, rf} case i0 && i1 && i2 && i3 && !i4: return struct { http.ResponseWriter http.Hijacker http.CloseNotifier http.Pusher http.Flusher }{r, hj, cn, pu, fl} case i0 && i1 && i2 && i3 && i4: return struct { http.ResponseWriter http.Hijacker http.CloseNotifier http.Pusher http.Flusher io.ReaderFrom }{r, hj, cn, pu, fl, rf} default: return struct { http.ResponseWriter }{r} } } zipkin-go-0.4.3/middleware/http/server_test.go000066400000000000000000000172331461356266100214350ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package http_test import ( "bytes" "net/http" "net/http/httptest" "strconv" "testing" "github.com/openzipkin/zipkin-go" mw "github.com/openzipkin/zipkin-go/middleware/http" "github.com/openzipkin/zipkin-go/reporter/recorder" ) var ( lep, _ = zipkin.NewEndpoint("testSvc", "127.0.0.1:0") ) func httpHandler(code int, headers http.Header, body *bytes.Buffer) http.HandlerFunc { return func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(code) for key, value := range headers { w.Header().Add(key, value[0]) } _, _ = w.Write(body.Bytes()) } } func TestHTTPHandlerWrapping(t *testing.T) { var ( spanRecorder = &recorder.ReporterRecorder{} tr, _ = zipkin.NewTracer(spanRecorder, zipkin.WithLocalEndpoint(lep)) headers = make(http.Header) spanName = "wrapper_test" code = 404 request *http.Request ) headers.Add("some-key", "some-value") headers.Add("other-key", "other-value") testCases := []struct { method string requestBody *bytes.Buffer responseBody *bytes.Buffer hasRequestSize bool hasResponseSize bool }{ { method: "POST", requestBody: bytes.NewBufferString("incoming data"), responseBody: bytes.NewBufferString("oh oh we have a 404"), hasRequestSize: true, hasResponseSize: true, }, { method: "POST", requestBody: bytes.NewBufferString(""), responseBody: bytes.NewBufferString("oh oh we have a 404"), hasRequestSize: false, hasResponseSize: true, }, { method: "GET", requestBody: nil, responseBody: bytes.NewBufferString(""), hasRequestSize: false, hasResponseSize: false, }, } for _, c := range testCases { httpRecorder := httptest.NewRecorder() var err error if c.requestBody == nil { request, err = http.NewRequest(c.method, "/test", nil) } else { request, err = http.NewRequest(c.method, "/test", c.requestBody) } if err != nil { t.Fatalf("unable to create request") } httpHandlerFunc := httpHandler(code, headers, c.responseBody) tags := map[string]string{ "component": "testServer", } handler := mw.NewServerMiddleware( tr, mw.SpanName(spanName), mw.TagResponseSize(true), mw.ServerTags(tags), )(httpHandlerFunc) handler.ServeHTTP(httpRecorder, request) spans := spanRecorder.Flush() if want, have := 1, len(spans); want != have { t.Errorf("Expected %d spans, got %d", want, have) } span := spans[0] if want, have := spanName, span.Name; want != have { t.Errorf("Expected span name %s, got %s", want, have) } if c.hasRequestSize { if want, have := strconv.Itoa(c.requestBody.Len()), span.Tags["http.request.size"]; want != have { t.Errorf("Expected span request size %s, got %s", want, have) } } else { // http.request.size should not be present as request body is empty. if _, ok := span.Tags["http.request.size"]; ok { t.Errorf("Unexpected span request size") } } if c.hasResponseSize { if want, have := strconv.Itoa(c.responseBody.Len()), span.Tags["http.response.size"]; want != have { t.Errorf("Expected span response size %s, got %s", want, have) } } else { // http.response.size should not be present as request body is empty. if _, ok := span.Tags["http.response.size"]; ok { t.Errorf("Unexpected span response size") } } if want, have := strconv.Itoa(code), span.Tags["http.status_code"]; want != have { t.Errorf("Expected span status code %s, got %s", want, have) } if want, have := strconv.Itoa(code), span.Tags["error"]; want != have { t.Errorf("Expected span error %q, got %q", want, have) } if want, have := len(headers), len(httpRecorder.HeaderMap); want != have { t.Errorf("Expected http header count %d, got %d", want, have) } if want, have := code, httpRecorder.Code; want != have { t.Errorf("Expected http status code %d, got %d", want, have) } for key, value := range headers { if want, have := value, httpRecorder.HeaderMap.Get(key); want[0] != have { t.Errorf("Expected header %s value %s, got %s", key, want, have) } } if want, have := c.responseBody.String(), httpRecorder.Body.String(); want != have { t.Errorf("Expected body value %q, got %q", want, have) } } } func TestHTTPDefaultSpanName(t *testing.T) { var ( spanRecorder = &recorder.ReporterRecorder{} tr, _ = zipkin.NewTracer(spanRecorder, zipkin.WithLocalEndpoint(lep)) httpRecorder = httptest.NewRecorder() requestBuf = bytes.NewBufferString("incoming data") methodType = "POST" code = "" ) request, err := http.NewRequest(methodType, "/test", requestBuf) if err != nil { t.Fatalf("unable to create request") } httpHandlerFunc := httpHandler(200, nil, bytes.NewBufferString("")) handler := mw.NewServerMiddleware(tr)(httpHandlerFunc) handler.ServeHTTP(httpRecorder, request) spans := spanRecorder.Flush() if want, have := 1, len(spans); want != have { t.Errorf("Expected %d spans, got %d", want, have) } span := spans[0] if want, have := methodType, span.Name; want != have { t.Errorf("Expected span name %s, got %s", want, have) } if want, have := code, span.Tags["http.status_code"]; want != have { t.Errorf("Expected span status code %s, got %s", want, have) } } func TestHTTPRequestSampler(t *testing.T) { var ( spanRecorder = &recorder.ReporterRecorder{} httpRecorder = httptest.NewRecorder() requestBuf = bytes.NewBufferString("incoming data") methodType = "POST" httpHandlerFunc = httpHandler(200, nil, bytes.NewBufferString("")) ) samplers := []func(r *http.Request) *bool{ nil, func(*http.Request) *bool { return mw.Sample() }, func(*http.Request) *bool { return mw.Discard() }, func(*http.Request) *bool { return nil }, } for idx, sampler := range samplers { tr, _ := zipkin.NewTracer(spanRecorder, zipkin.WithLocalEndpoint(lep), zipkin.WithSampler(zipkin.AlwaysSample)) request, err := http.NewRequest(methodType, "/test", requestBuf) if err != nil { t.Fatalf("unable to create request") } handler := mw.NewServerMiddleware(tr, mw.RequestSampler(sampler))(httpHandlerFunc) handler.ServeHTTP(httpRecorder, request) spans := spanRecorder.Flush() sampledSpans := 0 if sampler == nil || sampler(request) == nil || *(sampler(request)) { sampledSpans = 1 } if want, have := sampledSpans, len(spans); want != have { t.Errorf("[%d] Expected %d spans, got %d", idx, want, have) } } for idx, sampler := range samplers { tr, _ := zipkin.NewTracer(spanRecorder, zipkin.WithLocalEndpoint(lep), zipkin.WithSampler(zipkin.NeverSample)) request, err := http.NewRequest(methodType, "/test", requestBuf) if err != nil { t.Fatalf("unable to create request") } handler := mw.NewServerMiddleware(tr, mw.RequestSampler(sampler))(httpHandlerFunc) handler.ServeHTTP(httpRecorder, request) spans := spanRecorder.Flush() sampledSpans := 0 if sampler != nil && sampler(request) != nil && *(sampler(request)) { sampledSpans = 1 } if want, have := sampledSpans, len(spans); want != have { t.Errorf("[%d] Expected %d spans, got %d", idx, want, have) } } } zipkin-go-0.4.3/middleware/http/spancloser.go000066400000000000000000000016451461356266100212410ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package http import ( "io" "time" zipkin "github.com/openzipkin/zipkin-go" ) type spanCloser struct { io.ReadCloser sp zipkin.Span traceEnabled bool } func (s *spanCloser) Close() (err error) { if s.traceEnabled { s.sp.Annotate(time.Now(), "Body Close") } err = s.ReadCloser.Close() s.sp.Finish() return } zipkin-go-0.4.3/middleware/http/spantrace.go000066400000000000000000000062701461356266100210470ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package http import ( "crypto/tls" "net/http/httptrace" "strconv" "strings" "time" zipkin "github.com/openzipkin/zipkin-go" ) type spanTrace struct { zipkin.Span c *httptrace.ClientTrace } func (s *spanTrace) getConn(hostPort string) { s.Annotate(time.Now(), "Connecting") s.Tag("httptrace.get_connection.host_port", hostPort) } func (s *spanTrace) gotConn(info httptrace.GotConnInfo) { s.Annotate(time.Now(), "Connected") s.Tag("httptrace.got_connection.reused", strconv.FormatBool(info.Reused)) s.Tag("httptrace.got_connection.was_idle", strconv.FormatBool(info.WasIdle)) if info.WasIdle { s.Tag("httptrace.got_connection.idle_time", info.IdleTime.String()) } } func (s *spanTrace) putIdleConn(err error) { s.Annotate(time.Now(), "Put Idle Connection") if err != nil { s.Tag("httptrace.put_idle_connection.error", err.Error()) } } func (s *spanTrace) gotFirstResponseByte() { s.Annotate(time.Now(), "First Response Byte") } func (s *spanTrace) got100Continue() { s.Annotate(time.Now(), "Got 100 Continue") } func (s *spanTrace) dnsStart(info httptrace.DNSStartInfo) { s.Annotate(time.Now(), "DNS Start") s.Tag("httptrace.dns_start.host", info.Host) } func (s *spanTrace) dnsDone(info httptrace.DNSDoneInfo) { s.Annotate(time.Now(), "DNS Done") var addrs []string for _, addr := range info.Addrs { addrs = append(addrs, addr.String()) } s.Tag("httptrace.dns_done.addrs", strings.Join(addrs, " , ")) if info.Err != nil { s.Tag("httptrace.dns_done.error", info.Err.Error()) } } func (s *spanTrace) connectStart(network, addr string) { s.Annotate(time.Now(), "Connect Start") s.Tag("httptrace.connect_start.network", network) s.Tag("httptrace.connect_start.addr", addr) } func (s *spanTrace) connectDone(network, addr string, err error) { s.Annotate(time.Now(), "Connect Done") s.Tag("httptrace.connect_done.network", network) s.Tag("httptrace.connect_done.addr", addr) if err != nil { s.Tag("httptrace.connect_done.error", err.Error()) } } func (s *spanTrace) tlsHandshakeStart() { s.Annotate(time.Now(), "TLS Handshake Start") } func (s *spanTrace) tlsHandshakeDone(_ tls.ConnectionState, err error) { s.Annotate(time.Now(), "TLS Handshake Done") if err != nil { s.Tag("httptrace.tls_handshake_done.error", err.Error()) } } func (s *spanTrace) wroteHeaders() { s.Annotate(time.Now(), "Wrote Headers") } func (s *spanTrace) wait100Continue() { s.Annotate(time.Now(), "Wait 100 Continue") } func (s *spanTrace) wroteRequest(info httptrace.WroteRequestInfo) { s.Annotate(time.Now(), "Wrote Request") if info.Err != nil { s.Tag("httptrace.wrote_request.error", info.Err.Error()) } } zipkin-go-0.4.3/middleware/http/transport.go000066400000000000000000000157221461356266100211250ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package http import ( "bytes" "io" "io/ioutil" "log" "net/http" "net/http/httptrace" "os" "strconv" "github.com/openzipkin/zipkin-go" "github.com/openzipkin/zipkin-go/model" "github.com/openzipkin/zipkin-go/propagation/b3" ) // ErrHandler allows instrumentations to decide how to tag errors // based on the response status code >399 and the error from the // Transport.RoundTrip type ErrHandler func(sp zipkin.Span, err error, statusCode int) func defaultErrHandler(sp zipkin.Span, err error, statusCode int) { if err != nil { zipkin.TagError.Set(sp, err.Error()) return } statusCodeVal := strconv.FormatInt(int64(statusCode), 10) zipkin.TagError.Set(sp, statusCodeVal) } // ErrResponseReader allows instrumentations to read the error body // and decide to obtain information to it and add it to the span i.e. // tag the span with a more meaningful error code or with error details. type ErrResponseReader func(sp zipkin.Span, body io.Reader) type transport struct { tracer *zipkin.Tracer rt http.RoundTripper httpTrace bool defaultTags map[string]string errHandler ErrHandler errResponseReader *ErrResponseReader logger *log.Logger requestSampler RequestSamplerFunc remoteEndpoint *model.Endpoint } // TransportOption allows one to configure optional transport configuration. type TransportOption func(*transport) // RoundTripper adds the Transport RoundTripper to wrap. func RoundTripper(rt http.RoundTripper) TransportOption { return func(t *transport) { if rt != nil { t.rt = rt } } } // TransportTags adds default Tags to inject into transport spans. func TransportTags(tags map[string]string) TransportOption { return func(t *transport) { t.defaultTags = tags } } // TransportTrace allows one to enable Go's net/http/httptrace. func TransportTrace(enable bool) TransportOption { return func(t *transport) { t.httpTrace = enable } } // TransportErrHandler allows to pass a custom error handler for the round trip func TransportErrHandler(h ErrHandler) TransportOption { return func(t *transport) { t.errHandler = h } } // TransportErrResponseReader allows to pass a custom ErrResponseReader func TransportErrResponseReader(r ErrResponseReader) TransportOption { return func(t *transport) { t.errResponseReader = &r } } // TransportRemoteEndpoint will set the remote endpoint for all spans. func TransportRemoteEndpoint(remoteEndpoint *model.Endpoint) TransportOption { return func(c *transport) { c.remoteEndpoint = remoteEndpoint } } // TransportLogger allows to plug a logger into the transport func TransportLogger(l *log.Logger) TransportOption { return func(t *transport) { t.logger = l } } // TransportRequestSampler allows one to set the sampling decision based on // the details found in the http.Request. It has preference over the existing // sampling decision contained in the context. The function returns a *bool, // if returning nil, sampling decision is not being changed whereas returning // something else than nil is being used as sampling decision. func TransportRequestSampler(sampleFunc RequestSamplerFunc) TransportOption { return func(t *transport) { t.requestSampler = sampleFunc } } // NewTransport returns a new Zipkin instrumented http RoundTripper which can be // used with a standard library http Client. func NewTransport(tracer *zipkin.Tracer, options ...TransportOption) (http.RoundTripper, error) { if tracer == nil { return nil, ErrValidTracerRequired } t := &transport{ tracer: tracer, rt: http.DefaultTransport, httpTrace: false, errHandler: defaultErrHandler, logger: log.New(os.Stderr, "", log.LstdFlags), } for _, option := range options { option(t) } return t, nil } // RoundTrip satisfies the RoundTripper interface. func (t *transport) RoundTrip(req *http.Request) (res *http.Response, err error) { sp, _ := t.tracer.StartSpanFromContext( req.Context(), req.URL.Scheme+"/"+req.Method, zipkin.Kind(model.Client), zipkin.RemoteEndpoint(t.remoteEndpoint), ) // inject registered headers from span context into the outgoing HTTP request headers if sp.Context().Baggage != nil { sp.Context().Baggage.Iterate(func(key string, values []string) { for _, val := range values { req.Header.Add(key, val) } }) } if zipkin.IsNoop(sp) { // While the span is not being recorded, we still want to propagate the context. _ = b3.InjectHTTP(req)(sp.Context()) return t.rt.RoundTrip(req) } for k, v := range t.defaultTags { sp.Tag(k, v) } if t.httpTrace { sptr := spanTrace{ Span: sp, } sptr.c = &httptrace.ClientTrace{ GetConn: sptr.getConn, GotConn: sptr.gotConn, PutIdleConn: sptr.putIdleConn, GotFirstResponseByte: sptr.gotFirstResponseByte, Got100Continue: sptr.got100Continue, DNSStart: sptr.dnsStart, DNSDone: sptr.dnsDone, ConnectStart: sptr.connectStart, ConnectDone: sptr.connectDone, TLSHandshakeStart: sptr.tlsHandshakeStart, TLSHandshakeDone: sptr.tlsHandshakeDone, WroteHeaders: sptr.wroteHeaders, Wait100Continue: sptr.wait100Continue, WroteRequest: sptr.wroteRequest, } req = req.WithContext( httptrace.WithClientTrace(req.Context(), sptr.c), ) } zipkin.TagHTTPMethod.Set(sp, req.Method) zipkin.TagHTTPPath.Set(sp, req.URL.Path) spCtx := sp.Context() if t.requestSampler != nil { if shouldSample := t.requestSampler(req); shouldSample != nil { spCtx.Sampled = shouldSample } } _ = b3.InjectHTTP(req)(spCtx) res, err = t.rt.RoundTrip(req) if err != nil { t.errHandler(sp, err, 0) sp.Finish() return nil, err } if res.ContentLength > 0 { zipkin.TagHTTPResponseSize.Set(sp, strconv.FormatInt(res.ContentLength, 10)) } if res.StatusCode < 200 || res.StatusCode > 299 { statusCode := strconv.FormatInt(int64(res.StatusCode), 10) zipkin.TagHTTPStatusCode.Set(sp, statusCode) if res.StatusCode > 399 { t.errHandler(sp, nil, res.StatusCode) if t.errResponseReader != nil { sBody, err := ioutil.ReadAll(res.Body) if err == nil { res.Body.Close() (*t.errResponseReader)(sp, ioutil.NopCloser(bytes.NewBuffer(sBody))) res.Body = ioutil.NopCloser(bytes.NewBuffer(sBody)) } else { t.logger.Printf("failed to read the response body in the ErrResponseReader: %v", err) } } } } sp.Finish() return res, err } zipkin-go-0.4.3/middleware/http/transport_test.go000066400000000000000000000136121461356266100221600ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package http import ( "context" "errors" "io" "io/ioutil" "net/http" "net/http/httptest" "testing" "github.com/openzipkin/zipkin-go" "github.com/openzipkin/zipkin-go/reporter/recorder" ) type errRoundTripper struct { err error } func (r errRoundTripper) RoundTrip(_ *http.Request) (*http.Response, error) { return nil, r.err } func TestRoundTripErrHandlingForRoundTripError(t *testing.T) { expectedErr := errors.New("error message") tracer, err := zipkin.NewTracer(nil) if err != nil { t.Fatalf("unexpected error when creating tracer: %v", err) } req, _ := http.NewRequest("GET", "localhost", nil) tr, _ := NewTransport( tracer, TransportErrHandler(func(_ zipkin.Span, err error, _ int) { if want, have := expectedErr, err; want != have { t.Errorf("unexpected error, want %q, have %q", want, have) } }), RoundTripper(&errRoundTripper{err: expectedErr}), ) _, err = tr.RoundTrip(req) if err == nil { t.Fatalf("expected error: %v", expectedErr) } } func TestRoundTripErrHandlingForStatusCode(t *testing.T) { tcs := []struct { actualStatusCode int expectedError int }{ // we start on 200, if we pass 100 it will wait until timeout. { actualStatusCode: 200, }, { actualStatusCode: 301, }, { actualStatusCode: 403, expectedError: 403, }, { actualStatusCode: 504, expectedError: 504, }, } for _, tc := range tcs { srv := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { rw.WriteHeader(tc.actualStatusCode) })) tracer, err := zipkin.NewTracer(nil) if err != nil { t.Fatalf("unexpected error when creating tracer: %v", err) } req, _ := http.NewRequest("GET", srv.URL, nil) tr, _ := NewTransport( tracer, TransportErrHandler(func(_ zipkin.Span, _ error, statusCode int) { if want, have := tc.expectedError, statusCode; want != 0 && want != have { t.Errorf("unexpected status code, want %d, have %d", want, have) } }), ) _, err = tr.RoundTrip(req) if err != nil { t.Fatalf("unexpected error in the round trip: %v", err) } srv.Close() } } func TestRoundTripErrResponseReadingSuccess(t *testing.T) { expectedBody := []byte("message") srv := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { rw.WriteHeader(500) _, _ = rw.Write(expectedBody) })) defer srv.Close() tracer, err := zipkin.NewTracer(nil) if err != nil { t.Fatalf("unexpected error when creating tracer: %v", err) } req, _ := http.NewRequest("GET", srv.URL, nil) tr, _ := NewTransport( tracer, TransportErrResponseReader(func(_ zipkin.Span, br io.Reader) { body, _ := ioutil.ReadAll(br) if want, have := expectedBody, body; string(want) != string(have) { t.Errorf("unexpected body, want %q, have %q", want, have) } }), ) res, err := tr.RoundTrip(req) if err != nil { t.Fatalf("unexpected error: %v", err) } actualBody, _ := ioutil.ReadAll(res.Body) if want, have := expectedBody, actualBody; string(expectedBody) != string(actualBody) { t.Errorf("unexpected body: want %s, have %s", want, have) } } func TestTransportRequestSamplerOverridesSamplingFromContext(t *testing.T) { cases := []struct { Sampler func(uint64) bool RequestSampler RequestSamplerFunc ExpectedSampling string }{ // Test proper handling when there is no RequestSampler { Sampler: zipkin.AlwaysSample, RequestSampler: nil, ExpectedSampling: "1", }, // Test proper handling when there is no RequestSampler { Sampler: zipkin.NeverSample, RequestSampler: nil, ExpectedSampling: "0", }, // Test RequestSampler override sample -> no sample { Sampler: zipkin.AlwaysSample, RequestSampler: func(*http.Request) *bool { return Discard() }, ExpectedSampling: "0", }, // Test RequestSampler override no sample -> sample { Sampler: zipkin.NeverSample, RequestSampler: func(*http.Request) *bool { return Sample() }, ExpectedSampling: "1", }, // Test RequestSampler pass through of sampled decision { Sampler: zipkin.AlwaysSample, RequestSampler: func(*http.Request) *bool { return nil }, ExpectedSampling: "1", }, // Test RequestSampler pass through of not sampled decision { Sampler: zipkin.NeverSample, RequestSampler: func(*http.Request) *bool { return nil }, ExpectedSampling: "0", }, } for i, c := range cases { srv := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { if want, have := c.ExpectedSampling, r.Header.Get("x-b3-sampled"); want != have { t.Errorf("unexpected sampling decision in case #%d, want %q, have %q", i, want, have) } })) // we need to use a valid reporter or Tracer will implement noop mode which makes this test invalid rep := recorder.NewReporter() tracer, err := zipkin.NewTracer(rep, zipkin.WithSampler(c.Sampler)) if err != nil { t.Fatalf("unexpected error when creating tracer: %v", err) } sp := tracer.StartSpan("op1") ctx := zipkin.NewContext(context.Background(), sp) req, _ := http.NewRequest("GET", srv.URL, nil) tr, _ := NewTransport( tracer, TransportRequestSampler(c.RequestSampler), ) _, err = tr.RoundTrip(req.WithContext(ctx)) sp.Finish() if err != nil { t.Fatalf("unexpected error: %v", err) } _ = rep.Close() srv.Close() } } zipkin-go-0.4.3/model/000077500000000000000000000000001461356266100145375ustar00rootroot00000000000000zipkin-go-0.4.3/model/annotation.go000066400000000000000000000032021461356266100172350ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package model import ( "encoding/json" "errors" "time" ) // ErrValidTimestampRequired error var ErrValidTimestampRequired = errors.New("valid annotation timestamp required") // Annotation associates an event that explains latency with a timestamp. type Annotation struct { Timestamp time.Time Value string } // MarshalJSON implements custom JSON encoding func (a *Annotation) MarshalJSON() ([]byte, error) { return json.Marshal(&struct { Timestamp int64 `json:"timestamp"` Value string `json:"value"` }{ Timestamp: a.Timestamp.Round(time.Microsecond).UnixNano() / 1e3, Value: a.Value, }) } // UnmarshalJSON implements custom JSON decoding func (a *Annotation) UnmarshalJSON(b []byte) error { type Alias Annotation annotation := &struct { TimeStamp uint64 `json:"timestamp"` *Alias }{ Alias: (*Alias)(a), } if err := json.Unmarshal(b, &annotation); err != nil { return err } if annotation.TimeStamp < 1 { return ErrValidTimestampRequired } a.Timestamp = time.Unix(0, int64(annotation.TimeStamp)*1e3) return nil } zipkin-go-0.4.3/model/annotation_test.go000066400000000000000000000021101461356266100202710ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package model import ( "encoding/json" "testing" ) func TestAnnotationNegativeTimestamp(t *testing.T) { var ( span SpanModel b1 = []byte(`{"annotations":[{"timestamp":-1}]}`) b2 = []byte(`{"annotations":[{"timestamp":0}]}`) ) if err := json.Unmarshal(b1, &span); err == nil { t.Errorf("Unmarshal should have failed with error, have: %+v", span) } if err := json.Unmarshal(b2, &span); err == nil { t.Errorf("Unmarshal should have failed with error, have: %+v", span) } } zipkin-go-0.4.3/model/doc.go000066400000000000000000000016711461356266100156400ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /* Package model contains the Zipkin V2 model which is used by the Zipkin Go tracer implementation. Third party instrumentation libraries can use the model and transport packages found in this Zipkin Go library to directly interface with the Zipkin Server or Zipkin Collectors without the need to use the tracer implementation itself. */ package model zipkin-go-0.4.3/model/endpoint.go000066400000000000000000000026741461356266100167170ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package model import ( "encoding/json" "net" "strings" ) // Endpoint holds the network context of a node in the service graph. type Endpoint struct { ServiceName string IPv4 net.IP IPv6 net.IP Port uint16 } // MarshalJSON exports our Endpoint into the correct format for the Zipkin V2 API. func (e Endpoint) MarshalJSON() ([]byte, error) { return json.Marshal(&struct { ServiceName string `json:"serviceName,omitempty"` IPv4 net.IP `json:"ipv4,omitempty"` IPv6 net.IP `json:"ipv6,omitempty"` Port uint16 `json:"port,omitempty"` }{ strings.ToLower(e.ServiceName), e.IPv4, e.IPv6, e.Port, }) } // Empty returns if all Endpoint properties are empty / unspecified. func (e *Endpoint) Empty() bool { return e == nil || (e.ServiceName == "" && e.Port == 0 && len(e.IPv4) == 0 && len(e.IPv6) == 0) } zipkin-go-0.4.3/model/endpoint_test.go000066400000000000000000000024261461356266100177510ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package model_test import ( "net" "testing" "github.com/openzipkin/zipkin-go/model" ) func TestEmptyEndpoint(t *testing.T) { var e *model.Endpoint if want, have := true, e.Empty(); want != have { t.Errorf("Endpoint want %t, have %t", want, have) } e = &model.Endpoint{} if want, have := true, e.Empty(); want != have { t.Errorf("Endpoint want %t, have %t", want, have) } e = &model.Endpoint{ IPv4: net.IPv4zero, } if want, have := false, e.Empty(); want != have { t.Errorf("Endpoint want %t, have %t", want, have) } e = &model.Endpoint{ IPv6: net.IPv6zero, } if want, have := false, e.Empty(); want != have { t.Errorf("Endpoint want %t, have %t", want, have) } } zipkin-go-0.4.3/model/kind.go000066400000000000000000000015771461356266100160250ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package model // Kind clarifies context of timestamp, duration and remoteEndpoint in a span. type Kind string // Available Kind values const ( Undetermined Kind = "" Client Kind = "CLIENT" Server Kind = "SERVER" Producer Kind = "PRODUCER" Consumer Kind = "CONSUMER" ) zipkin-go-0.4.3/model/span.go000066400000000000000000000121331461356266100160270ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package model import ( "encoding/json" "errors" "strings" "time" ) // unmarshal errors var ( ErrValidTraceIDRequired = errors.New("valid traceId required") ErrValidIDRequired = errors.New("valid span id required") ErrValidDurationRequired = errors.New("valid duration required") ) // BaggageFields holds the interface for consumers needing to interact with // the fields in application logic. type BaggageFields interface { // Get returns the values for a field identified by its key. Get(key string) []string // Add adds the provided values to a header designated by key. If not // accepted by the baggage implementation, it will return false. Add(key string, value ...string) bool // Set sets the provided values to a header designated by key. If not // accepted by the baggage implementation, it will return false. Set(key string, value ...string) bool // Delete removes the field data designated by key. If not accepted by the // baggage implementation, it will return false. Delete(key string) bool // Iterate will iterate over the available fields and for each one it will // trigger the callback function. Iterate(f func(key string, values []string)) } // SpanContext holds the context of a Span. type SpanContext struct { TraceID TraceID `json:"traceId"` ID ID `json:"id"` ParentID *ID `json:"parentId,omitempty"` Debug bool `json:"debug,omitempty"` Sampled *bool `json:"-"` Err error `json:"-"` Baggage BaggageFields `json:"-"` } // SpanModel structure. // // If using this library to instrument your application you will not need to // directly access or modify this representation. The SpanModel is exported for // use cases involving 3rd party Go instrumentation libraries desiring to // export data to a Zipkin server using the Zipkin V2 Span model. type SpanModel struct { SpanContext Name string `json:"name,omitempty"` Kind Kind `json:"kind,omitempty"` Timestamp time.Time `json:"-"` Duration time.Duration `json:"-"` Shared bool `json:"shared,omitempty"` LocalEndpoint *Endpoint `json:"localEndpoint,omitempty"` RemoteEndpoint *Endpoint `json:"remoteEndpoint,omitempty"` Annotations []Annotation `json:"annotations,omitempty"` Tags map[string]string `json:"tags,omitempty"` } // MarshalJSON exports our Model into the correct format for the Zipkin V2 API. func (s SpanModel) MarshalJSON() ([]byte, error) { type Alias SpanModel var timestamp int64 if !s.Timestamp.IsZero() { if s.Timestamp.Unix() < 1 { // Zipkin does not allow Timestamps before Unix epoch return nil, ErrValidTimestampRequired } timestamp = s.Timestamp.Round(time.Microsecond).UnixNano() / 1e3 } if s.Duration < time.Microsecond { if s.Duration < 0 { // negative duration is not allowed and signals a timing logic error return nil, ErrValidDurationRequired } else if s.Duration > 0 { // sub microsecond durations are reported as 1 microsecond s.Duration = 1 * time.Microsecond } } else { // Duration will be rounded to nearest microsecond representation. // // NOTE: Duration.Round() is not available in Go 1.8 which we still support. // To handle microsecond resolution rounding we'll add 500 nanoseconds to // the duration. When truncated to microseconds in the call to marshal, it // will be naturally rounded. See TestSpanDurationRounding in span_test.go s.Duration += 500 * time.Nanosecond } s.Name = strings.ToLower(s.Name) if s.LocalEndpoint.Empty() { s.LocalEndpoint = nil } if s.RemoteEndpoint.Empty() { s.RemoteEndpoint = nil } return json.Marshal(&struct { T int64 `json:"timestamp,omitempty"` D int64 `json:"duration,omitempty"` Alias }{ T: timestamp, D: s.Duration.Nanoseconds() / 1e3, Alias: (Alias)(s), }) } // UnmarshalJSON imports our Model from a Zipkin V2 API compatible span // representation. func (s *SpanModel) UnmarshalJSON(b []byte) error { type Alias SpanModel span := &struct { T uint64 `json:"timestamp,omitempty"` D uint64 `json:"duration,omitempty"` *Alias }{ Alias: (*Alias)(s), } if err := json.Unmarshal(b, &span); err != nil { return err } if s.ID < 1 { return ErrValidIDRequired } if span.T > 0 { s.Timestamp = time.Unix(0, int64(span.T)*1e3) } s.Duration = time.Duration(span.D*1e3) * time.Nanosecond if s.LocalEndpoint.Empty() { s.LocalEndpoint = nil } if s.RemoteEndpoint.Empty() { s.RemoteEndpoint = nil } return nil } zipkin-go-0.4.3/model/span_id.go000066400000000000000000000023021461356266100165000ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package model import ( "fmt" "strconv" ) // ID type type ID uint64 // String outputs the 64-bit ID as hex string. func (i ID) String() string { return fmt.Sprintf("%016x", uint64(i)) } // MarshalJSON serializes an ID type (SpanID, ParentSpanID) to HEX. func (i ID) MarshalJSON() ([]byte, error) { return []byte(fmt.Sprintf("%q", i.String())), nil } // UnmarshalJSON deserializes an ID type (SpanID, ParentSpanID) from HEX. func (i *ID) UnmarshalJSON(b []byte) (err error) { var id uint64 if len(b) < 3 { return nil } id, err = strconv.ParseUint(string(b[1:len(b)-1]), 16, 64) *i = ID(id) return err } zipkin-go-0.4.3/model/span_test.go000066400000000000000000000132621461356266100170720ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package model import ( "encoding/json" "errors" "fmt" "net" "reflect" "strings" "testing" "time" ) func TestSpanJSON(t *testing.T) { var ( span1 SpanModel span2 SpanModel parentID = ID(1003) sampled = true tags = make(map[string]string) ) tags["myKey"] = "myValue" tags["another"] = "tag" span1 = SpanModel{ SpanContext: SpanContext{ TraceID: TraceID{ High: 1001, Low: 1002, }, ID: ID(1004), ParentID: &parentID, Debug: true, Sampled: &sampled, Err: errors.New("dummy"), }, Name: "myMethod", Kind: Server, Timestamp: time.Now().Add(-100 * time.Millisecond), Duration: 50 * time.Millisecond, Shared: true, LocalEndpoint: &Endpoint{ ServiceName: "myService", IPv4: net.IPv4(127, 0, 0, 1), IPv6: net.IPv6loopback, }, RemoteEndpoint: nil, Annotations: []Annotation{ {time.Now().Add(-90 * time.Millisecond), "myAnnotation"}, }, Tags: tags, } b, err := json.Marshal(&span1) if err != nil { t.Errorf("expected successful serialization to JSON, got error: %+v", err) } err = json.Unmarshal(b, &span2) if err != nil { t.Errorf("expected successful deserialization from JSON, got error: %+v", err) } /* remove items from span1 which should not have exported */ span1.Sampled = nil span1.Err = nil // trim resolution back to microseconds (Zipkin's smallest time unit) span1.Timestamp = span1.Timestamp.Round(time.Microsecond) for idx := range span1.Annotations { span1.Annotations[idx].Timestamp = span1.Annotations[idx].Timestamp.Round(time.Microsecond) } span1.Name = strings.ToLower(span1.Name) span1.LocalEndpoint.ServiceName = strings.ToLower(span1.LocalEndpoint.ServiceName) if !reflect.DeepEqual(span1, span2) { t.Errorf("want SpanModel: %+v, have: %+v", span1, span2) } } func TestEmptyTraceID(t *testing.T) { var ( span SpanModel b = []byte(`{"traceId":"","id":"1"}`) ) if err := json.Unmarshal(b, &span); err == nil { t.Errorf("Unmarshal should have failed with error, have: %+v", span) } } func TestEmptySpanID(t *testing.T) { var ( span SpanModel b = []byte(`{"traceId":"1","id":""}`) ) if err := json.Unmarshal(b, &span); err == nil { t.Errorf("Unmarshal should have failed with error, have: %+v", span) } } func TestSpanEmptyTimeStamp(t *testing.T) { var ( span1 SpanModel span2 SpanModel ts time.Time ) span1 = SpanModel{ SpanContext: SpanContext{ TraceID: TraceID{ Low: 1, }, ID: 1, }, } b, err := json.Marshal(span1) if err != nil { t.Fatalf("unable to marshal span: %+v", err) } if err := json.Unmarshal(b, &span2); err != nil { t.Fatalf("unable to unmarshal span: %+v", err) } if want, have := ts, span2.Timestamp; want != have { t.Errorf("Timestamp want %s, have %s", want, have) } } func TestSpanDurationRounding(t *testing.T) { durations := []struct { nano time.Duration micro time.Duration }{ {0, 0}, {1, 1000}, {999, 1000}, {1000, 1000}, {1001, 1000}, {1499, 1000}, {1500, 2000}, {2000, 2000}, {2001, 2000}, {2499, 2000}, {2500, 3000}, {2999, 3000}, {3000, 3000}, } for i, duration := range durations { span := SpanModel{ SpanContext: SpanContext{ TraceID: TraceID{Low: 1}, ID: ID(1), }, Timestamp: time.Now(), Duration: duration.nano, } b, err := json.Marshal(span) if err != nil { t.Fatalf("span marshal failed: %+v", err) } span2 := SpanModel{} if err := json.Unmarshal(b, &span2); err != nil { t.Fatalf("span unmarshal failed: %+v", err) } if want, have := duration.micro, span2.Duration; want != have { t.Errorf("[%d] Duration want %d, have %d", i, want, have) } } } func TestSpanNegativeDuration(t *testing.T) { var ( err error span SpanModel b = []byte(`{"duration":-1}`) ) if err = json.Unmarshal(b, &span); err == nil { t.Errorf("Unmarshal should have failed with error, have: %+v", span) } span = SpanModel{ SpanContext: SpanContext{ TraceID: TraceID{Low: 1}, ID: ID(1), }, Timestamp: time.Now(), Duration: -1 * time.Nanosecond, } if _, err = json.Marshal(span); err == nil { t.Fatalf("Span Marshal Error expected, have nil") } want := fmt.Sprintf( "json: error calling MarshalJSON for type model.SpanModel: %s", ErrValidDurationRequired.Error(), ) if have := err.Error(); want != have { t.Errorf("Span Marshal Error want %s, have %s", want, have) } } func TestSpanNegativeTimestamp(t *testing.T) { var ( err error span SpanModel b = []byte(`{"timestamp":-1}`) ) if err = json.Unmarshal(b, &span); err == nil { t.Errorf("Unmarshal should have failed with error, have: %+v", span) } span = SpanModel{ SpanContext: SpanContext{ TraceID: TraceID{Low: 1}, ID: ID(1), }, Timestamp: time.Unix(-1, 0), } if _, err = json.Marshal(span); err == nil { t.Fatalf("Span Marshal Error expected, have nil") } want := fmt.Sprintf( "json: error calling MarshalJSON for type model.SpanModel: %s", ErrValidTimestampRequired.Error(), ) if have := err.Error(); want != have { t.Errorf("Span Marshal Error want %s, have %s", want, have) } } zipkin-go-0.4.3/model/traceid.go000066400000000000000000000041651461356266100165070ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package model import ( "fmt" "strconv" ) // TraceID is a 128 bit number internally stored as 2x uint64 (high & low). // In case of 64 bit traceIDs, the value can be found in Low. type TraceID struct { High uint64 Low uint64 } // Empty returns if TraceID has zero value. func (t TraceID) Empty() bool { return t.Low == 0 && t.High == 0 } // String outputs the 128-bit traceID as hex string. func (t TraceID) String() string { if t.High == 0 { return fmt.Sprintf("%016x", t.Low) } return fmt.Sprintf("%016x%016x", t.High, t.Low) } // TraceIDFromHex returns the TraceID from a hex string. func TraceIDFromHex(h string) (t TraceID, err error) { if len(h) > 16 { if t.High, err = strconv.ParseUint(h[0:len(h)-16], 16, 64); err != nil { return } t.Low, err = strconv.ParseUint(h[len(h)-16:], 16, 64) return } t.Low, err = strconv.ParseUint(h, 16, 64) return } // MarshalJSON custom JSON serializer to export the TraceID in the required // zero padded hex representation. func (t TraceID) MarshalJSON() ([]byte, error) { return []byte(fmt.Sprintf("%q", t.String())), nil } // UnmarshalJSON custom JSON deserializer to retrieve the traceID from the hex // encoded representation. func (t *TraceID) UnmarshalJSON(traceID []byte) error { if len(traceID) < 3 { return ErrValidTraceIDRequired } // A valid JSON string is encoded wrapped in double quotes. We need to trim // these before converting the hex payload. tID, err := TraceIDFromHex(string(traceID[1 : len(traceID)-1])) if err != nil { return err } *t = tID return nil } zipkin-go-0.4.3/model/traceid_test.go000066400000000000000000000045531461356266100175470ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package model import ( "encoding/json" "testing" ) func TestTraceID(t *testing.T) { traceID := TraceID{High: 1, Low: 2} if len(traceID.String()) != 32 { t.Errorf("Expected zero-padded TraceID to have 32 characters") } b, err := json.Marshal(traceID) if err != nil { t.Fatalf("Expected successful json serialization, got error: %+v", err) } if want, have := string(b), `"00000000000000010000000000000002"`; want != have { t.Fatalf("Expected json serialization, want %q, have %q", want, have) } var traceID2 TraceID if err = json.Unmarshal(b, &traceID2); err != nil { t.Fatalf("Expected successful json deserialization, got error: %+v", err) } if traceID2.High != traceID.High || traceID2.Low != traceID.Low { t.Fatalf("Unexpected traceID2, want: %#v, have %#v", traceID, traceID2) } have, err := TraceIDFromHex(traceID.String()) if err != nil { t.Fatalf("Expected traceID got error: %+v", err) } if traceID.High != have.High || traceID.Low != have.Low { t.Errorf("Expected %+v, got %+v", traceID, have) } traceID = TraceID{High: 0, Low: 2} if len(traceID.String()) != 16 { t.Errorf("Expected zero-padded TraceID to have 16 characters, got %d", len(traceID.String())) } have, err = TraceIDFromHex(traceID.String()) if err != nil { t.Fatalf("Expected traceID got error: %+v", err) } if traceID.High != have.High || traceID.Low != have.Low { t.Errorf("Expected %+v, got %+v", traceID, have) } traceID = TraceID{High: 0, Low: 0} if !traceID.Empty() { t.Errorf("Expected TraceID to be empty") } if _, err = TraceIDFromHex("12345678901234zz12345678901234zz"); err == nil { t.Errorf("Expected error got nil") } if err = json.Unmarshal([]byte(`"12345678901234zz12345678901234zz"`), &traceID); err == nil { t.Errorf("Expected error got nil") } } zipkin-go-0.4.3/noop.go000066400000000000000000000024311461356266100147410ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package zipkin import ( "time" "github.com/openzipkin/zipkin-go/model" ) type noopSpan struct { model.SpanContext } func (n *noopSpan) Context() model.SpanContext { return n.SpanContext } func (n *noopSpan) SetName(string) {} func (*noopSpan) SetRemoteEndpoint(*model.Endpoint) {} func (*noopSpan) Annotate(time.Time, string) {} func (*noopSpan) Tag(string, string) {} func (*noopSpan) Finish() {} func (*noopSpan) FinishedWithDuration(_ time.Duration) {} func (*noopSpan) Flush() {} // IsNoop tells whether the span is noop or not. Usually used to avoid resource misusage // when customizing a span as data won't be recorded func IsNoop(s Span) bool { _, ok := s.(*noopSpan) return ok } zipkin-go-0.4.3/noop_test.go000066400000000000000000000040371461356266100160040ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package zipkin import ( "reflect" "testing" "time" "github.com/openzipkin/zipkin-go/model" "github.com/openzipkin/zipkin-go/reporter" ) func TestNoopContext(t *testing.T) { var ( span Span sc model.SpanContext parentID = model.ID(3) tr, _ = NewTracer( reporter.NewNoopReporter(), WithNoopSpan(true), WithSampler(NeverSample), WithSharedSpans(true), ) ) sc = model.SpanContext{ TraceID: model.TraceID{High: 1, Low: 2}, ID: model.ID(4), ParentID: &parentID, Debug: false, // debug must be false Sampled: new(bool), // bool must be pointer to false } span = tr.StartSpan("testNoop", Parent(sc), Kind(model.Server)) noop, ok := span.(*noopSpan) if !ok { t.Fatalf("Span type want %s, have %s", reflect.TypeOf(&spanImpl{}), reflect.TypeOf(span)) } if have := noop.Context(); !reflect.DeepEqual(sc, have) { t.Errorf("Context want %+v, have %+v", sc, have) } span.Tag("dummy", "dummy") span.Annotate(time.Now(), "dummy") span.SetName("dummy") span.SetRemoteEndpoint(nil) span.Flush() } func TestIsNoop(t *testing.T) { sc := model.SpanContext{ TraceID: model.TraceID{High: 1, Low: 2}, ID: model.ID(3), Sampled: new(bool), } ns := &noopSpan{sc} if want, have := true, IsNoop(ns); want != have { t.Error("unexpected noop") } span := &spanImpl{SpanModel: model.SpanModel{SpanContext: sc}} if want, have := false, IsNoop(span); want != have { t.Error("expected noop") } } zipkin-go-0.4.3/propagation/000077500000000000000000000000001461356266100157625ustar00rootroot00000000000000zipkin-go-0.4.3/propagation/b3/000077500000000000000000000000001461356266100162665ustar00rootroot00000000000000zipkin-go-0.4.3/propagation/b3/doc.go000066400000000000000000000012761461356266100173700ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /* Package b3 implements serialization and deserialization logic for Zipkin B3 Headers. */ package b3 zipkin-go-0.4.3/propagation/b3/grpc.go000066400000000000000000000047141461356266100175560ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package b3 import ( "google.golang.org/grpc/metadata" "github.com/openzipkin/zipkin-go/model" "github.com/openzipkin/zipkin-go/propagation" ) // ExtractGRPC will extract a span.Context from the gRPC Request metadata if // found in B3 header format. func ExtractGRPC(md *metadata.MD) propagation.Extractor { return func() (*model.SpanContext, error) { var ( traceIDHeader = GetGRPCHeader(md, TraceID) spanIDHeader = GetGRPCHeader(md, SpanID) parentSpanIDHeader = GetGRPCHeader(md, ParentSpanID) sampledHeader = GetGRPCHeader(md, Sampled) flagsHeader = GetGRPCHeader(md, Flags) ) return ParseHeaders( traceIDHeader, spanIDHeader, parentSpanIDHeader, sampledHeader, flagsHeader, ) } } // InjectGRPC will inject a span.Context into gRPC metadata. func InjectGRPC(md *metadata.MD) propagation.Injector { return func(sc model.SpanContext) error { if (model.SpanContext{}) == sc { return ErrEmptyContext } if sc.Debug { setGRPCHeader(md, Flags, "1") } else if sc.Sampled != nil { // Debug is encoded as X-B3-Flags: 1. Since Debug implies Sampled, // we don't send "X-B3-Sampled" if Debug is set. if *sc.Sampled { setGRPCHeader(md, Sampled, "1") } else { setGRPCHeader(md, Sampled, "0") } } if !sc.TraceID.Empty() && sc.ID > 0 { // set identifiers setGRPCHeader(md, TraceID, sc.TraceID.String()) setGRPCHeader(md, SpanID, sc.ID.String()) if sc.ParentID != nil { setGRPCHeader(md, ParentSpanID, sc.ParentID.String()) } } return nil } } // GetGRPCHeader retrieves the last value found for a particular key. If key is // not found it returns an empty string. func GetGRPCHeader(md *metadata.MD, key string) string { v := (*md)[key] if len(v) < 1 { return "" } return v[len(v)-1] } func setGRPCHeader(md *metadata.MD, key, value string) { (*md)[key] = append((*md)[key], value) } zipkin-go-0.4.3/propagation/b3/grpc_test.go000066400000000000000000000173321461356266100206150ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package b3_test import ( "testing" zipkin "github.com/openzipkin/zipkin-go" "github.com/openzipkin/zipkin-go/model" "github.com/openzipkin/zipkin-go/propagation/b3" "github.com/openzipkin/zipkin-go/reporter/recorder" "google.golang.org/grpc/metadata" ) func TestGRPCExtractFlagsOnly(t *testing.T) { md := metadata.Pairs(b3.Flags, "1") sc, err := b3.ExtractGRPC(&md)() if err != nil { t.Fatalf("ExtractGRPC Failed: %+v", err) } if want, have := true, sc.Debug; want != have { t.Errorf("sc.Debug want %+v, have: %+v", want, have) } } func TestGRPCExtractSampledOnly(t *testing.T) { md := metadata.Pairs(b3.Sampled, "0") sc, err := b3.ExtractGRPC(&md)() if err != nil { t.Fatalf("ExtractGRPC failed: %+v", err) } if sc.Sampled == nil { t.Fatalf("Sampled want %t, have nil", false) } if want, have := false, *sc.Sampled; want != have { t.Errorf("Sampled want %t, have %t", want, have) } md = metadata.Pairs(b3.Sampled, "1") sc, err = b3.ExtractGRPC(&md)() if err != nil { t.Fatalf("ExtractGRPC failed: %+v", err) } if sc.Sampled == nil { t.Fatalf("Sampled want %t, have nil", true) } if want, have := true, *sc.Sampled; want != have { t.Errorf("Sampled want %t, have %t", want, have) } } func TestGRPCExtractFlagsAndSampledOnly(t *testing.T) { md := metadata.Pairs( b3.Flags, "1", b3.Sampled, "1", ) sc, err := b3.ExtractGRPC(&md)() if err != nil { t.Fatalf("ExtractGRPC failed: %+v", err) } if want, have := true, sc.Debug; want != have { t.Errorf("Debug want %t, have %t", want, have) } if sc.Sampled != nil { t.Fatalf("Sampled want nil, have %+v", *sc.Sampled) } } func TestGRPCExtractSampledErrors(t *testing.T) { md := metadata.Pairs(b3.Sampled, "2") sc, err := b3.ExtractGRPC(&md)() if want, have := b3.ErrInvalidSampledHeader, err; want != have { t.Errorf("SpanContext Error want %+v, have %+v", want, have) } if sc != nil { t.Errorf("SpanContext want nil, have: %+v", sc) } } func TestGRPCExtractFlagsErrors(t *testing.T) { values := map[string]bool{ "1": true, // only acceptable Flags value, debug switches to true "true": false, // true is not a valid value for Flags "3": false, // Flags is not a bitset "6": false, // Flags is not a bitset "7": false, // Flags is not a bitset } for value, debug := range values { md := metadata.Pairs(b3.Flags, value) spanContext, err := b3.ExtractGRPC(&md)() if err != nil { // Flags should not trigger failed extraction t.Fatalf("ExtractHTTP failed: %+v", err) } if want, have := debug, spanContext.Debug; want != have { t.Errorf("SpanContext Error want %t, have %t", want, have) } } } func TestGRPCExtractScope(t *testing.T) { recorder := &recorder.ReporterRecorder{} defer recorder.Close() tracer, err := zipkin.NewTracer(recorder, zipkin.WithTraceID128Bit(true)) if err != nil { t.Fatalf("Tracer failed: %+v", err) } iterations := 1000 for i := 0; i < iterations; i++ { var ( parent = tracer.StartSpan("parent") child = tracer.StartSpan("child", zipkin.Parent(parent.Context())) wantContext = child.Context() ) md := metadata.MD{} b3.InjectGRPC(&md)(wantContext) haveContext, err := b3.ExtractGRPC(&md)() if err != nil { t.Errorf("ExtractGRPC failed: %+v", err) } if haveContext == nil { t.Fatalf("SpanContext want valid value, have nil") } if want, have := wantContext.TraceID, haveContext.TraceID; want != have { t.Errorf("Traceid want %+v, have %+v", want, have) } if want, have := wantContext.ID, haveContext.ID; want != have { t.Errorf("ID want %+v, have %+v", want, have) } if want, have := *wantContext.ParentID, *haveContext.ParentID; want != have { t.Errorf("ParentID want %+v, have %+v", want, have) } child.Finish() parent.Finish() } // check if we have all spans (2x the iterations: parent+child span) if want, have := 2*iterations, len(recorder.Flush()); want != have { t.Errorf("Recorded Span Count want %d, have %d", want, have) } } func TestGRPCExtractTraceIDError(t *testing.T) { md := metadata.Pairs(b3.TraceID, "invalid_data") _, err := b3.ExtractGRPC(&md)() if want, have := b3.ErrInvalidTraceIDHeader, err; want != have { t.Errorf("ExtractGRPC Error want %+v, have %+v", want, have) } } func TestGRPCExtractSpanIDError(t *testing.T) { md := metadata.Pairs(b3.SpanID, "invalid_data") _, err := b3.ExtractGRPC(&md)() if want, have := b3.ErrInvalidSpanIDHeader, err; want != have { t.Errorf("ExtractGRPC Error want %+v, have %+v", want, have) } } func TestGRPCExtractTraceIDOnlyError(t *testing.T) { md := metadata.Pairs(b3.TraceID, "1") _, err := b3.ExtractGRPC(&md)() if want, have := b3.ErrInvalidScope, err; want != have { t.Errorf("ExtractGRPC Error want %+v, got %+v", want, have) } } func TestGRPCExtractSpanIDOnlyError(t *testing.T) { md := metadata.Pairs(b3.SpanID, "1") _, err := b3.ExtractGRPC(&md)() if want, have := b3.ErrInvalidScope, err; want != have { t.Errorf("ExtractGRPC Error want %+v, have %+v", want, have) } } func TestGRPCExtractParentIDOnlyError(t *testing.T) { md := metadata.Pairs(b3.ParentSpanID, "1") _, err := b3.ExtractGRPC(&md)() if want, have := b3.ErrInvalidScopeParent, err; want != have { t.Errorf("ExtractGRPC Error want %+v, have %+v", want, have) } } func TestGRPCExtractInvalidParentIDError(t *testing.T) { md := metadata.Pairs( b3.TraceID, "1", b3.SpanID, "2", b3.ParentSpanID, "invalid_data", ) _, err := b3.ExtractGRPC(&md)() if want, have := b3.ErrInvalidParentSpanIDHeader, err; want != have { t.Errorf("ExtractGRPC Error want %+v, have %+v", want, have) } } func TestGRPCInjectEmptyContextError(t *testing.T) { err := b3.InjectGRPC(nil)(model.SpanContext{}) if want, have := b3.ErrEmptyContext, err; want != have { t.Errorf("GRPCInject Error want %+v, have %+v", want, have) } } func TestGRPCInjectDebugOnly(t *testing.T) { md := &metadata.MD{} sc := model.SpanContext{ Debug: true, } b3.InjectGRPC(md)(sc) if want, have := "1", b3.GetGRPCHeader(md, b3.Flags); want != have { t.Errorf("Flags want %s, have %s", want, have) } } func TestGRPCInjectSampledOnly(t *testing.T) { md := &metadata.MD{} sampled := false sc := model.SpanContext{ Sampled: &sampled, } b3.InjectGRPC(md)(sc) if want, have := "0", b3.GetGRPCHeader(md, b3.Sampled); want != have { t.Errorf("Sampled want %s, have %s", want, have) } } func TestGRPCInjectUnsampledTrace(t *testing.T) { md := &metadata.MD{} sampled := false sc := model.SpanContext{ TraceID: model.TraceID{Low: 1}, ID: model.ID(2), Sampled: &sampled, } b3.InjectGRPC(md)(sc) if want, have := "0", b3.GetGRPCHeader(md, b3.Sampled); want != have { t.Errorf("Sampled want %s, have %s", want, have) } } func TestGRPCInjectSampledAndDebugTrace(t *testing.T) { md := &metadata.MD{} sampled := true sc := model.SpanContext{ TraceID: model.TraceID{Low: 1}, ID: model.ID(2), Debug: true, Sampled: &sampled, } b3.InjectGRPC(md)(sc) if want, have := "", b3.GetGRPCHeader(md, b3.Sampled); want != have { t.Errorf("Sampled want empty, have %s", have) } if want, have := "1", b3.GetGRPCHeader(md, b3.Flags); want != have { t.Errorf("Debug want %s, have %s", want, have) } } zipkin-go-0.4.3/propagation/b3/http.go000066400000000000000000000065321461356266100176020ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package b3 import ( "net/http" "github.com/openzipkin/zipkin-go/model" "github.com/openzipkin/zipkin-go/propagation" ) // InjectOption provides functional option handler type. type InjectOption func(opts *InjectOptions) // InjectOptions provides the available functional options. type InjectOptions struct { shouldInjectSingleHeader bool shouldInjectMultiHeader bool } // WithSingleAndMultiHeader allows to include both single and multiple // headers in the context injection func WithSingleAndMultiHeader() InjectOption { return func(opts *InjectOptions) { opts.shouldInjectSingleHeader = true opts.shouldInjectMultiHeader = true } } // WithSingleHeaderOnly allows to include only single header in the context // injection func WithSingleHeaderOnly() InjectOption { return func(opts *InjectOptions) { opts.shouldInjectSingleHeader = true opts.shouldInjectMultiHeader = false } } // ExtractHTTP will extract a span.Context from the HTTP Request if found in // B3 header format. func ExtractHTTP(r *http.Request) propagation.Extractor { return func() (*model.SpanContext, error) { var ( traceIDHeader = r.Header.Get(TraceID) spanIDHeader = r.Header.Get(SpanID) parentSpanIDHeader = r.Header.Get(ParentSpanID) sampledHeader = r.Header.Get(Sampled) flagsHeader = r.Header.Get(Flags) singleHeader = r.Header.Get(Context) ) var ( sc *model.SpanContext sErr error mErr error ) if singleHeader != "" { sc, sErr = ParseSingleHeader(singleHeader) if sErr == nil { return sc, nil } } sc, mErr = ParseHeaders( traceIDHeader, spanIDHeader, parentSpanIDHeader, sampledHeader, flagsHeader, ) if mErr != nil && sErr != nil { return nil, sErr } return sc, mErr } } // InjectHTTP will inject a span.Context into a HTTP Request func InjectHTTP(r *http.Request, opts ...InjectOption) propagation.Injector { options := InjectOptions{shouldInjectMultiHeader: true} for _, opt := range opts { opt(&options) } return func(sc model.SpanContext) error { if (model.SpanContext{}) == sc { return ErrEmptyContext } if options.shouldInjectMultiHeader { if sc.Debug { r.Header.Set(Flags, "1") } else if sc.Sampled != nil { // Debug is encoded as X-B3-Flags: 1. Since Debug implies Sampled, // so don't also send "X-B3-Sampled: 1". if *sc.Sampled { r.Header.Set(Sampled, "1") } else { r.Header.Set(Sampled, "0") } } if !sc.TraceID.Empty() && sc.ID > 0 { r.Header.Set(TraceID, sc.TraceID.String()) r.Header.Set(SpanID, sc.ID.String()) if sc.ParentID != nil { r.Header.Set(ParentSpanID, sc.ParentID.String()) } } } if options.shouldInjectSingleHeader { r.Header.Set(Context, BuildSingleHeader(sc)) } return nil } } zipkin-go-0.4.3/propagation/b3/http_test.go000066400000000000000000000236601461356266100206420ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package b3_test import ( "net/http" "testing" zipkin "github.com/openzipkin/zipkin-go" "github.com/openzipkin/zipkin-go/model" "github.com/openzipkin/zipkin-go/propagation/b3" "github.com/openzipkin/zipkin-go/reporter/recorder" ) const ( invalidID = "invalid_data" ) func TestHTTPExtractFlagsOnly(t *testing.T) { r := newHTTPRequest(t) r.Header.Set(b3.Flags, "1") sc, err := b3.ExtractHTTP(r)() if err != nil { t.Fatalf("ExtractHTTP failed: %+v", err) } if want, have := true, sc.Debug; want != have { t.Errorf("sc.Debug want %+v, have %+v", want, have) } } func TestHTTPExtractSampledOnly(t *testing.T) { r := newHTTPRequest(t) r.Header.Set(b3.Sampled, "0") sc, err := b3.ExtractHTTP(r)() if err != nil { t.Fatalf("ExtractHTTP failed: %+v", err) } if sc.Sampled == nil { t.Fatalf("Sampled want %t, have nil", false) } if want, have := false, *sc.Sampled; want != have { t.Errorf("Sampled want %t, have %t", want, have) } r = newHTTPRequest(t) r.Header.Set(b3.Sampled, "1") sc, err = b3.ExtractHTTP(r)() if err != nil { t.Fatalf("ExtractHTTP failed: %+v", err) } if sc.Sampled == nil { t.Fatalf("Sampled want %t, have nil", true) } if want, have := true, *sc.Sampled; want != have { t.Errorf("Sampled want %t, have %t", want, have) } } func TestHTTPExtractFlagsAndSampledOnly(t *testing.T) { r := newHTTPRequest(t) r.Header.Set(b3.Flags, "1") r.Header.Set(b3.Sampled, "1") sc, err := b3.ExtractHTTP(r)() if err != nil { t.Fatalf("ExtractHTTP failed: %+v", err) } if want, have := true, sc.Debug; want != have { t.Errorf("Debug want %+v, have %+v", want, have) } // Sampled should not be set when sc.Debug is set. if sc.Sampled != nil { t.Errorf("Sampled want nil, have %+v", *sc.Sampled) } } func TestHTTPExtractSampledErrors(t *testing.T) { r := newHTTPRequest(t) r.Header.Set(b3.Sampled, "2") sc, err := b3.ExtractHTTP(r)() if want, have := b3.ErrInvalidSampledHeader, err; want != have { t.Errorf("SpanContext Error want %+v, have %+v", want, have) } if sc != nil { t.Errorf("SpanContext want nil, have: %+v", sc) } } func TestHTTPExtractFlagsErrors(t *testing.T) { values := map[string]bool{ "1": true, // only acceptable Flags value, debug switches to true "true": false, // true is not a valid value for Flags "3": false, // Flags is not a bitset "6": false, // Flags is not a bitset "7": false, // Flags is not a bitset } for value, debug := range values { r := newHTTPRequest(t) r.Header.Set(b3.Flags, value) spanContext, err := b3.ExtractHTTP(r)() if err != nil { // Flags should not trigger failed extraction t.Fatalf("ExtractHTTP failed: %+v", err) } if want, have := debug, spanContext.Debug; want != have { t.Errorf("SpanContext Error want %t, have %t", want, have) } } } func TestHTTPExtractScope(t *testing.T) { recorder := &recorder.ReporterRecorder{} defer recorder.Close() tracer, err := zipkin.NewTracer(recorder, zipkin.WithTraceID128Bit(true)) if err != nil { t.Fatalf("Tracer failed: %+v", err) } iterations := 1000 for i := 0; i < iterations; i++ { var ( parent = tracer.StartSpan("parent") child = tracer.StartSpan("child", zipkin.Parent(parent.Context())) wantContext = child.Context() ) r := newHTTPRequest(t) b3.InjectHTTP(r)(wantContext) haveContext, err := b3.ExtractHTTP(r)() if err != nil { t.Errorf("ExtractHTTP failed: %+v", err) } if haveContext == nil { t.Fatal("SpanContext want valid value, have nil") } if want, have := wantContext.TraceID, haveContext.TraceID; want != have { t.Errorf("TraceID want %+v, have %+v", want, have) } if want, have := wantContext.ID, haveContext.ID; want != have { t.Errorf("ID want %+v, have %+v", want, have) } if want, have := *wantContext.ParentID, *haveContext.ParentID; want != have { t.Errorf("ParentID want %+v, have %+v", want, have) } child.Finish() parent.Finish() } // check if we have all spans (2x the iterations: parent+child span) if want, have := 2*iterations, len(recorder.Flush()); want != have { t.Errorf("Recorded Span Count want %d, have %d", want, have) } } func TestHTTPExtractTraceIDError(t *testing.T) { r := newHTTPRequest(t) r.Header.Set(b3.TraceID, invalidID) _, err := b3.ExtractHTTP(r)() if want, have := b3.ErrInvalidTraceIDHeader, err; want != have { t.Errorf("ExtractHTTP Error want %+v, have %+v", want, have) } } func TestHTTPExtractSpanIDError(t *testing.T) { r := newHTTPRequest(t) r.Header.Set(b3.SpanID, invalidID) _, err := b3.ExtractHTTP(r)() if want, have := b3.ErrInvalidSpanIDHeader, err; want != have { t.Errorf("ExtractHTTP Error want %+v, have %+v", want, have) } } func TestHTTPExtractTraceIDOnlyError(t *testing.T) { r := newHTTPRequest(t) r.Header.Set(b3.TraceID, "1") _, err := b3.ExtractHTTP(r)() if want, have := b3.ErrInvalidScope, err; want != have { t.Errorf("ExtractHTTP Error want %+v, have %+v", want, have) } } func TestHTTPExtractSpanIDOnlyError(t *testing.T) { r := newHTTPRequest(t) r.Header.Set(b3.SpanID, "1") _, err := b3.ExtractHTTP(r)() if want, have := b3.ErrInvalidScope, err; want != have { t.Errorf("ExtractHTTP Error want %+v, have %+v", want, have) } } func TestHTTPExtractParentIDOnlyError(t *testing.T) { r := newHTTPRequest(t) r.Header.Set(b3.ParentSpanID, "1") _, err := b3.ExtractHTTP(r)() if want, have := b3.ErrInvalidScopeParent, err; want != have { t.Errorf("ExtractHTTP Error want %+v, have %+v", want, have) } } func TestHTTPExtractInvalidParentIDError(t *testing.T) { r := newHTTPRequest(t) r.Header.Set(b3.TraceID, "1") r.Header.Set(b3.SpanID, "2") r.Header.Set(b3.ParentSpanID, invalidID) _, err := b3.ExtractHTTP(r)() if want, have := b3.ErrInvalidParentSpanIDHeader, err; want != have { t.Errorf("ExtractHTTP Error want %+v, have %+v", want, have) } } func TestHTTPExtractSingleFailsAndMultipleFallsbackSuccessfully(t *testing.T) { r := newHTTPRequest(t) r.Header.Set(b3.Context, "invalid") r.Header.Set(b3.TraceID, "1") r.Header.Set(b3.SpanID, "2") _, err := b3.ExtractHTTP(r)() if err != nil { t.Errorf("ExtractHTTP Unexpected error %+v", err) } } func TestHTTPExtractSingleFailsAndMultipleFallsbackFailing(t *testing.T) { r := newHTTPRequest(t) r.Header.Set(b3.Context, "0000000000000001-0000000000000002-x") r.Header.Set(b3.TraceID, "1") r.Header.Set(b3.SpanID, "2") r.Header.Set(b3.ParentSpanID, invalidID) _, err := b3.ExtractHTTP(r)() if want, have := b3.ErrInvalidSampledByte, err; want != have { t.Errorf("HTTPExtract Error want %+v, have %+v", want, have) } } func TestHTTPInjectEmptyContextError(t *testing.T) { err := b3.InjectHTTP(nil)(model.SpanContext{}) if want, have := b3.ErrEmptyContext, err; want != have { t.Errorf("HTTPInject Error want %+v, have %+v", want, have) } } func TestHTTPInjectDebugOnly(t *testing.T) { r := newHTTPRequest(t) sc := model.SpanContext{ Debug: true, } b3.InjectHTTP(r)(sc) if want, have := "1", r.Header.Get(b3.Flags); want != have { t.Errorf("Flags want %s, have %s", want, have) } } func TestHTTPInjectSampledOnly(t *testing.T) { r := newHTTPRequest(t) sampled := false sc := model.SpanContext{ Sampled: &sampled, } b3.InjectHTTP(r)(sc) if want, have := "0", r.Header.Get(b3.Sampled); want != have { t.Errorf("Sampled want %s, have %s", want, have) } } func TestHTTPInjectUnsampledTrace(t *testing.T) { r := newHTTPRequest(t) sampled := false sc := model.SpanContext{ TraceID: model.TraceID{Low: 1}, ID: model.ID(2), Sampled: &sampled, } b3.InjectHTTP(r)(sc) if want, have := "0", r.Header.Get(b3.Sampled); want != have { t.Errorf("Sampled want %s, have %s", want, have) } } func TestHTTPInjectSampledAndDebugTrace(t *testing.T) { r := newHTTPRequest(t) sampled := true sc := model.SpanContext{ TraceID: model.TraceID{Low: 3}, ID: model.ID(4), Debug: true, Sampled: &sampled, } b3.InjectHTTP(r)(sc) if want, have := "", r.Header.Get(b3.Sampled); want != have { t.Errorf("Sampled want empty, have %s", have) } if want, have := "1", r.Header.Get(b3.Flags); want != have { t.Errorf("Debug want %s, have %s", want, have) } } func TestHTTPInjectWithSingleOnlyHeaders(t *testing.T) { r := newHTTPRequest(t) sampled := true sc := model.SpanContext{ TraceID: model.TraceID{Low: 5}, ID: model.ID(6), Debug: true, Sampled: &sampled, } b3.InjectHTTP(r, b3.WithSingleHeaderOnly())(sc) if want, have := "", r.Header.Get(b3.TraceID); want != have { t.Errorf("TraceID want empty, have %s", have) } if want, have := "0000000000000005-0000000000000006-d", r.Header.Get(b3.Context); want != have { t.Errorf("Context want %s, have %s", want, have) } } func TestHTTPInjectWithBothSingleAndMultipleHeaders(t *testing.T) { r := newHTTPRequest(t) sampled := true sc := model.SpanContext{ TraceID: model.TraceID{Low: 7}, ID: model.ID(8), Debug: true, Sampled: &sampled, } b3.InjectHTTP(r, b3.WithSingleAndMultiHeader())(sc) if want, have := "0000000000000007", r.Header.Get(b3.TraceID); want != have { t.Errorf("Trace ID want %s, have %s", want, have) } if want, have := "0000000000000007-0000000000000008-d", r.Header.Get(b3.Context); want != have { t.Errorf("Context want %s, have %s", want, have) } } func newHTTPRequest(t *testing.T) *http.Request { r, err := http.NewRequest("test", "", nil) if err != nil { t.Fatalf("HTTP Request failed: %+v", err) } return r } zipkin-go-0.4.3/propagation/b3/map.go000066400000000000000000000046301461356266100173750ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package b3 import ( "github.com/openzipkin/zipkin-go/model" "github.com/openzipkin/zipkin-go/propagation" ) // Map allows serialization and deserialization of SpanContext into a standard Go map. type Map map[string]string // Extract implements Extractor func (m *Map) Extract() (*model.SpanContext, error) { var ( traceIDHeader = (*m)[TraceID] spanIDHeader = (*m)[SpanID] parentSpanIDHeader = (*m)[ParentSpanID] sampledHeader = (*m)[Sampled] flagsHeader = (*m)[Flags] singleHeader = (*m)[Context] ) var ( sc *model.SpanContext sErr error mErr error ) if singleHeader != "" { sc, sErr = ParseSingleHeader(singleHeader) if sErr == nil { return sc, nil } } sc, mErr = ParseHeaders( traceIDHeader, spanIDHeader, parentSpanIDHeader, sampledHeader, flagsHeader, ) if mErr != nil && sErr != nil { return nil, sErr } return sc, mErr } // Inject implements Injector func (m *Map) Inject(opts ...InjectOption) propagation.Injector { options := InjectOptions{shouldInjectMultiHeader: true} for _, opt := range opts { opt(&options) } return func(sc model.SpanContext) error { if (model.SpanContext{}) == sc { return ErrEmptyContext } if options.shouldInjectMultiHeader { if sc.Debug { (*m)[Flags] = "1" } else if sc.Sampled != nil { // Debug is encoded as X-B3-Flags: 1. Since Debug implies Sampled, // so don't also send "X-B3-Sampled: 1". if *sc.Sampled { (*m)[Sampled] = "1" } else { (*m)[Sampled] = "0" } } if !sc.TraceID.Empty() && sc.ID > 0 { (*m)[TraceID] = sc.TraceID.String() (*m)[SpanID] = sc.ID.String() if sc.ParentID != nil { (*m)[ParentSpanID] = sc.ParentID.String() } } } if options.shouldInjectSingleHeader { (*m)[Context] = BuildSingleHeader(sc) } return nil } } zipkin-go-0.4.3/propagation/b3/map_test.go000066400000000000000000000221301461356266100204270ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package b3_test import ( "testing" zipkin "github.com/openzipkin/zipkin-go" "github.com/openzipkin/zipkin-go/model" "github.com/openzipkin/zipkin-go/propagation/b3" "github.com/openzipkin/zipkin-go/reporter/recorder" ) func TestMapExtractFlagsOnly(t *testing.T) { m := make(b3.Map) m[b3.Flags] = "1" sc, err := m.Extract() if err != nil { t.Fatalf("Extract failed: %+v", err) } if want, have := true, sc.Debug; want != have { t.Errorf("sc.Debug want %+v, have %+v", want, have) } } func TestMapExtractSampledOnly(t *testing.T) { m := make(b3.Map) m[b3.Sampled] = "0" sc, err := m.Extract() if err != nil { t.Fatalf("Extract failed: %+v", err) } if sc.Sampled == nil { t.Fatalf("Sampled want %t, have nil", false) } if want, have := false, *sc.Sampled; want != have { t.Errorf("Sampled want %t, have %t", want, have) } m = make(b3.Map) m[b3.Sampled] = "1" sc, err = m.Extract() if err != nil { t.Fatalf("Extract failed: %+v", err) } if sc.Sampled == nil { t.Fatalf("Sampled want %t, have nil", true) } if want, have := true, *sc.Sampled; want != have { t.Errorf("Sampled want %t, have %t", want, have) } } func TestMapExtractFlagsAndSampledOnly(t *testing.T) { m := make(b3.Map) m[b3.Flags] = "1" m[b3.Sampled] = "1" sc, err := m.Extract() if err != nil { t.Fatalf("Extract failed: %+v", err) } if want, have := true, sc.Debug; want != have { t.Errorf("Debug want %+v, have %+v", want, have) } // Sampled should not be set when sc.Debug is set. if sc.Sampled != nil { t.Errorf("Sampled want nil, have %+v", *sc.Sampled) } } func TestMapExtractSampledErrors(t *testing.T) { m := make(b3.Map) m[b3.Sampled] = "2" sc, err := m.Extract() if want, have := b3.ErrInvalidSampledHeader, err; want != have { t.Errorf("SpanContext Error want %+v, have %+v", want, have) } if sc != nil { t.Errorf("SpanContext want nil, have: %+v", sc) } } func TestMapExtractFlagsErrors(t *testing.T) { values := map[string]bool{ "1": true, // only acceptable Flags value, debug switches to true "true": false, // true is not a valid value for Flags "3": false, // Flags is not a bitset "6": false, // Flags is not a bitset "7": false, // Flags is not a bitset } for value, debug := range values { m := make(b3.Map) m[b3.Flags] = value spanContext, err := m.Extract() if err != nil { // Flags should not trigger failed extraction t.Fatalf("Extract failed: %+v", err) } if want, have := debug, spanContext.Debug; want != have { t.Errorf("SpanContext Error want %t, have %t", want, have) } } } func TestMapExtractScope(t *testing.T) { recorder := &recorder.ReporterRecorder{} defer recorder.Close() tracer, err := zipkin.NewTracer(recorder, zipkin.WithTraceID128Bit(true)) if err != nil { t.Fatalf("Tracer failed: %+v", err) } iterations := 1000 for i := 0; i < iterations; i++ { var ( parent = tracer.StartSpan("parent") child = tracer.StartSpan("child", zipkin.Parent(parent.Context())) wantContext = child.Context() ) w := make(b3.Map) w.Inject()(wantContext) haveContext, err := w.Extract() if err != nil { t.Errorf("Extract failed: %+v", err) } if haveContext == nil { t.Fatal("SpanContext want valid value, have nil") } if want, have := wantContext.TraceID, haveContext.TraceID; want != have { t.Errorf("TraceID want %+v, have %+v", want, have) } if want, have := wantContext.ID, haveContext.ID; want != have { t.Errorf("ID want %+v, have %+v", want, have) } if want, have := *wantContext.ParentID, *haveContext.ParentID; want != have { t.Errorf("ParentID want %+v, have %+v", want, have) } child.Finish() parent.Finish() } // check if we have all spans (2x the iterations: parent+child span) if want, have := 2*iterations, len(recorder.Flush()); want != have { t.Errorf("Recorded Span Count want %d, have %d", want, have) } } func TestMapExtractTraceIDError(t *testing.T) { m := make(b3.Map) m[b3.TraceID] = invalidID _, err := m.Extract() if want, have := b3.ErrInvalidTraceIDHeader, err; want != have { t.Errorf("ExtractHTTP Error want %+v, have %+v", want, have) } } func TestMapExtractSpanIDError(t *testing.T) { m := make(b3.Map) m[b3.SpanID] = invalidID _, err := m.Extract() if want, have := b3.ErrInvalidSpanIDHeader, err; want != have { t.Errorf("ExtractHTTP Error want %+v, have %+v", want, have) } } func TestMapExtractTraceIDOnlyError(t *testing.T) { m := make(b3.Map) m[b3.TraceID] = "1" _, err := m.Extract() if want, have := b3.ErrInvalidScope, err; want != have { t.Errorf("ExtractHTTP Error want %+v, have %+v", want, have) } } func TestMapExtractSpanIDOnlyError(t *testing.T) { m := make(b3.Map) m[b3.SpanID] = "1" _, err := m.Extract() if want, have := b3.ErrInvalidScope, err; want != have { t.Errorf("ExtractHTTP Error want %+v, have %+v", want, have) } } func TestMapExtractParentIDOnlyError(t *testing.T) { m := make(b3.Map) m[b3.ParentSpanID] = "1" _, err := m.Extract() if want, have := b3.ErrInvalidScopeParent, err; want != have { t.Errorf("ExtractHTTP Error want %+v, have %+v", want, have) } } func TestMapExtractInvalidParentIDError(t *testing.T) { m := make(b3.Map) m[b3.TraceID] = "1" m[b3.SpanID] = "2" m[b3.ParentSpanID] = invalidID _, err := m.Extract() if want, have := b3.ErrInvalidParentSpanIDHeader, err; want != have { t.Errorf("ExtractHTTP Error want %+v, have %+v", want, have) } } func TestMapExtractSingleFailsAndMultipleFallsbackSuccessfully(t *testing.T) { m := make(b3.Map) m[b3.Context] = "invalid" m[b3.TraceID] = "1" m[b3.SpanID] = "2" _, err := m.Extract() if err != nil { t.Errorf("ExtractHTTP Unexpected error %+v", err) } } func TestMapExtractSingleFailsAndMultipleFallsbackFailing(t *testing.T) { m := make(b3.Map) m[b3.Context] = "0000000000000001-0000000000000002-x" m[b3.TraceID] = "1" m[b3.SpanID] = "2" m[b3.ParentSpanID] = invalidID _, err := m.Extract() if want, have := b3.ErrInvalidSampledByte, err; want != have { t.Errorf("HTTPExtract Error want %+v, have %+v", want, have) } } func TestMapInjectEmptyContextError(t *testing.T) { err := b3.InjectHTTP(nil)(model.SpanContext{}) if want, have := b3.ErrEmptyContext, err; want != have { t.Errorf("HTTPInject Error want %+v, have %+v", want, have) } } func TestMapInjectDebugOnly(t *testing.T) { m := make(b3.Map) sc := model.SpanContext{ Debug: true, } m.Inject()(sc) if want, have := "1", m[b3.Flags]; want != have { t.Errorf("Flags want %s, have %s", want, have) } } func TestMapInjectSampledOnly(t *testing.T) { m := make(b3.Map) sampled := false sc := model.SpanContext{ Sampled: &sampled, } m.Inject()(sc) if want, have := "0", m[b3.Sampled]; want != have { t.Errorf("Sampled want %s, have %s", want, have) } } func TestMapInjectUnsampledTrace(t *testing.T) { m := make(b3.Map) sampled := false sc := model.SpanContext{ TraceID: model.TraceID{Low: 1}, ID: model.ID(2), Sampled: &sampled, } m.Inject()(sc) if want, have := "0", m[b3.Sampled]; want != have { t.Errorf("Sampled want %s, have %s", want, have) } } func TestMapInjectSampledAndDebugTrace(t *testing.T) { m := make(b3.Map) sampled := true sc := model.SpanContext{ TraceID: model.TraceID{Low: 1}, ID: model.ID(2), Debug: true, Sampled: &sampled, } m.Inject()(sc) if want, have := "", m[b3.Sampled]; want != have { t.Errorf("Sampled want empty, have %s", have) } if want, have := "1", m[b3.Flags]; want != have { t.Errorf("Debug want %s, have %s", want, have) } } func TestMapInjectWithSingleOnlyHeaders(t *testing.T) { m := make(b3.Map) sampled := true sc := model.SpanContext{ TraceID: model.TraceID{Low: 1}, ID: model.ID(2), Debug: true, Sampled: &sampled, } m.Inject(b3.WithSingleHeaderOnly())(sc) if want, have := "", m[b3.TraceID]; want != have { t.Errorf("TraceID want empty, have %s", have) } if want, have := "0000000000000001-0000000000000002-d", m[b3.Context]; want != have { t.Errorf("Context want %s, have %s", want, have) } } func TestMapInjectWithBothSingleAndMultipleHeaders(t *testing.T) { m := make(b3.Map) sampled := true sc := model.SpanContext{ TraceID: model.TraceID{Low: 1}, ID: model.ID(2), Debug: true, Sampled: &sampled, } m.Inject(b3.WithSingleAndMultiHeader())(sc) if want, have := "0000000000000001", m[b3.TraceID]; want != have { t.Errorf("Trace ID want %s, have %s", want, have) } if want, have := "0000000000000001-0000000000000002-d", m[b3.Context]; want != have { t.Errorf("Context want %s, have %s", want, have) } } zipkin-go-0.4.3/propagation/b3/shared.go000066400000000000000000000036721461356266100200730ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package b3 import "errors" // Common Header Extraction / Injection errors var ( ErrInvalidSampledByte = errors.New("invalid B3 Sampled found") ErrInvalidSampledHeader = errors.New("invalid B3 Sampled header found") ErrInvalidFlagsHeader = errors.New("invalid B3 Flags header found") ErrInvalidTraceIDHeader = errors.New("invalid B3 TraceID header found") ErrInvalidSpanIDHeader = errors.New("invalid B3 SpanID header found") ErrInvalidParentSpanIDHeader = errors.New("invalid B3 ParentSpanID header found") ErrInvalidScope = errors.New("require either both TraceID and SpanID or none") ErrInvalidScopeParent = errors.New("ParentSpanID requires both TraceID and SpanID to be available") ErrInvalidScopeParentSingle = errors.New("ParentSpanID requires TraceID, SpanID and Sampled to be available") ErrEmptyContext = errors.New("empty request context") ErrInvalidTraceIDValue = errors.New("invalid B3 TraceID value found") ErrInvalidSpanIDValue = errors.New("invalid B3 SpanID value found") ErrInvalidParentSpanIDValue = errors.New("invalid B3 ParentSpanID value found") ) // Default B3 Header keys const ( TraceID = "x-b3-traceid" SpanID = "x-b3-spanid" ParentSpanID = "x-b3-parentspanid" Sampled = "x-b3-sampled" Flags = "x-b3-flags" Context = "b3" ) zipkin-go-0.4.3/propagation/b3/spancontext.go000066400000000000000000000116741461356266100211740ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package b3 import ( "strconv" "strings" "github.com/openzipkin/zipkin-go/model" ) // ParseHeaders takes values found from B3 Headers and tries to reconstruct a // SpanContext. func ParseHeaders( hdrTraceID, hdrSpanID, hdrParentSpanID, hdrSampled, hdrFlags string, ) (*model.SpanContext, error) { var ( err error spanID uint64 requiredCount int sc = &model.SpanContext{} ) // correct values for an existing sampled header are "0" and "1". // For legacy support and being lenient to other tracing implementations we // allow "true" and "false" as inputs for interop purposes. switch strings.ToLower(hdrSampled) { case "0", "false": sampled := false sc.Sampled = &sampled case "1", "true": sampled := true sc.Sampled = &sampled case "": // sc.Sampled = nil default: return nil, ErrInvalidSampledHeader } // The only accepted value for Flags is "1". This will set Debug to true. All // other values and omission of header will be ignored. if hdrFlags == "1" { sc.Debug = true sc.Sampled = nil } if hdrTraceID != "" { requiredCount++ if sc.TraceID, err = model.TraceIDFromHex(hdrTraceID); err != nil { return nil, ErrInvalidTraceIDHeader } } if hdrSpanID != "" { requiredCount++ if spanID, err = strconv.ParseUint(hdrSpanID, 16, 64); err != nil { return nil, ErrInvalidSpanIDHeader } sc.ID = model.ID(spanID) } if requiredCount != 0 && requiredCount != 2 { return nil, ErrInvalidScope } if hdrParentSpanID != "" { if requiredCount == 0 { return nil, ErrInvalidScopeParent } if spanID, err = strconv.ParseUint(hdrParentSpanID, 16, 64); err != nil { return nil, ErrInvalidParentSpanIDHeader } parentSpanID := model.ID(spanID) sc.ParentID = &parentSpanID } return sc, nil } // ParseSingleHeader takes values found from B3 Single Header and tries to reconstruct a // SpanContext. func ParseSingleHeader(contextHeader string) (*model.SpanContext, error) { if contextHeader == "" { return nil, ErrEmptyContext } var ( sc = model.SpanContext{} sampling string ) headerLen := len(contextHeader) if headerLen == 1 { sampling = contextHeader } else if headerLen == 16 || headerLen == 32 { return nil, ErrInvalidScope } else if headerLen >= 16+16+1 { var high, low uint64 pos := 0 if string(contextHeader[16]) != "-" { // traceID must be 128 bits var err error high, err = strconv.ParseUint(contextHeader[0:16], 16, 64) if err != nil { return nil, ErrInvalidTraceIDValue } pos = 16 } low, err := strconv.ParseUint(contextHeader[pos:pos+16], 16, 64) if err != nil { return nil, ErrInvalidTraceIDValue } sc.TraceID = model.TraceID{High: high, Low: low} rawID, err := strconv.ParseUint(contextHeader[pos+16+1:pos+16+1+16], 16, 64) if err != nil { return nil, ErrInvalidSpanIDValue } sc.ID = model.ID(rawID) if headerLen > pos+16+1+16 { if headerLen == pos+16+1+16+1 { return nil, ErrInvalidSampledByte } if headerLen == pos+16+1+16+1+1 { sampling = string(contextHeader[pos+16+1+16+1]) } else if headerLen == pos+16+1+16+1+16 { return nil, ErrInvalidScopeParentSingle } else if headerLen == pos+16+1+16+1+1+1+16 { sampling = string(contextHeader[pos+16+1+16+1]) var rawParentID uint64 rawParentID, err = strconv.ParseUint(contextHeader[pos+16+1+16+1+1+1:], 16, 64) if err != nil { return nil, ErrInvalidParentSpanIDValue } parentID := model.ID(rawParentID) sc.ParentID = &parentID } else { return nil, ErrInvalidParentSpanIDValue } } } else { return nil, ErrInvalidTraceIDValue } switch sampling { case "d": sc.Debug = true case "1": trueVal := true sc.Sampled = &trueVal case "0": falseVal := false sc.Sampled = &falseVal case "": default: return nil, ErrInvalidSampledByte } return &sc, nil } // BuildSingleHeader takes the values from the SpanContext and builds the B3 header func BuildSingleHeader(sc model.SpanContext) string { var header []string if !sc.TraceID.Empty() && sc.ID > 0 { header = append(header, sc.TraceID.String(), sc.ID.String()) } if sc.Debug { header = append(header, "d") } else if sc.Sampled != nil { if *sc.Sampled { header = append(header, "1") } else { header = append(header, "0") } } if sc.ParentID != nil { header = append(header, sc.ParentID.String()) } return strings.Join(header, "-") } zipkin-go-0.4.3/propagation/b3/spancontext_test.go000066400000000000000000000126751461356266100222350ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package b3 import ( "testing" "github.com/openzipkin/zipkin-go/model" ) func TestParseHeaderSuccess(t *testing.T) { testCases := []struct { header string expectedContext *model.SpanContext expectedErr error }{ {"d", &model.SpanContext{Debug: true}, nil}, {"1", &model.SpanContext{Sampled: pointerBool(true)}, nil}, { "000000000000007b00000000000001c8-000000000000007b", &model.SpanContext{ TraceID: model.TraceID{High: 123, Low: 456}, ID: model.ID(123), }, nil, }, { "000000000000007b00000000000001c8-000000000000007b-0", &model.SpanContext{ TraceID: model.TraceID{High: 123, Low: 456}, ID: model.ID(123), Sampled: pointerBool(false), }, nil, }, { "000000000000007b00000000000001c8-000000000000007b-1-00000000000001c8", &model.SpanContext{ TraceID: model.TraceID{High: 123, Low: 456}, ID: model.ID(123), ParentID: pointerID(model.ID(456)), Sampled: pointerBool(true), }, nil, }, { "", nil, ErrEmptyContext, }, { "80f198ee56343ba864fe8b2a57d3eff7-e457b5a2e4d86bd1-1-05e3ac9a4f6e3b90", &model.SpanContext{ TraceID: model.TraceID{High: 9291375655657946024, Low: 7277407061855694839}, ID: model.ID(16453819474850114513), ParentID: pointerID(model.ID(424372568660523920)), Sampled: pointerBool(true), }, nil, }, { "d4c3c787ce202dc5-77c6a763a5a72544-0-6a3211d95bed2c99", &model.SpanContext{ TraceID: model.TraceID{High: 0, Low: 15331316942592028101}, ID: model.ID(8630769782324929860), ParentID: pointerID(model.ID(7652198342103739545)), Sampled: pointerBool(false), }, nil, }, } for _, testCase := range testCases { actualContext, actualErr := ParseSingleHeader(testCase.header) if testCase.expectedContext != nil { if actualErr != nil { t.Fatalf("unexpected error for header %q: %s", testCase.header, actualErr.Error()) } if !(actualContext.TraceID == testCase.expectedContext.TraceID && actualContext.ID == testCase.expectedContext.ID && ((actualContext.ParentID == nil && testCase.expectedContext.ParentID == nil) || *actualContext.ParentID == *testCase.expectedContext.ParentID) && ((actualContext.Sampled == nil && testCase.expectedContext.Sampled == nil) || *actualContext.Sampled == *testCase.expectedContext.Sampled) && actualContext.Debug == testCase.expectedContext.Debug) { t.Fatalf("unexpected context for header %q, want: %v, have %v", testCase.header, *testCase.expectedContext, *actualContext) } } if want, have := actualErr, testCase.expectedErr; want != have { t.Fatalf("unexpected error for header %q, want: %v, have %v", testCase.header, want, have) } } } func TestParseHeaderFails(t *testing.T) { testCases := []struct { header string expectedErr error }{ {"a", ErrInvalidSampledByte}, {"3", ErrInvalidSampledByte}, {"000000000000007b", ErrInvalidScope}, {"000000000000007b00000000000001c8", ErrInvalidScope}, {"000000000000007b00000000000001c8-000000000000007b-", ErrInvalidSampledByte}, {"000000000000007b00000000000001c8-000000000000007b-3", ErrInvalidSampledByte}, {"000000000000007b00000000000001c8-000000000000007b-00000000000001c8", ErrInvalidScopeParentSingle}, {"000000000000007b00000000000001c8-000000000000007b-1-00000000000001c", ErrInvalidParentSpanIDValue}, {"", ErrEmptyContext}, } for _, testCase := range testCases { _, actualErr := ParseSingleHeader(testCase.header) if want, have := testCase.expectedErr, actualErr; want != have { t.Fatalf("unexpected error for header %q, want: %q, have: %q", testCase.header, want, have) } } } func TestBuildHeader(t *testing.T) { testCases := []struct { context model.SpanContext expectedHeader string }{ {model.SpanContext{ID: model.ID(123)}, ""}, {model.SpanContext{Debug: true}, "d"}, {model.SpanContext{Sampled: pointerBool(true)}, "1"}, { model.SpanContext{ TraceID: model.TraceID{High: 123, Low: 456}, ID: model.ID(123), }, "000000000000007b00000000000001c8-000000000000007b", }, { model.SpanContext{ TraceID: model.TraceID{High: 123, Low: 456}, ID: model.ID(123), Sampled: pointerBool(false), }, "000000000000007b00000000000001c8-000000000000007b-0", }, { model.SpanContext{ TraceID: model.TraceID{High: 123, Low: 456}, ID: model.ID(123), ParentID: pointerID(model.ID(456)), Sampled: pointerBool(false), }, "000000000000007b00000000000001c8-000000000000007b-0-00000000000001c8", }, } for _, testCase := range testCases { actualHeader := BuildSingleHeader(testCase.context) if want, have := actualHeader, testCase.expectedHeader; want != have { t.Fatalf("unexpected header value, want: %s, have %s", want, have) } } } func pointerID(value model.ID) *model.ID { return &value } func pointerBool(value bool) *bool { return &value } zipkin-go-0.4.3/propagation/baggage/000077500000000000000000000000001461356266100173375ustar00rootroot00000000000000zipkin-go-0.4.3/propagation/baggage/baggage.go000066400000000000000000000053121461356266100212440ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package baggage holds a Baggage propagation implementation based on // explicit allowList semantics. package baggage import ( "strings" "github.com/openzipkin/zipkin-go/middleware" "github.com/openzipkin/zipkin-go/model" ) var ( _ middleware.BaggageHandler = (*baggage)(nil) _ model.BaggageFields = (*baggage)(nil) ) type baggage struct { // registry holds our registry of allowed fields to propagate registry map[string]struct{} // fields holds the retrieved key-values pairs to propagate fields map[string][]string } // New returns a new Baggage interface which is configured to propagate the // registered fields. func New(keys ...string) middleware.BaggageHandler { b := &baggage{ registry: make(map[string]struct{}), } for _, key := range keys { b.registry[strings.ToLower(key)] = struct{}{} } return b } // New is called by server middlewares and returns a fresh initialized // baggage implementation. func (b *baggage) New() model.BaggageFields { return &baggage{ registry: b.registry, fields: make(map[string][]string), } } func (b *baggage) Get(key string) []string { return b.fields[strings.ToLower(key)] } func (b *baggage) Add(key string, values ...string) bool { if len(values) == 0 { return false } key = strings.ToLower(key) if _, ok := b.registry[key]; !ok { return false } // multiple values for a header is allowed b.fields[key] = append(b.fields[key], values...) return true } func (b *baggage) Set(key string, values ...string) bool { if len(values) == 0 { return false } key = strings.ToLower(key) if _, ok := b.registry[key]; !ok { return false } b.fields[key] = values return true } func (b *baggage) Delete(key string) bool { key = strings.ToLower(key) if _, ok := b.registry[key]; !ok { return false } for k := range b.fields { if key == k { delete(b.fields, k) } } return true } func (b *baggage) Iterate(f func(key string, values []string)) { for key, v := range b.fields { values := make([]string, len(v)) copy(values, v) f(key, values) } } func (b *baggage) IterateKeys(f func(key string)) { for key := range b.registry { f(key) } } zipkin-go-0.4.3/propagation/baggage/baggage_test.go000066400000000000000000000072311461356266100223050ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package baggage import ( "strings" "testing" ) func TestBaggageRegistry(t *testing.T) { baggageHandler := New("X-Request-Id", "some-header", "x-request-id") var items int baggageHandler.(*baggage).IterateKeys(func(key string) { if key != "some-header" && key != "x-request-id" { t.Errorf("Unexpected registry item: %s", key) } items++ }) if items != 2 { t.Errorf("Unexpected registration count: want %d, have %d", 2, items) } } func TestBaggageValues(t *testing.T) { // register the baggage fields we'll propagate baggageHandler := New("X-Request-Id", "Some-Header") // initialize fresh BaggageFields container baggage := baggageHandler.New() t.Run("AddHeader", func(t *testing.T) { if baggage.Add("Invalid-Key", "Invalid-Key-Value") { t.Errorf("expected Invalid-Key to return false") } if !baggage.Add("X-Request-Id", "X-Request-Id-Value") { t.Errorf("expected X-Request-Id to return true") } if !baggage.Add("Some-Header", "Some-Header-Value1", "Some-Header-Value2") { t.Errorf("expected Some-Header to return true") } if !baggage.Add("Some-Header", "Some-Header-Value3") { t.Errorf("expected Some-Header to return true") } }) baggageHandler.New().Iterate(func(key string, values []string) { t.Errorf("expected no header data to exist, have: key=%s values=%v", key, values) }) t.Run("IterateHeaders", func(t *testing.T) { baggage.Iterate(func(key string, have []string) { if strings.EqualFold(key, "x-request-id") { want := 1 if len(have) != want { t.Errorf("expected different value count: want %d, have %d", want, len(have)) } if have[0] != "X-Request-Id-Value" { t.Errorf("expected different value: want %s, have %s", "X-Request-Id-Value", have[0]) } return } if strings.EqualFold(key, "some-header") { want := 3 if len(have) != want { t.Errorf("expected different value count: want %d, have %d", want, len(have)) } wantVal := "Some-Header-Value1" if have[0] != wantVal { t.Errorf("expected different value: want %s, have %s", wantVal, have[0]) } wantVal = "Some-Header-Value2" if have[1] != wantVal { t.Errorf("expected different value: want %s, have %s", wantVal, have[1]) } wantVal = "Some-Header-Value3" if have[2] != wantVal { t.Errorf("expected different value: want %s, have %s", wantVal, have[2]) } return } t.Errorf("unexpected header key: %s", key) }) }) t.Run("DeleteHeader", func(t *testing.T) { if baggage.Delete("Invalid-Key") { t.Errorf("expected Invalid-Key to return false") } if !baggage.Delete("some-header") { t.Errorf("expected some-header to return true") } baggage.Iterate(func(key string, have []string) { if strings.EqualFold(key, "x-request-id") { want := 1 if len(have) != want { t.Errorf("expected different value count: want %d, have %d", want, len(have)) } if have[0] != "X-Request-Id-Value" { t.Errorf("expected different value: want %s, have %s", "X-Request-Id-Value", have[0]) } return } t.Errorf("unexpected header key: %s", key) }) }) } zipkin-go-0.4.3/propagation/propagation.go000066400000000000000000000020271461356266100206350ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /* Package propagation holds the required function signatures for Injection and Extraction as used by the Zipkin Tracer. Subpackages of this package contain officially supported standard propagation implementations. */ package propagation import "github.com/openzipkin/zipkin-go/model" // Extractor function signature type Extractor func() (*model.SpanContext, error) // Injector function signature type Injector func(model.SpanContext) error zipkin-go-0.4.3/proto/000077500000000000000000000000001461356266100146025ustar00rootroot00000000000000zipkin-go-0.4.3/proto/testing/000077500000000000000000000000001461356266100162575ustar00rootroot00000000000000zipkin-go-0.4.3/proto/testing/baggage.pb.go000066400000000000000000000106661461356266100205740ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.28.1 // protoc v3.19.0 // source: proto/testing/baggage.proto package testing import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" emptypb "google.golang.org/protobuf/types/known/emptypb" reflect "reflect" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) var File_proto_testing_baggage_proto protoreflect.FileDescriptor var file_proto_testing_baggage_proto_rawDesc = []byte{ 0x0a, 0x1b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2f, 0x62, 0x61, 0x67, 0x67, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0e, 0x7a, 0x69, 0x70, 0x6b, 0x69, 0x6e, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x88, 0x01, 0x0a, 0x0e, 0x42, 0x61, 0x67, 0x67, 0x61, 0x67, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3a, 0x0a, 0x08, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x31, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3a, 0x0a, 0x08, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x32, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x2f, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x7a, 0x69, 0x70, 0x6b, 0x69, 0x6e, 0x2f, 0x7a, 0x69, 0x70, 0x6b, 0x69, 0x6e, 0x2d, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var file_proto_testing_baggage_proto_goTypes = []interface{}{ (*emptypb.Empty)(nil), // 0: google.protobuf.Empty } var file_proto_testing_baggage_proto_depIdxs = []int32{ 0, // 0: zipkin.testing.BaggageService.Handler1:input_type -> google.protobuf.Empty 0, // 1: zipkin.testing.BaggageService.Handler2:input_type -> google.protobuf.Empty 0, // 2: zipkin.testing.BaggageService.Handler1:output_type -> google.protobuf.Empty 0, // 3: zipkin.testing.BaggageService.Handler2:output_type -> google.protobuf.Empty 2, // [2:4] is the sub-list for method output_type 0, // [0:2] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name } func init() { file_proto_testing_baggage_proto_init() } func file_proto_testing_baggage_proto_init() { if File_proto_testing_baggage_proto != nil { return } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proto_testing_baggage_proto_rawDesc, NumEnums: 0, NumMessages: 0, NumExtensions: 0, NumServices: 1, }, GoTypes: file_proto_testing_baggage_proto_goTypes, DependencyIndexes: file_proto_testing_baggage_proto_depIdxs, }.Build() File_proto_testing_baggage_proto = out.File file_proto_testing_baggage_proto_rawDesc = nil file_proto_testing_baggage_proto_goTypes = nil file_proto_testing_baggage_proto_depIdxs = nil } zipkin-go-0.4.3/proto/testing/baggage.proto000066400000000000000000000016331461356266100207240ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. syntax = "proto3"; import "google/protobuf/empty.proto"; package zipkin.testing; option go_package = "github.com/openzipkin/zipkin-go/proto/testing"; service BaggageService { rpc Handler1(google.protobuf.Empty) returns (google.protobuf.Empty); rpc Handler2(google.protobuf.Empty) returns (google.protobuf.Empty); } zipkin-go-0.4.3/proto/testing/baggage_grpc.pb.go000066400000000000000000000121761461356266100216050ustar00rootroot00000000000000// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.2.0 // - protoc v3.19.0 // source: proto/testing/baggage.proto package testing import ( context "context" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" emptypb "google.golang.org/protobuf/types/known/emptypb" ) // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. // Requires gRPC-Go v1.32.0 or later. const _ = grpc.SupportPackageIsVersion7 // BaggageServiceClient is the client API for BaggageService service. // // 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. type BaggageServiceClient interface { Handler1(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) Handler2(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) } type baggageServiceClient struct { cc grpc.ClientConnInterface } func NewBaggageServiceClient(cc grpc.ClientConnInterface) BaggageServiceClient { return &baggageServiceClient{cc} } func (c *baggageServiceClient) Handler1(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) { out := new(emptypb.Empty) err := c.cc.Invoke(ctx, "/zipkin.testing.BaggageService/Handler1", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *baggageServiceClient) Handler2(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) { out := new(emptypb.Empty) err := c.cc.Invoke(ctx, "/zipkin.testing.BaggageService/Handler2", in, out, opts...) if err != nil { return nil, err } return out, nil } // BaggageServiceServer is the server API for BaggageService service. // All implementations must embed UnimplementedBaggageServiceServer // for forward compatibility type BaggageServiceServer interface { Handler1(context.Context, *emptypb.Empty) (*emptypb.Empty, error) Handler2(context.Context, *emptypb.Empty) (*emptypb.Empty, error) mustEmbedUnimplementedBaggageServiceServer() } // UnimplementedBaggageServiceServer must be embedded to have forward compatible implementations. type UnimplementedBaggageServiceServer struct { } func (UnimplementedBaggageServiceServer) Handler1(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method Handler1 not implemented") } func (UnimplementedBaggageServiceServer) Handler2(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method Handler2 not implemented") } func (UnimplementedBaggageServiceServer) mustEmbedUnimplementedBaggageServiceServer() {} // UnsafeBaggageServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to BaggageServiceServer will // result in compilation errors. type UnsafeBaggageServiceServer interface { mustEmbedUnimplementedBaggageServiceServer() } func RegisterBaggageServiceServer(s grpc.ServiceRegistrar, srv BaggageServiceServer) { s.RegisterService(&BaggageService_ServiceDesc, srv) } func _BaggageService_Handler1_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(emptypb.Empty) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(BaggageServiceServer).Handler1(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/zipkin.testing.BaggageService/Handler1", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(BaggageServiceServer).Handler1(ctx, req.(*emptypb.Empty)) } return interceptor(ctx, in, info, handler) } func _BaggageService_Handler2_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(emptypb.Empty) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(BaggageServiceServer).Handler2(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/zipkin.testing.BaggageService/Handler2", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(BaggageServiceServer).Handler2(ctx, req.(*emptypb.Empty)) } return interceptor(ctx, in, info, handler) } // BaggageService_ServiceDesc is the grpc.ServiceDesc for BaggageService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var BaggageService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "zipkin.testing.BaggageService", HandlerType: (*BaggageServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "Handler1", Handler: _BaggageService_Handler1_Handler, }, { MethodName: "Handler2", Handler: _BaggageService_Handler2_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "proto/testing/baggage.proto", } zipkin-go-0.4.3/proto/testing/service.pb.go000066400000000000000000000247001461356266100206510ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.28.1 // protoc v3.19.0 // source: proto/testing/service.proto package testing import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) type HelloRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Payload string `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` } func (x *HelloRequest) Reset() { *x = HelloRequest{} if protoimpl.UnsafeEnabled { mi := &file_proto_testing_service_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *HelloRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*HelloRequest) ProtoMessage() {} func (x *HelloRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_testing_service_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use HelloRequest.ProtoReflect.Descriptor instead. func (*HelloRequest) Descriptor() ([]byte, []int) { return file_proto_testing_service_proto_rawDescGZIP(), []int{0} } func (x *HelloRequest) GetPayload() string { if x != nil { return x.Payload } return "" } type HelloResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Payload string `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` Metadata map[string]string `protobuf:"bytes,2,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` SpanContext map[string]string `protobuf:"bytes,3,rep,name=span_context,json=spanContext,proto3" json:"span_context,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *HelloResponse) Reset() { *x = HelloResponse{} if protoimpl.UnsafeEnabled { mi := &file_proto_testing_service_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *HelloResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*HelloResponse) ProtoMessage() {} func (x *HelloResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_testing_service_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use HelloResponse.ProtoReflect.Descriptor instead. func (*HelloResponse) Descriptor() ([]byte, []int) { return file_proto_testing_service_proto_rawDescGZIP(), []int{1} } func (x *HelloResponse) GetPayload() string { if x != nil { return x.Payload } return "" } func (x *HelloResponse) GetMetadata() map[string]string { if x != nil { return x.Metadata } return nil } func (x *HelloResponse) GetSpanContext() map[string]string { if x != nil { return x.SpanContext } return nil } var File_proto_testing_service_proto protoreflect.FileDescriptor var file_proto_testing_service_proto_rawDesc = []byte{ 0x0a, 0x1b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0e, 0x7a, 0x69, 0x70, 0x6b, 0x69, 0x6e, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x22, 0x28, 0x0a, 0x0c, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0xc2, 0x02, 0x0a, 0x0d, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x47, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x7a, 0x69, 0x70, 0x6b, 0x69, 0x6e, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x51, 0x0a, 0x0c, 0x73, 0x70, 0x61, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x7a, 0x69, 0x70, 0x6b, 0x69, 0x6e, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x70, 0x61, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0b, 0x73, 0x70, 0x61, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x3e, 0x0a, 0x10, 0x53, 0x70, 0x61, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x32, 0x54, 0x0a, 0x0c, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x44, 0x0a, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x1c, 0x2e, 0x7a, 0x69, 0x70, 0x6b, 0x69, 0x6e, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x7a, 0x69, 0x70, 0x6b, 0x69, 0x6e, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x2f, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x7a, 0x69, 0x70, 0x6b, 0x69, 0x6e, 0x2f, 0x7a, 0x69, 0x70, 0x6b, 0x69, 0x6e, 0x2d, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_proto_testing_service_proto_rawDescOnce sync.Once file_proto_testing_service_proto_rawDescData = file_proto_testing_service_proto_rawDesc ) func file_proto_testing_service_proto_rawDescGZIP() []byte { file_proto_testing_service_proto_rawDescOnce.Do(func() { file_proto_testing_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_testing_service_proto_rawDescData) }) return file_proto_testing_service_proto_rawDescData } var file_proto_testing_service_proto_msgTypes = make([]protoimpl.MessageInfo, 4) var file_proto_testing_service_proto_goTypes = []interface{}{ (*HelloRequest)(nil), // 0: zipkin.testing.HelloRequest (*HelloResponse)(nil), // 1: zipkin.testing.HelloResponse nil, // 2: zipkin.testing.HelloResponse.MetadataEntry nil, // 3: zipkin.testing.HelloResponse.SpanContextEntry } var file_proto_testing_service_proto_depIdxs = []int32{ 2, // 0: zipkin.testing.HelloResponse.metadata:type_name -> zipkin.testing.HelloResponse.MetadataEntry 3, // 1: zipkin.testing.HelloResponse.span_context:type_name -> zipkin.testing.HelloResponse.SpanContextEntry 0, // 2: zipkin.testing.HelloService.Hello:input_type -> zipkin.testing.HelloRequest 1, // 3: zipkin.testing.HelloService.Hello:output_type -> zipkin.testing.HelloResponse 3, // [3:4] is the sub-list for method output_type 2, // [2:3] is the sub-list for method input_type 2, // [2:2] is the sub-list for extension type_name 2, // [2:2] is the sub-list for extension extendee 0, // [0:2] is the sub-list for field type_name } func init() { file_proto_testing_service_proto_init() } func file_proto_testing_service_proto_init() { if File_proto_testing_service_proto != nil { return } if !protoimpl.UnsafeEnabled { file_proto_testing_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*HelloRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_proto_testing_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*HelloResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proto_testing_service_proto_rawDesc, NumEnums: 0, NumMessages: 4, NumExtensions: 0, NumServices: 1, }, GoTypes: file_proto_testing_service_proto_goTypes, DependencyIndexes: file_proto_testing_service_proto_depIdxs, MessageInfos: file_proto_testing_service_proto_msgTypes, }.Build() File_proto_testing_service_proto = out.File file_proto_testing_service_proto_rawDesc = nil file_proto_testing_service_proto_goTypes = nil file_proto_testing_service_proto_depIdxs = nil } zipkin-go-0.4.3/proto/testing/service.proto000066400000000000000000000017071461356266100210110ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. syntax = "proto3"; package zipkin.testing; option go_package = "github.com/openzipkin/zipkin-go/proto/testing"; message HelloRequest { string payload = 1; } message HelloResponse { string payload = 1; map metadata = 2; map span_context = 3; } service HelloService { rpc Hello (HelloRequest) returns (HelloResponse); } zipkin-go-0.4.3/proto/testing/service_grpc.pb.go000066400000000000000000000071451461356266100216700ustar00rootroot00000000000000// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.2.0 // - protoc v3.19.0 // source: proto/testing/service.proto package testing import ( context "context" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" ) // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. // Requires gRPC-Go v1.32.0 or later. const _ = grpc.SupportPackageIsVersion7 // HelloServiceClient is the client API for HelloService service. // // 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. type HelloServiceClient interface { Hello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error) } type helloServiceClient struct { cc grpc.ClientConnInterface } func NewHelloServiceClient(cc grpc.ClientConnInterface) HelloServiceClient { return &helloServiceClient{cc} } func (c *helloServiceClient) Hello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error) { out := new(HelloResponse) err := c.cc.Invoke(ctx, "/zipkin.testing.HelloService/Hello", in, out, opts...) if err != nil { return nil, err } return out, nil } // HelloServiceServer is the server API for HelloService service. // All implementations must embed UnimplementedHelloServiceServer // for forward compatibility type HelloServiceServer interface { Hello(context.Context, *HelloRequest) (*HelloResponse, error) mustEmbedUnimplementedHelloServiceServer() } // UnimplementedHelloServiceServer must be embedded to have forward compatible implementations. type UnimplementedHelloServiceServer struct { } func (UnimplementedHelloServiceServer) Hello(context.Context, *HelloRequest) (*HelloResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Hello not implemented") } func (UnimplementedHelloServiceServer) mustEmbedUnimplementedHelloServiceServer() {} // UnsafeHelloServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to HelloServiceServer will // result in compilation errors. type UnsafeHelloServiceServer interface { mustEmbedUnimplementedHelloServiceServer() } func RegisterHelloServiceServer(s grpc.ServiceRegistrar, srv HelloServiceServer) { s.RegisterService(&HelloService_ServiceDesc, srv) } func _HelloService_Hello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(HelloRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(HelloServiceServer).Hello(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/zipkin.testing.HelloService/Hello", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HelloServiceServer).Hello(ctx, req.(*HelloRequest)) } return interceptor(ctx, in, info, handler) } // HelloService_ServiceDesc is the grpc.ServiceDesc for HelloService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var HelloService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "zipkin.testing.HelloService", HandlerType: (*HelloServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "Hello", Handler: _HelloService_Hello_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "proto/testing/service.proto", } zipkin-go-0.4.3/proto/zipkin_proto3/000077500000000000000000000000001461356266100174145ustar00rootroot00000000000000zipkin-go-0.4.3/proto/zipkin_proto3/decode_proto.go000066400000000000000000000102741461356266100224150ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /* Package zipkin_proto3 adds support for the Zipkin protobuf definition to allow Go applications to consume model.SpanModel from protobuf serialized data. */ package zipkin_proto3 import ( "encoding/binary" "errors" "fmt" "net" "time" "google.golang.org/protobuf/proto" zipkinmodel "github.com/openzipkin/zipkin-go/model" ) // ParseSpans parses zipkinmodel.SpanModel values from data serialized by Protobuf3. // debugWasSet is a boolean that toggles the Debug field of each Span. Its value // is usually retrieved from the transport headers when the "X-B3-Flags" header has a value of 1. func ParseSpans(protoBlob []byte, debugWasSet bool) (zss []*zipkinmodel.SpanModel, err error) { var listOfSpans ListOfSpans if err := proto.Unmarshal(protoBlob, &listOfSpans); err != nil { return nil, err } for _, zps := range listOfSpans.Spans { zms, err := protoSpanToModelSpan(zps, debugWasSet) if err != nil { return zss, err } zss = append(zss, zms) } return zss, nil } var errNilZipkinSpan = errors.New("expecting a non-nil Span") func protoSpanToModelSpan(s *Span, debugWasSet bool) (*zipkinmodel.SpanModel, error) { if s == nil { return nil, errNilZipkinSpan } if len(s.TraceId) != 16 { return nil, fmt.Errorf("invalid TraceID: has length %d yet wanted length 16", len(s.TraceId)) } traceID, err := zipkinmodel.TraceIDFromHex(fmt.Sprintf("%x", s.TraceId)) if err != nil { return nil, fmt.Errorf("invalid TraceID: %v", err) } parentSpanID, _, err := protoSpanIDToModelSpanID(s.ParentId) if err != nil { return nil, fmt.Errorf("invalid ParentID: %v", err) } spanIDPtr, spanIDBlank, err := protoSpanIDToModelSpanID(s.Id) if err != nil { return nil, fmt.Errorf("invalid SpanID: %v", err) } if spanIDBlank || spanIDPtr == nil { // This is a logical error return nil, errors.New("expected a non-nil SpanID") } zmsc := zipkinmodel.SpanContext{ TraceID: traceID, ID: *spanIDPtr, ParentID: parentSpanID, Debug: debugWasSet, } zms := &zipkinmodel.SpanModel{ SpanContext: zmsc, Name: s.Name, Kind: zipkinmodel.Kind(s.Kind.String()), Timestamp: microsToTime(s.Timestamp), Tags: s.Tags, Duration: microsToDuration(s.Duration), LocalEndpoint: protoEndpointToModelEndpoint(s.LocalEndpoint), RemoteEndpoint: protoEndpointToModelEndpoint(s.RemoteEndpoint), Shared: s.Shared, Annotations: protoAnnotationsToModelAnnotations(s.Annotations), } return zms, nil } func microsToDuration(us uint64) time.Duration { // us to ns; ns are the units of Duration return time.Duration(us * 1e3) } func protoEndpointToModelEndpoint(zpe *Endpoint) *zipkinmodel.Endpoint { if zpe == nil { return nil } return &zipkinmodel.Endpoint{ ServiceName: zpe.ServiceName, IPv4: net.IP(zpe.Ipv4), IPv6: net.IP(zpe.Ipv6), Port: uint16(zpe.Port), } } func protoSpanIDToModelSpanID(spanId []byte) (zid *zipkinmodel.ID, blank bool, err error) { if len(spanId) == 0 { return nil, true, nil } if len(spanId) != 8 { return nil, true, fmt.Errorf("has length %d yet wanted length 8", len(spanId)) } // Converting [8]byte --> uint64 u64 := binary.BigEndian.Uint64(spanId) zid_ := zipkinmodel.ID(u64) return &zid_, false, nil } func protoAnnotationsToModelAnnotations(zpa []*Annotation) (zma []zipkinmodel.Annotation) { for _, za := range zpa { if za != nil { zma = append(zma, zipkinmodel.Annotation{ Timestamp: microsToTime(za.Timestamp), Value: za.Value, }) } } if len(zma) == 0 { return nil } return zma } func microsToTime(us uint64) time.Time { return time.Unix(0, int64(us*1e3)).UTC() } zipkin-go-0.4.3/proto/zipkin_proto3/decode_proto_test.go000066400000000000000000000175011461356266100234540ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package zipkin_proto3_test import ( "bytes" "encoding/json" "net" "reflect" "strings" "testing" "time" "google.golang.org/protobuf/proto" zipkinmodel "github.com/openzipkin/zipkin-go/model" "github.com/openzipkin/zipkin-go/proto/zipkin_proto3" ) func TestParseSpans(t *testing.T) { // 1. Generate some spans then serialize them with protobuf protoBlob, err := proto.Marshal(payloadFromWild) if err != nil { t.Fatalf("Failed to parse payload from wild: %v", err) } got, err := zipkin_proto3.ParseSpans(protoBlob, true) if err != nil { t.Fatalf("Failed to parse spans from protobuf blob: %v", err) } want := []*zipkinmodel.SpanModel{ { SpanContext: zipkinmodel.SpanContext{ TraceID: zipkinmodel.TraceID{ High: 0x7F6F5F4F3F2F1F0F, Low: 0xF7F6F5F4F3F2F1F0, }, ID: 0xF7F6F5F4F3F2F1F0, ParentID: idPtr(0xF7F6F5F4F3F2F1F0), Debug: true, }, Name: "ProtoSpan1", Timestamp: now, Duration: 12 * time.Second, Shared: false, Kind: zipkinmodel.Consumer, LocalEndpoint: &zipkinmodel.Endpoint{ ServiceName: "svc-1", IPv4: net.IP{0xC0, 0xA8, 0x00, 0x01}, Port: 8009, }, RemoteEndpoint: &zipkinmodel.Endpoint{ ServiceName: "memcached", IPv6: net.IP{0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x53, 0xa7, 0x7c, 0xda, 0x4d, 0xd2, 0x1b}, Port: 11211, }, }, { SpanContext: zipkinmodel.SpanContext{ TraceID: zipkinmodel.TraceID{ High: 0x7A6A5A4A3A2A1A0A, Low: 0xC7C6C5C4C3C2C1C0, }, ID: 0x6766656463626160, ParentID: idPtr(0x1716151413121110), Debug: true, }, Name: "CacheWarmUp", Timestamp: minus10hr5ms, Kind: zipkinmodel.Producer, Duration: 7 * time.Second, LocalEndpoint: &zipkinmodel.Endpoint{ ServiceName: "search", IPv4: net.IP{0x0A, 0x00, 0x00, 0x0D}, Port: 8009, }, RemoteEndpoint: &zipkinmodel.Endpoint{ ServiceName: "redis", IPv6: net.IP{0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x53, 0xa7, 0x7c, 0xda, 0x4d, 0xd2, 0x1b}, Port: 6379, }, Annotations: []zipkinmodel.Annotation{ { Timestamp: minus10hr5ms, Value: "DB reset", }, { Timestamp: minus10hr5ms, Value: "GC Cycle 39", }, }, }, } if g, w := len(got), len(want); g != w { t.Errorf("Number of spans doesn't match:: Got %d Want %d", g, w) } if !reflect.DeepEqual(got, want) { gj, _ := json.Marshal(got) wj, _ := json.Marshal(want) if len(gj) < 100 { t.Errorf("Unexpected found short output: %v", len(gj)) } if !bytes.Equal(gj, wj) { t.Errorf("Failed to get roundtripped spans\nGot: %s\nWant:%s\n", gj, wj) } } } func TestParseSpans_failures(t *testing.T) { tests := []struct { spans []*zipkin_proto3.Span wantErr string }{ { spans: []*zipkin_proto3.Span{ {TraceId: nil}, }, wantErr: "invalid TraceID: has length 0 yet wanted length 16", }, { spans: []*zipkin_proto3.Span{ {TraceId: []byte{0x01, 0x02}}, }, wantErr: "invalid TraceID: has length 2 yet wanted length 16", }, { spans: []*zipkin_proto3.Span{ { TraceId: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10}, Id: nil, }, }, wantErr: "expected a non-nil SpanID", }, { spans: []*zipkin_proto3.Span{ { TraceId: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10}, Id: []byte{0x01, 0x02}, }, }, wantErr: "invalid SpanID: has length 2 yet wanted length 8", }, { spans: []*zipkin_proto3.Span{ { TraceId: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10}, ParentId: []byte{0x01, 0x02}, }, }, wantErr: "invalid ParentID: has length 2 yet wanted length 8", }, { spans: []*zipkin_proto3.Span{ { TraceId: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10}, ParentId: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, }, }, wantErr: "expected a non-nil SpanID", }, { spans: []*zipkin_proto3.Span{ { TraceId: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10}, ParentId: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, Id: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}, }, }, wantErr: "invalid SpanID: has length 7 yet wanted length 8", }, } for i, tt := range tests { payload := &zipkin_proto3.ListOfSpans{Spans: tt.spans} protoBlob, err := proto.Marshal(payload) if err != nil { t.Errorf("Test #%d: Failed to serialize ProtoPayload: %v", i, err) continue } zms, err := zipkin_proto3.ParseSpans(protoBlob, true) if err == nil { t.Errorf("#%d: unexpectedly passed and got span\n%#v", i, zms) continue } if zms != nil { t.Errorf("#%d: inconsistency, ParseSpan is non-nil and so is the error", i) } if !strings.Contains(err.Error(), tt.wantErr) { t.Errorf("#%d: Mismatched errors\nGot: (%q)\nWant:(%q)", i, err, tt.wantErr) } } } func idPtr(id zipkinmodel.ID) *zipkinmodel.ID { return &id } var ( now = time.Date(2018, 10, 31, 19, 43, 35, 789, time.UTC).Round(time.Microsecond) minus10hr5ms = now.Add(-(10*time.Hour + 5*time.Millisecond)).Round(time.Microsecond) ) var payloadFromWild = &zipkin_proto3.ListOfSpans{ Spans: []*zipkin_proto3.Span{ { TraceId: []byte{0x7F, 0x6F, 0x5F, 0x4F, 0x3F, 0x2F, 0x1F, 0x0F, 0xF7, 0xF6, 0xF5, 0xF4, 0xF3, 0xF2, 0xF1, 0xF0}, Id: []byte{0xF7, 0xF6, 0xF5, 0xF4, 0xF3, 0xF2, 0xF1, 0xF0}, ParentId: []byte{0xF7, 0xF6, 0xF5, 0xF4, 0xF3, 0xF2, 0xF1, 0xF0}, Name: "ProtoSpan1", Kind: zipkin_proto3.Span_CONSUMER, Timestamp: uint64(now.UnixNano() / 1e3), Duration: 12e6, LocalEndpoint: &zipkin_proto3.Endpoint{ ServiceName: "svc-1", Ipv4: []byte{0xC0, 0xA8, 0x00, 0x01}, Port: 8009, }, RemoteEndpoint: &zipkin_proto3.Endpoint{ ServiceName: "memcached", Ipv6: []byte{0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x53, 0xa7, 0x7c, 0xda, 0x4d, 0xd2, 0x1b}, Port: 11211, }, }, { TraceId: []byte{0x7A, 0x6A, 0x5A, 0x4A, 0x3A, 0x2A, 0x1A, 0x0A, 0xC7, 0xC6, 0xC5, 0xC4, 0xC3, 0xC2, 0xC1, 0xC0}, Id: []byte{0x67, 0x66, 0x65, 0x64, 0x63, 0x62, 0x61, 0x60}, ParentId: []byte{0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10}, Name: "CacheWarmUp", Kind: zipkin_proto3.Span_PRODUCER, Timestamp: uint64(minus10hr5ms.UnixNano() / 1e3), Duration: 7e6, LocalEndpoint: &zipkin_proto3.Endpoint{ ServiceName: "search", Ipv4: []byte{0x0A, 0x00, 0x00, 0x0D}, Port: 8009, }, RemoteEndpoint: &zipkin_proto3.Endpoint{ ServiceName: "redis", Ipv6: []byte{0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x53, 0xa7, 0x7c, 0xda, 0x4d, 0xd2, 0x1b}, Port: 6379, }, Annotations: []*zipkin_proto3.Annotation{ { Timestamp: uint64(minus10hr5ms.UnixNano() / 1e3), Value: "DB reset", }, { Timestamp: uint64(minus10hr5ms.UnixNano() / 1e3), Value: "GC Cycle 39", }, }, }, }, } zipkin-go-0.4.3/proto/zipkin_proto3/encode_proto.go000066400000000000000000000067061461356266100224340ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package zipkin_proto3 import ( "encoding/binary" "errors" "time" zipkinmodel "github.com/openzipkin/zipkin-go/model" "google.golang.org/protobuf/proto" ) var errNilProtoSpan = errors.New("expecting a non-nil Span") // SpanSerializer implements http.SpanSerializer type SpanSerializer struct{} // Serialize takes an array of zipkin SpanModel objects and serializes it to a protobuf blob. func (SpanSerializer) Serialize(sms []*zipkinmodel.SpanModel) (protoBlob []byte, err error) { var listOfSpans ListOfSpans for _, sm := range sms { sp, err := modelSpanToProtoSpan(sm) if err != nil { return nil, err } listOfSpans.Spans = append(listOfSpans.Spans, sp) } return proto.Marshal(&listOfSpans) } // ContentType returns the ContentType needed for this encoding. func (SpanSerializer) ContentType() string { return "application/x-protobuf" } func modelSpanToProtoSpan(sm *zipkinmodel.SpanModel) (*Span, error) { if sm == nil { return nil, errNilProtoSpan } traceID := make([]byte, 16) binary.BigEndian.PutUint64(traceID[0:8], uint64(sm.TraceID.High)) binary.BigEndian.PutUint64(traceID[8:16], uint64(sm.TraceID.Low)) parentID := make([]byte, 8) if sm.ParentID != nil { binary.BigEndian.PutUint64(parentID, uint64(*sm.ParentID)) } id := make([]byte, 8) binary.BigEndian.PutUint64(id, uint64(sm.ID)) var timeStamp uint64 if !sm.Timestamp.IsZero() { timeStamp = uint64(sm.Timestamp.Round(time.Microsecond).UnixNano() / 1e3) } return &Span{ TraceId: traceID, ParentId: parentID, Id: id, Debug: sm.Debug, Kind: Span_Kind(Span_Kind_value[string(sm.Kind)]), Name: sm.Name, Timestamp: timeStamp, Tags: sm.Tags, Duration: uint64(sm.Duration.Nanoseconds() / 1e3), LocalEndpoint: modelEndpointToProtoEndpoint(sm.LocalEndpoint), RemoteEndpoint: modelEndpointToProtoEndpoint(sm.RemoteEndpoint), Shared: sm.Shared, Annotations: modelAnnotationsToProtoAnnotations(sm.Annotations), }, nil } func durationToMicros(d time.Duration) (uint64, error) { if d < time.Microsecond { if d < 0 { return 0, zipkinmodel.ErrValidDurationRequired } else if d > 0 { d = 1 * time.Microsecond } } else { d += 500 * time.Nanosecond } return uint64(d.Nanoseconds() / 1e3), nil } func modelEndpointToProtoEndpoint(ep *zipkinmodel.Endpoint) *Endpoint { if ep == nil { return nil } return &Endpoint{ ServiceName: ep.ServiceName, Ipv4: []byte(ep.IPv4), Ipv6: []byte(ep.IPv6), Port: int32(ep.Port), } } func modelAnnotationsToProtoAnnotations(mas []zipkinmodel.Annotation) (pas []*Annotation) { for _, ma := range mas { pas = append(pas, &Annotation{ Timestamp: timeToMicros(ma.Timestamp), Value: ma.Value, }) } return } func timeToMicros(t time.Time) uint64 { if t.IsZero() { return 0 } return uint64(t.UnixNano()) / 1e3 } zipkin-go-0.4.3/proto/zipkin_proto3/encode_proto_test.go000066400000000000000000000055711461356266100234720ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package zipkin_proto3_test import ( "encoding/json" "net" "reflect" "testing" "time" zipkinmodel "github.com/openzipkin/zipkin-go/model" "github.com/openzipkin/zipkin-go/proto/zipkin_proto3" ) func TestExportSpans(t *testing.T) { want := []*zipkinmodel.SpanModel{ { SpanContext: zipkinmodel.SpanContext{ TraceID: zipkinmodel.TraceID{ High: 0x7F6F5F4F3F2F1F0F, Low: 0xF7F6F5F4F3F2F1F0, }, ID: 0xF7F6F5F4F3F2F1F0, ParentID: idPtr(0xF7F6F5F4F3F2F1F0), Debug: true, }, Name: "ProtoSpan1", Timestamp: now, Duration: 12 * time.Second, Shared: false, Kind: zipkinmodel.Consumer, LocalEndpoint: &zipkinmodel.Endpoint{ ServiceName: "svc-1", IPv4: net.IP{0xC0, 0xA8, 0x00, 0x01}, Port: 8009, }, RemoteEndpoint: &zipkinmodel.Endpoint{ ServiceName: "memcached", IPv6: net.IP{0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x53, 0xa7, 0x7c, 0xda, 0x4d, 0xd2, 0x1b}, Port: 11211, }, }, { SpanContext: zipkinmodel.SpanContext{ TraceID: zipkinmodel.TraceID{ High: 0x7A6A5A4A3A2A1A0A, Low: 0xC7C6C5C4C3C2C1C0, }, ID: 0x6766656463626160, ParentID: idPtr(0x1716151413121110), Debug: true, }, Name: "CacheWarmUp", Timestamp: minus10hr5ms, Kind: zipkinmodel.Producer, Duration: 7 * time.Second, LocalEndpoint: &zipkinmodel.Endpoint{ ServiceName: "search", IPv4: net.IP{0x0A, 0x00, 0x00, 0x0D}, Port: 8009, }, RemoteEndpoint: &zipkinmodel.Endpoint{ ServiceName: "redis", IPv6: net.IP{0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x53, 0xa7, 0x7c, 0xda, 0x4d, 0xd2, 0x1b}, Port: 6379, }, Annotations: []zipkinmodel.Annotation{ { Timestamp: minus10hr5ms, Value: "DB reset", }, { Timestamp: minus10hr5ms, Value: "GC Cycle 39", }, }, }, } protoBlob, err := zipkin_proto3.SpanSerializer{}.Serialize(want) if err != nil { t.Fatalf("Failed to parse spans from protobuf blob: %v", err) } if got, _ := zipkin_proto3.ParseSpans(protoBlob, true); !reflect.DeepEqual(want, got) { w, _ := json.Marshal(want) g, _ := json.Marshal(got) t.Errorf("conversion error!\nWANT:\n%s\n\nGOT:\n%s\n", w, g) } } zipkin-go-0.4.3/proto/zipkin_proto3/zipkin.pb.go000066400000000000000000000664011461356266100216560ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.28.1 // protoc v3.19.0 // source: proto/zipkin_proto3/zipkin.proto // This is the package for using protobuf with Zipkin API V2, but for historical // reasons uses the protoc syntax version instead. package zipkin_proto3 import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // When present, kind clarifies timestamp, duration and remote_endpoint. When // absent, the span is local or incomplete. Unlike client and server, there // is no direct critical path latency relationship between producer and // consumer spans. type Span_Kind int32 const ( // Default value interpreted as absent. Span_SPAN_KIND_UNSPECIFIED Span_Kind = 0 // The span represents the client side of an RPC operation, implying the // following: // // timestamp is the moment a request was sent to the server. // duration is the delay until a response or an error was received. // remote_endpoint is the server. Span_CLIENT Span_Kind = 1 // The span represents the server side of an RPC operation, implying the // following: // // timestamp is the moment a client request was received. // duration is the delay until a response was sent or an error. // remote_endpoint is the client. Span_SERVER Span_Kind = 2 // The span represents production of a message to a remote broker, implying // the following: // // timestamp is the moment a message was sent to a destination. // duration is the delay sending the message, such as batching. // remote_endpoint is the broker. Span_PRODUCER Span_Kind = 3 // The span represents consumption of a message from a remote broker, not // time spent servicing it. For example, a message processor would be an // in-process child span of a consumer. Consumer spans imply the following: // // timestamp is the moment a message was received from an origin. // duration is the delay consuming the message, such as from backlog. // remote_endpoint is the broker. Span_CONSUMER Span_Kind = 4 ) // Enum value maps for Span_Kind. var ( Span_Kind_name = map[int32]string{ 0: "SPAN_KIND_UNSPECIFIED", 1: "CLIENT", 2: "SERVER", 3: "PRODUCER", 4: "CONSUMER", } Span_Kind_value = map[string]int32{ "SPAN_KIND_UNSPECIFIED": 0, "CLIENT": 1, "SERVER": 2, "PRODUCER": 3, "CONSUMER": 4, } ) func (x Span_Kind) Enum() *Span_Kind { p := new(Span_Kind) *p = x return p } func (x Span_Kind) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (Span_Kind) Descriptor() protoreflect.EnumDescriptor { return file_proto_zipkin_proto3_zipkin_proto_enumTypes[0].Descriptor() } func (Span_Kind) Type() protoreflect.EnumType { return &file_proto_zipkin_proto3_zipkin_proto_enumTypes[0] } func (x Span_Kind) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use Span_Kind.Descriptor instead. func (Span_Kind) EnumDescriptor() ([]byte, []int) { return file_proto_zipkin_proto3_zipkin_proto_rawDescGZIP(), []int{0, 0} } // A span is a single-host view of an operation. A trace is a series of spans // (often RPC calls) which nest to form a latency tree. Spans are in the same // trace when they share the same trace ID. The parent_id field establishes the // position of one span in the tree. // // The root span is where parent_id is Absent and usually has the longest // duration in the trace. However, nested asynchronous work can materialize as // child spans whose duration exceed the root span. // // Spans usually represent remote activity such as RPC calls, or messaging // producers and consumers. However, they can also represent in-process // activity in any position of the trace. For example, a root span could // represent a server receiving an initial client request. A root span could // also represent a scheduled job that has no remote context. // // Encoding notes: // // Epoch timestamp are encoded fixed64 as varint would also be 8 bytes, and more // expensive to encode and size. Duration is stored uint64, as often the numbers // are quite small. // // Default values are ok, as only natural numbers are used. For example, zero is // an invalid timestamp and an invalid duration, false values for debug or shared // are ignorable, and zero-length strings also coerce to null. // // The next id is 14. // // Note fields up to 15 take 1 byte to encode. Take care when adding new fields // https://developers.google.com/protocol-buffers/docs/proto3#assigning-tags type Span struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Randomly generated, unique identifier for a trace, set on all spans within // it. // // This field is required and encoded as 8 or 16 opaque bytes. TraceId []byte `protobuf:"bytes,1,opt,name=trace_id,json=traceId,proto3" json:"trace_id,omitempty"` // The parent span ID or absent if this the root span in a trace. ParentId []byte `protobuf:"bytes,2,opt,name=parent_id,json=parentId,proto3" json:"parent_id,omitempty"` // Unique identifier for this operation within the trace. // // This field is required and encoded as 8 opaque bytes. Id []byte `protobuf:"bytes,3,opt,name=id,proto3" json:"id,omitempty"` // When present, used to interpret remote_endpoint Kind Span_Kind `protobuf:"varint,4,opt,name=kind,proto3,enum=zipkin.proto3.Span_Kind" json:"kind,omitempty"` // The logical operation this span represents in lowercase (e.g. rpc method). // Leave absent if unknown. // // As these are lookup labels, take care to ensure names are low cardinality. // For example, do not embed variables into the name. Name string `protobuf:"bytes,5,opt,name=name,proto3" json:"name,omitempty"` // Epoch microseconds of the start of this span, possibly absent if // incomplete. // // For example, 1502787600000000 corresponds to 2017-08-15 09:00 UTC // // This value should be set directly by instrumentation, using the most // precise value possible. For example, gettimeofday or multiplying epoch // millis by 1000. // // There are three known edge-cases where this could be reported absent. // - A span was allocated but never started (ex not yet received a timestamp) // - The span's start event was lost // - Data about a completed span (ex tags) were sent after the fact Timestamp uint64 `protobuf:"fixed64,6,opt,name=timestamp,proto3" json:"timestamp,omitempty"` // Duration in microseconds of the critical path, if known. Durations of less // than one are rounded up. Duration of children can be longer than their // parents due to asynchronous operations. // // For example 150 milliseconds is 150000 microseconds. Duration uint64 `protobuf:"varint,7,opt,name=duration,proto3" json:"duration,omitempty"` // The host that recorded this span, primarily for query by service name. // // Instrumentation should always record this. Usually, absent implies late // data. The IP address corresponding to this is usually the site local or // advertised service address. When present, the port indicates the listen // port. LocalEndpoint *Endpoint `protobuf:"bytes,8,opt,name=local_endpoint,json=localEndpoint,proto3" json:"local_endpoint,omitempty"` // When an RPC (or messaging) span, indicates the other side of the // connection. // // By recording the remote endpoint, your trace will contain network context // even if the peer is not tracing. For example, you can record the IP from // the "X-Forwarded-For" header or the service name and socket of a remote // peer. RemoteEndpoint *Endpoint `protobuf:"bytes,9,opt,name=remote_endpoint,json=remoteEndpoint,proto3" json:"remote_endpoint,omitempty"` // Associates events that explain latency with the time they happened. Annotations []*Annotation `protobuf:"bytes,10,rep,name=annotations,proto3" json:"annotations,omitempty"` // Tags give your span context for search, viewing and analysis. // // For example, a key "your_app.version" would let you lookup traces by // version. A tag "sql.query" isn't searchable, but it can help in debugging // when viewing a trace. Tags map[string]string `protobuf:"bytes,11,rep,name=tags,proto3" json:"tags,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` // True is a request to store this span even if it overrides sampling policy. // // This is true when the "X-B3-Flags" header has a value of 1. Debug bool `protobuf:"varint,12,opt,name=debug,proto3" json:"debug,omitempty"` // True if we are contributing to a span started by another tracer (ex on a // different host). Shared bool `protobuf:"varint,13,opt,name=shared,proto3" json:"shared,omitempty"` } func (x *Span) Reset() { *x = Span{} if protoimpl.UnsafeEnabled { mi := &file_proto_zipkin_proto3_zipkin_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Span) String() string { return protoimpl.X.MessageStringOf(x) } func (*Span) ProtoMessage() {} func (x *Span) ProtoReflect() protoreflect.Message { mi := &file_proto_zipkin_proto3_zipkin_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Span.ProtoReflect.Descriptor instead. func (*Span) Descriptor() ([]byte, []int) { return file_proto_zipkin_proto3_zipkin_proto_rawDescGZIP(), []int{0} } func (x *Span) GetTraceId() []byte { if x != nil { return x.TraceId } return nil } func (x *Span) GetParentId() []byte { if x != nil { return x.ParentId } return nil } func (x *Span) GetId() []byte { if x != nil { return x.Id } return nil } func (x *Span) GetKind() Span_Kind { if x != nil { return x.Kind } return Span_SPAN_KIND_UNSPECIFIED } func (x *Span) GetName() string { if x != nil { return x.Name } return "" } func (x *Span) GetTimestamp() uint64 { if x != nil { return x.Timestamp } return 0 } func (x *Span) GetDuration() uint64 { if x != nil { return x.Duration } return 0 } func (x *Span) GetLocalEndpoint() *Endpoint { if x != nil { return x.LocalEndpoint } return nil } func (x *Span) GetRemoteEndpoint() *Endpoint { if x != nil { return x.RemoteEndpoint } return nil } func (x *Span) GetAnnotations() []*Annotation { if x != nil { return x.Annotations } return nil } func (x *Span) GetTags() map[string]string { if x != nil { return x.Tags } return nil } func (x *Span) GetDebug() bool { if x != nil { return x.Debug } return false } func (x *Span) GetShared() bool { if x != nil { return x.Shared } return false } // The network context of a node in the service graph. // // The next id is 5. type Endpoint struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Lower-case label of this node in the service graph, such as "favstar". // Leave absent if unknown. // // This is a primary label for trace lookup and aggregation, so it should be // intuitive and consistent. Many use a name from service discovery. ServiceName string `protobuf:"bytes,1,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` // 4 byte representation of the primary IPv4 address associated with this // connection. Absent if unknown. Ipv4 []byte `protobuf:"bytes,2,opt,name=ipv4,proto3" json:"ipv4,omitempty"` // 16 byte representation of the primary IPv6 address associated with this // connection. Absent if unknown. // // Prefer using the ipv4 field for mapped addresses. Ipv6 []byte `protobuf:"bytes,3,opt,name=ipv6,proto3" json:"ipv6,omitempty"` // Depending on context, this could be a listen port or the client-side of a // socket. Absent if unknown. Port int32 `protobuf:"varint,4,opt,name=port,proto3" json:"port,omitempty"` } func (x *Endpoint) Reset() { *x = Endpoint{} if protoimpl.UnsafeEnabled { mi := &file_proto_zipkin_proto3_zipkin_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Endpoint) String() string { return protoimpl.X.MessageStringOf(x) } func (*Endpoint) ProtoMessage() {} func (x *Endpoint) ProtoReflect() protoreflect.Message { mi := &file_proto_zipkin_proto3_zipkin_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Endpoint.ProtoReflect.Descriptor instead. func (*Endpoint) Descriptor() ([]byte, []int) { return file_proto_zipkin_proto3_zipkin_proto_rawDescGZIP(), []int{1} } func (x *Endpoint) GetServiceName() string { if x != nil { return x.ServiceName } return "" } func (x *Endpoint) GetIpv4() []byte { if x != nil { return x.Ipv4 } return nil } func (x *Endpoint) GetIpv6() []byte { if x != nil { return x.Ipv6 } return nil } func (x *Endpoint) GetPort() int32 { if x != nil { return x.Port } return 0 } // Associates an event that explains latency with a timestamp. // Unlike log statements, annotations are often codes. Ex. "ws" for WireSend // // The next id is 3. type Annotation struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Epoch microseconds of this event. // // For example, 1502787600000000 corresponds to 2017-08-15 09:00 UTC // // This value should be set directly by instrumentation, using the most // precise value possible. For example, gettimeofday or multiplying epoch // millis by 1000. Timestamp uint64 `protobuf:"fixed64,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"` // Usually a short tag indicating an event, like "error" // // While possible to add larger data, such as garbage collection details, low // cardinality event names both keep the size of spans down and also are easy // to search against. Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` } func (x *Annotation) Reset() { *x = Annotation{} if protoimpl.UnsafeEnabled { mi := &file_proto_zipkin_proto3_zipkin_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Annotation) String() string { return protoimpl.X.MessageStringOf(x) } func (*Annotation) ProtoMessage() {} func (x *Annotation) ProtoReflect() protoreflect.Message { mi := &file_proto_zipkin_proto3_zipkin_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Annotation.ProtoReflect.Descriptor instead. func (*Annotation) Descriptor() ([]byte, []int) { return file_proto_zipkin_proto3_zipkin_proto_rawDescGZIP(), []int{2} } func (x *Annotation) GetTimestamp() uint64 { if x != nil { return x.Timestamp } return 0 } func (x *Annotation) GetValue() string { if x != nil { return x.Value } return "" } // A list of spans with possibly different trace ids, in no particular order. // // This is used for all transports: POST, Kafka messages etc. No other fields // are expected, This message facilitates the mechanics of encoding a list, as // a field number is required. The name of this type is the same in the OpenApi // aka Swagger specification. https://zipkin.io/zipkin-api/#/default/post_spans type ListOfSpans struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Spans []*Span `protobuf:"bytes,1,rep,name=spans,proto3" json:"spans,omitempty"` } func (x *ListOfSpans) Reset() { *x = ListOfSpans{} if protoimpl.UnsafeEnabled { mi := &file_proto_zipkin_proto3_zipkin_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ListOfSpans) String() string { return protoimpl.X.MessageStringOf(x) } func (*ListOfSpans) ProtoMessage() {} func (x *ListOfSpans) ProtoReflect() protoreflect.Message { mi := &file_proto_zipkin_proto3_zipkin_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ListOfSpans.ProtoReflect.Descriptor instead. func (*ListOfSpans) Descriptor() ([]byte, []int) { return file_proto_zipkin_proto3_zipkin_proto_rawDescGZIP(), []int{3} } func (x *ListOfSpans) GetSpans() []*Span { if x != nil { return x.Spans } return nil } var File_proto_zipkin_proto3_zipkin_proto protoreflect.FileDescriptor var file_proto_zipkin_proto3_zipkin_proto_rawDesc = []byte{ 0x0a, 0x20, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x7a, 0x69, 0x70, 0x6b, 0x69, 0x6e, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x2f, 0x7a, 0x69, 0x70, 0x6b, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d, 0x7a, 0x69, 0x70, 0x6b, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x22, 0xfa, 0x04, 0x0a, 0x04, 0x53, 0x70, 0x61, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x74, 0x72, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x2c, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x7a, 0x69, 0x70, 0x6b, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x2e, 0x53, 0x70, 0x61, 0x6e, 0x2e, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x06, 0x20, 0x01, 0x28, 0x06, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3e, 0x0a, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x7a, 0x69, 0x70, 0x6b, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x0d, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x40, 0x0a, 0x0f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x7a, 0x69, 0x70, 0x6b, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x3b, 0x0a, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x7a, 0x69, 0x70, 0x6b, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x2e, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x31, 0x0a, 0x04, 0x74, 0x61, 0x67, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x7a, 0x69, 0x70, 0x6b, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x2e, 0x53, 0x70, 0x61, 0x6e, 0x2e, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x64, 0x65, 0x62, 0x75, 0x67, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x64, 0x65, 0x62, 0x75, 0x67, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x1a, 0x37, 0x0a, 0x09, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x55, 0x0a, 0x04, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x50, 0x41, 0x4e, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x4c, 0x49, 0x45, 0x4e, 0x54, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x50, 0x52, 0x4f, 0x44, 0x55, 0x43, 0x45, 0x52, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x4f, 0x4e, 0x53, 0x55, 0x4d, 0x45, 0x52, 0x10, 0x04, 0x22, 0x69, 0x0a, 0x08, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x70, 0x76, 0x34, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x69, 0x70, 0x76, 0x34, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x70, 0x76, 0x36, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x69, 0x70, 0x76, 0x36, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x40, 0x0a, 0x0a, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x06, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x38, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x66, 0x53, 0x70, 0x61, 0x6e, 0x73, 0x12, 0x29, 0x0a, 0x05, 0x73, 0x70, 0x61, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x7a, 0x69, 0x70, 0x6b, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x2e, 0x53, 0x70, 0x61, 0x6e, 0x52, 0x05, 0x73, 0x70, 0x61, 0x6e, 0x73, 0x42, 0x47, 0x0a, 0x0e, 0x7a, 0x69, 0x70, 0x6b, 0x69, 0x6e, 0x32, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x50, 0x01, 0x5a, 0x33, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x7a, 0x69, 0x70, 0x6b, 0x69, 0x6e, 0x2f, 0x7a, 0x69, 0x70, 0x6b, 0x69, 0x6e, 0x2d, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x7a, 0x69, 0x70, 0x6b, 0x69, 0x6e, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_proto_zipkin_proto3_zipkin_proto_rawDescOnce sync.Once file_proto_zipkin_proto3_zipkin_proto_rawDescData = file_proto_zipkin_proto3_zipkin_proto_rawDesc ) func file_proto_zipkin_proto3_zipkin_proto_rawDescGZIP() []byte { file_proto_zipkin_proto3_zipkin_proto_rawDescOnce.Do(func() { file_proto_zipkin_proto3_zipkin_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_zipkin_proto3_zipkin_proto_rawDescData) }) return file_proto_zipkin_proto3_zipkin_proto_rawDescData } var file_proto_zipkin_proto3_zipkin_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_proto_zipkin_proto3_zipkin_proto_msgTypes = make([]protoimpl.MessageInfo, 5) var file_proto_zipkin_proto3_zipkin_proto_goTypes = []interface{}{ (Span_Kind)(0), // 0: zipkin.proto3.Span.Kind (*Span)(nil), // 1: zipkin.proto3.Span (*Endpoint)(nil), // 2: zipkin.proto3.Endpoint (*Annotation)(nil), // 3: zipkin.proto3.Annotation (*ListOfSpans)(nil), // 4: zipkin.proto3.ListOfSpans nil, // 5: zipkin.proto3.Span.TagsEntry } var file_proto_zipkin_proto3_zipkin_proto_depIdxs = []int32{ 0, // 0: zipkin.proto3.Span.kind:type_name -> zipkin.proto3.Span.Kind 2, // 1: zipkin.proto3.Span.local_endpoint:type_name -> zipkin.proto3.Endpoint 2, // 2: zipkin.proto3.Span.remote_endpoint:type_name -> zipkin.proto3.Endpoint 3, // 3: zipkin.proto3.Span.annotations:type_name -> zipkin.proto3.Annotation 5, // 4: zipkin.proto3.Span.tags:type_name -> zipkin.proto3.Span.TagsEntry 1, // 5: zipkin.proto3.ListOfSpans.spans:type_name -> zipkin.proto3.Span 6, // [6:6] is the sub-list for method output_type 6, // [6:6] is the sub-list for method input_type 6, // [6:6] is the sub-list for extension type_name 6, // [6:6] is the sub-list for extension extendee 0, // [0:6] is the sub-list for field type_name } func init() { file_proto_zipkin_proto3_zipkin_proto_init() } func file_proto_zipkin_proto3_zipkin_proto_init() { if File_proto_zipkin_proto3_zipkin_proto != nil { return } if !protoimpl.UnsafeEnabled { file_proto_zipkin_proto3_zipkin_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Span); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_proto_zipkin_proto3_zipkin_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Endpoint); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_proto_zipkin_proto3_zipkin_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Annotation); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_proto_zipkin_proto3_zipkin_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ListOfSpans); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proto_zipkin_proto3_zipkin_proto_rawDesc, NumEnums: 1, NumMessages: 5, NumExtensions: 0, NumServices: 0, }, GoTypes: file_proto_zipkin_proto3_zipkin_proto_goTypes, DependencyIndexes: file_proto_zipkin_proto3_zipkin_proto_depIdxs, EnumInfos: file_proto_zipkin_proto3_zipkin_proto_enumTypes, MessageInfos: file_proto_zipkin_proto3_zipkin_proto_msgTypes, }.Build() File_proto_zipkin_proto3_zipkin_proto = out.File file_proto_zipkin_proto3_zipkin_proto_rawDesc = nil file_proto_zipkin_proto3_zipkin_proto_goTypes = nil file_proto_zipkin_proto3_zipkin_proto_depIdxs = nil } zipkin-go-0.4.3/proto/zipkin_proto3/zipkin.proto000066400000000000000000000221211461356266100220030ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. syntax = "proto3"; // This is the package for using protobuf with Zipkin API V2, but for historical // reasons uses the protoc syntax version instead. package zipkin.proto3; // In Java, the closest model type to this proto is in the "zipkin2" package option java_package = "zipkin2.proto3"; option java_multiple_files = true; option go_package = "github.com/openzipkin/zipkin-go/proto/zipkin_proto3"; // A span is a single-host view of an operation. A trace is a series of spans // (often RPC calls) which nest to form a latency tree. Spans are in the same // trace when they share the same trace ID. The parent_id field establishes the // position of one span in the tree. // // The root span is where parent_id is Absent and usually has the longest // duration in the trace. However, nested asynchronous work can materialize as // child spans whose duration exceed the root span. // // Spans usually represent remote activity such as RPC calls, or messaging // producers and consumers. However, they can also represent in-process // activity in any position of the trace. For example, a root span could // represent a server receiving an initial client request. A root span could // also represent a scheduled job that has no remote context. // // Encoding notes: // // Epoch timestamp are encoded fixed64 as varint would also be 8 bytes, and more // expensive to encode and size. Duration is stored uint64, as often the numbers // are quite small. // // Default values are ok, as only natural numbers are used. For example, zero is // an invalid timestamp and an invalid duration, false values for debug or shared // are ignorable, and zero-length strings also coerce to null. // // The next id is 14. // // Note fields up to 15 take 1 byte to encode. Take care when adding new fields // https://developers.google.com/protocol-buffers/docs/proto3#assigning-tags message Span { // Randomly generated, unique identifier for a trace, set on all spans within // it. // // This field is required and encoded as 8 or 16 opaque bytes. bytes trace_id = 1; // The parent span ID or absent if this the root span in a trace. bytes parent_id = 2; // Unique identifier for this operation within the trace. // // This field is required and encoded as 8 opaque bytes. bytes id = 3; // When present, kind clarifies timestamp, duration and remote_endpoint. When // absent, the span is local or incomplete. Unlike client and server, there // is no direct critical path latency relationship between producer and // consumer spans. enum Kind { // Default value interpreted as absent. SPAN_KIND_UNSPECIFIED = 0; // The span represents the client side of an RPC operation, implying the // following: // // timestamp is the moment a request was sent to the server. // duration is the delay until a response or an error was received. // remote_endpoint is the server. CLIENT = 1; // The span represents the server side of an RPC operation, implying the // following: // // timestamp is the moment a client request was received. // duration is the delay until a response was sent or an error. // remote_endpoint is the client. SERVER = 2; // The span represents production of a message to a remote broker, implying // the following: // // timestamp is the moment a message was sent to a destination. // duration is the delay sending the message, such as batching. // remote_endpoint is the broker. PRODUCER = 3; // The span represents consumption of a message from a remote broker, not // time spent servicing it. For example, a message processor would be an // in-process child span of a consumer. Consumer spans imply the following: // // timestamp is the moment a message was received from an origin. // duration is the delay consuming the message, such as from backlog. // remote_endpoint is the broker. CONSUMER = 4; } // When present, used to interpret remote_endpoint Kind kind = 4; // The logical operation this span represents in lowercase (e.g. rpc method). // Leave absent if unknown. // // As these are lookup labels, take care to ensure names are low cardinality. // For example, do not embed variables into the name. string name = 5; // Epoch microseconds of the start of this span, possibly absent if // incomplete. // // For example, 1502787600000000 corresponds to 2017-08-15 09:00 UTC // // This value should be set directly by instrumentation, using the most // precise value possible. For example, gettimeofday or multiplying epoch // millis by 1000. // // There are three known edge-cases where this could be reported absent. // - A span was allocated but never started (ex not yet received a timestamp) // - The span's start event was lost // - Data about a completed span (ex tags) were sent after the fact fixed64 timestamp = 6; // Duration in microseconds of the critical path, if known. Durations of less // than one are rounded up. Duration of children can be longer than their // parents due to asynchronous operations. // // For example 150 milliseconds is 150000 microseconds. uint64 duration = 7; // The host that recorded this span, primarily for query by service name. // // Instrumentation should always record this. Usually, absent implies late // data. The IP address corresponding to this is usually the site local or // advertised service address. When present, the port indicates the listen // port. Endpoint local_endpoint = 8; // When an RPC (or messaging) span, indicates the other side of the // connection. // // By recording the remote endpoint, your trace will contain network context // even if the peer is not tracing. For example, you can record the IP from // the "X-Forwarded-For" header or the service name and socket of a remote // peer. Endpoint remote_endpoint = 9; // Associates events that explain latency with the time they happened. repeated Annotation annotations = 10; // Tags give your span context for search, viewing and analysis. // // For example, a key "your_app.version" would let you lookup traces by // version. A tag "sql.query" isn't searchable, but it can help in debugging // when viewing a trace. map tags = 11; // True is a request to store this span even if it overrides sampling policy. // // This is true when the "X-B3-Flags" header has a value of 1. bool debug = 12; // True if we are contributing to a span started by another tracer (ex on a // different host). bool shared = 13; } // The network context of a node in the service graph. // // The next id is 5. message Endpoint { // Lower-case label of this node in the service graph, such as "favstar". // Leave absent if unknown. // // This is a primary label for trace lookup and aggregation, so it should be // intuitive and consistent. Many use a name from service discovery. string service_name = 1; // 4 byte representation of the primary IPv4 address associated with this // connection. Absent if unknown. bytes ipv4 = 2; // 16 byte representation of the primary IPv6 address associated with this // connection. Absent if unknown. // // Prefer using the ipv4 field for mapped addresses. bytes ipv6 = 3; // Depending on context, this could be a listen port or the client-side of a // socket. Absent if unknown. int32 port = 4; } // Associates an event that explains latency with a timestamp. // Unlike log statements, annotations are often codes. Ex. "ws" for WireSend // // The next id is 3. message Annotation { // Epoch microseconds of this event. // // For example, 1502787600000000 corresponds to 2017-08-15 09:00 UTC // // This value should be set directly by instrumentation, using the most // precise value possible. For example, gettimeofday or multiplying epoch // millis by 1000. fixed64 timestamp = 1; // Usually a short tag indicating an event, like "error" // // While possible to add larger data, such as garbage collection details, low // cardinality event names both keep the size of spans down and also are easy // to search against. string value = 2; } // A list of spans with possibly different trace ids, in no particular order. // // This is used for all transports: POST, Kafka messages etc. No other fields // are expected, This message facilitates the mechanics of encoding a list, as // a field number is required. The name of this type is the same in the OpenApi // aka Swagger specification. https://zipkin.io/zipkin-api/#/default/post_spans message ListOfSpans { repeated Span spans = 1; } zipkin-go-0.4.3/reporter/000077500000000000000000000000001461356266100153015ustar00rootroot00000000000000zipkin-go-0.4.3/reporter/amqp/000077500000000000000000000000001461356266100162375ustar00rootroot00000000000000zipkin-go-0.4.3/reporter/amqp/amqp.go000066400000000000000000000107601461356266100175300ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /* Package amqp implements a RabbitMq reporter to send spans to a Rabbit server/cluster. */ package amqp import ( "encoding/json" "fmt" "log" "os" amqp "github.com/rabbitmq/amqp091-go" "github.com/openzipkin/zipkin-go/model" "github.com/openzipkin/zipkin-go/reporter" ) // defaultRmqRoutingKey/Exchange/Kind sets the standard RabbitMQ queue our Reporter will publish on. const ( defaultRmqRoutingKey = "zipkin" defaultRmqExchange = "zipkin" defaultExchangeKind = "direct" ) // rmqReporter implements Reporter by publishing spans to a RabbitMQ exchange type rmqReporter struct { e chan error channel *amqp.Channel conn *amqp.Connection exchange string queue string logger *log.Logger } // ReporterOption sets a parameter for the rmqReporter type ReporterOption func(c *rmqReporter) // Logger sets the logger used to report errors in the collection // process. func Logger(logger *log.Logger) ReporterOption { return func(c *rmqReporter) { c.logger = logger } } // Exchange sets the Exchange used to send messages ( // see https://github.com/openzipkin/zipkin/tree/master/zipkin-collector/rabbitmq // if want to change default routing key or exchange func Exchange(exchange string) ReporterOption { return func(c *rmqReporter) { c.exchange = exchange } } // Queue sets the Queue used to send messages func Queue(queue string) ReporterOption { return func(c *rmqReporter) { c.queue = queue } } // Channel sets the Channel used to send messages func Channel(ch *amqp.Channel) ReporterOption { return func(c *rmqReporter) { c.channel = ch } } // Connection sets the Connection used to send messages func Connection(conn *amqp.Connection) ReporterOption { return func(c *rmqReporter) { c.conn = conn } } // NewReporter returns a new RabbitMq-backed Reporter. address should be as described here: https://www.rabbitmq.com/uri-spec.html func NewReporter(address string, options ...ReporterOption) (reporter.Reporter, error) { r := &rmqReporter{ logger: log.New(os.Stderr, "", log.LstdFlags), queue: defaultRmqRoutingKey, exchange: defaultRmqExchange, e: make(chan error), } for _, option := range options { option(r) } checks := []func() error{ r.queueVerify, r.exchangeVerify, r.queueBindVerify, } var err error if r.conn == nil { r.conn, err = amqp.Dial(address) if err != nil { return nil, err } } if r.channel == nil { r.channel, err = r.conn.Channel() if err != nil { return nil, err } } for i := 0; i < len(checks); i++ { if err := checks[i](); err != nil { return nil, err } } go r.logErrors() return r, nil } func (r *rmqReporter) logErrors() { for err := range r.e { r.logger.Print("msg", err.Error()) } } func (r *rmqReporter) Send(s model.SpanModel) { // Zipkin expects the message to be wrapped in an array ss := []model.SpanModel{s} m, err := json.Marshal(ss) if err != nil { r.e <- fmt.Errorf("failed when marshalling the span: %s", err.Error()) return } msg := amqp.Publishing{ Body: m, } err = r.channel.Publish(defaultRmqExchange, defaultRmqRoutingKey, false, false, msg) if err != nil { r.e <- fmt.Errorf("failed when publishing the span: %s", err.Error()) } } func (r *rmqReporter) queueBindVerify() error { return r.channel.QueueBind( defaultRmqRoutingKey, defaultRmqRoutingKey, defaultRmqExchange, false, nil) } func (r *rmqReporter) exchangeVerify() error { err := r.channel.ExchangeDeclare( defaultRmqExchange, defaultExchangeKind, true, false, false, false, nil, ) if err != nil { return err } return nil } func (r *rmqReporter) queueVerify() error { _, err := r.channel.QueueDeclare( defaultRmqExchange, true, false, false, false, nil, ) if err != nil { return err } return nil } func (r *rmqReporter) Close() error { err := r.channel.Close() if err != nil { return err } err = r.conn.Close() if err != nil { return err } return nil } zipkin-go-0.4.3/reporter/amqp/amqp_test.go000066400000000000000000000073371461356266100205750ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //go:build !windows // +build !windows package amqp_test import ( "encoding/json" "os" "testing" "time" "github.com/openzipkin/zipkin-go/model" zipkinamqp "github.com/openzipkin/zipkin-go/reporter/amqp" amqp "github.com/rabbitmq/amqp091-go" ) var spans = []*model.SpanModel{ makeNewSpan("avg", 123, 456, 0, true), makeNewSpan("sum", 123, 789, 456, true), makeNewSpan("div", 123, 101112, 456, true), } func TestRabbitProduce(t *testing.T) { address := os.Getenv("AMQP_ADDR") if address == "" { t.Skip("AMQP_ADDR not set, skipping test...") } _, ch, closeFunc := setupRabbit(t, address) defer closeFunc() c, err := zipkinamqp.NewReporter(address, zipkinamqp.Channel(ch)) if err != nil { t.Fatal(err) } msgs := setupConsume(t, ch) for _, s := range spans { c.Send(*s) } for _, s := range spans { msg := <-msgs ds := deserializeSpan(t, msg.Body) testEqual(t, s, ds) } } func TestRabbitClose(t *testing.T) { address := os.Getenv("AMQP_ADDR") if address == "" { t.Skip("AMQP_ADDR not set, skipping test...") } conn, ch, closeFunc := setupRabbit(t, address) defer closeFunc() r, err := zipkinamqp.NewReporter(address, zipkinamqp.Channel(ch), zipkinamqp.Connection(conn)) if err != nil { t.Fatal(err) } if err = r.Close(); err != nil { t.Fatal(err) } } func setupRabbit(t *testing.T, address string) (*amqp.Connection, *amqp.Channel, func()) { var err error conn, err := amqp.Dial(address) failOnError(t, err, "Failed to connect to RabbitMQ") ch, err := conn.Channel() failOnError(t, err, "Failed to open a channel") return conn, ch, func() { conn.Close(); ch.Close() } } func setupConsume(t *testing.T, ch *amqp.Channel) <-chan amqp.Delivery { csm, err := ch.Consume( "zipkin", "", true, false, false, false, nil, ) failOnError(t, err, "Failed to register a consumer") return csm } func deserializeSpan(t *testing.T, data []byte) *model.SpanModel { var receivedSpans []model.SpanModel err := json.Unmarshal(data, &receivedSpans) if err != nil { t.Fatal(err) } return &receivedSpans[0] } func failOnError(t *testing.T, err error, msg string) { if err != nil { t.Fatalf("%s: %s", msg, err) } } func testEqual(t *testing.T, want *model.SpanModel, have *model.SpanModel) { if have.TraceID != want.TraceID { t.Errorf("incorrect trace_id. have %d, want %d", have.TraceID, want.TraceID) } if have.ID != want.ID { t.Errorf("incorrect id. have %d, want %d", have.ID, want.ID) } if have.ParentID == nil { if want.ParentID != nil { t.Errorf("incorrect parent_id. have %d, want %d", have.ParentID, want.ParentID) } } else if *have.ParentID != *want.ParentID { t.Errorf("incorrect parent_id. have %d, want %d", have.ParentID, want.ParentID) } } func makeNewSpan(methodName string, traceID, spanID, parentSpanID uint64, debug bool) *model.SpanModel { timestamp := time.Now() parentID := new(model.ID) if parentSpanID != 0 { *parentID = model.ID(parentSpanID) } return &model.SpanModel{ SpanContext: model.SpanContext{ TraceID: model.TraceID{Low: traceID}, ID: model.ID(spanID), ParentID: parentID, Debug: debug, }, Name: methodName, Timestamp: timestamp, } } zipkin-go-0.4.3/reporter/http/000077500000000000000000000000001461356266100162605ustar00rootroot00000000000000zipkin-go-0.4.3/reporter/http/http.go000066400000000000000000000167371461356266100176040ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /* Package http implements a HTTP reporter to send spans to Zipkin V2 collectors. */ package http import ( "bytes" "context" "log" "net/http" "os" "sync" "time" "github.com/openzipkin/zipkin-go/model" "github.com/openzipkin/zipkin-go/reporter" ) // defaults const ( defaultTimeout = 5 * time.Second // timeout for http request in seconds defaultBatchInterval = 1 * time.Second // BatchInterval in seconds defaultBatchSize = 100 defaultMaxBacklog = 1000 ) // HTTPDoer will do a request to the Zipkin HTTP Collector type HTTPDoer interface { // nolint: revive // keep as is, we don't want to break dependendants Do(req *http.Request) (*http.Response, error) } // httpReporter will send spans to a Zipkin HTTP Collector using Zipkin V2 API. type httpReporter struct { url string client HTTPDoer logger *log.Logger batchInterval time.Duration batchSize int maxBacklog int batchMtx *sync.Mutex batch []*model.SpanModel spanC chan *model.SpanModel sendC chan struct{} quit chan struct{} shutdown chan error reqCallback RequestCallbackFn reqTimeout time.Duration serializer reporter.SpanSerializer } // Send implements reporter func (r *httpReporter) Send(s model.SpanModel) { r.spanC <- &s } // Close implements reporter func (r *httpReporter) Close() error { close(r.quit) return <-r.shutdown } func (r *httpReporter) loop() { var ( nextSend = time.Now().Add(r.batchInterval) ticker = time.NewTicker(r.batchInterval / 10) tickerChan = ticker.C ) defer ticker.Stop() for { select { case span := <-r.spanC: currentBatchSize := r.append(span) if currentBatchSize >= r.batchSize { nextSend = time.Now().Add(r.batchInterval) r.enqueueSend() } case <-tickerChan: if time.Now().After(nextSend) { nextSend = time.Now().Add(r.batchInterval) r.enqueueSend() } case <-r.quit: close(r.sendC) return } } } func (r *httpReporter) sendLoop() { for range r.sendC { _ = r.sendBatch() } r.shutdown <- r.sendBatch() } func (r *httpReporter) enqueueSend() { select { case r.sendC <- struct{}{}: default: // Do nothing if there's a pending send request already } } func (r *httpReporter) append(span *model.SpanModel) (newBatchSize int) { r.batchMtx.Lock() r.batch = append(r.batch, span) if len(r.batch) > r.maxBacklog { dispose := len(r.batch) - r.maxBacklog r.logger.Printf("backlog too long, disposing %d spans", dispose) r.batch = r.batch[dispose:] } newBatchSize = len(r.batch) r.batchMtx.Unlock() return } func (r *httpReporter) sendBatch() error { // Select all current spans in the batch to be sent r.batchMtx.Lock() sendBatch := r.batch[:] r.batchMtx.Unlock() if len(sendBatch) == 0 { return nil } body, err := r.serializer.Serialize(sendBatch) if err != nil { r.logger.Printf("failed when marshalling the spans batch: %s\n", err.Error()) return err } req, err := http.NewRequest("POST", r.url, bytes.NewReader(body)) if err != nil { r.logger.Printf("failed when creating the request: %s\n", err.Error()) return err } // By default we send b3:0 header to mitigate trace reporting amplification in // service mesh environments where the sidecar proxies might trace the call // we do here towards the Zipkin collector. req.Header.Set("b3", "0") req.Header.Set("Content-Type", r.serializer.ContentType()) if r.reqCallback != nil { r.reqCallback(req) } ctx, cancel := context.WithTimeout(req.Context(), r.reqTimeout) defer cancel() resp, err := r.client.Do(req.WithContext(ctx)) if err != nil { r.logger.Printf("failed to send the request: %s\n", err.Error()) return err } _ = resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode > 299 { r.logger.Printf("failed the request with status code %d\n", resp.StatusCode) } // Remove sent spans from the batch even if they were not saved r.batchMtx.Lock() r.batch = r.batch[len(sendBatch):] r.batchMtx.Unlock() return nil } // RequestCallbackFn receives the initialized request from the Collector before // sending it over the wire. This allows one to plug in additional headers or // do other customization. type RequestCallbackFn func(*http.Request) // ReporterOption sets a parameter for the HTTP Reporter type ReporterOption func(r *httpReporter) // Timeout sets maximum timeout for the http request through its context. func Timeout(duration time.Duration) ReporterOption { return func(r *httpReporter) { r.reqTimeout = duration } } // BatchSize sets the maximum batch size, after which a collect will be // triggered. The default batch size is 100 traces. func BatchSize(n int) ReporterOption { return func(r *httpReporter) { r.batchSize = n } } // MaxBacklog sets the maximum backlog size. When batch size reaches this // threshold, spans from the beginning of the batch will be disposed. func MaxBacklog(n int) ReporterOption { return func(r *httpReporter) { r.maxBacklog = n } } // BatchInterval sets the maximum duration we will buffer traces before // emitting them to the collector. The default batch interval is 1 second. func BatchInterval(d time.Duration) ReporterOption { return func(r *httpReporter) { r.batchInterval = d } } // Client sets a custom http client to use under the interface HTTPDoer // which includes a `Do` method with same signature as the *http.Client func Client(client HTTPDoer) ReporterOption { return func(r *httpReporter) { r.client = client } } // RequestCallback registers a callback function to adjust the reporter // *http.Request before it sends the request to Zipkin. func RequestCallback(rc RequestCallbackFn) ReporterOption { return func(r *httpReporter) { r.reqCallback = rc } } // Logger sets the logger used to report errors in the collection // process. func Logger(l *log.Logger) ReporterOption { return func(r *httpReporter) { r.logger = l } } // Serializer sets the serialization function to use for sending span data to // Zipkin. func Serializer(serializer reporter.SpanSerializer) ReporterOption { return func(r *httpReporter) { if serializer != nil { r.serializer = serializer } } } // NewReporter returns a new HTTP Reporter. // url should be the endpoint to send the spans to, e.g. // http://localhost:9411/api/v2/spans func NewReporter(url string, opts ...ReporterOption) reporter.Reporter { r := httpReporter{ url: url, logger: log.New(os.Stderr, "", log.LstdFlags), client: &http.Client{}, batchInterval: defaultBatchInterval, batchSize: defaultBatchSize, maxBacklog: defaultMaxBacklog, batch: []*model.SpanModel{}, spanC: make(chan *model.SpanModel), sendC: make(chan struct{}, 1), quit: make(chan struct{}, 1), shutdown: make(chan error, 1), batchMtx: &sync.Mutex{}, serializer: reporter.JSONSerializer{}, reqTimeout: defaultTimeout, } for _, opt := range opts { opt(&r) } go r.loop() go r.sendLoop() return &r } zipkin-go-0.4.3/reporter/http/http_test.go000066400000000000000000000140311461356266100206240ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package http_test import ( "bytes" "encoding/json" "io/ioutil" "net/http" "net/http/httptest" "reflect" "sync/atomic" "testing" "time" "github.com/openzipkin/zipkin-go/idgenerator" "github.com/openzipkin/zipkin-go/model" "github.com/openzipkin/zipkin-go/reporter" zipkinhttp "github.com/openzipkin/zipkin-go/reporter/http" ) func generateSpans(n int) []*model.SpanModel { spans := make([]*model.SpanModel, n) idGen := idgenerator.NewRandom64() traceID := idGen.TraceID() for i := 0; i < n; i++ { spans[i] = &model.SpanModel{ SpanContext: model.SpanContext{ TraceID: traceID, ID: idGen.SpanID(traceID), }, Name: "name", Kind: model.Client, Timestamp: time.Now(), } } return spans } func newTestServer(t *testing.T, spans []*model.SpanModel, serializer reporter.SpanSerializer, onReceive func(int)) *httptest.Server { sofar := 0 return httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { if r.Method != "POST" { t.Errorf("expected 'POST' request, got '%s'", r.Method) } aPayload, err := ioutil.ReadAll(r.Body) if err != nil { t.Errorf("unexpected error: %v", err) } var aSpans []*model.SpanModel err = json.Unmarshal(aPayload, &aSpans) if err != nil { t.Errorf("failed to parse json payload: %v", err) } eSpans := spans[sofar : sofar+len(aSpans)] sofar += len(aSpans) onReceive(len(aSpans)) ePayload, err := serializer.Serialize(eSpans) if err != nil { t.Errorf("unexpected error: %v", err) } if !bytes.Equal(aPayload, ePayload) { t.Errorf("unexpected span payload\nhave %s\nwant %s", string(aPayload), string(ePayload)) } })) } func TestSpanIsBeingReported(t *testing.T) { serializer := reporter.JSONSerializer{} var numSpans int64 eNumSpans := 2 spans := generateSpans(eNumSpans) ts := newTestServer(t, spans, serializer, func(num int) { atomic.AddInt64(&numSpans, int64(num)) }) defer ts.Close() rep := zipkinhttp.NewReporter(ts.URL, zipkinhttp.Serializer(serializer)) for _, span := range spans { rep.Send(*span) } rep.Close() aNumSpans := int(atomic.LoadInt64(&numSpans)) if aNumSpans != eNumSpans { t.Errorf("unexpected number of spans received\nhave: %d, want: %d", aNumSpans, eNumSpans) } } func TestSpanIsReportedOnTime(t *testing.T) { serializer := reporter.JSONSerializer{} batchInterval := 200 * time.Millisecond var numSpans int64 eNumSpans := 2 spans := generateSpans(eNumSpans) ts := newTestServer(t, spans, serializer, func(num int) { atomic.AddInt64(&numSpans, int64(num)) }) defer ts.Close() rep := zipkinhttp.NewReporter(ts.URL, zipkinhttp.Serializer(serializer), zipkinhttp.BatchInterval(batchInterval)) for _, span := range spans { rep.Send(*span) } time.Sleep(3 * batchInterval / 2) aNumSpans := int(atomic.LoadInt64(&numSpans)) if aNumSpans != eNumSpans { t.Errorf("unexpected number of spans received\nhave: %d, want: %d", aNumSpans, eNumSpans) } rep.Close() } func TestSpanIsReportedAfterBatchSize(t *testing.T) { serializer := reporter.JSONSerializer{} batchSize := 2 var numSpans int64 eNumSpans := 6 spans := generateSpans(eNumSpans) ts := newTestServer(t, spans, serializer, func(num int) { atomic.AddInt64(&numSpans, int64(num)) }) defer ts.Close() rep := zipkinhttp.NewReporter(ts.URL, zipkinhttp.Serializer(serializer), zipkinhttp.BatchSize(batchSize)) for _, span := range spans[:batchSize] { rep.Send(*span) } time.Sleep(100 * time.Millisecond) aNumSpans := int(atomic.LoadInt64(&numSpans)) if aNumSpans != batchSize { t.Errorf("unexpected number of spans received\nhave: %d, want: %d", aNumSpans, batchSize) } for _, span := range spans[batchSize:] { rep.Send(*span) } rep.Close() aNumSpans = int(atomic.LoadInt64(&numSpans)) if aNumSpans != eNumSpans { t.Errorf("unexpected number of spans received\nhave: %d, want: %d", aNumSpans, eNumSpans) } } func TestSpanCustomHeaders(t *testing.T) { serializer := reporter.JSONSerializer{} hc := headerClient{ headers: http.Header{ "Key1": []string{"val1a", "val1b"}, "Key2": []string{"val2"}, }, } var haveHeaders http.Header ts := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { haveHeaders = r.Header })) defer ts.Close() spans := generateSpans(1) rep := zipkinhttp.NewReporter( ts.URL, zipkinhttp.Serializer(serializer), zipkinhttp.Client(hc), ) for _, span := range spans { rep.Send(*span) } rep.Close() for _, key := range []string{"Key1", "Key2"} { if want, have := hc.headers[key], haveHeaders[key]; !reflect.DeepEqual(want, have) { t.Errorf("header %s: want: %v, have: %v\n", key, want, have) } } } func TestB3SamplingHeader(t *testing.T) { serializer := reporter.JSONSerializer{} var haveHeaders map[string][]string ts := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { haveHeaders = r.Header })) defer ts.Close() spans := generateSpans(1) rep := zipkinhttp.NewReporter( ts.URL, zipkinhttp.Serializer(serializer), ) for _, span := range spans { rep.Send(*span) } rep.Close() if want, have := []string{"0"}, haveHeaders["B3"]; !reflect.DeepEqual(want, have) { t.Errorf("B3 header: want: %v, have %v", want, have) } } type headerClient struct { client http.Client headers map[string][]string } func (h headerClient) Do(req *http.Request) (*http.Response, error) { for key, item := range h.headers { for _, val := range item { req.Header.Add(key, val) } } return h.client.Do(req) } zipkin-go-0.4.3/reporter/kafka/000077500000000000000000000000001461356266100163565ustar00rootroot00000000000000zipkin-go-0.4.3/reporter/kafka/kafka.go000066400000000000000000000067011461356266100177660ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /* Package kafka implements a Kafka reporter to send spans to a Kafka server/cluster. */ package kafka import ( "log" "os" "github.com/IBM/sarama" "github.com/openzipkin/zipkin-go/model" "github.com/openzipkin/zipkin-go/reporter" ) // defaultKafkaTopic sets the standard Kafka topic our Reporter will publish // on. The default topic for zipkin-receiver-kafka is "zipkin", see: // https://github.com/openzipkin/zipkin/tree/master/zipkin-receiver-kafka const defaultKafkaTopic = "zipkin" // kafkaReporter implements Reporter by publishing spans to a Kafka // broker. type kafkaReporter struct { producer sarama.AsyncProducer logger *log.Logger topic string serializer reporter.SpanSerializer } // ReporterOption sets a parameter for the kafkaReporter type ReporterOption func(c *kafkaReporter) // Logger sets the logger used to report errors in the collection // process. func Logger(logger *log.Logger) ReporterOption { return func(c *kafkaReporter) { c.logger = logger } } // Producer sets the producer used to produce to Kafka. For tweaking // the reporting settings (e.g. reporting timeout or authentication) // check the sarama.Config struct. func Producer(p sarama.AsyncProducer) ReporterOption { return func(c *kafkaReporter) { c.producer = p } } // Topic sets the kafka topic to attach the reporter producer on. func Topic(t string) ReporterOption { return func(c *kafkaReporter) { c.topic = t } } // Serializer sets the serialization function to use for sending span data to // Zipkin. func Serializer(serializer reporter.SpanSerializer) ReporterOption { return func(c *kafkaReporter) { if serializer != nil { c.serializer = serializer } } } // NewReporter returns a new Kafka-backed Reporter. address should be a slice of // TCP endpoints of the form "host:port". func NewReporter(address []string, options ...ReporterOption) (reporter.Reporter, error) { r := &kafkaReporter{ logger: log.New(os.Stderr, "", log.LstdFlags), topic: defaultKafkaTopic, serializer: reporter.JSONSerializer{}, } for _, option := range options { option(r) } if r.producer == nil { p, err := sarama.NewAsyncProducer(address, nil) if err != nil { return nil, err } r.producer = p } go r.logErrors() return r, nil } func (r *kafkaReporter) logErrors() { for pe := range r.producer.Errors() { r.logger.Print("msg", pe.Msg, "err", pe.Err, "result", "failed to produce msg") } } func (r *kafkaReporter) Send(s model.SpanModel) { // Zipkin expects the message to be wrapped in an array ss := []*model.SpanModel{&s} m, err := r.serializer.Serialize(ss) if err != nil { r.logger.Printf("failed when marshalling the span: %s\n", err.Error()) return } r.producer.Input() <- &sarama.ProducerMessage{ Topic: r.topic, Key: nil, Value: sarama.ByteEncoder(m), } } func (r *kafkaReporter) Close() error { return r.producer.Close() } zipkin-go-0.4.3/reporter/kafka/kafka_test.go000066400000000000000000000156201461356266100210250ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package kafka_test import ( "encoding/json" "errors" "log" "testing" "time" "github.com/IBM/sarama" "github.com/openzipkin/zipkin-go/model" zp3 "github.com/openzipkin/zipkin-go/proto/zipkin_proto3" "github.com/openzipkin/zipkin-go/reporter" "github.com/openzipkin/zipkin-go/reporter/kafka" ) type stubProducer struct { in chan *sarama.ProducerMessage err chan *sarama.ProducerError kafkaDown bool closed bool } func (p *stubProducer) IsTransactional() bool { return false } func (p *stubProducer) TxnStatus() sarama.ProducerTxnStatusFlag { return sarama.ProducerTxnFlagEndTransaction } func (p *stubProducer) BeginTxn() error { return nil } func (p *stubProducer) CommitTxn() error { return nil } func (p *stubProducer) AbortTxn() error { return nil } func (p *stubProducer) AddOffsetsToTxn(_ map[string][]*sarama.PartitionOffsetMetadata, _ string) error { return nil } func (p *stubProducer) AddMessageToTxn(_ *sarama.ConsumerMessage, _ string, _ *string) error { return nil } func (p *stubProducer) AsyncClose() {} func (p *stubProducer) Close() error { if p.kafkaDown { return errors.New("kafka is down") } p.closed = true return nil } func (p *stubProducer) Input() chan<- *sarama.ProducerMessage { return p.in } func (p *stubProducer) Successes() <-chan *sarama.ProducerMessage { return nil } func (p *stubProducer) Errors() <-chan *sarama.ProducerError { return p.err } func newStubProducer(kafkaDown bool) *stubProducer { return &stubProducer{ make(chan *sarama.ProducerMessage), make(chan *sarama.ProducerError), kafkaDown, false, } } var spans = []*model.SpanModel{ makeNewSpan("avg", 123, 456, 0, true), makeNewSpan("sum", 123, 789, 456, true), makeNewSpan("div", 123, 101112, 456, true), } func jsonDeserializer(body []byte) ([]*model.SpanModel, error) { spans := make([]*model.SpanModel, 0) err := json.Unmarshal(body, &spans) return spans, err } func protoDeserializer(body []byte) ([]*model.SpanModel, error) { spans, err := zp3.ParseSpans(body, true) return spans, err } func TestKafkaProduce(t *testing.T) { p := newStubProducer(false) c, err := kafka.NewReporter( []string{"192.0.2.10:9092"}, kafka.Producer(p), ) if err != nil { t.Fatal(err) } for _, want := range spans { m := sendSpan(t, c, p, *want) testMetadata(t, m) have := deserializeSpan(t, m.Value, jsonDeserializer) testEqual(t, want, have) } } func TestKafkaProduceProto(t *testing.T) { p := newStubProducer(false) c, err := kafka.NewReporter( []string{"192.0.2.10:9092"}, kafka.Producer(p), kafka.Serializer(zp3.SpanSerializer{}), ) if err != nil { t.Fatal(err) } for _, want := range spans { m := sendSpan(t, c, p, *want) testMetadata(t, m) have := deserializeSpan(t, m.Value, protoDeserializer) testEqual(t, want, have) } } func TestKafkaClose(t *testing.T) { p := newStubProducer(false) r, err := kafka.NewReporter( []string{"192.0.2.10:9092"}, kafka.Producer(p), ) if err != nil { t.Fatal(err) } if err = r.Close(); err != nil { t.Fatal(err) } if !p.closed { t.Fatal("producer not closed") } } func TestKafkaCloseError(t *testing.T) { p := newStubProducer(true) c, err := kafka.NewReporter( []string{"192.0.2.10:9092"}, kafka.Producer(p), ) if err != nil { t.Fatal(err) } if err = c.Close(); err == nil { t.Error("no error on close") } } type chanWriter struct { errs chan []interface{} } func (cw *chanWriter) Write(p []byte) (n int, err error) { cw.errs <- []interface{}{p} return 1, nil } func TestKafkaErrors(t *testing.T) { p := newStubProducer(true) errs := make(chan []interface{}, len(spans)) c, err := kafka.NewReporter( []string{"192.0.2.10:9092"}, kafka.Producer(p), kafka.Logger(log.New(&chanWriter{errs}, "", log.LstdFlags)), ) if err != nil { t.Fatal(err) } var have []model.SpanModel for _, want := range spans { message := sendSpan(t, c, p, *want) messageBody, err := message.Value.Encode() if err != nil { t.Errorf("unexpected error: %s", err.Error()) } _ = json.Unmarshal(messageBody, &have) testEqual(t, want, &have[0]) } for i := 0; i < len(spans); i++ { select { case <-errs: case <-time.After(100 * time.Millisecond): t.Fatalf("errors not logged. have %d, wanted %d", i, len(spans)) } } } func sendSpan(t *testing.T, r reporter.Reporter, p *stubProducer, s model.SpanModel) *sarama.ProducerMessage { var m *sarama.ProducerMessage received := make(chan bool, 1) go func() { select { case m = <-p.in: received <- true if p.kafkaDown { p.err <- &sarama.ProducerError{ Msg: m, Err: errors.New("kafka is down"), } } case <-time.After(100 * time.Millisecond): received <- false } }() r.Send(s) if !<-received { t.Fatal("expected message to be received") } return m } func testMetadata(t *testing.T, m *sarama.ProducerMessage) { if m.Topic != "zipkin" { t.Errorf("unexpected topic. have %q, want %q", m.Topic, "zipkin") } if m.Key != nil { t.Errorf("unexpected key. have %q, want nil", m.Key) } } func deserializeSpan(t *testing.T, e sarama.Encoder, deserializer func([]byte) ([]*model.SpanModel, error)) *model.SpanModel { bytes, err := e.Encode() if err != nil { t.Errorf("unexpected error in encoding: %v", err) } s, err := deserializer(bytes) if err != nil { t.Errorf("unexpected error in decoding: %v", err) return nil } return s[0] } func testEqual(t *testing.T, want *model.SpanModel, have *model.SpanModel) { if have.TraceID != want.TraceID { t.Errorf("incorrect trace_id. have %d, want %d", have.TraceID, want.TraceID) } if have.ID != want.ID { t.Errorf("incorrect id. have %d, want %d", have.ID, want.ID) } if have.ParentID == nil { if want.ParentID != nil { t.Errorf("incorrect parent_id. have %d, want %d", have.ParentID, want.ParentID) } } else if *have.ParentID != *want.ParentID { t.Errorf("incorrect parent_id. have %d, want %d", have.ParentID, want.ParentID) } } func makeNewSpan(methodName string, traceID, spanID, parentSpanID uint64, debug bool) *model.SpanModel { timestamp := time.Now() parentID := new(model.ID) if parentSpanID != 0 { *parentID = model.ID(parentSpanID) } return &model.SpanModel{ SpanContext: model.SpanContext{ TraceID: model.TraceID{Low: traceID}, ID: model.ID(spanID), ParentID: parentID, Debug: debug, }, Name: methodName, Timestamp: timestamp, } } zipkin-go-0.4.3/reporter/log/000077500000000000000000000000001461356266100160625ustar00rootroot00000000000000zipkin-go-0.4.3/reporter/log/log.go000066400000000000000000000027051461356266100171760ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /* Package log implements a reporter to send spans in V2 JSON format to the Go standard Logger. */ package log import ( "encoding/json" "log" "os" "time" "github.com/openzipkin/zipkin-go/model" "github.com/openzipkin/zipkin-go/reporter" ) // logReporter will send spans to the default Go Logger. type logReporter struct { logger *log.Logger } // NewReporter returns a new log reporter. func NewReporter(l *log.Logger) reporter.Reporter { if l == nil { // use standard type of log setup l = log.New(os.Stderr, "", log.LstdFlags) } return &logReporter{ logger: l, } } // Send outputs a span to the Go logger. func (r *logReporter) Send(s model.SpanModel) { if b, err := json.MarshalIndent(s, "", " "); err == nil { r.logger.Printf("%s:\n%s\n\n", time.Now(), string(b)) } } // Close closes the reporter func (*logReporter) Close() error { return nil } zipkin-go-0.4.3/reporter/recorder/000077500000000000000000000000001461356266100171065ustar00rootroot00000000000000zipkin-go-0.4.3/reporter/recorder/recorder.go000066400000000000000000000027231461356266100212460ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /* Package recorder implements a reporter to record spans in v2 format. */ package recorder import ( "sync" "github.com/openzipkin/zipkin-go/model" ) // ReporterRecorder records Zipkin spans. type ReporterRecorder struct { mtx sync.Mutex spans []model.SpanModel } // NewReporter returns a new recording reporter. func NewReporter() *ReporterRecorder { return &ReporterRecorder{} } // Send adds the provided span to the span list held by the recorder. func (r *ReporterRecorder) Send(span model.SpanModel) { r.mtx.Lock() r.spans = append(r.spans, span) r.mtx.Unlock() } // Flush returns all recorded spans and clears its internal span storage func (r *ReporterRecorder) Flush() []model.SpanModel { r.mtx.Lock() spans := r.spans r.spans = nil r.mtx.Unlock() return spans } // Close flushes the reporter func (r *ReporterRecorder) Close() error { r.Flush() return nil } zipkin-go-0.4.3/reporter/recorder/recorder_test.go000066400000000000000000000024141461356266100223020ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package recorder import ( "testing" "github.com/openzipkin/zipkin-go/model" ) func TestFlushInRecorderSuccess(t *testing.T) { rec := NewReporter() span := model.SpanModel{} rec.Send(span) if len(rec.spans) != 1 { t.Fatalf("Span Count want 1, have %d", len(rec.spans)) } rec.Flush() if len(rec.spans) != 0 { t.Fatalf("Span Count want 0, have %d", len(rec.spans)) } } func TestCloseInRecorderSuccess(t *testing.T) { rec := NewReporter() span := model.SpanModel{} rec.Send(span) if len(rec.spans) != 1 { t.Fatalf("Span Count want 1, have %d", len(rec.spans)) } rec.Close() if len(rec.spans) != 0 { t.Fatalf("Span Count want 0, have %d", len(rec.spans)) } } zipkin-go-0.4.3/reporter/reporter.go000066400000000000000000000025511461356266100174750ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /* Package reporter holds the Reporter interface which is used by the Zipkin Tracer to send finished spans. Subpackages of package reporter contain officially supported standard reporter implementations. */ package reporter import "github.com/openzipkin/zipkin-go/model" // Reporter interface can be used to provide the Zipkin Tracer with custom // implementations to publish Zipkin Span data. type Reporter interface { Send(model.SpanModel) // Send Span data to the reporter Close() error // Close the reporter } type noopReporter struct{} func (r *noopReporter) Send(model.SpanModel) {} func (r *noopReporter) Close() error { return nil } // NewNoopReporter returns a no-op Reporter implementation. func NewNoopReporter() Reporter { return &noopReporter{} } zipkin-go-0.4.3/reporter/serializer.go000066400000000000000000000025161461356266100200050ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package reporter import ( "encoding/json" "github.com/openzipkin/zipkin-go/model" ) // SpanSerializer describes the methods needed for allowing to set Span encoding // type for the various Zipkin transports. type SpanSerializer interface { Serialize([]*model.SpanModel) ([]byte, error) ContentType() string } // JSONSerializer implements the default JSON encoding SpanSerializer. type JSONSerializer struct{} // Serialize takes an array of Zipkin SpanModel objects and returns a JSON // encoding of it. func (JSONSerializer) Serialize(spans []*model.SpanModel) ([]byte, error) { return json.Marshal(spans) } // ContentType returns the ContentType needed for this encoding. func (JSONSerializer) ContentType() string { return "application/json" } zipkin-go-0.4.3/sample.go000066400000000000000000000071151461356266100152530ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package zipkin import ( "fmt" "math" "math/rand" "sync" "time" ) // Sampler functions return if a Zipkin span should be sampled, based on its // traceID. type Sampler func(id uint64) bool // NeverSample will always return false. If used by a service it will not allow // the service to start traces but will still allow the service to participate // in traces started upstream. func NeverSample(_ uint64) bool { return false } // AlwaysSample will always return true. If used by a service it will always start // traces if no upstream trace has been propagated. If an incoming upstream trace // is not sampled the service will adhere to this and only propagate the context. func AlwaysSample(_ uint64) bool { return true } // NewModuloSampler provides a generic type Sampler. func NewModuloSampler(mod uint64) Sampler { if mod < 2 { return AlwaysSample } return func(id uint64) bool { return (id % mod) == 0 } } // NewBoundarySampler is appropriate for high-traffic instrumentation who // provision random trace ids, and make the sampling decision only once. // It defends against nodes in the cluster selecting exactly the same ids. func NewBoundarySampler(rate float64, salt int64) (Sampler, error) { if rate == 0.0 { return NeverSample, nil } if rate == 1.0 { return AlwaysSample, nil } if rate < 0.0001 || rate > 1 { return nil, fmt.Errorf("rate should be 0.0 or between 0.0001 and 1: was %f", rate) } var ( boundary = int64(rate * 10000) usalt = uint64(salt) ) return func(id uint64) bool { return int64(math.Abs(float64(id^usalt)))%10000 < boundary }, nil } // NewCountingSampler is appropriate for low-traffic instrumentation or // those who do not provision random trace ids. It is not appropriate for // collectors as the sampling decision isn't idempotent (consistent based // on trace id). func NewCountingSampler(rate float64) (Sampler, error) { if rate == 0.0 { return NeverSample, nil } if rate == 1.0 { return AlwaysSample, nil } if rate < 0.01 || rate > 1 { return nil, fmt.Errorf("rate should be 0.0 or between 0.01 and 1: was %f", rate) } var ( i = 0 outOf100 = int(rate*100 + math.Copysign(0.5, rate*100)) // for rounding float to int conversion instead of truncation decisions = randomBitSet(100, outOf100, rand.New(rand.NewSource(time.Now().UnixNano()))) mtx = &sync.Mutex{} ) return func(_ uint64) bool { mtx.Lock() result := decisions[i] i++ if i == 100 { i = 0 } mtx.Unlock() return result }, nil } /** * Reservoir sampling algorithm borrowed from Stack Overflow. * * http://stackoverflow.com/questions/12817946/generate-a-random-bitset-with-n-1s */ func randomBitSet(size int, cardinality int, rnd *rand.Rand) []bool { result := make([]bool, size) chosen := make([]int, cardinality) var i int for i = 0; i < cardinality; i++ { chosen[i] = i result[i] = true } for ; i < size; i++ { j := rnd.Intn(i + 1) if j < cardinality { result[chosen[j]] = false result[i] = true chosen[j] = i } } return result } zipkin-go-0.4.3/sample_test.go000066400000000000000000000063711461356266100163150ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package zipkin_test import ( "fmt" "math/rand" "testing" "time" zipkin "github.com/openzipkin/zipkin-go" ) func TestBoundarySampler(t *testing.T) { type triple struct { id uint64 salt int64 rate float64 hasError bool } for input, sampled := range map[triple]bool{ {123, 456, 1.0, false}: true, {123, 456, 999, true}: true, {123, 456, 0.0, false}: false, {123, 456, -42, true}: false, {1229998, 0, 0.01, false}: false, {1229999, 0, 0.01, false}: false, {1230000, 0, 0.01, false}: true, {1230001, 0, 0.01, false}: true, {1230098, 0, 0.01, false}: true, {1230099, 0, 0.01, false}: true, {1230100, 0, 0.01, false}: false, {1230101, 0, 0.01, false}: false, {1, 9999999, 0.01, false}: false, {999, 0, 0.99, false}: true, {9999, 0, 0.99, false}: false, } { sampler, err := zipkin.NewBoundarySampler(input.rate, input.salt) if want, have := input.hasError, (err != nil); want != have { t.Fatalf("%#+v: want error %t, have error %t", input, want, have) } if input.hasError { want := fmt.Errorf("rate should be 0.0 or between 0.0001 and 1: was %f", input.rate) if have := err; have == nil || want.Error() != have.Error() { t.Fatalf("%#+v: want error %+v, have error %+v", input, want, have) } continue } if want, have := sampled, sampler(input.id); want != have { t.Errorf("%#+v: want %v, have %v", input, want, have) } } } func TestCountingSampler(t *testing.T) { { _, have := zipkin.NewCountingSampler(0.009) want := fmt.Errorf("rate should be 0.0 or between 0.01 and 1: was %f", 0.009) if have == nil || want.Error() != have.Error() { t.Errorf("rate 0.009, want error %+v, got %+v", want, have) } } { _, have := zipkin.NewCountingSampler(1.001) want := fmt.Errorf("rate should be 0.0 or between 0.01 and 1: was %f", 1.001) if have == nil || want.Error() != have.Error() { t.Errorf("rate 1.001, want error %+v, got %+v", want, have) } } for n := 0; n <= 100; n++ { var ( rate = float64(n) / 100 sampler, _ = zipkin.NewCountingSampler(rate) found = 0 ) for i := 0; i < 1000; i++ { if sampler(1) { found++ } } if found != n*10 { t.Errorf("rate %f, want %d, have %d", rate, n, found) } } } func TestModuleSampler(t *testing.T) { rand.Seed(time.Now().Unix()) for mod := uint64(1); mod <= 100; mod++ { var ( sampler = zipkin.NewModuloSampler(mod) want = uint64(rand.Intn(1000)) max = mod * want found = uint64(0) ) for i := uint64(0); i < max; i++ { if sampler(i) { found++ } } if want, have := max/mod, found; want != have { t.Errorf("expected %d samples, got %d", want, have) } } } zipkin-go-0.4.3/span.go000066400000000000000000000040061461356266100147270ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package zipkin import ( "time" "github.com/openzipkin/zipkin-go/model" ) // Span interface as returned by Tracer.StartSpan() type Span interface { // Context returns the Span's SpanContext. Context() model.SpanContext // SetName updates the Span's name. SetName(string) // SetRemoteEndpoint updates the Span's Remote Endpoint. SetRemoteEndpoint(*model.Endpoint) // Annotate adds a timed event to the Span. Annotate(time.Time, string) // Tag sets Tag with given key and value to the Span. If key already exists in // the Span the value will be overridden except for error tags where the first // value is persisted. Tag(string, string) // Finish the Span and send to Reporter. If DelaySend option was used at // Span creation time, Finish will not send the Span to the Reporter. It then // becomes the user's responsibility to get the Span reported (by using // span.Flush). Finish() // Finish the Span with duration and send to Reporter. If DelaySend option was used at // Span creation time, FinishedWithDuration will not send the Span to the Reporter. It then // becomes the user's responsibility to get the Span reported (by using // span.Flush). FinishedWithDuration(duration time.Duration) // Flush the Span to the Reporter (regardless of being finished or not). // This can be used if the DelaySend SpanOption was set or when dealing with // one-way RPC tracing where duration might not be measured. Flush() } zipkin-go-0.4.3/span_implementation.go000066400000000000000000000043731461356266100200430ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package zipkin import ( "sync" "sync/atomic" "time" "github.com/openzipkin/zipkin-go/model" ) type spanImpl struct { mtx sync.RWMutex model.SpanModel tracer *Tracer mustCollect int32 // used as atomic bool (1 = true, 0 = false) flushOnFinish bool } func (s *spanImpl) Context() model.SpanContext { return s.SpanContext } func (s *spanImpl) SetName(name string) { s.mtx.Lock() s.Name = name s.mtx.Unlock() } func (s *spanImpl) SetRemoteEndpoint(e *model.Endpoint) { s.mtx.Lock() if e == nil { s.RemoteEndpoint = nil } else { s.RemoteEndpoint = &model.Endpoint{} *s.RemoteEndpoint = *e } s.mtx.Unlock() } func (s *spanImpl) Annotate(t time.Time, value string) { a := model.Annotation{ Timestamp: t, Value: value, } s.mtx.Lock() s.Annotations = append(s.Annotations, a) s.mtx.Unlock() } func (s *spanImpl) Tag(key, value string) { s.mtx.Lock() if key == string(TagError) { if _, found := s.Tags[key]; found { s.mtx.Unlock() return } } s.Tags[key] = value s.mtx.Unlock() } func (s *spanImpl) Finish() { if atomic.CompareAndSwapInt32(&s.mustCollect, 1, 0) { s.Duration = time.Since(s.Timestamp) if s.flushOnFinish { s.mtx.RLock() s.tracer.reporter.Send(s.SpanModel) s.mtx.RUnlock() } } } func (s *spanImpl) FinishedWithDuration(d time.Duration) { if atomic.CompareAndSwapInt32(&s.mustCollect, 1, 0) { s.Duration = d if s.flushOnFinish { s.mtx.RLock() s.tracer.reporter.Send(s.SpanModel) s.mtx.RUnlock() } } } func (s *spanImpl) Flush() { if s.SpanModel.Debug || (s.SpanModel.Sampled != nil && *s.SpanModel.Sampled) { s.mtx.RLock() s.tracer.reporter.Send(s.SpanModel) s.mtx.RUnlock() } } zipkin-go-0.4.3/span_options.go000066400000000000000000000052241461356266100165050ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package zipkin import ( "time" "github.com/openzipkin/zipkin-go/model" ) // SpanOption allows for functional options to adjust behavior and payload of // the Span to be created with tracer.StartSpan(). type SpanOption func(t *Tracer, s *spanImpl) // Kind sets the kind of the span being created. func Kind(kind model.Kind) SpanOption { return func(_ *Tracer, s *spanImpl) { s.Kind = kind } } // Parent will use provided SpanContext as parent to the span being created. func Parent(sc model.SpanContext) SpanOption { return func(t *Tracer, s *spanImpl) { if sc.Err != nil { // encountered an extraction error switch t.extractFailurePolicy { case ExtractFailurePolicyRestart: case ExtractFailurePolicyError: panic(s.SpanContext.Err) case ExtractFailurePolicyTagAndRestart: s.Tags["error.extract"] = sc.Err.Error() default: panic(ErrInvalidExtractFailurePolicy) } /* don't use provided SpanContext, but restart trace */ return } s.SpanContext = sc } } // StartTime uses a given start time for the span being created. func StartTime(start time.Time) SpanOption { return func(_ *Tracer, s *spanImpl) { s.Timestamp = start } } // RemoteEndpoint sets the remote endpoint of the span being created. func RemoteEndpoint(e *model.Endpoint) SpanOption { return func(_ *Tracer, s *spanImpl) { s.RemoteEndpoint = e } } // Tags sets initial tags for the span being created. If default tracer tags // are present they will be overwritten on key collisions. func Tags(tags map[string]string) SpanOption { return func(_ *Tracer, s *spanImpl) { for k, v := range tags { s.Tags[k] = v } } } // FlushOnFinish when set to false will disable span.Finish() to send the Span // to the Reporter automatically (which is the default behavior). If set to // false, having the Span be reported becomes the responsibility of the user. // This is available if late tag data is expected to be only available after the // required finish time of the Span. func FlushOnFinish(b bool) SpanOption { return func(_ *Tracer, s *spanImpl) { s.flushOnFinish = b } } zipkin-go-0.4.3/span_test.go000066400000000000000000000073011461356266100157670ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package zipkin import ( "reflect" "testing" "time" "github.com/openzipkin/zipkin-go/reporter" "github.com/openzipkin/zipkin-go/reporter/recorder" ) func TestSpanNameUpdate(t *testing.T) { var ( oldName = "oldName" newName = "newName" ) tracer, _ := NewTracer(reporter.NewNoopReporter()) span := tracer.StartSpan(oldName) if want, have := oldName, span.(*spanImpl).Name; want != have { t.Errorf("Name want %q, have %q", want, have) } span.SetName(newName) if want, have := newName, span.(*spanImpl).Name; want != have { t.Errorf("Name want %q, have %q", want, have) } } func TestRemoteEndpoint(t *testing.T) { tracer, err := NewTracer(reporter.NewNoopReporter()) if err != nil { t.Fatalf("expected valid tracer, got error: %+v", err) } ep1, err := NewEndpoint("myService", "www.google.com:80") if err != nil { t.Fatalf("expected valid endpoint, got error: %+v", err) } span := tracer.StartSpan("test", RemoteEndpoint(ep1)) if !reflect.DeepEqual(span.(*spanImpl).RemoteEndpoint, ep1) { t.Errorf("RemoteEndpoint want %+v, have %+v", ep1, span.(*spanImpl).RemoteEndpoint) } ep2, err := NewEndpoint("otherService", "www.microsoft.com:443") if err != nil { t.Fatalf("expected valid endpoint, got error: %+v", err) } span.SetRemoteEndpoint(ep2) if !reflect.DeepEqual(span.(*spanImpl).RemoteEndpoint, ep2) { t.Errorf("RemoteEndpoint want %+v, have %+v", ep1, span.(*spanImpl).RemoteEndpoint) } span.SetRemoteEndpoint(nil) if have := span.(*spanImpl).RemoteEndpoint; have != nil { t.Errorf("RemoteEndpoint want nil, have %+v", have) } } func TestTagsSpanOption(t *testing.T) { tracerTags := map[string]string{ "key1": "value1", "key2": "will_be_overwritten", } tracer, err := NewTracer(reporter.NewNoopReporter(), WithTags(tracerTags)) if err != nil { t.Fatalf("expected valid tracer, got error: %+v", err) } spanTags := map[string]string{ "key2": "value2", "key3": "value3", } span := tracer.StartSpan("test", Tags(spanTags)) defer span.Finish() allTags := map[string]string{ "key1": "value1", "key2": "value2", "key3": "value3", } if want, have := allTags, span.(*spanImpl).Tags; !reflect.DeepEqual(want, have) { t.Errorf("Tags don't match:\nwant: %+v\nhave: %+v", want, have) } } func TestFlushOnFinishSpanOption(t *testing.T) { rec := recorder.NewReporter() defer rec.Close() tracer, _ := NewTracer(rec) span := tracer.StartSpan("test") time.Sleep(5 * time.Millisecond) span.Finish() spans := rec.Flush() if want, have := 1, len(spans); want != have { t.Errorf("Spans want: %d, have %d", want, have) } span = tracer.StartSpan("test", FlushOnFinish(false)) time.Sleep(5 * time.Millisecond) span.Finish() spans = rec.Flush() if want, have := 0, len(spans); want != have { t.Errorf("Spans want: %d, have %d", want, have) } span.Tag("post", "finish") span.Flush() spans = rec.Flush() if want, have := 1, len(spans); want != have { t.Errorf("Spans want: %d, have %d", want, have) } if want, have := map[string]string{"post": "finish"}, spans[0].Tags; !reflect.DeepEqual(want, have) { t.Errorf("Tags want: %+v, have: %+v", want, have) } } zipkin-go-0.4.3/tags.go000066400000000000000000000023241461356266100147250ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package zipkin // Tag holds available types type Tag string // Common Tag values const ( TagHTTPMethod Tag = "http.method" TagHTTPPath Tag = "http.path" TagHTTPUrl Tag = "http.url" TagHTTPRoute Tag = "http.route" TagHTTPStatusCode Tag = "http.status_code" TagHTTPRequestSize Tag = "http.request.size" TagHTTPResponseSize Tag = "http.response.size" TagGRPCStatusCode Tag = "grpc.status_code" TagSQLQuery Tag = "sql.query" TagError Tag = "error" ) // Set a standard Tag with a payload on provided Span. func (t Tag) Set(s Span, value string) { s.Tag(string(t), value) } zipkin-go-0.4.3/tracer.go000066400000000000000000000126151461356266100152530ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package zipkin import ( "context" "sync/atomic" "time" "github.com/openzipkin/zipkin-go/idgenerator" "github.com/openzipkin/zipkin-go/model" "github.com/openzipkin/zipkin-go/propagation" "github.com/openzipkin/zipkin-go/reporter" ) // Tracer is our Zipkin tracer implementation. It should be initialized using // the NewTracer method. type Tracer struct { defaultTags map[string]string extractFailurePolicy ExtractFailurePolicy sampler Sampler generate idgenerator.IDGenerator reporter reporter.Reporter localEndpoint *model.Endpoint noop int32 // used as atomic bool (1 = true, 0 = false) sharedSpans bool unsampledNoop bool } // NewTracer returns a new Zipkin Tracer. func NewTracer(rep reporter.Reporter, opts ...TracerOption) (*Tracer, error) { // set default tracer options t := &Tracer{ defaultTags: make(map[string]string), extractFailurePolicy: ExtractFailurePolicyRestart, sampler: AlwaysSample, generate: idgenerator.NewRandom64(), reporter: rep, localEndpoint: nil, noop: 0, sharedSpans: true, unsampledNoop: false, } // if no reporter was provided we default to noop implementation. if t.reporter == nil { t.reporter = reporter.NewNoopReporter() t.noop = 1 } // process functional options for _, opt := range opts { if err := opt(t); err != nil { return nil, err } } return t, nil } // StartSpanFromContext creates and starts a span using the span found in // context as parent. If no parent span is found a root span is created. func (t *Tracer) StartSpanFromContext(ctx context.Context, name string, options ...SpanOption) (Span, context.Context) { if parentSpan := SpanFromContext(ctx); parentSpan != nil { options = append(options, Parent(parentSpan.Context())) } span := t.StartSpan(name, options...) return span, NewContext(ctx, span) } // StartSpan creates and starts a span. func (t *Tracer) StartSpan(name string, options ...SpanOption) Span { if atomic.LoadInt32(&t.noop) == 1 { // even though we're going to return a noopSpan, we need to initialize // a spanImpl to fetch the parent context that might be provided as a // SpanOption s := &spanImpl{ SpanModel: model.SpanModel{ Tags: make(map[string]string), }, } for _, option := range options { option(t, s) } // return noopSpan with the extracted SpanContext from spanImpl. return &noopSpan{SpanContext: s.SpanContext} } s := &spanImpl{ SpanModel: model.SpanModel{ Kind: model.Undetermined, Name: name, LocalEndpoint: t.localEndpoint, Annotations: make([]model.Annotation, 0), Tags: make(map[string]string), }, flushOnFinish: true, tracer: t, } // add default tracer tags to span for k, v := range t.defaultTags { s.Tag(k, v) } // handle provided functional options for _, option := range options { option(t, s) } if s.TraceID.Empty() { // create root span s.SpanContext.TraceID = t.generate.TraceID() s.SpanContext.ID = t.generate.SpanID(s.SpanContext.TraceID) } else { // valid parent context found if t.sharedSpans && s.Kind == model.Server { // join span s.Shared = true } else { // regular child span parentID := s.SpanContext.ID s.SpanContext.ParentID = &parentID s.SpanContext.ID = t.generate.SpanID(model.TraceID{}) } } if !s.SpanContext.Debug && s.Sampled == nil { // deferred sampled context found, invoke sampler sampled := t.sampler(s.SpanContext.TraceID.Low) s.SpanContext.Sampled = &sampled if sampled { s.mustCollect = 1 } } else { if s.SpanContext.Debug || *s.Sampled { s.mustCollect = 1 } } if t.unsampledNoop && s.mustCollect == 0 { // trace not being sampled and noop requested return &noopSpan{ SpanContext: s.SpanContext, } } // add start time if s.Timestamp.IsZero() { s.Timestamp = time.Now() } return s } // Extract extracts a SpanContext using the provided Extractor function. func (t *Tracer) Extract(extractor propagation.Extractor) (sc model.SpanContext) { if atomic.LoadInt32(&t.noop) == 1 { return } psc, err := extractor() if psc != nil { sc = *psc } sc.Err = err return } // SetNoop allows for killswitch behavior. If set to true the tracer will return // noopSpans and all data is dropped. This allows operators to stop tracing in // risk scenarios. Set back to false to resume tracing. func (t *Tracer) SetNoop(noop bool) { if noop { atomic.CompareAndSwapInt32(&t.noop, 0, 1) } else { atomic.CompareAndSwapInt32(&t.noop, 1, 0) } } // LocalEndpoint returns a copy of the currently set local endpoint of the // tracer instance. func (t *Tracer) LocalEndpoint() *model.Endpoint { if t.localEndpoint == nil { return nil } ep := *t.localEndpoint return &ep } zipkin-go-0.4.3/tracer_options.go000066400000000000000000000073221461356266100170250ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package zipkin import ( "errors" "github.com/openzipkin/zipkin-go/idgenerator" "github.com/openzipkin/zipkin-go/model" ) // Tracer Option Errors var ( ErrInvalidEndpoint = errors.New("requires valid local endpoint") ErrInvalidExtractFailurePolicy = errors.New("invalid extract failure policy provided") ) // ExtractFailurePolicy deals with Extraction errors type ExtractFailurePolicy int // ExtractFailurePolicyOptions const ( ExtractFailurePolicyRestart ExtractFailurePolicy = iota ExtractFailurePolicyError ExtractFailurePolicyTagAndRestart ) // TracerOption allows for functional options to adjust behavior of the Tracer // to be created with NewTracer(). type TracerOption func(o *Tracer) error // WithLocalEndpoint sets the local endpoint of the tracer. func WithLocalEndpoint(e *model.Endpoint) TracerOption { return func(o *Tracer) error { if e == nil { o.localEndpoint = nil return nil } ep := *e o.localEndpoint = &ep return nil } } // WithExtractFailurePolicy allows one to set the ExtractFailurePolicy. func WithExtractFailurePolicy(p ExtractFailurePolicy) TracerOption { return func(o *Tracer) error { if p < 0 || p > ExtractFailurePolicyTagAndRestart { return ErrInvalidExtractFailurePolicy } o.extractFailurePolicy = p return nil } } // WithNoopSpan if set to true will switch to a NoopSpan implementation // if the trace is not sampled. func WithNoopSpan(unsampledNoop bool) TracerOption { return func(o *Tracer) error { o.unsampledNoop = unsampledNoop return nil } } // WithSharedSpans allows to place client-side and server-side annotations // for a RPC call in the same span (Zipkin V1 behavior) or different spans // (more in line with other tracing solutions). By default this Tracer // uses shared host spans (so client-side and server-side in the same span). func WithSharedSpans(val bool) TracerOption { return func(o *Tracer) error { o.sharedSpans = val return nil } } // WithSampler allows one to set a Sampler function func WithSampler(sampler Sampler) TracerOption { return func(o *Tracer) error { o.sampler = sampler return nil } } // WithTraceID128Bit if set to true will instruct the Tracer to start traces // with 128 bit TraceID's. If set to false the Tracer will start traces with // 64 bits. func WithTraceID128Bit(val bool) TracerOption { return func(o *Tracer) error { if val { o.generate = idgenerator.NewRandom128() } else { o.generate = idgenerator.NewRandom64() } return nil } } // WithIDGenerator allows one to set a custom ID Generator func WithIDGenerator(generator idgenerator.IDGenerator) TracerOption { return func(o *Tracer) error { o.generate = generator return nil } } // WithTags allows one to set default tags to be added to each created span func WithTags(tags map[string]string) TracerOption { return func(o *Tracer) error { for k, v := range tags { o.defaultTags[k] = v } return nil } } // WithNoopTracer allows one to start the Tracer as Noop implementation. func WithNoopTracer(tracerNoop bool) TracerOption { return func(o *Tracer) error { if tracerNoop { o.noop = 1 } else { o.noop = 0 } return nil } } zipkin-go-0.4.3/tracer_test.go000066400000000000000000000460101461356266100163060ustar00rootroot00000000000000// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package zipkin import ( "context" "errors" "reflect" "testing" "time" "github.com/openzipkin/zipkin-go/idgenerator" "github.com/openzipkin/zipkin-go/model" "github.com/openzipkin/zipkin-go/reporter" ) func TestTracerOptionLocalEndpoint(t *testing.T) { var ( err error wantEP *model.Endpoint ) tr, err := NewTracer(nil, WithLocalEndpoint(nil)) if err != nil { t.Fatalf("unexpected tracer creation failure: %+v", err.Error()) } if tr == nil { t.Error("expected valid tracer, got: nil") } if want, have := wantEP, tr.LocalEndpoint(); !reflect.DeepEqual(want, have) { t.Errorf("local Endpoint want %+v, have %+v", want, have) } wantEP, err = NewEndpoint("testService", "localhost:80") if err != nil { t.Fatalf("expected valid endpoint, got error: %+v", err) } rep := reporter.NewNoopReporter() defer rep.Close() tr, err = NewTracer(rep, WithLocalEndpoint(wantEP)) if err != nil { t.Fatalf("expected valid tracer, got error: %+v", err) } if tr == nil { t.Error("expected valid tracer, got nil") } haveEP := tr.LocalEndpoint() if want, have := wantEP.ServiceName, haveEP.ServiceName; want != have { t.Errorf("ServiceName want %s, have %s", want, have) } if !wantEP.IPv4.Equal(haveEP.IPv4) { t.Errorf(" IPv4 want %+v, have %+v", wantEP.IPv4, haveEP.IPv4) } if !wantEP.IPv6.Equal(haveEP.IPv6) { t.Errorf("IPv6 want %+v, have %+v", wantEP.IPv6, haveEP.IPv6) } } func TestTracerOptionExtractFailurePolicy(t *testing.T) { rep := reporter.NewNoopReporter() defer rep.Close() policies := []struct { policy ExtractFailurePolicy err error }{ {-1, ErrInvalidExtractFailurePolicy}, {ExtractFailurePolicyRestart, nil}, {ExtractFailurePolicyError, nil}, {ExtractFailurePolicyTagAndRestart, nil}, {3, ErrInvalidExtractFailurePolicy}, } for idx, item := range policies { tr, err := NewTracer(rep, WithExtractFailurePolicy(item.policy)) if want, have := item.err, err; want != have { t.Fatalf("[%d] expected tracer creation failure: want %+v, have %+v", idx, item.err, err) } if err != nil && tr != nil { t.Fatalf("[%d] expected tracer to be nil, have: %+v", idx, tr) } if err != nil { tr, _ = NewTracer(rep) tr.extractFailurePolicy = item.policy } errStr := failSpan(t, tr, idx, item.err) if item.policy == ExtractFailurePolicyTagAndRestart { if want, have := "dummy", errStr; want != have { t.Errorf("[%d] tag[error.extract tag] want %s, have %s", idx, want, have) } } } } func failSpan(t *testing.T, tr *Tracer, idx int, want error) string { sc := model.SpanContext{ Err: errors.New("dummy"), } defer func() { if err := recover(); err != nil { if err != want { t.Errorf("[%d] Context Error want %+v, have %+v", idx, want, err) } } }() sp := tr.StartSpan("test", Parent(sc)) sp.Finish() return sp.(*spanImpl).Tags["error.extract"] } func TestTracerIDGeneratorOption(t *testing.T) { rep := reporter.NewNoopReporter() defer rep.Close() gen := idgenerator.NewRandomTimestamped() tr, err := NewTracer(rep, WithIDGenerator(gen)) if err != nil { t.Fatalf("expected valid tracer, got error: %+v", err) } if want, have := gen, tr.generate; want != have { t.Errorf("id generator want %+v, have %+v", want, have) } } func TestTracerWithTraceID128BitOption(t *testing.T) { rep := reporter.NewNoopReporter() defer rep.Close() tr, err := NewTracer(rep, WithTraceID128Bit(false)) if err != nil { t.Fatalf("expected valid tracer, got error: %+v", err) } if want, have := reflect.TypeOf(idgenerator.NewRandom64()), reflect.TypeOf(tr.generate); want != have { t.Errorf("id generator want %+v, have %+v", want, have) } tr, err = NewTracer(rep, WithTraceID128Bit(true)) if err != nil { t.Fatalf("expected valid tracer, got error: %+v", err) } if want, have := reflect.TypeOf(idgenerator.NewRandom128()), reflect.TypeOf(tr.generate); want != have { t.Errorf("id generator want %+v, have %+v", want, have) } } func TestTracerExtractor(t *testing.T) { rep := reporter.NewNoopReporter() defer rep.Close() tr, err := NewTracer(rep) if err != nil { t.Fatalf("unable to create tracer instance: %+v", err) } testErr1 := errors.New("extractor error") extractorErr := func() (*model.SpanContext, error) { return nil, testErr1 } sc := tr.Extract(extractorErr) if want, have := testErr1, sc.Err; want != have { t.Errorf("Err want %+v, have %+v", want, have) } spanContext := model.SpanContext{} extractor := func() (*model.SpanContext, error) { return &spanContext, nil } sc = tr.Extract(extractor) if want, have := spanContext, sc; want != have { t.Errorf("SpanContext want %+v, have %+v", want, have) } if want, have := &spanContext, ≻ want == have { t.Error("expected different span context objects") } } func TestNoopTracer(t *testing.T) { rep := reporter.NewNoopReporter() defer rep.Close() tr, err := NewTracer(rep) if err != nil { t.Fatalf("unable to create tracer instance: %+v", err) } pSC := model.SpanContext{ TraceID: model.TraceID{ High: 0, Low: 1, }, ID: model.ID(1), } span := tr.StartSpan("test", Parent(pSC)) if want, have := reflect.TypeOf(&spanImpl{}), reflect.TypeOf(span); want != have { t.Errorf("span implementation type want %+v, have %+v", want, have) } span.Finish() tr.SetNoop(true) testErr1 := errors.New("extractor error") extractor := func() (*model.SpanContext, error) { return nil, testErr1 } sc := tr.Extract(extractor) if sc.Err != nil { t.Errorf("Err want nil, have %+v", sc.Err) } span = tr.StartSpan("test", Parent(pSC)) if want, have := reflect.TypeOf(&noopSpan{}), reflect.TypeOf(span); want != have { t.Errorf("span implementation type want %+v, have %+v", want, have) } span.Finish() tr.SetNoop(false) span = tr.StartSpan("test", Parent(pSC)) if want, have := reflect.TypeOf(&spanImpl{}), reflect.TypeOf(span); want != have { t.Errorf("span implementation type want %+v, have %+v", want, have) } span.Finish() tr, err = NewTracer(rep, WithNoopTracer(true)) if err != nil { t.Fatalf("unable to create tracer instance: %+v", err) } testErr1 = errors.New("extractor error") extractor = func() (*model.SpanContext, error) { return nil, testErr1 } sc = tr.Extract(extractor) if sc.Err != nil { t.Errorf("Err want nil, have %+v", sc.Err) } span = tr.StartSpan("test", Parent(pSC)) if want, have := reflect.TypeOf(&noopSpan{}), reflect.TypeOf(span); want != have { t.Errorf("span implementation type want %+v, have %+v", want, have) } tr, err = NewTracer(rep, WithNoopTracer(false)) if err != nil { t.Fatalf("unable to create tracer instance: %+v", err) } span = tr.StartSpan("test", Parent(pSC)) if want, have := reflect.TypeOf(&spanImpl{}), reflect.TypeOf(span); want != have { t.Errorf("span implementation type want %+v, have %+v", want, have) } } func TestNoopSpan(t *testing.T) { rep := reporter.NewNoopReporter() defer rep.Close() tr, err := NewTracer(rep, WithNoopSpan(true)) if err != nil { t.Fatalf("unable to create tracer instance: %+v", err) } sampled := false pSC := model.SpanContext{ TraceID: model.TraceID{ High: 0, Low: 1, }, ID: model.ID(1), Sampled: &sampled, } span := tr.StartSpan("test", Parent(pSC)) if want, have := reflect.TypeOf(&noopSpan{}), reflect.TypeOf(span); want != have { t.Errorf("span implementation type want %+v, have %+v", want, have) } span.Finish() } func TestUnsampledSpan(t *testing.T) { rep := reporter.NewNoopReporter() defer rep.Close() tr, err := NewTracer(rep, WithTraceID128Bit(false)) if err != nil { t.Fatalf("unable to create tracer instance: %+v", err) } sampled := false pSC := model.SpanContext{ TraceID: model.TraceID{ High: 0, Low: 1, }, ID: model.ID(1), Sampled: &sampled, } span := tr.StartSpan("test", Parent(pSC)) if want, have := reflect.TypeOf(&spanImpl{}), reflect.TypeOf(span); want != have { t.Errorf("span implementation type want %+v, have %+v", want, have) } cSC := span.Context() if cSC.Err != nil { t.Errorf("Err want nil, have %+v", cSC.Err) } if want, have := pSC.Debug, cSC.Debug; want != have { t.Errorf("Debug want %t, have %t", want, have) } if want, have := pSC.TraceID, cSC.TraceID; want != have { t.Errorf("TraceID want %+v, have %+v", want, have) } if cSC.ID == 0 { t.Error("ID want valid value, have 0") } if cSC.ParentID == nil { t.Errorf("ParentID want %+v, have nil", pSC.ID) } else if want, have := pSC.ID, *cSC.ParentID; want != have { t.Errorf("ParentID want %+v, have %+v", want, have) } if cSC.Sampled == nil { t.Error("Sampled want false, have nil") } else if *cSC.Sampled { t.Errorf("Sampled want false, have %+v", *cSC.Sampled) } if want, have := int32(0), span.(*spanImpl).mustCollect; want != have { t.Errorf("expected mustCollect %d, got %d", want, have) } span.Finish() } func TestDefaultTags(t *testing.T) { var ( scTagKey = "spanScopedTag" scTagValue = "spanPayload" tags = make(map[string]string) ) tags["platform"] = "zipkin_test" tags["version"] = "1.0" rep := reporter.NewNoopReporter() defer rep.Close() tr, err := NewTracer(rep, WithTags(tags), WithTraceID128Bit(true)) if err != nil { t.Fatalf("unable to create tracer instance: %+v", err) } pSC := model.SpanContext{ TraceID: model.TraceID{ High: 0, Low: 1, }, ID: model.ID(1), } span := tr.StartSpan("test", Kind(model.Server), Parent(pSC)) span.Tag(scTagKey, scTagValue) foundTags := span.(*spanImpl).Tags for key, value := range tags { foundValue, foundKey := foundTags[key] if !foundKey { t.Errorf("Tag want %s=%s, have key not found", key, value) } else if value != foundValue { t.Errorf("Tag want %s=%s, have %s=%s", key, value, key, foundValue) } } foundValue, foundKey := foundTags[scTagKey] if !foundKey { t.Errorf("Tag want %s=%s, have key not found", scTagKey, scTagValue) } else if want, have := scTagValue, foundValue; want != have { t.Errorf("Tag want %s=%s, have %s=%s", scTagKey, scTagValue, scTagKey, foundValue) } } func TestTagOverwriteRules(t *testing.T) { var ( k1 = "key1" v1First = "value to overwrite" v1Last = "value to keep" k2 = string(TagError) ) rep := reporter.NewNoopReporter() defer rep.Close() tr, err := NewTracer(rep, WithIDGenerator(idgenerator.NewRandomTimestamped())) if err != nil { t.Fatalf("unable to create tracer instance: %+v", err) } s := tr.StartSpan("test_tags") defer s.Finish() s.Tag(k1, v1First) if want, have := v1First, s.(*spanImpl).Tags[k1]; want != have { t.Errorf("Tag want %s=%s, have %s=%s", k1, want, k1, have) } s.Tag(k1, v1Last) if want, have := v1Last, s.(*spanImpl).Tags[k1]; want != have { t.Errorf("Tag want %s=%s, have %s=%s", k1, want, k1, have) } s.Tag(k2, v1First) if want, have := v1First, s.(*spanImpl).Tags[k2]; want != have { t.Errorf("Tag want %s=%s, have %s=%s", k1, want, k1, have) } s.Tag(k2, v1Last) if want, have := v1First, s.(*spanImpl).Tags[k2]; want != have { t.Errorf("Tag want %s=%s, have %s=%s", k1, want, k1, have) } TagError.Set(s, v1Last) if want, have := v1First, s.(*spanImpl).Tags[k2]; want != have { t.Errorf("Tag want %s=%s, have %s=%s", k1, want, k1, have) } } func TestAnnotations(t *testing.T) { rep := reporter.NewNoopReporter() defer rep.Close() tr, err := NewTracer(rep) if err != nil { t.Fatalf("unable to create tracer instance: %+v", err) } s := tr.StartSpan("test_tags") defer s.Finish() annotations := []model.Annotation{ { Timestamp: time.Now().Add(10 * time.Millisecond), Value: "annotation 1", }, { Timestamp: time.Now().Add(20 * time.Millisecond), Value: "annotation 2", }, { Timestamp: time.Now().Add(30 * time.Millisecond), Value: "annotation 3", }, } for _, annotation := range annotations { s.Annotate(annotation.Timestamp, annotation.Value) } time.Sleep(40 * time.Millisecond) if want, have := len(annotations), len(s.(*spanImpl).Annotations); want != have { t.Fatalf("Annotation count want %d, have %d", want, have) } for idx, annotation := range annotations { if want, have := annotation, s.(*spanImpl).Annotations[idx]; want != have { t.Errorf("Annotation #%d want %+v, have %+v", idx, want, have) } } } func TestExplicitStartTime(t *testing.T) { rep := reporter.NewNoopReporter() defer rep.Close() tr, err := NewTracer(rep, WithSampler(NewModuloSampler(2))) if err != nil { t.Fatalf("unable to create tracer instance: %+v", err) } st := time.Now() time.Sleep(10 * time.Millisecond) s := tr.StartSpan("test_tags", StartTime(st)) defer s.Finish() if want, have := st, s.(*spanImpl).Timestamp; want != have { t.Errorf("Timestamp want %+v, have %+v", want, have) } } func TestDebugFlagWithoutParentTrace(t *testing.T) { /* Test handling of a single Debug flag without an existing trace */ rep := reporter.NewNoopReporter() defer rep.Close() tr, err := NewTracer(rep, WithSharedSpans(true)) if err != nil { t.Fatalf("unable to create tracer instance: %+v", err) } pSC := model.SpanContext{ Debug: true, } span := tr.StartSpan("test", Parent(pSC)) cSC := span.Context() if cSC.Err != nil { t.Errorf("Err want nil, have %+v", cSC.Err) } if want, have := pSC.Debug, cSC.Debug; want != have { t.Errorf("Debug want %t, have %t", want, have) } if want, have := false, cSC.TraceID.Empty(); want != have { t.Error("expected valid TraceID") } if cSC.ID == 0 { t.Error("expected valid ID") } if cSC.ParentID != nil { t.Errorf("ParentID want nil, have %+v", cSC.ParentID) } if cSC.Sampled != nil { t.Errorf("Sampled want nil, have %+v", cSC.Sampled) } if want, have := int32(1), span.(*spanImpl).mustCollect; want != have { t.Errorf("mustCollect want %d, have %d", want, have) } } func TestParentSpanInSharedMode(t *testing.T) { rep := reporter.NewNoopReporter() defer rep.Close() tr, err := NewTracer(rep, WithSharedSpans(true)) if err != nil { t.Fatalf("unable to create tracer instance: %+v", err) } parentID := model.ID(1) pSC := model.SpanContext{ TraceID: model.TraceID{ High: 0, Low: 1, }, ID: model.ID(2), ParentID: &parentID, } span := tr.StartSpan("test", Kind(model.Server), Parent(pSC)) cSC := span.Context() if cSC.Err != nil { t.Errorf("Err want nil, have %+v", cSC.Err) } if want, have := pSC.Debug, cSC.Debug; want != have { t.Errorf("Debug want %t, have %t", want, have) } if want, have := pSC.TraceID, cSC.TraceID; want != have { t.Errorf("TraceID want %+v, have %+v", want, have) } if want, have := pSC.ID, cSC.ID; want != have { t.Errorf("ID want %+v, have %+v", want, have) } if cSC.ParentID == nil { t.Error("ParentID want valid value, have nil") } else if want, have := parentID, *cSC.ParentID; want != have { t.Errorf("ParentID want %+v, have %+v", want, have) } if cSC.Sampled == nil { t.Error("Sampled want explicit value, have nil") } else if !*cSC.Sampled { t.Errorf("Sampled want true, have %+v", *cSC.Sampled) } if want, have := int32(1), span.(*spanImpl).mustCollect; want != have { t.Errorf("mustCollect want %d, have %d", want, have) } } func TestParentSpanInSpanPerNodeMode(t *testing.T) { rep := reporter.NewNoopReporter() defer rep.Close() tr, err := NewTracer(rep, WithSharedSpans(false)) if err != nil { t.Fatalf("unable to create tracer instance: %+v", err) } pSC := model.SpanContext{ TraceID: model.TraceID{ High: 0, Low: 1, }, ID: model.ID(1), } span := tr.StartSpan("test", Kind(model.Server), Parent(pSC)) cSC := span.Context() if cSC.Err != nil { t.Errorf("Err want nil, have %+v", cSC.Err) } if want, have := pSC.Debug, cSC.Debug; want != have { t.Errorf("Debug want %t, have %t", want, have) } if want, have := pSC.TraceID, cSC.TraceID; want != have { t.Errorf("TraceID want %+v, have: %+v", want, have) } if cSC.ID == 0 { t.Error("expected valid ID") } if cSC.ParentID == nil { t.Error("ParentID want valid value, have nil") } else if want, have := pSC.ID, *cSC.ParentID; want != have { t.Errorf("ParentID want %+v, have %+v", want, have) } if cSC.Sampled == nil { t.Error("Sampled want explicit value, have nil") } else if !*cSC.Sampled { t.Errorf("Sampled want true, have %+v", *cSC.Sampled) } if want, have := int32(1), span.(*spanImpl).mustCollect; want != have { t.Errorf("mustCollect want %d, have %d", want, have) } } func TestStartSpanFromContext(t *testing.T) { ctx := context.Background() rep := reporter.NewNoopReporter() defer rep.Close() tr, err := NewTracer(rep, WithSharedSpans(true)) if err != nil { t.Fatalf("unable to create tracer instance: %+v", err) } if ctxSpan := SpanFromContext(ctx); ctxSpan != nil { t.Errorf("SpanFromContext want nil, have %+v", ctxSpan) } cSpan := tr.StartSpan("test", Kind(model.Client)) ctx = NewContext(ctx, cSpan) sSpan, _ := tr.StartSpanFromContext(ctx, "testChild", Kind(model.Server)) cS, sS := cSpan.(*spanImpl), sSpan.(*spanImpl) if want, have := model.Client, cS.Kind; want != have { t.Errorf("Kind want %+v, have %+v", want, have) } if want, have := model.Server, sS.Kind; want != have { t.Errorf("Kind want %+v, have: %+v", want, have) } if want, have := cS.TraceID, sS.TraceID; want != have { t.Errorf("TraceID want %+v, have: %+v", want, have) } if want, have := cS.ID, sS.ID; want != have { t.Errorf("ID want %+v, have %+v", want, have) } if want, have := cS.ParentID, sS.ParentID; want != have { t.Errorf("ParentID want %+v, have %+v", want, have) } } func TestStartSpanFromContextForEmptyContext(t *testing.T) { ctx := context.Background() rep := reporter.NewNoopReporter() defer rep.Close() tr, err := NewTracer(rep, WithSampler(NeverSample)) if err != nil { t.Fatalf("unable to create tracer instance: %+v", err) } span, _ := tr.StartSpanFromContext(ctx, "my span") span.Finish() } func TestLocalEndpoint(t *testing.T) { rep := reporter.NewNoopReporter() defer rep.Close() ep, err := NewEndpoint("my service", "localhost:80") if err != nil { t.Fatalf("expected valid endpoint, got error: %+v", err) } tracer, err := NewTracer(rep, WithLocalEndpoint(ep)) if err != nil { t.Fatalf("expected valid tracer, got error: %+v", err) } want, have := ep, tracer.LocalEndpoint() if have == nil { t.Fatalf("endpoint want %+v, have nil", want) } if want.ServiceName != have.ServiceName { t.Errorf("serviceName want %s, have %s", want.ServiceName, have.ServiceName) } if !want.IPv4.Equal(have.IPv4) { t.Errorf("IPv4 endpoint want %+v, have %+v", want.IPv4, have.IPv4) } if !want.IPv6.Equal(have.IPv6) { t.Errorf("IPv6 endpoint want %+v, have %+v", want.IPv6, have.IPv6) } }