opentracing -> opencensus (#802)

* update vendor directory, add go.opencensus.io

* update imports

* oops

* s/opentracing/opencensus/ & remove prometheus / zipkin stuff & remove old stats

* the dep train rides again

* fix gin build

* deps from last guy

* start in on the agent metrics

* she builds

* remove tags for now, cardinality error is fussing. subscribe instead of register

* update to patched version of opencensus to proceed for now TODO switch to a release

* meh

fix imports

* println debug the bad boys

* lace it with the tags

* update deps again

* fix all inconsistent cardinality errors

* add our own logger

* fix init

* fix oom measure

* remove bugged removal code

* fix s3 measures

* fix prom handler nil
This commit is contained in:
Reed Allman
2018-03-05 09:35:28 -08:00
committed by GitHub
parent 924d27559c
commit 206aa3c203
5975 changed files with 158755 additions and 566592 deletions

24
vendor/github.com/openzipkin/zipkin-go/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,24 @@
# 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

19
vendor/github.com/openzipkin/zipkin-go/.travis.yml generated vendored Normal file
View File

@@ -0,0 +1,19 @@
language: go
sudo: false
go:
- 1.8.x
- 1.9.x
- tip
before_install:
- go get golang.org/x/tools/cmd/cover
- go get github.com/mattn/goveralls
install:
- go get -d -t ./...
- go get -u github.com/golang/lint/...
script:
- make test vet lint bench
- $GOPATH/bin/goveralls -service=travis-ci

201
vendor/github.com/openzipkin/zipkin-go/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,201 @@
Apache 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.

26
vendor/github.com/openzipkin/zipkin-go/Makefile generated vendored Normal file
View File

@@ -0,0 +1,26 @@
.DEFAULT_GOAL := test
.PHONY: test
test:
go test -v -race -cover ./...
.PHONY: bench
bench:
go test -v -run - -bench . -benchmem ./...
.PHONY: lint
lint:
# Ignore grep's exit code since no match returns 1.
-golint ./... | grep --invert-match -E '^.*\.pb\.go|^thrift'
@
@! (golint ./... | grep --invert-match -E '^.*\.pb\.go|^thrift' | read dummy)
.PHONY: vet
vet:
go vet ./...
.PHONY: all
all: vet lint test bench
.PHONY: example

79
vendor/github.com/openzipkin/zipkin-go/README.md generated vendored Normal file
View File

@@ -0,0 +1,79 @@
# Zipkin Library for Go
[![Travis CI](https://travis-ci.org/openzipkin/zipkin-go.svg?branch=master)](https://travis-ci.org/openzipkin/zipkin-go)
[![CircleCI](https://circleci.com/gh/openzipkin/zipkin-go.svg?style=shield)](https://circleci.com/gh/openzipkin/zipkin-go)
[![Appveyor CI](https://ci.appveyor.com/api/projects/status/1d0e5k96g10ajl63/branch/master?svg=true)](https://ci.appveyor.com/project/basvanbeek/zipkin-go)
[![Coverage Status](https://img.shields.io/coveralls/github/openzipkin/zipkin-go.svg)](https://coveralls.io/github/openzipkin/zipkin-go?branch=master)
[![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 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
gRPC middleware / interceptors are planned for the near future.
### 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://godoc.org/github.com/Shopify/sarama#AsyncProducer)
underneath.
## usage and examples
[HTTP Server Example](example_httpserver_test.go)

21
vendor/github.com/openzipkin/zipkin-go/appveyor.yml generated vendored Normal file
View File

@@ -0,0 +1,21 @@
version: v1.0.0.{build}
platform: x64
clone_folder: c:\gopath\src\github.com\openzipkin\zipkin-go
environment:
GOPATH: c:\gopath
install:
- echo %PATH%
- echo %GOPATH%
- set PATH=%GOPATH%\bin;c:\go\bin;%PATH%
- go version
- go env
build_script:
- go get -t -v ./...
- go vet ./...
- go test -v -race -cover ./...
- go test -v -run - -bench . -benchmem ./...

182
vendor/github.com/openzipkin/zipkin-go/bench_test.go generated vendored Normal file
View File

@@ -0,0 +1,182 @@
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
}

10
vendor/github.com/openzipkin/zipkin-go/circle.yml generated vendored Normal file
View File

@@ -0,0 +1,10 @@
dependencies:
override:
- sudo rm -rf /home/ubuntu/.go_workspace/src/github.com/openzipkin
- mkdir -p /home/ubuntu/.go_workspace/src/github.com/openzipkin
- mv /home/ubuntu/zipkin-go /home/ubuntu/.go_workspace/src/github.com/openzipkin
- ln -s /home/ubuntu/.go_workspace/src/github.com/openzipkin/zipkin-go /home/ubuntu/zipkin-go
- go get -u -t -v github.com/openzipkin/zipkin-go/...
test:
override:
- make test bench

23
vendor/github.com/openzipkin/zipkin-go/context.go generated vendored Normal file
View File

@@ -0,0 +1,23 @@
package zipkin
import (
"context"
)
// 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
}
// 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)
}
type ctxKey struct{}
var spanKey = ctxKey{}

6
vendor/github.com/openzipkin/zipkin-go/doc.go generated vendored Normal file
View File

@@ -0,0 +1,6 @@
/*
Package zipkin implements a native Zipkin instrumentation library for Go.
See https://zipkin.io for more information about Zipkin.
*/
package zipkin

71
vendor/github.com/openzipkin/zipkin-go/endpoint.go generated vendored Normal file
View File

@@ -0,0 +1,71 @@
package zipkin
import (
"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, 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
}
}
// default to 0 filled 4 byte array for IPv4 if IPv6 only host was found
if e.IPv4 == nil {
e.IPv4 = make([]byte, 4)
}
return e, nil
}

151
vendor/github.com/openzipkin/zipkin-go/endpoint_test.go generated vendored Normal file
View File

@@ -0,0 +1,151 @@
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 = "localhost:" + 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(), "no such host") {
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)
}
}
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)
}
}

