You've already forked opentelemetry-go
mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2025-07-03 00:27:03 +02:00
Zipkin exporter (#495)
* Add zipkin exporter The zipkin exporter implements the SpanBatcher interface. It follows the current-at-the-time-of-writing document about conversion from OpenTelemetry span data to Zipkin spans. Which means that endpoint information is not yet filled. * Fix typo in docs * Add a zipkin example This sends span information to a locally running zipkin collector. Currently I have a problem getting the collector to show me the spans after accepting them with HTTP 202. Not sure if this is because of missing endpoint information. * Make gitignore consistent The fixed paths should be prefixed with a slash. The "relative" paths mean that git will ignore all the files that end with the path. * Add tests for zipkin exporter
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@ -9,10 +9,11 @@ Thumbs.db
|
||||
coverage.*
|
||||
|
||||
/example/basic/basic
|
||||
example/grpc/client/client
|
||||
example/grpc/server/server
|
||||
/example/grpc/client/client
|
||||
/example/grpc/server/server
|
||||
/example/http/client/client
|
||||
/example/http/server/server
|
||||
/example/jaeger/jaeger
|
||||
/example/namedtracer/namedtracer
|
||||
/example/prometheus/prometheus
|
||||
/example/zipkin/zipkin
|
||||
|
16
example/zipkin/README.md
Normal file
16
example/zipkin/README.md
Normal file
@ -0,0 +1,16 @@
|
||||
# Zipkin Exporter Example
|
||||
|
||||
Sends spans to zipkin collector.
|
||||
|
||||
### Run collector
|
||||
|
||||
```sh
|
||||
docker run -d -p 9411:9411 openzipkin/zipkin
|
||||
```
|
||||
|
||||
### Run client
|
||||
|
||||
```sh
|
||||
go build .
|
||||
./zipkin
|
||||
```
|
13
example/zipkin/go.mod
Normal file
13
example/zipkin/go.mod
Normal file
@ -0,0 +1,13 @@
|
||||
module go.opentelemetry.go/otel/example/zipkin
|
||||
|
||||
go 1.13
|
||||
|
||||
replace (
|
||||
go.opentelemetry.io/otel => ../..
|
||||
go.opentelemetry.io/otel/exporters/trace/zipkin => ../../exporters/trace/zipkin
|
||||
)
|
||||
|
||||
require (
|
||||
go.opentelemetry.io/otel v0.2.3
|
||||
go.opentelemetry.io/otel/exporters/trace/zipkin v0.2.3
|
||||
)
|
104
example/zipkin/go.sum
Normal file
104
example/zipkin/go.sum
Normal file
@ -0,0 +1,104 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7 h1:qELHH0AWCvf98Yf+CNIJx9vOZOfHFDDzgDRYsnNk/vs=
|
||||
github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60=
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
github.com/benbjohnson/clock v1.0.0 h1:78Jk/r6m4wCi6sndMpty7A//t4dw/RW5fV4ZgDVfX1w=
|
||||
github.com/benbjohnson/clock v1.0.0/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
|
||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/opentracing/opentracing-go v1.1.1-0.20190913142402-a7454ce5950e/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/openzipkin/zipkin-go v0.2.2 h1:nY8Hti+WKaP0cRsSeQ026wU03QsM762XBeCXBb9NAWI=
|
||||
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
|
||||
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03 h1:4HYDjxeNXAOTv3o1N2tjo8UUSlhQgAD52FVkwxnWgM8=
|
||||
google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
|
||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
79
example/zipkin/main.go
Normal file
79
example/zipkin/main.go
Normal file
@ -0,0 +1,79 @@
|
||||
// Copyright 2020, OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Command zipkin is an example program that creates spans
|
||||
// and uploads to openzipkin collector.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel/api/global"
|
||||
|
||||
"go.opentelemetry.io/otel/exporters/trace/zipkin"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
)
|
||||
|
||||
var logger = log.New(os.Stderr, "zipkin-example", log.Ldate|log.Ltime|log.Llongfile)
|
||||
|
||||
// initTracer creates a new trace provider instance and registers it as global trace provider.
|
||||
func initTracer() {
|
||||
// Create Zipkin Exporter
|
||||
exporter, err := zipkin.NewExporter(
|
||||
"http://localhost:9411/api/v2/spans",
|
||||
zipkin.WithLogger(logger),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// For demoing purposes, always sample. In a production application, you should
|
||||
// configure this to a trace.ProbabilitySampler set at the desired
|
||||
// probability.
|
||||
tp, err := sdktrace.NewProvider(
|
||||
sdktrace.WithConfig(sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()}),
|
||||
sdktrace.WithBatcher(exporter,
|
||||
sdktrace.WithScheduleDelayMillis(5),
|
||||
sdktrace.WithMaxExportBatchSize(10),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
global.SetTraceProvider(tp)
|
||||
}
|
||||
|
||||
func main() {
|
||||
initTracer()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
tr := global.TraceProvider().Tracer("component-main")
|
||||
ctx, span := tr.Start(ctx, "foo")
|
||||
<-time.After(6 * time.Millisecond)
|
||||
bar(ctx)
|
||||
<-time.After(6 * time.Millisecond)
|
||||
span.End()
|
||||
<-time.After(24 * time.Millisecond)
|
||||
}
|
||||
|
||||
func bar(ctx context.Context) {
|
||||
tr := global.TraceProvider().Tracer("component-bar")
|
||||
_, span := tr.Start(ctx, "bar")
|
||||
<-time.After(6 * time.Millisecond)
|
||||
span.End()
|
||||
}
|
237
exporters/trace/zipkin/exporter_test.go
Normal file
237
exporters/trace/zipkin/exporter_test.go
Normal file
@ -0,0 +1,237 @@
|
||||
package zipkin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
zkmodel "github.com/openzipkin/zipkin-go/model"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc/codes"
|
||||
|
||||
"go.opentelemetry.io/otel/api/core"
|
||||
"go.opentelemetry.io/otel/api/trace"
|
||||
export "go.opentelemetry.io/otel/sdk/export/trace"
|
||||
)
|
||||
|
||||
type mockZipkinCollector struct {
|
||||
t *testing.T
|
||||
url string
|
||||
closing bool
|
||||
server *http.Server
|
||||
wg *sync.WaitGroup
|
||||
|
||||
lock sync.RWMutex
|
||||
models []zkmodel.SpanModel
|
||||
}
|
||||
|
||||
func startMockZipkinCollector(t *testing.T) *mockZipkinCollector {
|
||||
collector := &mockZipkinCollector{
|
||||
t: t,
|
||||
closing: false,
|
||||
}
|
||||
listener, err := net.Listen("tcp", ":0")
|
||||
require.NoError(t, err)
|
||||
collector.url = fmt.Sprintf("http://%s", listener.Addr().String())
|
||||
server := &http.Server{
|
||||
Handler: http.HandlerFunc(collector.handler),
|
||||
}
|
||||
collector.server = server
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
collector.wg = wg
|
||||
go func() {
|
||||
err := server.Serve(listener)
|
||||
require.True(t, collector.closing)
|
||||
require.Equal(t, http.ErrServerClosed, err)
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
return collector
|
||||
}
|
||||
|
||||
func (c *mockZipkinCollector) handler(w http.ResponseWriter, r *http.Request) {
|
||||
jsonBytes, err := ioutil.ReadAll(r.Body)
|
||||
require.NoError(c.t, err)
|
||||
var models []zkmodel.SpanModel
|
||||
err = json.Unmarshal(jsonBytes, &models)
|
||||
require.NoError(c.t, err)
|
||||
// for some reason we may get the nonUTC timestamps in models,
|
||||
// fix that
|
||||
for midx := range models {
|
||||
models[midx].Timestamp = models[midx].Timestamp.UTC()
|
||||
for aidx := range models[midx].Annotations {
|
||||
models[midx].Annotations[aidx].Timestamp = models[midx].Annotations[aidx].Timestamp.UTC()
|
||||
}
|
||||
}
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
c.models = append(c.models, models...)
|
||||
}
|
||||
|
||||
func (c *mockZipkinCollector) Close() {
|
||||
if c.closing {
|
||||
return
|
||||
}
|
||||
c.closing = true
|
||||
server := c.server
|
||||
c.server = nil
|
||||
require.NoError(c.t, server.Shutdown(context.Background()))
|
||||
c.wg.Wait()
|
||||
}
|
||||
|
||||
func (c *mockZipkinCollector) ModelsLen() int {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
return len(c.models)
|
||||
}
|
||||
|
||||
func (c *mockZipkinCollector) StealModels() []zkmodel.SpanModel {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
models := c.models
|
||||
c.models = nil
|
||||
return models
|
||||
}
|
||||
|
||||
type logStore struct {
|
||||
T *testing.T
|
||||
Messages []string
|
||||
}
|
||||
|
||||
func (s *logStore) Write(p []byte) (n int, err error) {
|
||||
msg := (string)(p)
|
||||
if s.T != nil {
|
||||
s.T.Logf("%s", msg)
|
||||
}
|
||||
s.Messages = append(s.Messages, msg)
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func logStoreLogger(s *logStore) *log.Logger {
|
||||
return log.New(s, "", 0)
|
||||
}
|
||||
|
||||
func TestExportSpans(t *testing.T) {
|
||||
spans := []*export.SpanData{
|
||||
// parent
|
||||
{
|
||||
SpanContext: core.SpanContext{
|
||||
TraceID: core.TraceID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
|
||||
SpanID: core.SpanID{0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8},
|
||||
},
|
||||
ParentSpanID: core.SpanID{},
|
||||
SpanKind: trace.SpanKindServer,
|
||||
Name: "foo",
|
||||
StartTime: time.Date(2020, time.March, 11, 19, 24, 0, 0, time.UTC),
|
||||
EndTime: time.Date(2020, time.March, 11, 19, 25, 0, 0, time.UTC),
|
||||
Attributes: nil,
|
||||
MessageEvents: nil,
|
||||
StatusCode: codes.NotFound,
|
||||
StatusMessage: "404, file not found",
|
||||
},
|
||||
// child
|
||||
{
|
||||
SpanContext: core.SpanContext{
|
||||
TraceID: core.TraceID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
|
||||
SpanID: core.SpanID{0xDF, 0xDE, 0xDD, 0xDC, 0xDB, 0xDA, 0xD9, 0xD8},
|
||||
},
|
||||
ParentSpanID: core.SpanID{0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8},
|
||||
SpanKind: trace.SpanKindServer,
|
||||
Name: "bar",
|
||||
StartTime: time.Date(2020, time.March, 11, 19, 24, 15, 0, time.UTC),
|
||||
EndTime: time.Date(2020, time.March, 11, 19, 24, 45, 0, time.UTC),
|
||||
Attributes: nil,
|
||||
MessageEvents: nil,
|
||||
StatusCode: codes.PermissionDenied,
|
||||
StatusMessage: "403, forbidden",
|
||||
},
|
||||
}
|
||||
models := []zkmodel.SpanModel{
|
||||
// model of parent
|
||||
{
|
||||
SpanContext: zkmodel.SpanContext{
|
||||
TraceID: zkmodel.TraceID{
|
||||
High: 0x001020304050607,
|
||||
Low: 0x8090a0b0c0d0e0f,
|
||||
},
|
||||
ID: zkmodel.ID(0xfffefdfcfbfaf9f8),
|
||||
ParentID: nil,
|
||||
Debug: false,
|
||||
Sampled: nil,
|
||||
Err: nil,
|
||||
},
|
||||
Name: "foo",
|
||||
Kind: "SERVER",
|
||||
Timestamp: time.Date(2020, time.March, 11, 19, 24, 0, 0, time.UTC),
|
||||
Duration: time.Minute,
|
||||
Shared: false,
|
||||
LocalEndpoint: nil,
|
||||
RemoteEndpoint: nil,
|
||||
Annotations: nil,
|
||||
Tags: map[string]string{
|
||||
"ot.status_code": "NotFound",
|
||||
"ot.status_description": "404, file not found",
|
||||
},
|
||||
},
|
||||
// model of child
|
||||
{
|
||||
SpanContext: zkmodel.SpanContext{
|
||||
TraceID: zkmodel.TraceID{
|
||||
High: 0x001020304050607,
|
||||
Low: 0x8090a0b0c0d0e0f,
|
||||
},
|
||||
ID: zkmodel.ID(0xdfdedddcdbdad9d8),
|
||||
ParentID: zkmodelIDPtr(0xfffefdfcfbfaf9f8),
|
||||
Debug: false,
|
||||
Sampled: nil,
|
||||
Err: nil,
|
||||
},
|
||||
Name: "bar",
|
||||
Kind: "SERVER",
|
||||
Timestamp: time.Date(2020, time.March, 11, 19, 24, 15, 0, time.UTC),
|
||||
Duration: 30 * time.Second,
|
||||
Shared: false,
|
||||
LocalEndpoint: nil,
|
||||
RemoteEndpoint: nil,
|
||||
Annotations: nil,
|
||||
Tags: map[string]string{
|
||||
"ot.status_code": "PermissionDenied",
|
||||
"ot.status_description": "403, forbidden",
|
||||
},
|
||||
},
|
||||
}
|
||||
require.Len(t, models, len(spans))
|
||||
collector := startMockZipkinCollector(t)
|
||||
defer collector.Close()
|
||||
ls := &logStore{T: t}
|
||||
logger := logStoreLogger(ls)
|
||||
exporter, err := NewExporter(collector.url, WithLogger(logger))
|
||||
require.NoError(t, err)
|
||||
ctx := context.Background()
|
||||
require.Len(t, ls.Messages, 0)
|
||||
exporter.ExportSpans(ctx, spans[0:1])
|
||||
require.Len(t, ls.Messages, 2)
|
||||
require.Contains(t, ls.Messages[0], "send a POST request")
|
||||
require.Contains(t, ls.Messages[1], "zipkin responded")
|
||||
ls.Messages = nil
|
||||
exporter.ExportSpans(ctx, nil)
|
||||
require.Len(t, ls.Messages, 1)
|
||||
require.Contains(t, ls.Messages[0], "no spans to export")
|
||||
ls.Messages = nil
|
||||
exporter.ExportSpans(ctx, spans[1:2])
|
||||
require.Contains(t, ls.Messages[0], "send a POST request")
|
||||
require.Contains(t, ls.Messages[1], "zipkin responded")
|
||||
checkFunc := func() bool {
|
||||
return collector.ModelsLen() == len(models)
|
||||
}
|
||||
require.Eventually(t, checkFunc, time.Second, 10*time.Millisecond)
|
||||
require.Equal(t, models, collector.StealModels())
|
||||
}
|
12
exporters/trace/zipkin/go.mod
Normal file
12
exporters/trace/zipkin/go.mod
Normal file
@ -0,0 +1,12 @@
|
||||
module go.opentelemetry.go/otel/exporters/trace/zipkin
|
||||
|
||||
go 1.13
|
||||
|
||||
replace go.opentelemetry.io/otel => ../../..
|
||||
|
||||
require (
|
||||
github.com/openzipkin/zipkin-go v0.2.2
|
||||
github.com/stretchr/testify v1.4.0
|
||||
go.opentelemetry.io/otel v0.2.3
|
||||
google.golang.org/grpc v1.27.1
|
||||
)
|
101
exporters/trace/zipkin/go.sum
Normal file
101
exporters/trace/zipkin/go.sum
Normal file
@ -0,0 +1,101 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60=
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
github.com/benbjohnson/clock v1.0.0/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
|
||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/opentracing/opentracing-go v1.1.1-0.20190913142402-a7454ce5950e/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/openzipkin/zipkin-go v0.2.2 h1:nY8Hti+WKaP0cRsSeQ026wU03QsM762XBeCXBb9NAWI=
|
||||
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
|
||||
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03 h1:4HYDjxeNXAOTv3o1N2tjo8UUSlhQgAD52FVkwxnWgM8=
|
||||
google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
|
||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
131
exporters/trace/zipkin/model.go
Normal file
131
exporters/trace/zipkin/model.go
Normal file
@ -0,0 +1,131 @@
|
||||
package zipkin
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
zkmodel "github.com/openzipkin/zipkin-go/model"
|
||||
|
||||
"go.opentelemetry.io/otel/api/core"
|
||||
"go.opentelemetry.io/otel/api/trace"
|
||||
export "go.opentelemetry.io/otel/sdk/export/trace"
|
||||
)
|
||||
|
||||
func toZipkinSpanModels(batch []*export.SpanData) []zkmodel.SpanModel {
|
||||
models := make([]zkmodel.SpanModel, 0, len(batch))
|
||||
for _, data := range batch {
|
||||
models = append(models, toZipkinSpanModel(data))
|
||||
}
|
||||
return models
|
||||
}
|
||||
|
||||
func toZipkinSpanModel(data *export.SpanData) zkmodel.SpanModel {
|
||||
return zkmodel.SpanModel{
|
||||
SpanContext: toZipkinSpanContext(data),
|
||||
Name: data.Name,
|
||||
Kind: toZipkinKind(data.SpanKind),
|
||||
Timestamp: data.StartTime,
|
||||
Duration: data.EndTime.Sub(data.StartTime),
|
||||
Shared: false,
|
||||
LocalEndpoint: nil, // *Endpoint
|
||||
RemoteEndpoint: nil, // *Endpoint
|
||||
Annotations: toZipkinAnnotations(data.MessageEvents),
|
||||
Tags: toZipkinTags(data),
|
||||
}
|
||||
}
|
||||
|
||||
func toZipkinSpanContext(data *export.SpanData) zkmodel.SpanContext {
|
||||
return zkmodel.SpanContext{
|
||||
TraceID: toZipkinTraceID(data.SpanContext.TraceID),
|
||||
ID: toZipkinID(data.SpanContext.SpanID),
|
||||
ParentID: toZipkinParentID(data.ParentSpanID),
|
||||
Debug: false,
|
||||
Sampled: nil,
|
||||
Err: nil,
|
||||
}
|
||||
}
|
||||
|
||||
func toZipkinTraceID(traceID core.TraceID) zkmodel.TraceID {
|
||||
return zkmodel.TraceID{
|
||||
High: binary.BigEndian.Uint64(traceID[:8]),
|
||||
Low: binary.BigEndian.Uint64(traceID[8:]),
|
||||
}
|
||||
}
|
||||
|
||||
func toZipkinID(spanID core.SpanID) zkmodel.ID {
|
||||
return zkmodel.ID(binary.BigEndian.Uint64(spanID[:]))
|
||||
}
|
||||
|
||||
func toZipkinParentID(spanID core.SpanID) *zkmodel.ID {
|
||||
if spanID.IsValid() {
|
||||
id := toZipkinID(spanID)
|
||||
return &id
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func toZipkinKind(kind trace.SpanKind) zkmodel.Kind {
|
||||
switch kind {
|
||||
case trace.SpanKindUnspecified:
|
||||
return zkmodel.Undetermined
|
||||
case trace.SpanKindInternal:
|
||||
// The spec says we should set the kind to nil, but
|
||||
// the model does not allow that.
|
||||
return zkmodel.Undetermined
|
||||
case trace.SpanKindServer:
|
||||
return zkmodel.Server
|
||||
case trace.SpanKindClient:
|
||||
return zkmodel.Client
|
||||
case trace.SpanKindProducer:
|
||||
return zkmodel.Producer
|
||||
case trace.SpanKindConsumer:
|
||||
return zkmodel.Consumer
|
||||
}
|
||||
return zkmodel.Undetermined
|
||||
}
|
||||
|
||||
func toZipkinAnnotations(events []export.Event) []zkmodel.Annotation {
|
||||
if len(events) == 0 {
|
||||
return nil
|
||||
}
|
||||
annotations := make([]zkmodel.Annotation, 0, len(events))
|
||||
for _, event := range events {
|
||||
value := event.Name
|
||||
if len(event.Attributes) > 0 {
|
||||
jsonString := attributesToJSONMapString(event.Attributes)
|
||||
if jsonString != "" {
|
||||
value = fmt.Sprintf("%s: %s", event.Name, jsonString)
|
||||
}
|
||||
}
|
||||
annotations = append(annotations, zkmodel.Annotation{
|
||||
Timestamp: event.Time,
|
||||
Value: value,
|
||||
})
|
||||
}
|
||||
return annotations
|
||||
}
|
||||
|
||||
func attributesToJSONMapString(attributes []core.KeyValue) string {
|
||||
m := make(map[string]interface{}, len(attributes))
|
||||
for _, attribute := range attributes {
|
||||
m[(string)(attribute.Key)] = attribute.Value.AsInterface()
|
||||
}
|
||||
// if an error happens, the result will be an empty string
|
||||
jsonBytes, _ := json.Marshal(m)
|
||||
return (string)(jsonBytes)
|
||||
}
|
||||
|
||||
func toZipkinTags(data *export.SpanData) map[string]string {
|
||||
// +2 for status code and for status message
|
||||
m := make(map[string]string, len(data.Attributes)+2)
|
||||
for _, kv := range data.Attributes {
|
||||
m[(string)(kv.Key)] = kv.Value.Emit()
|
||||
}
|
||||
if v, ok := m["error"]; ok && v == "false" {
|
||||
delete(m, "error")
|
||||
}
|
||||
m["ot.status_code"] = data.StatusCode.String()
|
||||
m["ot.status_description"] = data.StatusMessage
|
||||
return m
|
||||
}
|
627
exporters/trace/zipkin/model_test.go
Normal file
627
exporters/trace/zipkin/model_test.go
Normal file
@ -0,0 +1,627 @@
|
||||
package zipkin
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
zkmodel "github.com/openzipkin/zipkin-go/model"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc/codes"
|
||||
|
||||
"go.opentelemetry.io/otel/api/core"
|
||||
"go.opentelemetry.io/otel/api/key"
|
||||
"go.opentelemetry.io/otel/api/trace"
|
||||
export "go.opentelemetry.io/otel/sdk/export/trace"
|
||||
)
|
||||
|
||||
func TestModelConversion(t *testing.T) {
|
||||
inputBatch := []*export.SpanData{
|
||||
// typical span data
|
||||
{
|
||||
SpanContext: core.SpanContext{
|
||||
TraceID: core.TraceID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
|
||||
SpanID: core.SpanID{0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8},
|
||||
},
|
||||
ParentSpanID: core.SpanID{0x3F, 0x3E, 0x3D, 0x3C, 0x3B, 0x3A, 0x39, 0x38},
|
||||
SpanKind: trace.SpanKindServer,
|
||||
Name: "foo",
|
||||
StartTime: time.Date(2020, time.March, 11, 19, 24, 0, 0, time.UTC),
|
||||
EndTime: time.Date(2020, time.March, 11, 19, 25, 0, 0, time.UTC),
|
||||
Attributes: []core.KeyValue{
|
||||
key.Uint64("attr1", 42),
|
||||
key.String("attr2", "bar"),
|
||||
},
|
||||
MessageEvents: []export.Event{
|
||||
{
|
||||
Time: time.Date(2020, time.March, 11, 19, 24, 30, 0, time.UTC),
|
||||
Name: "ev1",
|
||||
Attributes: []core.KeyValue{
|
||||
key.Uint64("eventattr1", 123),
|
||||
},
|
||||
},
|
||||
{
|
||||
Time: time.Date(2020, time.March, 11, 19, 24, 45, 0, time.UTC),
|
||||
Name: "ev2",
|
||||
Attributes: nil,
|
||||
},
|
||||
},
|
||||
StatusCode: codes.NotFound,
|
||||
StatusMessage: "404, file not found",
|
||||
},
|
||||
// span data with no parent (same as typical, but has
|
||||
// invalid parent)
|
||||
{
|
||||
SpanContext: core.SpanContext{
|
||||
TraceID: core.TraceID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
|
||||
SpanID: core.SpanID{0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8},
|
||||
},
|
||||
ParentSpanID: core.SpanID{},
|
||||
SpanKind: trace.SpanKindServer,
|
||||
Name: "foo",
|
||||
StartTime: time.Date(2020, time.March, 11, 19, 24, 0, 0, time.UTC),
|
||||
EndTime: time.Date(2020, time.March, 11, 19, 25, 0, 0, time.UTC),
|
||||
Attributes: []core.KeyValue{
|
||||
key.Uint64("attr1", 42),
|
||||
key.String("attr2", "bar"),
|
||||
},
|
||||
MessageEvents: []export.Event{
|
||||
{
|
||||
Time: time.Date(2020, time.March, 11, 19, 24, 30, 0, time.UTC),
|
||||
Name: "ev1",
|
||||
Attributes: []core.KeyValue{
|
||||
key.Uint64("eventattr1", 123),
|
||||
},
|
||||
},
|
||||
{
|
||||
Time: time.Date(2020, time.March, 11, 19, 24, 45, 0, time.UTC),
|
||||
Name: "ev2",
|
||||
Attributes: nil,
|
||||
},
|
||||
},
|
||||
StatusCode: codes.NotFound,
|
||||
StatusMessage: "404, file not found",
|
||||
},
|
||||
// span data of unspecified kind
|
||||
{
|
||||
SpanContext: core.SpanContext{
|
||||
TraceID: core.TraceID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
|
||||
SpanID: core.SpanID{0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8},
|
||||
},
|
||||
ParentSpanID: core.SpanID{0x3F, 0x3E, 0x3D, 0x3C, 0x3B, 0x3A, 0x39, 0x38},
|
||||
SpanKind: trace.SpanKindUnspecified,
|
||||
Name: "foo",
|
||||
StartTime: time.Date(2020, time.March, 11, 19, 24, 0, 0, time.UTC),
|
||||
EndTime: time.Date(2020, time.March, 11, 19, 25, 0, 0, time.UTC),
|
||||
Attributes: []core.KeyValue{
|
||||
key.Uint64("attr1", 42),
|
||||
key.String("attr2", "bar"),
|
||||
},
|
||||
MessageEvents: []export.Event{
|
||||
{
|
||||
Time: time.Date(2020, time.March, 11, 19, 24, 30, 0, time.UTC),
|
||||
Name: "ev1",
|
||||
Attributes: []core.KeyValue{
|
||||
key.Uint64("eventattr1", 123),
|
||||
},
|
||||
},
|
||||
{
|
||||
Time: time.Date(2020, time.March, 11, 19, 24, 45, 0, time.UTC),
|
||||
Name: "ev2",
|
||||
Attributes: nil,
|
||||
},
|
||||
},
|
||||
StatusCode: codes.NotFound,
|
||||
StatusMessage: "404, file not found",
|
||||
},
|
||||
// span data of internal kind
|
||||
{
|
||||
SpanContext: core.SpanContext{
|
||||
TraceID: core.TraceID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
|
||||
SpanID: core.SpanID{0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8},
|
||||
},
|
||||
ParentSpanID: core.SpanID{0x3F, 0x3E, 0x3D, 0x3C, 0x3B, 0x3A, 0x39, 0x38},
|
||||
SpanKind: trace.SpanKindInternal,
|
||||
Name: "foo",
|
||||
StartTime: time.Date(2020, time.March, 11, 19, 24, 0, 0, time.UTC),
|
||||
EndTime: time.Date(2020, time.March, 11, 19, 25, 0, 0, time.UTC),
|
||||
Attributes: []core.KeyValue{
|
||||
key.Uint64("attr1", 42),
|
||||
key.String("attr2", "bar"),
|
||||
},
|
||||
MessageEvents: []export.Event{
|
||||
{
|
||||
Time: time.Date(2020, time.March, 11, 19, 24, 30, 0, time.UTC),
|
||||
Name: "ev1",
|
||||
Attributes: []core.KeyValue{
|
||||
key.Uint64("eventattr1", 123),
|
||||
},
|
||||
},
|
||||
{
|
||||
Time: time.Date(2020, time.March, 11, 19, 24, 45, 0, time.UTC),
|
||||
Name: "ev2",
|
||||
Attributes: nil,
|
||||
},
|
||||
},
|
||||
StatusCode: codes.NotFound,
|
||||
StatusMessage: "404, file not found",
|
||||
},
|
||||
// span data of client kind
|
||||
{
|
||||
SpanContext: core.SpanContext{
|
||||
TraceID: core.TraceID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
|
||||
SpanID: core.SpanID{0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8},
|
||||
},
|
||||
ParentSpanID: core.SpanID{0x3F, 0x3E, 0x3D, 0x3C, 0x3B, 0x3A, 0x39, 0x38},
|
||||
SpanKind: trace.SpanKindClient,
|
||||
Name: "foo",
|
||||
StartTime: time.Date(2020, time.March, 11, 19, 24, 0, 0, time.UTC),
|
||||
EndTime: time.Date(2020, time.March, 11, 19, 25, 0, 0, time.UTC),
|
||||
Attributes: []core.KeyValue{
|
||||
key.Uint64("attr1", 42),
|
||||
key.String("attr2", "bar"),
|
||||
},
|
||||
MessageEvents: []export.Event{
|
||||
{
|
||||
Time: time.Date(2020, time.March, 11, 19, 24, 30, 0, time.UTC),
|
||||
Name: "ev1",
|
||||
Attributes: []core.KeyValue{
|
||||
key.Uint64("eventattr1", 123),
|
||||
},
|
||||
},
|
||||
{
|
||||
Time: time.Date(2020, time.March, 11, 19, 24, 45, 0, time.UTC),
|
||||
Name: "ev2",
|
||||
Attributes: nil,
|
||||
},
|
||||
},
|
||||
StatusCode: codes.NotFound,
|
||||
StatusMessage: "404, file not found",
|
||||
},
|
||||
// span data of producer kind
|
||||
{
|
||||
SpanContext: core.SpanContext{
|
||||
TraceID: core.TraceID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
|
||||
SpanID: core.SpanID{0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8},
|
||||
},
|
||||
ParentSpanID: core.SpanID{0x3F, 0x3E, 0x3D, 0x3C, 0x3B, 0x3A, 0x39, 0x38},
|
||||
SpanKind: trace.SpanKindProducer,
|
||||
Name: "foo",
|
||||
StartTime: time.Date(2020, time.March, 11, 19, 24, 0, 0, time.UTC),
|
||||
EndTime: time.Date(2020, time.March, 11, 19, 25, 0, 0, time.UTC),
|
||||
Attributes: []core.KeyValue{
|
||||
key.Uint64("attr1", 42),
|
||||
key.String("attr2", "bar"),
|
||||
},
|
||||
MessageEvents: []export.Event{
|
||||
{
|
||||
Time: time.Date(2020, time.March, 11, 19, 24, 30, 0, time.UTC),
|
||||
Name: "ev1",
|
||||
Attributes: []core.KeyValue{
|
||||
key.Uint64("eventattr1", 123),
|
||||
},
|
||||
},
|
||||
{
|
||||
Time: time.Date(2020, time.March, 11, 19, 24, 45, 0, time.UTC),
|
||||
Name: "ev2",
|
||||
Attributes: nil,
|
||||
},
|
||||
},
|
||||
StatusCode: codes.NotFound,
|
||||
StatusMessage: "404, file not found",
|
||||
},
|
||||
// span data of consumer kind
|
||||
{
|
||||
SpanContext: core.SpanContext{
|
||||
TraceID: core.TraceID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
|
||||
SpanID: core.SpanID{0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8},
|
||||
},
|
||||
ParentSpanID: core.SpanID{0x3F, 0x3E, 0x3D, 0x3C, 0x3B, 0x3A, 0x39, 0x38},
|
||||
SpanKind: trace.SpanKindConsumer,
|
||||
Name: "foo",
|
||||
StartTime: time.Date(2020, time.March, 11, 19, 24, 0, 0, time.UTC),
|
||||
EndTime: time.Date(2020, time.March, 11, 19, 25, 0, 0, time.UTC),
|
||||
Attributes: []core.KeyValue{
|
||||
key.Uint64("attr1", 42),
|
||||
key.String("attr2", "bar"),
|
||||
},
|
||||
MessageEvents: []export.Event{
|
||||
{
|
||||
Time: time.Date(2020, time.March, 11, 19, 24, 30, 0, time.UTC),
|
||||
Name: "ev1",
|
||||
Attributes: []core.KeyValue{
|
||||
key.Uint64("eventattr1", 123),
|
||||
},
|
||||
},
|
||||
{
|
||||
Time: time.Date(2020, time.March, 11, 19, 24, 45, 0, time.UTC),
|
||||
Name: "ev2",
|
||||
Attributes: nil,
|
||||
},
|
||||
},
|
||||
StatusCode: codes.NotFound,
|
||||
StatusMessage: "404, file not found",
|
||||
},
|
||||
// span data with no events
|
||||
{
|
||||
SpanContext: core.SpanContext{
|
||||
TraceID: core.TraceID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
|
||||
SpanID: core.SpanID{0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8},
|
||||
},
|
||||
ParentSpanID: core.SpanID{0x3F, 0x3E, 0x3D, 0x3C, 0x3B, 0x3A, 0x39, 0x38},
|
||||
SpanKind: trace.SpanKindServer,
|
||||
Name: "foo",
|
||||
StartTime: time.Date(2020, time.March, 11, 19, 24, 0, 0, time.UTC),
|
||||
EndTime: time.Date(2020, time.March, 11, 19, 25, 0, 0, time.UTC),
|
||||
Attributes: []core.KeyValue{
|
||||
key.Uint64("attr1", 42),
|
||||
key.String("attr2", "bar"),
|
||||
},
|
||||
MessageEvents: nil,
|
||||
StatusCode: codes.NotFound,
|
||||
StatusMessage: "404, file not found",
|
||||
},
|
||||
// span data with an "error" attribute set to "false"
|
||||
{
|
||||
SpanContext: core.SpanContext{
|
||||
TraceID: core.TraceID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
|
||||
SpanID: core.SpanID{0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8},
|
||||
},
|
||||
ParentSpanID: core.SpanID{0x3F, 0x3E, 0x3D, 0x3C, 0x3B, 0x3A, 0x39, 0x38},
|
||||
SpanKind: trace.SpanKindServer,
|
||||
Name: "foo",
|
||||
StartTime: time.Date(2020, time.March, 11, 19, 24, 0, 0, time.UTC),
|
||||
EndTime: time.Date(2020, time.March, 11, 19, 25, 0, 0, time.UTC),
|
||||
Attributes: []core.KeyValue{
|
||||
key.String("error", "false"),
|
||||
},
|
||||
MessageEvents: []export.Event{
|
||||
{
|
||||
Time: time.Date(2020, time.March, 11, 19, 24, 30, 0, time.UTC),
|
||||
Name: "ev1",
|
||||
Attributes: []core.KeyValue{
|
||||
key.Uint64("eventattr1", 123),
|
||||
},
|
||||
},
|
||||
{
|
||||
Time: time.Date(2020, time.March, 11, 19, 24, 45, 0, time.UTC),
|
||||
Name: "ev2",
|
||||
Attributes: nil,
|
||||
},
|
||||
},
|
||||
StatusCode: codes.NotFound,
|
||||
StatusMessage: "404, file not found",
|
||||
},
|
||||
}
|
||||
|
||||
expectedOutputBatch := []zkmodel.SpanModel{
|
||||
// model for typical span data
|
||||
{
|
||||
SpanContext: zkmodel.SpanContext{
|
||||
TraceID: zkmodel.TraceID{
|
||||
High: 0x001020304050607,
|
||||
Low: 0x8090a0b0c0d0e0f,
|
||||
},
|
||||
ID: zkmodel.ID(0xfffefdfcfbfaf9f8),
|
||||
ParentID: zkmodelIDPtr(0x3f3e3d3c3b3a3938),
|
||||
Debug: false,
|
||||
Sampled: nil,
|
||||
Err: nil,
|
||||
},
|
||||
Name: "foo",
|
||||
Kind: "SERVER",
|
||||
Timestamp: time.Date(2020, time.March, 11, 19, 24, 0, 0, time.UTC),
|
||||
Duration: time.Minute,
|
||||
Shared: false,
|
||||
LocalEndpoint: nil,
|
||||
RemoteEndpoint: nil,
|
||||
Annotations: []zkmodel.Annotation{
|
||||
{
|
||||
Timestamp: time.Date(2020, time.March, 11, 19, 24, 30, 0, time.UTC),
|
||||
Value: `ev1: {"eventattr1":123}`,
|
||||
},
|
||||
{
|
||||
Timestamp: time.Date(2020, time.March, 11, 19, 24, 45, 0, time.UTC),
|
||||
Value: "ev2",
|
||||
},
|
||||
},
|
||||
Tags: map[string]string{
|
||||
"attr1": "42",
|
||||
"attr2": "bar",
|
||||
"ot.status_code": "NotFound",
|
||||
"ot.status_description": "404, file not found",
|
||||
},
|
||||
},
|
||||
// model for span data with no parent
|
||||
{
|
||||
SpanContext: zkmodel.SpanContext{
|
||||
TraceID: zkmodel.TraceID{
|
||||
High: 0x001020304050607,
|
||||
Low: 0x8090a0b0c0d0e0f,
|
||||
},
|
||||
ID: zkmodel.ID(0xfffefdfcfbfaf9f8),
|
||||
ParentID: nil,
|
||||
Debug: false,
|
||||
Sampled: nil,
|
||||
Err: nil,
|
||||
},
|
||||
Name: "foo",
|
||||
Kind: "SERVER",
|
||||
Timestamp: time.Date(2020, time.March, 11, 19, 24, 0, 0, time.UTC),
|
||||
Duration: time.Minute,
|
||||
Shared: false,
|
||||
LocalEndpoint: nil,
|
||||
RemoteEndpoint: nil,
|
||||
Annotations: []zkmodel.Annotation{
|
||||
{
|
||||
Timestamp: time.Date(2020, time.March, 11, 19, 24, 30, 0, time.UTC),
|
||||
Value: `ev1: {"eventattr1":123}`,
|
||||
},
|
||||
{
|
||||
Timestamp: time.Date(2020, time.March, 11, 19, 24, 45, 0, time.UTC),
|
||||
Value: "ev2",
|
||||
},
|
||||
},
|
||||
Tags: map[string]string{
|
||||
"attr1": "42",
|
||||
"attr2": "bar",
|
||||
"ot.status_code": "NotFound",
|
||||
"ot.status_description": "404, file not found",
|
||||
},
|
||||
},
|
||||
// model for span data of unspecified kind
|
||||
{
|
||||
SpanContext: zkmodel.SpanContext{
|
||||
TraceID: zkmodel.TraceID{
|
||||
High: 0x001020304050607,
|
||||
Low: 0x8090a0b0c0d0e0f,
|
||||
},
|
||||
ID: zkmodel.ID(0xfffefdfcfbfaf9f8),
|
||||
ParentID: zkmodelIDPtr(0x3f3e3d3c3b3a3938),
|
||||
Debug: false,
|
||||
Sampled: nil,
|
||||
Err: nil,
|
||||
},
|
||||
Name: "foo",
|
||||
Kind: "",
|
||||
Timestamp: time.Date(2020, time.March, 11, 19, 24, 0, 0, time.UTC),
|
||||
Duration: time.Minute,
|
||||
Shared: false,
|
||||
LocalEndpoint: nil,
|
||||
RemoteEndpoint: nil,
|
||||
Annotations: []zkmodel.Annotation{
|
||||
{
|
||||
Timestamp: time.Date(2020, time.March, 11, 19, 24, 30, 0, time.UTC),
|
||||
Value: `ev1: {"eventattr1":123}`,
|
||||
},
|
||||
{
|
||||
Timestamp: time.Date(2020, time.March, 11, 19, 24, 45, 0, time.UTC),
|
||||
Value: "ev2",
|
||||
},
|
||||
},
|
||||
Tags: map[string]string{
|
||||
"attr1": "42",
|
||||
"attr2": "bar",
|
||||
"ot.status_code": "NotFound",
|
||||
"ot.status_description": "404, file not found",
|
||||
},
|
||||
},
|
||||
// model for span data of internal kind
|
||||
{
|
||||
SpanContext: zkmodel.SpanContext{
|
||||
TraceID: zkmodel.TraceID{
|
||||
High: 0x001020304050607,
|
||||
Low: 0x8090a0b0c0d0e0f,
|
||||
},
|
||||
ID: zkmodel.ID(0xfffefdfcfbfaf9f8),
|
||||
ParentID: zkmodelIDPtr(0x3f3e3d3c3b3a3938),
|
||||
Debug: false,
|
||||
Sampled: nil,
|
||||
Err: nil,
|
||||
},
|
||||
Name: "foo",
|
||||
Kind: "",
|
||||
Timestamp: time.Date(2020, time.March, 11, 19, 24, 0, 0, time.UTC),
|
||||
Duration: time.Minute,
|
||||
Shared: false,
|
||||
LocalEndpoint: nil,
|
||||
RemoteEndpoint: nil,
|
||||
Annotations: []zkmodel.Annotation{
|
||||
{
|
||||
Timestamp: time.Date(2020, time.March, 11, 19, 24, 30, 0, time.UTC),
|
||||
Value: `ev1: {"eventattr1":123}`,
|
||||
},
|
||||
{
|
||||
Timestamp: time.Date(2020, time.March, 11, 19, 24, 45, 0, time.UTC),
|
||||
Value: "ev2",
|
||||
},
|
||||
},
|
||||
Tags: map[string]string{
|
||||
"attr1": "42",
|
||||
"attr2": "bar",
|
||||
"ot.status_code": "NotFound",
|
||||
"ot.status_description": "404, file not found",
|
||||
},
|
||||
},
|
||||
// model for span data of client kind
|
||||
{
|
||||
SpanContext: zkmodel.SpanContext{
|
||||
TraceID: zkmodel.TraceID{
|
||||
High: 0x001020304050607,
|
||||
Low: 0x8090a0b0c0d0e0f,
|
||||
},
|
||||
ID: zkmodel.ID(0xfffefdfcfbfaf9f8),
|
||||
ParentID: zkmodelIDPtr(0x3f3e3d3c3b3a3938),
|
||||
Debug: false,
|
||||
Sampled: nil,
|
||||
Err: nil,
|
||||
},
|
||||
Name: "foo",
|
||||
Kind: "CLIENT",
|
||||
Timestamp: time.Date(2020, time.March, 11, 19, 24, 0, 0, time.UTC),
|
||||
Duration: time.Minute,
|
||||
Shared: false,
|
||||
LocalEndpoint: nil,
|
||||
RemoteEndpoint: nil,
|
||||
Annotations: []zkmodel.Annotation{
|
||||
{
|
||||
Timestamp: time.Date(2020, time.March, 11, 19, 24, 30, 0, time.UTC),
|
||||
Value: `ev1: {"eventattr1":123}`,
|
||||
},
|
||||
{
|
||||
Timestamp: time.Date(2020, time.March, 11, 19, 24, 45, 0, time.UTC),
|
||||
Value: "ev2",
|
||||
},
|
||||
},
|
||||
Tags: map[string]string{
|
||||
"attr1": "42",
|
||||
"attr2": "bar",
|
||||
"ot.status_code": "NotFound",
|
||||
"ot.status_description": "404, file not found",
|
||||
},
|
||||
},
|
||||
// model for span data of producer kind
|
||||
{
|
||||
SpanContext: zkmodel.SpanContext{
|
||||
TraceID: zkmodel.TraceID{
|
||||
High: 0x001020304050607,
|
||||
Low: 0x8090a0b0c0d0e0f,
|
||||
},
|
||||
ID: zkmodel.ID(0xfffefdfcfbfaf9f8),
|
||||
ParentID: zkmodelIDPtr(0x3f3e3d3c3b3a3938),
|
||||
Debug: false,
|
||||
Sampled: nil,
|
||||
Err: nil,
|
||||
},
|
||||
Name: "foo",
|
||||
Kind: "PRODUCER",
|
||||
Timestamp: time.Date(2020, time.March, 11, 19, 24, 0, 0, time.UTC),
|
||||
Duration: time.Minute,
|
||||
Shared: false,
|
||||
LocalEndpoint: nil,
|
||||
RemoteEndpoint: nil,
|
||||
Annotations: []zkmodel.Annotation{
|
||||
{
|
||||
Timestamp: time.Date(2020, time.March, 11, 19, 24, 30, 0, time.UTC),
|
||||
Value: `ev1: {"eventattr1":123}`,
|
||||
},
|
||||
{
|
||||
Timestamp: time.Date(2020, time.March, 11, 19, 24, 45, 0, time.UTC),
|
||||
Value: "ev2",
|
||||
},
|
||||
},
|
||||
Tags: map[string]string{
|
||||
"attr1": "42",
|
||||
"attr2": "bar",
|
||||
"ot.status_code": "NotFound",
|
||||
"ot.status_description": "404, file not found",
|
||||
},
|
||||
},
|
||||
// model for span data of consumer kind
|
||||
{
|
||||
SpanContext: zkmodel.SpanContext{
|
||||
TraceID: zkmodel.TraceID{
|
||||
High: 0x001020304050607,
|
||||
Low: 0x8090a0b0c0d0e0f,
|
||||
},
|
||||
ID: zkmodel.ID(0xfffefdfcfbfaf9f8),
|
||||
ParentID: zkmodelIDPtr(0x3f3e3d3c3b3a3938),
|
||||
Debug: false,
|
||||
Sampled: nil,
|
||||
Err: nil,
|
||||
},
|
||||
Name: "foo",
|
||||
Kind: "CONSUMER",
|
||||
Timestamp: time.Date(2020, time.March, 11, 19, 24, 0, 0, time.UTC),
|
||||
Duration: time.Minute,
|
||||
Shared: false,
|
||||
LocalEndpoint: nil,
|
||||
RemoteEndpoint: nil,
|
||||
Annotations: []zkmodel.Annotation{
|
||||
{
|
||||
Timestamp: time.Date(2020, time.March, 11, 19, 24, 30, 0, time.UTC),
|
||||
Value: `ev1: {"eventattr1":123}`,
|
||||
},
|
||||
{
|
||||
Timestamp: time.Date(2020, time.March, 11, 19, 24, 45, 0, time.UTC),
|
||||
Value: "ev2",
|
||||
},
|
||||
},
|
||||
Tags: map[string]string{
|
||||
"attr1": "42",
|
||||
"attr2": "bar",
|
||||
"ot.status_code": "NotFound",
|
||||
"ot.status_description": "404, file not found",
|
||||
},
|
||||
},
|
||||
// model for span data with no events
|
||||
{
|
||||
SpanContext: zkmodel.SpanContext{
|
||||
TraceID: zkmodel.TraceID{
|
||||
High: 0x001020304050607,
|
||||
Low: 0x8090a0b0c0d0e0f,
|
||||
},
|
||||
ID: zkmodel.ID(0xfffefdfcfbfaf9f8),
|
||||
ParentID: zkmodelIDPtr(0x3f3e3d3c3b3a3938),
|
||||
Debug: false,
|
||||
Sampled: nil,
|
||||
Err: nil,
|
||||
},
|
||||
Name: "foo",
|
||||
Kind: "SERVER",
|
||||
Timestamp: time.Date(2020, time.March, 11, 19, 24, 0, 0, time.UTC),
|
||||
Duration: time.Minute,
|
||||
Shared: false,
|
||||
LocalEndpoint: nil,
|
||||
RemoteEndpoint: nil,
|
||||
Annotations: nil,
|
||||
Tags: map[string]string{
|
||||
"attr1": "42",
|
||||
"attr2": "bar",
|
||||
"ot.status_code": "NotFound",
|
||||
"ot.status_description": "404, file not found",
|
||||
},
|
||||
},
|
||||
// model for span data with an "error" attribute set to "false"
|
||||
{
|
||||
SpanContext: zkmodel.SpanContext{
|
||||
TraceID: zkmodel.TraceID{
|
||||
High: 0x001020304050607,
|
||||
Low: 0x8090a0b0c0d0e0f,
|
||||
},
|
||||
ID: zkmodel.ID(0xfffefdfcfbfaf9f8),
|
||||
ParentID: zkmodelIDPtr(0x3f3e3d3c3b3a3938),
|
||||
Debug: false,
|
||||
Sampled: nil,
|
||||
Err: nil,
|
||||
},
|
||||
Name: "foo",
|
||||
Kind: "SERVER",
|
||||
Timestamp: time.Date(2020, time.March, 11, 19, 24, 0, 0, time.UTC),
|
||||
Duration: time.Minute,
|
||||
Shared: false,
|
||||
LocalEndpoint: nil,
|
||||
RemoteEndpoint: nil,
|
||||
Annotations: []zkmodel.Annotation{
|
||||
{
|
||||
Timestamp: time.Date(2020, time.March, 11, 19, 24, 30, 0, time.UTC),
|
||||
Value: `ev1: {"eventattr1":123}`,
|
||||
},
|
||||
{
|
||||
Timestamp: time.Date(2020, time.March, 11, 19, 24, 45, 0, time.UTC),
|
||||
Value: "ev2",
|
||||
},
|
||||
},
|
||||
Tags: map[string]string{
|
||||
"ot.status_code": "NotFound",
|
||||
"ot.status_description": "404, file not found",
|
||||
},
|
||||
},
|
||||
}
|
||||
gottenOutputBatch := toZipkinSpanModels(inputBatch)
|
||||
require.Equal(t, expectedOutputBatch, gottenOutputBatch)
|
||||
}
|
||||
|
||||
func zkmodelIDPtr(n uint64) *zkmodel.ID {
|
||||
id := zkmodel.ID(n)
|
||||
return &id
|
||||
}
|
101
exporters/trace/zipkin/zipkin.go
Normal file
101
exporters/trace/zipkin/zipkin.go
Normal file
@ -0,0 +1,101 @@
|
||||
package zipkin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
export "go.opentelemetry.io/otel/sdk/export/trace"
|
||||
)
|
||||
|
||||
// Exporter exports SpanData to the zipkin collector. It implements
|
||||
// the SpanBatcher interface, so it needs to be used together with the
|
||||
// WithBatcher option when setting up the exporter pipeline.
|
||||
type Exporter struct {
|
||||
url string
|
||||
client *http.Client
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
var (
|
||||
_ export.SpanBatcher = &Exporter{}
|
||||
)
|
||||
|
||||
// Options contains configuration for the exporter.
|
||||
type Options struct {
|
||||
client *http.Client
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
// Option defines a function that configures the exporter.
|
||||
type Option func(*Options)
|
||||
|
||||
// WithLogger configures the exporter to use the passed logger.
|
||||
func WithLogger(logger *log.Logger) Option {
|
||||
return func(opts *Options) {
|
||||
opts.logger = logger
|
||||
}
|
||||
}
|
||||
|
||||
// WithClient configures the exporter to use the passed HTTP client.
|
||||
func WithClient(client *http.Client) Option {
|
||||
return func(opts *Options) {
|
||||
opts.client = client
|
||||
}
|
||||
}
|
||||
|
||||
// NewExporter creates a new zipkin exporter.
|
||||
func NewExporter(collectorURL string, os ...Option) (*Exporter, error) {
|
||||
if _, err := url.Parse(collectorURL); err != nil {
|
||||
return nil, fmt.Errorf("invalid collector URL: %v", err)
|
||||
}
|
||||
opts := Options{}
|
||||
for _, o := range os {
|
||||
o(&opts)
|
||||
}
|
||||
if opts.client == nil {
|
||||
opts.client = http.DefaultClient
|
||||
}
|
||||
return &Exporter{
|
||||
url: collectorURL,
|
||||
client: opts.client,
|
||||
logger: opts.logger,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ExportSpans is a part of an implementation of the SpanBatcher
|
||||
// interface.
|
||||
func (e *Exporter) ExportSpans(ctx context.Context, batch []*export.SpanData) {
|
||||
if len(batch) == 0 {
|
||||
e.logf("no spans to export")
|
||||
return
|
||||
}
|
||||
models := toZipkinSpanModels(batch)
|
||||
body, err := json.Marshal(models)
|
||||
if err != nil {
|
||||
e.logf("failed to serialize zipkin models to JSON: %v", err)
|
||||
return
|
||||
}
|
||||
e.logf("about to send a POST request to %s with body %s", e.url, body)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, e.url, bytes.NewBuffer(body))
|
||||
if err != nil {
|
||||
e.logf("failed to create request to %s: %v", e.url, err)
|
||||
return
|
||||
}
|
||||
resp, err := e.client.Do(req)
|
||||
if err != nil {
|
||||
e.logf("request to %s failed: %v", e.url, err)
|
||||
return
|
||||
}
|
||||
e.logf("zipkin responded with status %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
func (e *Exporter) logf(format string, args ...interface{}) {
|
||||
if e.logger != nil {
|
||||
e.logger.Printf(format, args...)
|
||||
}
|
||||
}
|
@ -173,7 +173,7 @@ func WithSyncer(syncer export.SpanSyncer) ProviderOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithBatch options appends the batcher to the existing list of Batchers.
|
||||
// WithBatcher options appends the batcher to the existing list of Batchers.
|
||||
// This option can be used multiple times.
|
||||
// The Batchers are wrapped into BatchedSpanProcessors and registered
|
||||
// with the provider.
|
||||
|
Reference in New Issue
Block a user