You've already forked opentelemetry-go
							
							
				mirror of
				https://github.com/open-telemetry/opentelemetry-go.git
				synced 2025-10-31 00:07:40 +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