View File

@@ -0,0 +1,109 @@
package zipkin_test
import (
"log"
"net/http"
"net/http/httptest"
"os"
"time"
"github.com/gorilla/mux"
zipkin "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 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 := mux.NewRouter()
// start web service with zipkin http server middleware
ts := httptest.NewServer(serverMiddleware(router))
defer ts.Close()
// set-up handlers
router.Methods("GET").Path("/some_function").HandlerFunc(someFunc(client, ts.URL))
router.Methods("POST").Path("/other_function").HandlerFunc(otherFunc(client))
// initiate a call to some_func
req, err := http.NewRequest("GET", ts.URL+"/some_function", nil)
if err != nil {
log.Fatalf("unable to create http request: %+v\n", err)
}
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)
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(client *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)
}
}

104
vendor/github.com/openzipkin/zipkin-go/example_test.go generated vendored Normal file
View File

@@ -0,0 +1,104 @@
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:
}

View File

@@ -0,0 +1,116 @@
/*
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
}

View File

@@ -0,0 +1,116 @@
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 new, old := traceID.High>>32, latestTS; new < old {
t.Errorf("[%d] expected a higher timestamp part in traceid but got: old: %d new: %d", idx, old, new)
}
latestTS = traceID.High >> 32
}
}

View File

@@ -0,0 +1,131 @@
package http
import (
"errors"
"net/http"
"strconv"
"time"
zipkin "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
}
// 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
}
}
// 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),
)
transport, err := NewTransport(tracer, c.transportOptions...)
if err != nil {
return nil, err
}
c.Client.Transport = transport
return c, nil
}
// DoWithAppSpan wraps http.Client's Do with tracing using an application span.
func (c *Client) DoWithAppSpan(req *http.Request, name string) (res *http.Response, err 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.TagHTTPMethod.Set(appSpan, req.Method)
zipkin.TagHTTPUrl.Set(appSpan, req.URL.String())
zipkin.TagHTTPPath.Set(appSpan, req.URL.Path)
res, err = c.Client.Do(
req.WithContext(zipkin.NewContext(req.Context(), appSpan)),
)
if err != nil {
zipkin.TagError.Set(appSpan, err.Error())
appSpan.Finish()
return
}
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
}

View File

@@ -0,0 +1,81 @@
package http_test
import (
"net/http"
"testing"
zipkin "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 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",
}
client, err := httpclient.NewClient(
tracer,
httpclient.WithClient(&http.Client{}),
httpclient.ClientTrace(true),
httpclient.ClientTags(clientTags),
httpclient.TransportOptions(httpclient.TransportTags(transportTags)),
)
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))
}
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()
}

View File

@@ -0,0 +1,5 @@
/*
Package http contains several http middlewares which can be used for
instrumenting calls with Zipkin.
*/
package http

View File

@@ -0,0 +1,149 @@
package http
import (
"net/http"
"strconv"
"sync/atomic"
zipkin "github.com/openzipkin/zipkin-go"
"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
}
// 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
}
}
// 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,
}
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
sc := h.tracer.Extract(b3.ExtractHTTP(r))
remoteEndpoint, _ := zipkin.NewEndpoint("", r.RemoteAddr)
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(sc),
zipkin.RemoteEndpoint(remoteEndpoint),
)
for k, v := range h.defaultTags {
sp.Tag(k, v)
}
// add our span to context
ctx := zipkin.NewContext(r.Context(), sp)
// tag typical HTTP request items
zipkin.TagHTTPMethod.Set(sp, r.Method)
zipkin.TagHTTPUrl.Set(sp, r.URL.String())
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 > 399 {
zipkin.TagError.Set(sp, sCode)
}
zipkin.TagHTTPStatusCode.Set(sp, sCode)
if h.tagResponseSize {
zipkin.TagHTTPResponseSize.Set(sp, ri.getResponseSize())
}
sp.Finish()
}()
// call next http Handler func using our updated context.
h.next.ServeHTTP(ri, 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)
}

View File

@@ -0,0 +1,141 @@
package http_test
import (
"bytes"
"net/http"
"net/http/httptest"
"strconv"
"testing"
zipkin "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, r *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))
httpRecorder = httptest.NewRecorder()
requestBuf = bytes.NewBufferString("incoming data")
responseBuf = bytes.NewBufferString("oh oh we have a 404")
headers = make(http.Header)
spanName = "wrapper_test"
code = 404
)
headers.Add("some-key", "some-value")
headers.Add("other-key", "other-value")
request, err := http.NewRequest("POST", "/test", requestBuf)
if err != nil {
t.Fatalf("unable to create request")
}
httpHandlerFunc := http.HandlerFunc(httpHandler(code, headers, responseBuf))
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 want, have := strconv.Itoa(requestBuf.Len()), span.Tags["http.request.size"]; want != have {
t.Errorf("Expected span request size %s, got %s", want, have)
}
if want, have := strconv.Itoa(responseBuf.Len()), span.Tags["http.response.size"]; want != have {
t.Errorf("Expected span response size %s, got %s", want, have)
}
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 := responseBuf.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"
)
request, err := http.NewRequest(methodType, "/test", requestBuf)
if err != nil {
t.Fatalf("unable to create request")
}
httpHandlerFunc := http.HandlerFunc(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)
}
}

View File

@@ -0,0 +1,23 @@
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
}

View File

@@ -0,0 +1,103 @@
package http
import (
"crypto/tls"
"fmt"
"net/http/httptrace"
"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", fmt.Sprintf("%t", info.Reused))
s.Tag("httptrace.got_connection.was_idle", fmt.Sprintf("%t", 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())
}
}

View File

@@ -0,0 +1,128 @@
package http
import (
"net/http"
"net/http/httptrace"
"strconv"
zipkin "github.com/openzipkin/zipkin-go"
"github.com/openzipkin/zipkin-go/model"
"github.com/openzipkin/zipkin-go/propagation/b3"
)
type transport struct {
tracer *zipkin.Tracer
rt http.RoundTripper
httpTrace bool
defaultTags map[string]string
}
// 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
}
}
// 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,
}
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),
)
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.TagHTTPUrl.Set(sp, req.URL.String())
zipkin.TagHTTPPath.Set(sp, req.URL.Path)
_ = b3.InjectHTTP(req)(sp.Context())
res, err = t.rt.RoundTrip(req)
if err != nil {
zipkin.TagError.Set(sp, err.Error())
sp.Finish()
return
}
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 {
zipkin.TagError.Set(sp, statusCode)
}
}
sp.Finish()
return
}

View File

@@ -0,0 +1,46 @@
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
}

View File

@@ -0,0 +1,22 @@
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)
}
}

9
vendor/github.com/openzipkin/zipkin-go/model/doc.go generated vendored Normal file
View File

@@ -0,0 +1,9 @@
/*
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

View File

@@ -0,0 +1,17 @@
package model
import "net"
// Endpoint holds the network context of a node in the service graph.
type Endpoint struct {
ServiceName string `json:"serviceName,omitempty"`
IPv4 net.IP `json:"ipv4,omitempty"`
IPv6 net.IP `json:"ipv6,omitempty"`
Port uint16 `json:"port,omitempty"`
}
// 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)
}

View File

@@ -0,0 +1,38 @@
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)
}
}

13
vendor/github.com/openzipkin/zipkin-go/model/kind.go generated vendored Normal file
View File

@@ -0,0 +1,13 @@
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"
)

124
vendor/github.com/openzipkin/zipkin-go/model/span.go generated vendored Normal file
View File

@@ -0,0 +1,124 @@
package model
import (
"encoding/json"
"errors"
"time"
)
// unmarshal errors
var (
ErrValidTraceIDRequired = errors.New("valid traceId required")
ErrValidIDRequired = errors.New("valid span id required")
ErrValidDurationRequired = errors.New("valid duration required")
)
// 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:"-"`
}
// 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:"timestamp,omitempty"`
Duration time.Duration `json:"duration,omitempty"`
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
}
if s.LocalEndpoint.Empty() {
s.LocalEndpoint = nil
}
if s.RemoteEndpoint.Empty() {
s.RemoteEndpoint = nil
}
return json.Marshal(&struct {
Timestamp int64 `json:"timestamp,omitempty"`
Duration int64 `json:"duration,omitempty"`
Alias
}{
Timestamp: timestamp,
Duration: 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 {
TimeStamp uint64 `json:"timestamp,omitempty"`
Duration 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.TimeStamp > 0 {
s.Timestamp = time.Unix(0, int64(span.TimeStamp)*1e3)
}
s.Duration = time.Duration(span.Duration*1e3) * time.Nanosecond
if s.LocalEndpoint.Empty() {
s.LocalEndpoint = nil
}
if s.RemoteEndpoint.Empty() {
s.RemoteEndpoint = nil
}
return nil
}

View File

@@ -0,0 +1,30 @@
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
}

View File

@@ -0,0 +1,242 @@
package model
import (
"encoding/json"
"errors"
"fmt"
"net"
"reflect"
"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)
}
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)
}
}

View File

@@ -0,0 +1,59 @@
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
}
tID, err := TraceIDFromHex(string(traceID[1 : len(traceID)-1]))
if err != nil {
return err
}
*t = tID
return nil
}

View File

@@ -0,0 +1,60 @@
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)
}
var traceID2 TraceID
if err = json.Unmarshal(b, &traceID2); err != nil {
t.Fatalf("Expected successful json deserialization, got error: %+v", err)
}
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")
}
}

25
vendor/github.com/openzipkin/zipkin-go/noop.go generated vendored Normal file
View File

@@ -0,0 +1,25 @@
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) Flush() {}

49
vendor/github.com/openzipkin/zipkin-go/noop_test.go generated vendored Normal file
View File

@@ -0,0 +1,49 @@
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()
}

View File

@@ -0,0 +1,5 @@
/*
Package b3 implements serialization and deserialization logic for Zipkin
B3 Headers.
*/
package b3

View File

@@ -0,0 +1,73 @@
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)
}

View File

@@ -0,0 +1,290 @@
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) {
md := metadata.Pairs(b3.Flags, "2")
_, err := b3.ExtractGRPC(&md)()
if want, have := b3.ErrInvalidFlagsHeader, err; want != have {
t.Errorf("SpanContext Error want %+v, have %+v", 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)
}
}

View File

@@ -0,0 +1,58 @@
package b3
import (
"net/http"
"github.com/openzipkin/zipkin-go/model"
"github.com/openzipkin/zipkin-go/propagation"
)
// 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)
)
return ParseHeaders(
traceIDHeader, spanIDHeader, parentSpanIDHeader, sampledHeader,
flagsHeader,
)
}
}
// InjectHTTP will inject a span.Context into a HTTP Request
func InjectHTTP(r *http.Request) propagation.Injector {
return func(sc model.SpanContext) error {
if (model.SpanContext{}) == sc {
return ErrEmptyContext
}
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())
}
}
return nil
}
}

View File

@@ -0,0 +1,321 @@
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"
)
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) {
r := newHTTPRequest(t)
r.Header.Set(b3.Flags, "2")
_, err := b3.ExtractHTTP(r)()
if want, have := b3.ErrInvalidFlagsHeader, err; want != have {
t.Errorf("SpanContext Error want %+v, have %+v", 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, "invalid_data")
_, 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, "invalid_data")
_, 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, "invalid_data")
_, err := b3.ExtractHTTP(r)()
if want, have := b3.ErrInvalidParentSpanIDHeader, err; want != have {
t.Errorf("ExtractHTTP 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: 1},
ID: model.ID(2),
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 newHTTPRequest(t *testing.T) *http.Request {
r, err := http.NewRequest("test", "", nil)
if err != nil {
t.Fatalf("HTTP Request failed: %+v", err)
}
return r
}

View File

@@ -0,0 +1,24 @@
package b3
import "errors"
// Common Header Extraction / Injection errors
var (
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")
ErrEmptyContext = errors.New("empty request context")
)
// 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"
)

View File

@@ -0,0 +1,81 @@
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
}
switch hdrFlags {
case "", "0":
// sc.Debug = false
case "1":
sc.Debug = true
if sc.Sampled != nil {
sc.Sampled = nil
}
default:
return nil, ErrInvalidFlagsHeader
}
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
}

View File

@@ -0,0 +1,16 @@
/*
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

View File

@@ -0,0 +1,218 @@
/*
Package http implements a HTTP reporter to send spans to Zipkin V2 collectors.
*/
package http
import (
"bytes"
"encoding/json"
"log"
"net/http"
"os"
"sync"
"time"
"github.com/openzipkin/zipkin-go/model"
"github.com/openzipkin/zipkin-go/reporter"
)
// defaults
const (
defaultTimeout = time.Second * 5 // timeout for http request in seconds
defaultBatchInterval = time.Second * 1 // BatchInterval in seconds
defaultBatchSize = 100
defaultMaxBacklog = 1000
)
// httpReporter will send spans to a Zipkin HTTP Collector using Zipkin V2 API.
type httpReporter struct {
url string
client *http.Client
logger *log.Logger
batchInterval time.Duration
batchSize int
maxBacklog int
sendMtx *sync.Mutex
batchMtx *sync.Mutex
batch []*model.SpanModel
spanC chan *model.SpanModel
quit chan struct{}
shutdown chan error
reqCallback RequestCallbackFn
}
// 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)
go func() {
_ = r.sendBatch()
}()
}
case <-tickerChan:
if time.Now().After(nextSend) {
nextSend = time.Now().Add(r.batchInterval)
go func() {
_ = r.sendBatch()
}()
}
case <-r.quit:
r.shutdown <- r.sendBatch()
return
}
}
}
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 {
// in order to prevent sending the same batch twice
r.sendMtx.Lock()
defer r.sendMtx.Unlock()
// 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 := json.Marshal(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
}
req.Header.Set("Content-Type", "application/json")
if r.reqCallback != nil {
r.reqCallback(req)
}
resp, err := r.client.Do(req)
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 http request.
func Timeout(duration time.Duration) ReporterOption {
return func(r *httpReporter) { r.client.Timeout = 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.
func Client(client *http.Client) 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 }
}
// 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{Timeout: defaultTimeout},
batchInterval: defaultBatchInterval,
batchSize: defaultBatchSize,
maxBacklog: defaultMaxBacklog,
batch: []*model.SpanModel{},
spanC: make(chan *model.SpanModel),
quit: make(chan struct{}, 1),
shutdown: make(chan error, 1),
sendMtx: &sync.Mutex{},
batchMtx: &sync.Mutex{},
}
for _, opt := range opts {
opt(&r)
}
go r.loop()
return &r
}

View File

@@ -0,0 +1,77 @@
package http_test
import (
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"fmt"
"time"
"strings"
"github.com/openzipkin/zipkin-go/idgenerator"
"github.com/openzipkin/zipkin-go/model"
zipkinhttp "github.com/openzipkin/zipkin-go/reporter/http"
)
func TestSpanIsBeingReported(t *testing.T) {
idGen := idgenerator.NewRandom64()
traceID := idGen.TraceID()
nSpans := 2
var aSpans []model.SpanModel
var eSpans []string
for i := 0; i < nSpans; i++ {
span := model.SpanModel{
SpanContext: model.SpanContext{
TraceID: traceID,
ID: idGen.SpanID(traceID),
},
Name: "name",
Kind: model.Client,
Timestamp: time.Now(),
}
aSpans = append(aSpans, span)
eSpans = append(
eSpans,
fmt.Sprintf(
`{"timestamp":%d,"traceId":"%s","id":"%s","name":"%s","kind":"%s"}`,
span.Timestamp.Round(time.Microsecond).UnixNano()/1e3,
span.SpanContext.TraceID,
span.SpanContext.ID,
span.Name,
span.Kind,
),
)
}
eSpansPayload := fmt.Sprintf("[%s]", strings.Join(eSpans, ","))
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
t.Errorf("expected 'POST' request, got '%s'", r.Method)
}
aSpanPayload, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Errorf("unexpected error: %s", err.Error())
}
if eSpansPayload != string(aSpanPayload) {
t.Errorf("unexpected span payload \nwant %s, \nhave %s\n", eSpansPayload, string(aSpanPayload))
}
}))
defer ts.Close()
rep := zipkinhttp.NewReporter(ts.URL)
defer rep.Close()
for _, span := range aSpans {
rep.Send(span)
}
}

View File

@@ -0,0 +1,100 @@
/*
Package kafka implements a Kafka reporter to send spans to a Kafka server/cluster.
*/
package kafka
import (
"encoding/json"
"log"
"os"
"github.com/Shopify/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
}
// 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.
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
}
}
// 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) {
c := &kafkaReporter{
logger: log.New(os.Stderr, "", log.LstdFlags),
topic: defaultKafkaTopic,
}
for _, option := range options {
option(c)
}
if c.producer == nil {
p, err := sarama.NewAsyncProducer(address, nil)
if err != nil {
return nil, err
}
c.producer = p
}
go c.logErrors()
return c, nil
}
func (c *kafkaReporter) logErrors() {
for pe := range c.producer.Errors() {
c.logger.Print("msg", pe.Msg, "err", pe.Err, "result", "failed to produce msg")
}
}
func (c *kafkaReporter) Send(s model.SpanModel) {
m, err := json.Marshal(s)
if err != nil {
c.logger.Printf("failed when marshalling the span: %s\n", err.Error())
return
}
c.producer.Input() <- &sarama.ProducerMessage{
Topic: c.topic,
Key: nil,
Value: sarama.ByteEncoder(m),
}
}
func (c *kafkaReporter) Close() error {
return c.producer.Close()
}

View File

@@ -0,0 +1,218 @@
package kafka_test
import (
"errors"
"testing"
"time"
"encoding/json"
"log"
"github.com/Shopify/sarama"
"github.com/openzipkin/zipkin-go/model"
"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) 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 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)
got := deserializeSpan(t, m.Value)
testEqual(t, want, got)
}
}
func TestKafkaClose(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)
}
if err = c.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)
}
for _, want := range spans {
_ = sendSpan(t, c, p, *want)
}
for i := 0; i < len(spans); i++ {
select {
case <-errs:
case <-time.After(100 * time.Millisecond):
t.Fatalf("errors not logged. got %d, wanted %d", i, len(spans))
}
}
}
func sendSpan(t *testing.T, c reporter.Reporter, p *stubProducer, s model.SpanModel) *sarama.ProducerMessage {
var m *sarama.ProducerMessage
rcvd := make(chan bool, 1)
go func() {
select {
case m = <-p.in:
rcvd <- true
if p.kafkaDown {
p.err <- &sarama.ProducerError{
Msg: m,
Err: errors.New("kafka is down"),
}
}
case <-time.After(100 * time.Millisecond):
rcvd <- false
}
}()
c.Send(s)
if !<-rcvd {
t.Fatal("span message was not produced")
}
return m
}
func testMetadata(t *testing.T, m *sarama.ProducerMessage) {
if m.Topic != "zipkin" {
t.Errorf("produced to topic %q, want %q", m.Topic, "zipkin")
}
if m.Key != nil {
t.Errorf("produced with key %q, want nil", m.Key)
}
}
func deserializeSpan(t *testing.T, e sarama.Encoder) *model.SpanModel {
bytes, err := e.Encode()
if err != nil {
t.Errorf("error in encoding: %v", err)
}
var s model.SpanModel
err = json.Unmarshal(bytes, &s)
if err != nil {
t.Errorf("error in decoding: %v", err)
return nil
}
return &s
}
func testEqual(t *testing.T, want *model.SpanModel, got *model.SpanModel) {
if got.TraceID != want.TraceID {
t.Errorf("trace_id %d, want %d", got.TraceID, want.TraceID)
}
if got.ID != want.ID {
t.Errorf("id %d, want %d", got.ID, want.ID)
}
if got.ParentID == nil {
if want.ParentID != nil {
t.Errorf("parent_id %d, want %d", got.ParentID, want.ParentID)
}
} else if *got.ParentID != *want.ParentID {
t.Errorf("parent_id %d, want %d", got.ParentID, want.ParentID)
}
}
func makeNewSpan(methodName string, traceID, spanID, parentSpanID uint64, debug bool) *model.SpanModel {
timestamp := time.Now()
var 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,
}
}

View File

@@ -0,0 +1,41 @@
/*
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 }

View File

@@ -0,0 +1,43 @@
/*
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
}

View File

@@ -0,0 +1,41 @@
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))
}
}

View File

@@ -0,0 +1,27 @@
/*
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{}
}

107
vendor/github.com/openzipkin/zipkin-go/sample.go generated vendored Normal file
View File

@@ -0,0 +1,107 @@
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
func neverSample(_ uint64) bool { return false }
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
}

107
vendor/github.com/openzipkin/zipkin-go/sample_test.go generated vendored Normal file
View File

@@ -0,0 +1,107 @@
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)
}
}
}

38
vendor/github.com/openzipkin/zipkin-go/span.go generated vendored Normal file
View File

@@ -0,0 +1,38 @@
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()
// 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()
}

View File

@@ -0,0 +1,78 @@
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.tracer.reporter.Send(s.SpanModel)
}
}
}
func (s *spanImpl) Flush() {
if s.SpanModel.Debug || (s.SpanModel.Sampled != nil && *s.SpanModel.Sampled) {
s.tracer.reporter.Send(s.SpanModel)
}
}

74
vendor/github.com/openzipkin/zipkin-go/span_options.go generated vendored Normal file
View File

@@ -0,0 +1,74 @@
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(t *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(t *Tracer, s *spanImpl) {
s.Timestamp = start
}
}
// RemoteEndpoint sets the remote endpoint of the span being created.
func RemoteEndpoint(e *model.Endpoint) SpanOption {
return func(t *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(t *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(t *Tracer, s *spanImpl) {
s.flushOnFinish = b
}
}

137
vendor/github.com/openzipkin/zipkin-go/span_test.go generated vendored Normal file
View File

@@ -0,0 +1,137 @@
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 want: %+v, have: %+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)
}
}

21
vendor/github.com/openzipkin/zipkin-go/tags.go generated vendored Normal file
View File

@@ -0,0 +1,21 @@
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"
TagHTTPStatusCode Tag = "http.status_code"
TagHTTPRequestSize Tag = "http.request.size"
TagHTTPResponseSize Tag = "http.response.size"
TagGRPCStatusCode Tag = "grpc.status_code"
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)
}

173
vendor/github.com/openzipkin/zipkin-go/tracer.go generated vendored Normal file
View File

@@ -0,0 +1,173 @@
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 {
return &noopSpan{}
}
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
}

View File

@@ -0,0 +1,124 @@
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
}
}

782
vendor/github.com/openzipkin/zipkin-go/tracer_test.go generated vendored Normal file
View File

@@ -0,0 +1,782 @@
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, &sc; 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 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)
}
}