From 058ff83ec7d93f42bfe51003ece158a2693be110 Mon Sep 17 00:00:00 2001 From: fjviera Date: Mon, 17 Dec 2018 00:41:19 +0100 Subject: [PATCH 01/18] Add GCP pubsub reporter --- README.md | 3 + go.mod | 12 ++-- go.sum | 39 +++++++++++-- reporter/pubsub/pubsub.go | 102 +++++++++++++++++++++++++++++++++ reporter/pubsub/pubsub_test.go | 81 ++++++++++++++++++++++++++ 5 files changed, 228 insertions(+), 9 deletions(-) create mode 100644 reporter/pubsub/pubsub.go create mode 100644 reporter/pubsub/pubsub_test.go diff --git a/README.md b/README.md index 8ebf387..7c02629 100644 --- a/README.md +++ b/README.md @@ -75,5 +75,8 @@ Producer digesting JSON V2 Spans. The reporter uses the [Sarama async producer](https://godoc.org/github.com/Shopify/sarama#AsyncProducer) underneath. +#### GCP Pubsub Reporter +Reporter transporting Spans to the Zipkin server using a Pubsub + ## usage and examples [HTTP Server Example](example_httpserver_test.go) diff --git a/go.mod b/go.mod index 8792447..0fa13da 100644 --- a/go.mod +++ b/go.mod @@ -1,29 +1,31 @@ module github.com/openzipkin/zipkin-go require ( - cloud.google.com/go v0.33.1 // indirect + cloud.google.com/go v0.33.1 + github.com/GoogleCloudPlatform/golang-samples v0.0.0-20181215195521-297d1a46ea5c github.com/Shopify/sarama v1.19.0 + github.com/Shopify/toxiproxy v2.1.3+incompatible // indirect + github.com/broady/preprocess v0.0.0-20171110231930-97e2c618d939 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/eapache/go-resiliency v1.1.0 // indirect github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 // indirect github.com/eapache/queue v1.1.0 // indirect - github.com/golang/lint v0.0.0-20181026193005-c67002cb31c3 // indirect github.com/golang/protobuf v1.2.0 github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect + github.com/googleapis/gax-go v2.0.2+incompatible // indirect github.com/gorilla/context v1.1.1 // indirect github.com/gorilla/mux v1.6.2 github.com/onsi/ginkgo v1.7.0 github.com/onsi/gomega v1.4.3 github.com/pierrec/lz4 v2.0.5+incompatible // indirect github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a // indirect - golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3 // indirect + go.opencensus.io v0.18.0 // indirect golang.org/x/net v0.0.0-20181114220301-adae6a3d119a golang.org/x/oauth2 v0.0.0-20181120190819-8f65e3013eba // indirect golang.org/x/sync v0.0.0-20181108010431-42b317875d0f // indirect golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b // indirect - golang.org/x/tools v0.0.0-20181122213734-04b5d21e00f1 // indirect + google.golang.org/api v0.0.0-20181214000652-874d9dc5b186 // indirect google.golang.org/appengine v1.3.0 // indirect google.golang.org/genproto v0.0.0-20181109154231-b5d43981345b // indirect google.golang.org/grpc v1.16.0 - honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3 // indirect ) diff --git a/go.sum b/go.sum index 6089c37..dcaba25 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,16 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.33.1 h1:fmJQWZ1w9PGkHR1YL/P7HloDvqlmKQ4Vpb7PC2e+aCk= cloud.google.com/go v0.33.1/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= +github.com/GoogleCloudPlatform/golang-samples v0.0.0-20181215195521-297d1a46ea5c h1:p3yZr4LvJwrnolRDKsGIGY+TrIlxYD/m0f4HBqbN3HI= +github.com/GoogleCloudPlatform/golang-samples v0.0.0-20181215195521-297d1a46ea5c/go.mod h1:uHbVFn4F4dmm4WDqqSMNTRLJFfbsz85Ii/WWrdNUFt0= github.com/Shopify/sarama v1.19.0 h1:9oksLxC6uxVPHPVYUmq6xhr1BOF/hHobWH2UzO67z1s= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.3+incompatible h1:awiJqUYH4q4OmoBiRccJykjd7B+w0loJi2keSna4X/M= +github.com/Shopify/toxiproxy v2.1.3+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/broady/preprocess v0.0.0-20171110231930-97e2c618d939 h1:8Fs/HOZk/B0pjd2Y6ljIS3iCeIuVI2LH008bSkiX9KA= +github.com/broady/preprocess v0.0.0-20171110231930-97e2c618d939/go.mod h1:Abcc+rG702b+/4ggRB+cg3KciUCeUKl9Lh/nbvImdfc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 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= @@ -11,33 +20,47 @@ github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8 github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= -github.com/golang/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/googleapis/gax-go v2.0.2+incompatible h1:silFMLAnr330+NRuag/VjIGF7TLp/LBrV2CJKFLWEww= +github.com/googleapis/gax-go v2.0.2+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +go.opencensus.io v0.18.0 h1:Mk5rgZcggtbvtAun5aJzAtjKKN/t0R3jJPlWILlv938= +go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d h1:g9qWBGx4puODJTMVyoPrpoxPFgVGd+z1DZwjfRu4d0I= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -46,8 +69,10 @@ golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20181114220301-adae6a3d119a h1:gOpx8G595UYyvj8UK4+OFyY4rx037g3fmfhe5SasG3U= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181120190819-8f65e3013eba h1:YDkOrzGLLYybtuP6ZgebnO4OWYEYVMFSniazXsxrFN8= golang.org/x/oauth2 v0.0.0-20181120190819-8f65e3013eba/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 h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/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= @@ -55,15 +80,22 @@ golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b h1:MQE+LT/ABUuuvEZ+YQAMSXind golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52 h1:JG/0uqcGdTNgq7FdU+61l5Pdmb8putNZlXb65bJBROs= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181122213734-04b5d21e00f1/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.0.0-20181214000652-874d9dc5b186 h1:ZDe3pYgXQh0A6sgLlIejBym8PY8oYcX8dY7pwKS8+Yk= +google.golang.org/api v0.0.0-20181214000652-874d9dc5b186/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.3.0 h1:FBSsiFRMz3LBeXIomRnVzrQwSDj4ibvcRexLG0LZGQk= google.golang.org/appengine v1.3.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-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20181109154231-b5d43981345b h1:WkFtVmaZoTRVoRYr0LTC9SYNhlw0X0HrVPz2OVssVm4= google.golang.org/genproto v0.0.0-20181109154231-b5d43981345b/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0 h1:dz5IJGuC2BB7qXR5AyHNwAUBhZscK2xVez7mznh72sY= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= @@ -72,4 +104,3 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/reporter/pubsub/pubsub.go b/reporter/pubsub/pubsub.go new file mode 100644 index 0000000..2045fad --- /dev/null +++ b/reporter/pubsub/pubsub.go @@ -0,0 +1,102 @@ +package pubsub + +import ( + "cloud.google.com/go/pubsub" + "context" + "encoding/json" + "github.com/openzipkin/zipkin-go/model" + "github.com/openzipkin/zipkin-go/reporter" + "log" + "os" +) + +const defaultPubSubTopic = "pubsub" + +// PubSubReporter implements Reporter by publishing spans to a GCP pubsub. +type PubSubReporter struct { + logger *log.Logger + topic string + client *pubsub.Client +} + +// ReporterOption sets a parameter for the PubSubReporter +type ReporterOption func(c *PubSubReporter) + +func (r *PubSubReporter) Send(s model.SpanModel) { + // Zipkin expects the message to be wrapped in an array + ss := []model.SpanModel{s} + m, err := json.Marshal(ss) + if err != nil { + r.logger.Printf("failed when marshalling the span: %s\n", err.Error()) + return + } + err = r.publish(m) + if err != nil { + r.logger.Printf("Error publishing message to pubsub: %s msg: %s", err.Error(), string(m)) + } +} + +func (r *PubSubReporter) Close() error { + return r.client.Close() +} + +// Logger sets the logger used to report errors in the collection +// process. +func Logger(logger *log.Logger) ReporterOption { + return func(c *PubSubReporter) { + c.logger = logger + } +} + +// Client sets the client used to produce to pubsub. +func Client(client *pubsub.Client) ReporterOption { + return func(c *PubSubReporter) { + c.client = client + } +} + +// Topic sets the kafka topic to attach the reporter producer on. +func Topic(t string) ReporterOption { + return func(c *PubSubReporter) { + c.topic = t + } +} + +// NewReporter returns a new Kafka-backed Reporter. address should be a slice of +// TCP endpoints of the form "host:port". +func NewReporter(options ...ReporterOption) (reporter.Reporter, error) { + r := &PubSubReporter{ + logger: log.New(os.Stderr, "", log.LstdFlags), + topic: defaultPubSubTopic, + } + + for _, option := range options { + option(r) + } + if r.client == nil { + ctx := context.Background() + proj := os.Getenv("GOOGLE_CLOUD_PROJECT") + if proj == "" { + log.Fatal("GOOGLE_CLOUD_PROJECT environment variable must be set. Traces wont be sent to pubsub") + } + client, err := pubsub.NewClient(ctx, proj) + if err != nil { + log.Fatalf("Could not create pubsub Client: %v", err) + } + r.client = client + } + + return r, nil +} + +func (r *PubSubReporter) publish(msg []byte) error { + ctx := context.Background() + t := r.client.Topic(r.topic) + + result := t.Publish(ctx, &pubsub.Message{ + // data must be a ByteString + Data: msg, + }) + _, err := result.Get(ctx) + return err +} diff --git a/reporter/pubsub/pubsub_test.go b/reporter/pubsub/pubsub_test.go new file mode 100644 index 0000000..a4fbf8d --- /dev/null +++ b/reporter/pubsub/pubsub_test.go @@ -0,0 +1,81 @@ +package pubsub + +import ( + "context" + "fmt" + "github.com/openzipkin/zipkin-go/model" + "os" + "sync" + "testing" + "time" + + "cloud.google.com/go/pubsub" +) + +var topicID string + +var once sync.Once // guards cleanup related operations in setup. + +func setup(t *testing.T) *pubsub.Client { + ctx := context.Background() + proj := os.Getenv("GOOGLE_CLOUD_PROJECT") + topicID = "test-topic" + + client, err := pubsub.NewClient(ctx, proj) + if err != nil { + t.Fatalf("failed to create client: %v", err) + } + + _, err = client.CreateTopic(ctx, topicID) + if err != nil { + t.Fatalf("failed to create topic: %v", err) + } + fmt.Printf("Topic created: %s\n", t.Name()) + return client +} + +func TestPublish(t *testing.T) { + c := setup(t) + reporter, err := NewReporter(Client(c), Topic(topicID)) + if err != nil { + t.Errorf("failed creating reporter: %v", err) + } + span := makeNewSpan("avg", 123, 456, 0, true) + reporter.Send(*span) + + // Cleanup resources from the previous failed tests. + once.Do(func() { + ctx := context.Background() + topic := c.Topic(topicID) + ok, err := topic.Exists(ctx) + if err != nil { + t.Fatalf("failed to check if topic exists: %v", err) + } + if !ok { + return + } + if err := topic.Delete(ctx); err != nil { + t.Fatalf("failed to cleanup the topic (%q): %v", topicID, err) + } + }) +} + +func makeNewSpan(methodName string, traceID, spanID, parentSpanID uint64, debug bool) *model.SpanModel { + timestamp := time.Now() + var parentID = new(model.ID) + if parentSpanID != 0 { + *parentID = model.ID(parentSpanID) + } + + return &model.SpanModel{ + SpanContext: model.SpanContext{ + TraceID: model.TraceID{Low: traceID}, + ID: model.ID(spanID), + ParentID: parentID, + Debug: debug, + }, + Name: methodName, + Timestamp: timestamp, + } +} + From 092e9418b225e120e2d653cfdea758cd23d50301 Mon Sep 17 00:00:00 2001 From: "javier.viera" Date: Tue, 6 Aug 2019 08:56:47 +0200 Subject: [PATCH 02/18] Merge remote-tracking branch 'upstream/master' # Conflicts: # go.mod # go.sum --- .travis.yml | 31 ++- README.md | 25 ++- appveyor.yml | 2 - bench_test.go | 14 ++ circle.yml | 5 + context.go | 29 +++ context_test.go | 39 ++++ doc.go | 14 ++ endpoint.go | 14 ++ endpoint_test.go | 14 ++ example_httpserver_test.go | 14 ++ example_test.go | 14 ++ go.mod | 22 +- go.sum | 78 ++----- idgenerator/idgenerator.go | 14 ++ idgenerator/idgenerator_test.go | 14 ++ middleware/grpc/client.go | 60 ++--- middleware/grpc/client_test.go | 37 ++- middleware/grpc/doc.go | 14 +- middleware/grpc/grpc_suite_test.go | 81 ++++++- middleware/grpc/server.go | 91 ++++++++ middleware/grpc/server_test.go | 176 +++++++++++++++ middleware/grpc/shared.go | 58 ++++- middleware/http/client.go | 14 ++ middleware/http/client_test.go | 14 ++ middleware/http/doc.go | 14 ++ middleware/http/request_sampler.go | 21 ++ middleware/http/server.go | 45 +++- middleware/http/server_test.go | 33 ++- middleware/http/spancloser.go | 14 ++ middleware/http/spantrace.go | 14 ++ middleware/http/transport.go | 116 +++++++++- middleware/http/transport_test.go | 212 ++++++++++++++++++ model/annotation.go | 14 ++ model/annotation_test.go | 14 ++ model/doc.go | 14 ++ model/endpoint.go | 14 ++ model/endpoint_test.go | 14 ++ model/kind.go | 14 ++ model/span.go | 14 ++ model/span_id.go | 14 ++ model/span_test.go | 14 ++ model/traceid.go | 14 ++ model/traceid_test.go | 14 ++ noop.go | 16 ++ noop_test.go | 14 ++ propagation/b3/doc.go | 14 ++ propagation/b3/grpc.go | 14 ++ propagation/b3/grpc_test.go | 14 ++ propagation/b3/http.go | 107 +++++++-- propagation/b3/http_test.go | 85 +++++++ propagation/b3/shared.go | 20 ++ propagation/b3/spancontext.go | 126 +++++++++++ propagation/b3/spancontext_test.go | 125 +++++++++++ propagation/propagation.go | 14 ++ proto/testing/service.pb.go | 36 ++- proto/testing/service.proto | 4 +- .../v2/{convert_proto.go => decode_proto.go} | 8 +- ...ert_proto_test.go => decode_proto_test.go} | 2 +- proto/v2/encode_proto.go | 130 +++++++++++ proto/v2/encode_proto_test.go | 103 +++++++++ proto/v2/zipkin.proto | 2 +- reporter/amqp/amqp.go | 201 +++++++++++++++++ reporter/amqp/amqp_test.go | 135 +++++++++++ reporter/http/http.go | 37 ++- reporter/http/http_test.go | 14 ++ reporter/kafka/kafka.go | 36 ++- reporter/kafka/kafka_test.go | 34 +++ reporter/log/log.go | 14 ++ reporter/recorder/recorder.go | 14 ++ reporter/recorder/recorder_test.go | 14 ++ reporter/reporter.go | 14 ++ reporter/serializer.go | 42 ++++ sample.go | 18 +- sample_test.go | 14 ++ span.go | 20 ++ span_implementation.go | 23 ++ span_options.go | 14 ++ span_test.go | 14 ++ tags.go | 14 ++ tracer.go | 14 ++ tracer_options.go | 14 ++ tracer_test.go | 28 +++ 83 files changed, 2859 insertions(+), 214 deletions(-) create mode 100644 context_test.go create mode 100644 middleware/grpc/server.go create mode 100644 middleware/grpc/server_test.go create mode 100644 middleware/http/request_sampler.go create mode 100644 middleware/http/transport_test.go create mode 100644 propagation/b3/spancontext_test.go rename proto/v2/{convert_proto.go => decode_proto.go} (94%) rename proto/v2/{convert_proto_test.go => decode_proto_test.go} (99%) create mode 100644 proto/v2/encode_proto.go create mode 100644 proto/v2/encode_proto_test.go create mode 100644 reporter/amqp/amqp.go create mode 100644 reporter/amqp/amqp_test.go create mode 100644 reporter/serializer.go diff --git a/.travis.yml b/.travis.yml index e77869d..d440ed0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,25 @@ language: go +dist: trusty + +services: + - rabbitmq sudo: false -go: - - 1.9.x - - 1.10.x - - 1.11.x - - tip +matrix: + include: + - go: "1.9.x" + - go: "1.10.x" + - go: "1.11.x" + env: + - GO111MODULE=on + - go: "1.12.x" + env: + - GO111MODULE=on + - go: "tip" + env: + - GO111MODULE=on + before_install: - go get golang.org/x/tools/cmd/cover - go get github.com/mattn/goveralls @@ -18,3 +31,11 @@ install: script: - make test vet lint bench - $GOPATH/bin/goveralls -service=travis-ci + +notifications: + webhooks: + urls: + - https://webhooks.gitter.im/e/ead3c37d57527214e9f2 + - https://webhooks.gitter.im/e/e57478303f87ecd7bffc + on_success: change + on_failure: always diff --git a/README.md b/README.md index 7c02629..7f60195 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,30 @@ For convenience `NewClient` is provided which returns a HTTP Client which embeds calling the `DoWithAppSpan()` method. #### grpc -gRPC middleware / interceptors are planned for the near future. +Easy to use grpc.StatsHandler middleware are provided for tracing gRPC server and +client requests. + +For a server, pass `NewServerHandler` when calling `NewServer`, e.g., + +```go +import ( + "google.golang.org/grpc" + zipkingrpc "github.com/openzipkin/zipkin-go/middleware/grpc" +) + +server = grpc.NewServer(grpc.StatsHandler(zipkingrpc.NewServerHandler(tracer))) +``` + +For a client, pass `NewClientHandler` when calling `Dial`, e.g., + +```go +import ( + "google.golang.org/grpc" + zipkingrpc "github.com/openzipkin/zipkin-go/middleware/grpc" +) + +conn, err = grpc.Dial(addr, grpc.WithStatsHandler(zipkingrpc.NewClientHandler(tracer))) +``` ### reporter The reporter package holds the interface which the various Reporter diff --git a/appveyor.yml b/appveyor.yml index a567463..daaee03 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -10,8 +10,6 @@ environment: GOFLAGS: -mod=readonly install: - - echo %PATH% - - echo %GOPATH% - set PATH=%GOPATH%\bin;c:\go\bin;%PATH% - go version - go env diff --git a/bench_test.go b/bench_test.go index d2d5e01..89ff4a9 100644 --- a/bench_test.go +++ b/bench_test.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zipkin_test import ( diff --git a/circle.yml b/circle.yml index 808b54c..4e8b623 100644 --- a/circle.yml +++ b/circle.yml @@ -7,5 +7,10 @@ jobs: - image: circleci/golang steps: - checkout + - run: echo 'deb http://www.rabbitmq.com/debian/ testing main' | sudo tee /etc/apt/sources.list.d/rabbitmq.list + - run: wget -O- https://www.rabbitmq.com/rabbitmq-release-signing-key.asc | sudo apt-key add - + - run: sudo apt-get update + - run: sudo apt-get install rabbitmq-server + - run: sudo service rabbitmq-server start - run: go get -t -v -d ./... - run: make vet test bench diff --git a/context.go b/context.go index 171db90..bd25ddc 100644 --- a/context.go +++ b/context.go @@ -1,9 +1,25 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zipkin import ( "context" ) +var defaultNoopSpan = &noopSpan{} + // SpanFromContext retrieves a Zipkin Span from Go's context propagation // mechanism if found. If not found, returns nil. func SpanFromContext(ctx context.Context) Span { @@ -13,6 +29,19 @@ func SpanFromContext(ctx context.Context) Span { return nil } +// SpanOrNoopFromContext retrieves a Zipkin Span from Go's context propagation +// mechanism if found. If not found, returns a noopSpan. +// This function typically is used for modules that want to provide existing +// Zipkin spans with additional data, but can't guarantee that spans are +// properly propagated. It is preferred to use SpanFromContext() and test for +// Nil instead of using this function. +func SpanOrNoopFromContext(ctx context.Context) Span { + if s, ok := ctx.Value(spanKey).(Span); ok { + return s + } + return defaultNoopSpan +} + // NewContext stores a Zipkin Span into Go's context propagation mechanism. func NewContext(ctx context.Context, s Span) context.Context { return context.WithValue(ctx, spanKey, s) diff --git a/context_test.go b/context_test.go new file mode 100644 index 0000000..eae73ea --- /dev/null +++ b/context_test.go @@ -0,0 +1,39 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zipkin + +import ( + "context" + "testing" +) + +func TestSpanOrNoopFromContext(t *testing.T) { + var ( + ctx = context.Background() + tr, _ = NewTracer(nil, WithLocalEndpoint(nil)) + span = tr.StartSpan("test") + ) + + if want, have := defaultNoopSpan, SpanOrNoopFromContext(ctx); want != have { + t.Errorf("Invalid response want %+v, have %+v", want, have) + } + + ctx = NewContext(ctx, span) + + if want, have := span, SpanOrNoopFromContext(ctx); want != have { + t.Errorf("Invalid response want %+v, have %+v", want, have) + } + +} diff --git a/doc.go b/doc.go index a49deba..18cc62f 100644 --- a/doc.go +++ b/doc.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + /* Package zipkin implements a native Zipkin instrumentation library for Go. diff --git a/endpoint.go b/endpoint.go index 530091f..4a1a6c7 100644 --- a/endpoint.go +++ b/endpoint.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zipkin import ( diff --git a/endpoint_test.go b/endpoint_test.go index 9b6060b..9d510a0 100644 --- a/endpoint_test.go +++ b/endpoint_test.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zipkin_test import ( diff --git a/example_httpserver_test.go b/example_httpserver_test.go index fb308db..afad6e8 100644 --- a/example_httpserver_test.go +++ b/example_httpserver_test.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zipkin_test import ( diff --git a/example_test.go b/example_test.go index 4aec7e1..4e3dc4b 100644 --- a/example_test.go +++ b/example_test.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zipkin_test import ( diff --git a/go.mod b/go.mod index 0fa13da..ed37c1c 100644 --- a/go.mod +++ b/go.mod @@ -1,31 +1,25 @@ module github.com/openzipkin/zipkin-go require ( - cloud.google.com/go v0.33.1 - github.com/GoogleCloudPlatform/golang-samples v0.0.0-20181215195521-297d1a46ea5c github.com/Shopify/sarama v1.19.0 - github.com/Shopify/toxiproxy v2.1.3+incompatible // indirect - github.com/broady/preprocess v0.0.0-20171110231930-97e2c618d939 // indirect + github.com/Shopify/toxiproxy v2.1.4+incompatible // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/eapache/go-resiliency v1.1.0 // indirect github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 // indirect github.com/eapache/queue v1.1.0 // indirect + github.com/gogo/protobuf v1.2.0 github.com/golang/protobuf v1.2.0 github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect - github.com/googleapis/gax-go v2.0.2+incompatible // indirect github.com/gorilla/context v1.1.1 // indirect github.com/gorilla/mux v1.6.2 github.com/onsi/ginkgo v1.7.0 github.com/onsi/gomega v1.4.3 - github.com/pierrec/lz4 v2.0.5+incompatible // indirect + github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1 // indirect github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a // indirect - go.opencensus.io v0.18.0 // indirect - golang.org/x/net v0.0.0-20181114220301-adae6a3d119a - golang.org/x/oauth2 v0.0.0-20181120190819-8f65e3013eba // indirect + github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94 + golang.org/x/net v0.0.0-20190311183353-d8887717615a golang.org/x/sync v0.0.0-20181108010431-42b317875d0f // indirect - golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b // indirect - google.golang.org/api v0.0.0-20181214000652-874d9dc5b186 // indirect - google.golang.org/appengine v1.3.0 // indirect - google.golang.org/genproto v0.0.0-20181109154231-b5d43981345b // indirect - google.golang.org/grpc v1.16.0 + google.golang.org/grpc v1.20.0 ) + +go 1.12 diff --git a/go.sum b/go.sum index dcaba25..5274bc9 100644 --- a/go.sum +++ b/go.sum @@ -1,16 +1,9 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.33.1 h1:fmJQWZ1w9PGkHR1YL/P7HloDvqlmKQ4Vpb7PC2e+aCk= -cloud.google.com/go v0.33.1/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= -github.com/GoogleCloudPlatform/golang-samples v0.0.0-20181215195521-297d1a46ea5c h1:p3yZr4LvJwrnolRDKsGIGY+TrIlxYD/m0f4HBqbN3HI= -github.com/GoogleCloudPlatform/golang-samples v0.0.0-20181215195521-297d1a46ea5c/go.mod h1:uHbVFn4F4dmm4WDqqSMNTRLJFfbsz85Ii/WWrdNUFt0= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Shopify/sarama v1.19.0 h1:9oksLxC6uxVPHPVYUmq6xhr1BOF/hHobWH2UzO67z1s= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= -github.com/Shopify/toxiproxy v2.1.3+incompatible h1:awiJqUYH4q4OmoBiRccJykjd7B+w0loJi2keSna4X/M= -github.com/Shopify/toxiproxy v2.1.3+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/broady/preprocess v0.0.0-20171110231930-97e2c618d939 h1:8Fs/HOZk/B0pjd2Y6ljIS3iCeIuVI2LH008bSkiX9KA= -github.com/broady/preprocess v0.0.0-20171110231930-97e2c618d939/go.mod h1:Abcc+rG702b+/4ggRB+cg3KciUCeUKl9Lh/nbvImdfc= +github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 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= @@ -20,81 +13,58 @@ github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8 github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= 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/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/protobuf v1.2.0 h1:xU6/SpYbvkNYiptHJYEDRseDLvYE7wSqhYYNy0QSUzI= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/googleapis/gax-go v2.0.2+incompatible h1:silFMLAnr330+NRuag/VjIGF7TLp/LBrV2CJKFLWEww= -github.com/googleapis/gax-go v2.0.2+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +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 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= -github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= -github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1 h1:VGcrWe3yk6o+t7BdVNy5UDPWa4OZuDWtE1W1ZbS7Kyw= +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/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -go.opencensus.io v0.18.0 h1:Mk5rgZcggtbvtAun5aJzAtjKKN/t0R3jJPlWILlv938= -go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= -golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d h1:g9qWBGx4puODJTMVyoPrpoxPFgVGd+z1DZwjfRu4d0I= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94 h1:0ngsPmuP6XIjiFRNFYlvKwSr5zff2v+uPHaffZ6/M4k= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a h1:gOpx8G595UYyvj8UK4+OFyY4rx037g3fmfhe5SasG3U= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= +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/oauth2 v0.0.0-20181120190819-8f65e3013eba h1:YDkOrzGLLYybtuP6ZgebnO4OWYEYVMFSniazXsxrFN8= -golang.org/x/oauth2 v0.0.0-20181120190819-8f65e3013eba/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 h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/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-20181122145206-62eef0e2fa9b h1:MQE+LT/ABUuuvEZ+YQAMSXindAdUh7slEmAkup74op4= -golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52 h1:JG/0uqcGdTNgq7FdU+61l5Pdmb8putNZlXb65bJBROs= -golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= -google.golang.org/api v0.0.0-20181214000652-874d9dc5b186 h1:ZDe3pYgXQh0A6sgLlIejBym8PY8oYcX8dY7pwKS8+Yk= -google.golang.org/api v0.0.0-20181214000652-874d9dc5b186/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.3.0 h1:FBSsiFRMz3LBeXIomRnVzrQwSDj4ibvcRexLG0LZGQk= -google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20181109154231-b5d43981345b h1:WkFtVmaZoTRVoRYr0LTC9SYNhlw0X0HrVPz2OVssVm4= -google.golang.org/genproto v0.0.0-20181109154231-b5d43981345b/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= -google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.16.0 h1:dz5IJGuC2BB7qXR5AyHNwAUBhZscK2xVez7mznh72sY= -google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +google.golang.org/grpc v1.20.0 h1:DlsSIrgEBuZAUFJcta2B5i/lzeHHbnfkNFAfFXLVFYQ= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= @@ -103,4 +73,4 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/idgenerator/idgenerator.go b/idgenerator/idgenerator.go index a9de100..3e85701 100644 --- a/idgenerator/idgenerator.go +++ b/idgenerator/idgenerator.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + /* Package idgenerator contains several Span and Trace ID generators which can be used by the Zipkin tracer. Additional third party generators can be plugged in diff --git a/idgenerator/idgenerator_test.go b/idgenerator/idgenerator_test.go index cd16d87..724b1b0 100644 --- a/idgenerator/idgenerator_test.go +++ b/idgenerator/idgenerator_test.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package idgenerator_test import ( diff --git a/middleware/grpc/client.go b/middleware/grpc/client.go index 5fbf0e0..f39b0e8 100644 --- a/middleware/grpc/client.go +++ b/middleware/grpc/client.go @@ -1,15 +1,24 @@ -// +build go1.9 +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package grpc import ( "context" - "strings" - "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" "google.golang.org/grpc/stats" - "google.golang.org/grpc/status" "github.com/openzipkin/zipkin-go" "github.com/openzipkin/zipkin-go/model" @@ -18,28 +27,23 @@ import ( type clientHandler struct { tracer *zipkin.Tracer - rpcHandlers []RPCHandler remoteServiceName string } // A ClientOption can be passed to NewClientHandler to customize the returned handler. type ClientOption func(*clientHandler) -// A RPCHandler can be registered using WithRPCHandler to intercept calls to HandleRPC of a -// handler for additional span customization. -type RPCHandler func(span zipkin.Span, rpcStats stats.RPCStats) - -// WithRPCHandler allows one to add custom logic for handling a stats.RPCStats, e.g., -// to add additional tags. -func WithRPCHandler(handler RPCHandler) ClientOption { +// WithRemoteServiceName will set the value for the remote endpoint's service name on +// all spans. +func WithRemoteServiceName(name string) ClientOption { return func(c *clientHandler) { - c.rpcHandlers = append(c.rpcHandlers, handler) + c.remoteServiceName = name } } // NewClientHandler returns a stats.Handler which can be used with grpc.WithStatsHandler to add // tracing to a gRPC client. The gRPC method name is used as the span name and by default the only -// tags are the gRPC status code if the call fails. Use WithRPCHandler to add additional tags. +// tags are the gRPC status code if the call fails. func NewClientHandler(tracer *zipkin.Tracer, options ...ClientOption) stats.Handler { c := &clientHandler{ tracer: tracer, @@ -63,36 +67,18 @@ func (c *clientHandler) TagConn(ctx context.Context, cti *stats.ConnTagInfo) con // HandleRPC implements per-RPC tracing and stats instrumentation. func (c *clientHandler) HandleRPC(ctx context.Context, rs stats.RPCStats) { - span := zipkin.SpanFromContext(ctx) - - for _, h := range c.rpcHandlers { - h(span, rs) - } - - switch rs := rs.(type) { - case *stats.End: - s, ok := status.FromError(rs.Error) - // rs.Error should always be convertable to a status, this is just a defensive check. - if ok { - if s.Code() != codes.OK { - // Uppercase for consistency with Brave - c := strings.ToUpper(s.Code().String()) - span.Tag("grpc.status_code", c) - zipkin.TagError.Set(span, c) - } - } else { - zipkin.TagError.Set(span, rs.Error.Error()) - } - span.Finish() - } + handleRPC(ctx, rs) } // TagRPC implements per-RPC context management. func (c *clientHandler) TagRPC(ctx context.Context, rti *stats.RPCTagInfo) context.Context { var span zipkin.Span + ep := remoteEndpointFromContext(ctx, c.remoteServiceName) + name := spanName(rti) - span, ctx = c.tracer.StartSpanFromContext(ctx, name, zipkin.Kind(model.Client)) + span, ctx = c.tracer.StartSpanFromContext(ctx, name, zipkin.Kind(model.Client), zipkin.RemoteEndpoint(ep)) + md, ok := metadata.FromOutgoingContext(ctx) if ok { md = md.Copy() diff --git a/middleware/grpc/client_test.go b/middleware/grpc/client_test.go index b307b4a..957472a 100644 --- a/middleware/grpc/client_test.go +++ b/middleware/grpc/client_test.go @@ -1,4 +1,16 @@ -// +build go1.9 +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package grpc_test @@ -9,10 +21,10 @@ import ( "github.com/onsi/gomega" "google.golang.org/grpc" "google.golang.org/grpc/metadata" - "google.golang.org/grpc/stats" "github.com/openzipkin/zipkin-go" zipkingrpc "github.com/openzipkin/zipkin-go/middleware/grpc" + "github.com/openzipkin/zipkin-go/model" "github.com/openzipkin/zipkin-go/propagation/b3" service "github.com/openzipkin/zipkin-go/proto/testing" "github.com/openzipkin/zipkin-go/reporter/recorder" @@ -32,7 +44,7 @@ var _ = ginkgo.Describe("gRPC Client", func() { reporter = recorder.NewReporter() ep, _ := zipkin.NewEndpoint("grpcClient", "") tracer, err = zipkin.NewTracer( - reporter, zipkin.WithLocalEndpoint(ep), zipkin.WithIDGenerator(newSequentialIdGenerator())) + reporter, zipkin.WithLocalEndpoint(ep), zipkin.WithIDGenerator(newSequentialIdGenerator(1))) gomega.Expect(tracer, err).ToNot(gomega.BeNil()) }) @@ -56,8 +68,12 @@ var _ = ginkgo.Describe("gRPC Client", func() { spans := reporter.Flush() gomega.Expect(spans).To(gomega.HaveLen(1)) - gomega.Expect(spans[0].Name).To(gomega.Equal("zipkin.testing.HelloService.Hello")) - gomega.Expect(spans[0].Tags).To(gomega.BeEmpty()) + + span := spans[0] + gomega.Expect(span.Kind).To(gomega.Equal(model.Client)) + gomega.Expect(span.Name).To(gomega.Equal("zipkin.testing.HelloService.Hello")) + gomega.Expect(span.RemoteEndpoint).To(gomega.BeNil()) + gomega.Expect(span.Tags).To(gomega.BeEmpty()) }) ginkgo.It("propagates trace context", func() { @@ -94,7 +110,7 @@ var _ = ginkgo.Describe("gRPC Client", func() { }) }) - ginkgo.Context("with custom RPCHandler", func() { + ginkgo.Context("with remote service name", func() { ginkgo.BeforeEach(func() { var err error @@ -103,21 +119,18 @@ var _ = ginkgo.Describe("gRPC Client", func() { grpc.WithInsecure(), grpc.WithStatsHandler(zipkingrpc.NewClientHandler( tracer, - zipkingrpc.WithRPCHandler(func(span zipkin.Span, rpcStats stats.RPCStats) { - span.Tag("custom", "tag") - })))) + zipkingrpc.WithRemoteServiceName("remoteService")))) gomega.Expect(conn, err).ToNot(gomega.BeNil()) client = service.NewHelloServiceClient(conn) }) - ginkgo.It("calls custom RPCHandler", func() { + ginkgo.It("has remote service name", func() { resp, err := client.Hello(context.Background(), &service.HelloRequest{Payload: "Hello"}) gomega.Expect(resp, err).ToNot(gomega.BeNil()) spans := reporter.Flush() gomega.Expect(spans).To(gomega.HaveLen(1)) - gomega.Expect(spans[0].Tags).To(gomega.HaveLen(1)) - gomega.Expect(spans[0].Tags).To(gomega.HaveKeyWithValue("custom", "tag")) + gomega.Expect(spans[0].RemoteEndpoint.ServiceName).To(gomega.Equal("remoteService")) }) }) }) diff --git a/middleware/grpc/doc.go b/middleware/grpc/doc.go index bb51a2d..df5f6a7 100644 --- a/middleware/grpc/doc.go +++ b/middleware/grpc/doc.go @@ -1,4 +1,16 @@ -// +build go1.9 +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /* Package grpc contains several gRPC handlers which can be used for instrumenting calls with Zipkin. diff --git a/middleware/grpc/grpc_suite_test.go b/middleware/grpc/grpc_suite_test.go index c1ad74b..7895f32 100644 --- a/middleware/grpc/grpc_suite_test.go +++ b/middleware/grpc/grpc_suite_test.go @@ -1,4 +1,16 @@ -// +build go1.9 +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package grpc_test @@ -15,13 +27,23 @@ import ( "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" + "github.com/openzipkin/zipkin-go" + zipkingrpc "github.com/openzipkin/zipkin-go/middleware/grpc" "github.com/openzipkin/zipkin-go/model" + "github.com/openzipkin/zipkin-go/propagation/b3" service "github.com/openzipkin/zipkin-go/proto/testing" + "github.com/openzipkin/zipkin-go/reporter/recorder" ) var ( + serverIdGenerator *sequentialIdGenerator + serverReporter *recorder.ReporterRecorder + server *grpc.Server serverAddr string + + customServer *grpc.Server + customServerAddr string ) func TestGrpc(t *testing.T) { @@ -30,28 +52,53 @@ func TestGrpc(t *testing.T) { } var _ = ginkgo.BeforeSuite(func() { + var err error + + serverReporter = recorder.NewReporter() + ep, _ := zipkin.NewEndpoint("grpcServer", "") + serverIdGenerator = newSequentialIdGenerator(0x1000000) + tracer, err := zipkin.NewTracer( + serverReporter, zipkin.WithLocalEndpoint(ep), zipkin.WithIDGenerator(serverIdGenerator), zipkin.WithSharedSpans(false)) + lis, err := net.Listen("tcp", ":0") gomega.Expect(lis, err).ToNot(gomega.BeNil(), "failed to listen to tcp port") - server = grpc.NewServer() + server = grpc.NewServer(grpc.StatsHandler(zipkingrpc.NewServerHandler(tracer))) service.RegisterHelloServiceServer(server, &TestHelloService{}) go func() { _ = server.Serve(lis) }() serverAddr = lis.Addr().String() + + customLis, err := net.Listen("tcp", ":0") + gomega.Expect(customLis, err).ToNot(gomega.BeNil(), "failed to listen to tcp port") + + tracer, err = zipkin.NewTracer( + serverReporter, zipkin.WithLocalEndpoint(ep), zipkin.WithIDGenerator(serverIdGenerator), zipkin.WithSharedSpans(true)) + customServer = grpc.NewServer(grpc.StatsHandler(zipkingrpc.NewServerHandler( + tracer, + zipkingrpc.ServerTags(map[string]string{"default": "tag"})))) + service.RegisterHelloServiceServer(customServer, &TestHelloService{}) + go func() { + _ = customServer.Serve(customLis) + }() + customServerAddr = customLis.Addr().String() }) var _ = ginkgo.AfterSuite(func() { server.Stop() + customServer.Stop() + _ = serverReporter.Close() }) type sequentialIdGenerator struct { nextTraceId uint64 nextSpanId uint64 + start uint64 } -func newSequentialIdGenerator() *sequentialIdGenerator { - return &sequentialIdGenerator{1, 1} +func newSequentialIdGenerator(start uint64) *sequentialIdGenerator { + return &sequentialIdGenerator{start, start, start} } func (g *sequentialIdGenerator) SpanID(traceID model.TraceID) model.ID { @@ -69,6 +116,11 @@ func (g *sequentialIdGenerator) TraceID() model.TraceID { return id } +func (g *sequentialIdGenerator) reset() { + g.nextTraceId = g.start + g.nextSpanId = g.start +} + type TestHelloService struct{} func (s *TestHelloService) Hello(ctx context.Context, req *service.HelloRequest) (*service.HelloResponse, error) { @@ -76,20 +128,31 @@ func (s *TestHelloService) Hello(ctx context.Context, req *service.HelloRequest) return nil, status.Error(codes.Aborted, "fail") } + resp := &service.HelloResponse{ + Payload: "World", + Metadata: map[string]string{}, + SpanContext: map[string]string{}, + } + md, ok := metadata.FromIncomingContext(ctx) if !ok { return nil, errors.New("could not parse incoming metadata") } - resp := &service.HelloResponse{ - Payload: "World", - Metadata: map[string]string{}, - } - for k := range md { // Just append the first value for a key for simplicity since we don't use multi-value headers. resp.GetMetadata()[k] = md[k][0] } + span := zipkin.SpanFromContext(ctx) + if span != nil { + spanCtx := span.Context() + resp.GetSpanContext()[b3.SpanID] = spanCtx.ID.String() + resp.GetSpanContext()[b3.TraceID] = spanCtx.TraceID.String() + if spanCtx.ParentID != nil { + resp.GetSpanContext()[b3.ParentSpanID] = spanCtx.ParentID.String() + } + } + return resp, nil } diff --git a/middleware/grpc/server.go b/middleware/grpc/server.go new file mode 100644 index 0000000..8f7faac --- /dev/null +++ b/middleware/grpc/server.go @@ -0,0 +1,91 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package grpc + +import ( + "context" + + "github.com/openzipkin/zipkin-go" + "github.com/openzipkin/zipkin-go/model" + "github.com/openzipkin/zipkin-go/propagation/b3" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/stats" +) + +type serverHandler struct { + tracer *zipkin.Tracer + defaultTags map[string]string +} + +// A ServerOption can be passed to NewServerHandler to customize the returned handler. +type ServerOption func(*serverHandler) + +// ServerTags adds default Tags to inject into server spans. +func ServerTags(tags map[string]string) ServerOption { + return func(h *serverHandler) { + h.defaultTags = tags + } +} + +// NewServerHandler returns a stats.Handler which can be used with grpc.WithStatsHandler to add +// tracing to a gRPC server. The gRPC method name is used as the span name and by default the only +// tags are the gRPC status code if the call fails. Use ServerTags to add additional tags that +// should be applied to all spans. +func NewServerHandler(tracer *zipkin.Tracer, options ...ServerOption) stats.Handler { + c := &serverHandler{ + tracer: tracer, + } + for _, option := range options { + option(c) + } + return c +} + +// HandleConn exists to satisfy gRPC stats.Handler. +func (s *serverHandler) HandleConn(ctx context.Context, cs stats.ConnStats) { + // no-op +} + +// TagConn exists to satisfy gRPC stats.Handler. +func (s *serverHandler) TagConn(ctx context.Context, cti *stats.ConnTagInfo) context.Context { + // no-op + return ctx +} + +// HandleRPC implements per-RPC tracing and stats instrumentation. +func (s *serverHandler) HandleRPC(ctx context.Context, rs stats.RPCStats) { + handleRPC(ctx, rs) +} + +// TagRPC implements per-RPC context management. +func (s *serverHandler) TagRPC(ctx context.Context, rti *stats.RPCTagInfo) context.Context { + md, ok := metadata.FromIncomingContext(ctx) + // In practice, ok never seems to be false but add a defensive check. + if !ok { + md = metadata.New(nil) + } + + name := spanName(rti) + + sc := s.tracer.Extract(b3.ExtractGRPC(&md)) + + span := s.tracer.StartSpan(name, zipkin.Kind(model.Server), zipkin.Parent(sc), zipkin.RemoteEndpoint(remoteEndpointFromContext(ctx, ""))) + + for k, v := range s.defaultTags { + span.Tag(k, v) + } + + return zipkin.NewContext(ctx, span) +} diff --git a/middleware/grpc/server_test.go b/middleware/grpc/server_test.go new file mode 100644 index 0000000..ac552fe --- /dev/null +++ b/middleware/grpc/server_test.go @@ -0,0 +1,176 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package grpc_test + +import ( + "context" + + "github.com/onsi/ginkgo" + "github.com/onsi/gomega" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" + + "github.com/openzipkin/zipkin-go" + "github.com/openzipkin/zipkin-go/model" + "github.com/openzipkin/zipkin-go/propagation/b3" + service "github.com/openzipkin/zipkin-go/proto/testing" + "github.com/openzipkin/zipkin-go/reporter" +) + +var _ = ginkgo.Describe("gRPC Server", func() { + var ( + conn *grpc.ClientConn + client service.HelloServiceClient + ) + + ginkgo.BeforeEach(func() { + serverIdGenerator.reset() + serverReporter.Flush() + }) + + ginkgo.AfterEach(func() { + _ = conn.Close() + }) + + ginkgo.Context("with defaults", func() { + ginkgo.BeforeEach(func() { + var err error + + conn, err = grpc.Dial(serverAddr, grpc.WithInsecure()) + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + client = service.NewHelloServiceClient(conn) + }) + + ginkgo.It("creates a span and context", func() { + resp, err := client.Hello(context.Background(), &service.HelloRequest{Payload: "Hello"}) + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + + var spans []model.SpanModel + gomega.Eventually(func() []model.SpanModel { + spans = serverReporter.Flush() + return spans + }).Should(gomega.HaveLen(1)) + + span := spans[0] + gomega.Expect(span.Kind).To(gomega.Equal(model.Server)) + // Set to local host for tests, might be IPv4 or IPv6 not worth checking the actual address. + gomega.Expect(span.RemoteEndpoint.Empty()).To(gomega.BeFalse()) + gomega.Expect(span.Name).To(gomega.Equal("zipkin.testing.HelloService.Hello")) + gomega.Expect(span.Tags).To(gomega.BeEmpty()) + + spanCtx := resp.GetSpanContext() + gomega.Expect(spanCtx).To(gomega.HaveLen(2)) + gomega.Expect(spanCtx).To(gomega.HaveKeyWithValue(b3.TraceID, "0000000001000000")) + gomega.Expect(spanCtx).To(gomega.HaveKeyWithValue(b3.SpanID, "0000000001000000")) + }) + + ginkgo.It("propagates parent", func() { + // Manually create a client context + tracer, err := zipkin.NewTracer( + reporter.NewNoopReporter(), + zipkin.WithIDGenerator(newSequentialIdGenerator(1))) + testSpan := tracer.StartSpan("test") + md := metadata.New(nil) + _ = b3.InjectGRPC(&md)(testSpan.Context()) + ctx := metadata.NewOutgoingContext(context.Background(), md) + + resp, err := client.Hello(ctx, &service.HelloRequest{Payload: "Hello"}) + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + + var spans []model.SpanModel + gomega.Eventually(func() []model.SpanModel { + spans = serverReporter.Flush() + return spans + }).Should(gomega.HaveLen(1)) + + span := spans[0] + gomega.Expect(span.Kind).To(gomega.Equal(model.Server)) + gomega.Expect(span.RemoteEndpoint.Empty()).To(gomega.BeFalse()) + gomega.Expect(span.Name).To(gomega.Equal("zipkin.testing.HelloService.Hello")) + gomega.Expect(span.Tags).To(gomega.BeEmpty()) + + spanCtx := resp.GetSpanContext() + gomega.Expect(spanCtx).To(gomega.HaveLen(3)) + gomega.Expect(spanCtx).To(gomega.HaveKeyWithValue(b3.SpanID, "0000000001000000")) + gomega.Expect(spanCtx).To(gomega.HaveKeyWithValue(b3.TraceID, "0000000000000001")) + gomega.Expect(spanCtx).To(gomega.HaveKeyWithValue(b3.ParentSpanID, "0000000000000001")) + }) + + ginkgo.It("tags with error code", func() { + _, err := client.Hello(context.Background(), &service.HelloRequest{Payload: "fail"}) + gomega.Expect(err).To(gomega.HaveOccurred()) + + var spans []model.SpanModel + gomega.Eventually(func() []model.SpanModel { + spans = serverReporter.Flush() + return spans + }).Should(gomega.HaveLen(1)) + + gomega.Expect(spans[0].Tags).To(gomega.HaveLen(2)) + gomega.Expect(spans[0].Tags).To(gomega.HaveKeyWithValue("grpc.status_code", "ABORTED")) + gomega.Expect(spans[0].Tags).To(gomega.HaveKeyWithValue(string(zipkin.TagError), "ABORTED")) + }) + }) + + ginkgo.Context("with joined spans and server tags", func() { + ginkgo.BeforeEach(func() { + var err error + + conn, err = grpc.Dial(customServerAddr, grpc.WithInsecure()) + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + client = service.NewHelloServiceClient(conn) + }) + + ginkgo.It("has server tags", func() { + resp, err := client.Hello(context.Background(), &service.HelloRequest{Payload: "Hello"}) + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + + var spans []model.SpanModel + gomega.Eventually(func() []model.SpanModel { + spans = serverReporter.Flush() + return spans + }).Should(gomega.HaveLen(1)) + + span := spans[0] + gomega.Expect(span.RemoteEndpoint.Empty()).To(gomega.BeFalse()) + gomega.Expect(span.Tags).To(gomega.HaveLen(1)) + gomega.Expect(span.Tags).To(gomega.HaveKeyWithValue("default", "tag")) + + spanCtx := resp.GetSpanContext() + gomega.Expect(spanCtx).To(gomega.HaveLen(2)) + gomega.Expect(spanCtx).To(gomega.HaveKeyWithValue(b3.TraceID, "0000000001000000")) + gomega.Expect(spanCtx).To(gomega.HaveKeyWithValue(b3.SpanID, "0000000001000000")) + }) + + ginkgo.It("joins with caller", func() { + // Manually create a client context + tracer, err := zipkin.NewTracer( + reporter.NewNoopReporter(), + zipkin.WithIDGenerator(newSequentialIdGenerator(1))) + testSpan := tracer.StartSpan("test") + md := metadata.New(nil) + _ = b3.InjectGRPC(&md)(testSpan.Context()) + ctx := metadata.NewOutgoingContext(context.Background(), md) + + resp, err := client.Hello(ctx, &service.HelloRequest{Payload: "Hello"}) + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + + spanCtx := resp.GetSpanContext() + gomega.Expect(spanCtx).To(gomega.HaveLen(2)) + gomega.Expect(spanCtx).To(gomega.HaveKeyWithValue(b3.TraceID, "0000000000000001")) + gomega.Expect(spanCtx).To(gomega.HaveKeyWithValue(b3.SpanID, "0000000000000001")) + }) + }) +}) diff --git a/middleware/grpc/shared.go b/middleware/grpc/shared.go index 3831f82..c32c0bb 100644 --- a/middleware/grpc/shared.go +++ b/middleware/grpc/shared.go @@ -1,15 +1,71 @@ -// +build go1.9 +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package grpc import ( + "context" "strings" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/peer" "google.golang.org/grpc/stats" + "google.golang.org/grpc/status" + + "github.com/openzipkin/zipkin-go" + "github.com/openzipkin/zipkin-go/model" ) +// A RPCHandler can be registered using WithClientRPCHandler or WithServerRPCHandler to intercept calls to HandleRPC of +// a handler for additional span customization. +type RPCHandler func(span zipkin.Span, rpcStats stats.RPCStats) + func spanName(rti *stats.RPCTagInfo) string { name := strings.TrimPrefix(rti.FullMethodName, "/") name = strings.Replace(name, "/", ".", -1) return name } + +func handleRPC(ctx context.Context, rs stats.RPCStats) { + span := zipkin.SpanFromContext(ctx) + + switch rs := rs.(type) { + case *stats.End: + s, ok := status.FromError(rs.Error) + // rs.Error should always be convertable to a status, this is just a defensive check. + if ok { + if s.Code() != codes.OK { + // Uppercase for consistency with Brave + c := strings.ToUpper(s.Code().String()) + span.Tag("grpc.status_code", c) + zipkin.TagError.Set(span, c) + } + } else { + zipkin.TagError.Set(span, rs.Error.Error()) + } + span.Finish() + } +} + +func remoteEndpointFromContext(ctx context.Context, name string) *model.Endpoint { + remoteAddr := "" + + p, ok := peer.FromContext(ctx) + if ok { + remoteAddr = p.Addr.String() + } + + ep, _ := zipkin.NewEndpoint(name, remoteAddr) + return ep +} diff --git a/middleware/http/client.go b/middleware/http/client.go index 97c1537..10087de 100644 --- a/middleware/http/client.go +++ b/middleware/http/client.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package http import ( diff --git a/middleware/http/client_test.go b/middleware/http/client_test.go index c3c1718..4a0268d 100644 --- a/middleware/http/client_test.go +++ b/middleware/http/client_test.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package http_test import ( diff --git a/middleware/http/doc.go b/middleware/http/doc.go index b6262f9..5dddd0e 100644 --- a/middleware/http/doc.go +++ b/middleware/http/doc.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + /* Package http contains several http middlewares which can be used for instrumenting calls with Zipkin. diff --git a/middleware/http/request_sampler.go b/middleware/http/request_sampler.go new file mode 100644 index 0000000..536284e --- /dev/null +++ b/middleware/http/request_sampler.go @@ -0,0 +1,21 @@ +package http + +import "net/http" + +// RequestSamplerFunc can be implemented for client and/or server side sampling decisions that can override the existing +// upstream sampling decision. If the implementation returns nil, the existing sampling decision stays as is. +type RequestSamplerFunc func(r *http.Request) *bool + +// Sample is a convenience function that returns a pointer to a boolean true. Use this for RequestSamplerFuncs when +// wanting the RequestSampler to override the sampling decision to yes. +func Sample() *bool { + sample := true + return &sample +} + +// Discard is a convenience function that returns a pointer to a boolean false. Use this for RequestSamplerFuncs when +// wanting the RequestSampler to override the sampling decision to no. +func Discard() *bool { + sample := false + return &sample +} diff --git a/middleware/http/server.go b/middleware/http/server.go index 2978339..9c36755 100644 --- a/middleware/http/server.go +++ b/middleware/http/server.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package http import ( @@ -17,7 +31,8 @@ type handler struct { next http.Handler tagResponseSize bool defaultTags map[string]string - requestSampler func(r *http.Request) bool + requestSampler RequestSamplerFunc + errHandler ErrHandler } // ServerOption allows Middleware to be optionally configured. @@ -49,19 +64,28 @@ func SpanName(name string) ServerOption { } // RequestSampler allows one to set the sampling decision based on the details -// found in the http.Request. -func RequestSampler(sampleFunc func(r *http.Request) bool) ServerOption { +// found in the http.Request. If wanting to keep the existing sampling decision +// from upstream as is, this function should return nil. +func RequestSampler(sampleFunc RequestSamplerFunc) ServerOption { return func(h *handler) { h.requestSampler = sampleFunc } } +// ServerErrHandler allows to pass a custom error handler for the server response +func ServerErrHandler(eh ErrHandler) ServerOption { + return func(h *handler) { + h.errHandler = eh + } +} + // NewServerMiddleware returns a http.Handler middleware with Zipkin tracing. func NewServerMiddleware(t *zipkin.Tracer, options ...ServerOption) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { h := &handler{ - tracer: t, - next: next, + tracer: t, + next: next, + errHandler: defaultErrHandler, } for _, option := range options { option(h) @@ -77,9 +101,10 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // try to extract B3 Headers from upstream sc := h.tracer.Extract(b3.ExtractHTTP(r)) - if h.requestSampler != nil && sc.Sampled == nil { - sample := h.requestSampler(r) - sc.Sampled = &sample + if h.requestSampler != nil { + if sample := h.requestSampler(r); sample != nil { + sc.Sampled = sample + } } remoteEndpoint, _ := zipkin.NewEndpoint("", r.RemoteAddr) @@ -121,10 +146,10 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { code := ri.getStatusCode() sCode := strconv.Itoa(code) if code > 399 { - zipkin.TagError.Set(sp, sCode) + h.errHandler(sp, nil, code) } zipkin.TagHTTPStatusCode.Set(sp, sCode) - if h.tagResponseSize && ri.size > 0 { + if h.tagResponseSize && atomic.LoadUint64(&ri.size) > 0 { zipkin.TagHTTPResponseSize.Set(sp, ri.getResponseSize()) } sp.Finish() diff --git a/middleware/http/server_test.go b/middleware/http/server_test.go index 152d534..64d4034 100644 --- a/middleware/http/server_test.go +++ b/middleware/http/server_test.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package http_test import ( @@ -200,13 +214,14 @@ func TestHTTPRequestSampler(t *testing.T) { httpHandlerFunc = http.HandlerFunc(httpHandler(200, nil, bytes.NewBufferString(""))) ) - samplers := [](func(r *http.Request) bool){ + samplers := [](func(r *http.Request) *bool){ nil, - func(r *http.Request) bool { return true }, - func(r *http.Request) bool { return false }, + func(r *http.Request) *bool { return mw.Sample() }, + func(r *http.Request) *bool { return mw.Discard() }, + func(r *http.Request) *bool { return nil }, } - for _, sampler := range samplers { + for idx, sampler := range samplers { tr, _ := zipkin.NewTracer(spanRecorder, zipkin.WithLocalEndpoint(lep), zipkin.WithSampler(zipkin.AlwaysSample)) request, err := http.NewRequest(methodType, "/test", requestBuf) @@ -221,16 +236,16 @@ func TestHTTPRequestSampler(t *testing.T) { spans := spanRecorder.Flush() sampledSpans := 0 - if sampler == nil || sampler(request) { + if sampler == nil || sampler(request) == nil || *(sampler(request)) { sampledSpans = 1 } if want, have := sampledSpans, len(spans); want != have { - t.Errorf("Expected %d spans, got %d", want, have) + t.Errorf("[%d] Expected %d spans, got %d", idx, want, have) } } - for _, sampler := range samplers { + for idx, sampler := range samplers { tr, _ := zipkin.NewTracer(spanRecorder, zipkin.WithLocalEndpoint(lep), zipkin.WithSampler(zipkin.NeverSample)) request, err := http.NewRequest(methodType, "/test", requestBuf) @@ -245,12 +260,12 @@ func TestHTTPRequestSampler(t *testing.T) { spans := spanRecorder.Flush() sampledSpans := 0 - if sampler != nil && sampler(request) { + if sampler != nil && sampler(request) != nil && *(sampler(request)) { sampledSpans = 1 } if want, have := sampledSpans, len(spans); want != have { - t.Errorf("Expected %d spans, got %d", want, have) + t.Errorf("[%d] Expected %d spans, got %d", idx, want, have) } } diff --git a/middleware/http/spancloser.go b/middleware/http/spancloser.go index 4789f39..20bf086 100644 --- a/middleware/http/spancloser.go +++ b/middleware/http/spancloser.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package http import ( diff --git a/middleware/http/spantrace.go b/middleware/http/spantrace.go index 2c33a0f..14c16b6 100644 --- a/middleware/http/spantrace.go +++ b/middleware/http/spantrace.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package http import ( diff --git a/middleware/http/transport.go b/middleware/http/transport.go index 1097946..981013c 100644 --- a/middleware/http/transport.go +++ b/middleware/http/transport.go @@ -1,8 +1,27 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package http import ( + "bytes" + "io" + "io/ioutil" + "log" "net/http" "net/http/httptrace" + "os" "strconv" zipkin "github.com/openzipkin/zipkin-go" @@ -10,11 +29,35 @@ import ( "github.com/openzipkin/zipkin-go/propagation/b3" ) +// ErrHandler allows instrumentations to decide how to tag errors +// based on the response status code >399 and the error from the +// Transport.RoundTrip +type ErrHandler func(sp zipkin.Span, err error, statusCode int) + +func defaultErrHandler(sp zipkin.Span, err error, statusCode int) { + if err != nil { + zipkin.TagError.Set(sp, err.Error()) + return + } + + statusCodeVal := strconv.FormatInt(int64(statusCode), 10) + zipkin.TagError.Set(sp, statusCodeVal) +} + +// ErrResponseReader allows instrumentations to read the error body +// and decide to obtain information to it and add it to the span i.e. +// tag the span with a more meaningful error code or with error details. +type ErrResponseReader func(sp zipkin.Span, body io.Reader) + type transport struct { - tracer *zipkin.Tracer - rt http.RoundTripper - httpTrace bool - defaultTags map[string]string + tracer *zipkin.Tracer + rt http.RoundTripper + httpTrace bool + defaultTags map[string]string + errHandler ErrHandler + errResponseReader *ErrResponseReader + logger *log.Logger + requestSampler RequestSamplerFunc } // TransportOption allows one to configure optional transport configuration. @@ -43,6 +86,38 @@ func TransportTrace(enable bool) TransportOption { } } +// TransportErrHandler allows to pass a custom error handler for the round trip +func TransportErrHandler(h ErrHandler) TransportOption { + return func(t *transport) { + t.errHandler = h + } +} + +// TransportErrResponseReader allows to pass a custom ErrResponseReader +func TransportErrResponseReader(r ErrResponseReader) TransportOption { + return func(t *transport) { + t.errResponseReader = &r + } +} + +// TransportLogger allows to plug a logger into the transport +func TransportLogger(l *log.Logger) TransportOption { + return func(t *transport) { + t.logger = l + } +} + +// TransportRequestSampler allows one to set the sampling decision based on +// the details found in the http.Request. It has preference over the existing +// sampling decision contained in the context. The function returns a *bool, +// if returning nil, sampling decision is not being changed whereas returning +// something else than nil is being used as sampling decision. +func TransportRequestSampler(sampleFunc RequestSamplerFunc) TransportOption { + return func(t *transport) { + t.requestSampler = sampleFunc + } +} + // NewTransport returns a new Zipkin instrumented http RoundTripper which can be // used with a standard library http Client. func NewTransport(tracer *zipkin.Tracer, options ...TransportOption) (http.RoundTripper, error) { @@ -51,9 +126,11 @@ func NewTransport(tracer *zipkin.Tracer, options ...TransportOption) (http.Round } t := &transport{ - tracer: tracer, - rt: http.DefaultTransport, - httpTrace: false, + tracer: tracer, + rt: http.DefaultTransport, + httpTrace: false, + errHandler: defaultErrHandler, + logger: log.New(os.Stderr, "", log.LstdFlags), } for _, option := range options { @@ -102,12 +179,18 @@ func (t *transport) RoundTrip(req *http.Request) (res *http.Response, err error) zipkin.TagHTTPMethod.Set(sp, req.Method) zipkin.TagHTTPPath.Set(sp, req.URL.Path) - _ = b3.InjectHTTP(req)(sp.Context()) + spCtx := sp.Context() + if t.requestSampler != nil { + if shouldSample := t.requestSampler(req); shouldSample != nil { + spCtx.Sampled = shouldSample + } + } - res, err = t.rt.RoundTrip(req) + _ = b3.InjectHTTP(req)(spCtx) + res, err = t.rt.RoundTrip(req) if err != nil { - zipkin.TagError.Set(sp, err.Error()) + t.errHandler(sp, err, 0) sp.Finish() return } @@ -119,7 +202,18 @@ func (t *transport) RoundTrip(req *http.Request) (res *http.Response, err error) statusCode := strconv.FormatInt(int64(res.StatusCode), 10) zipkin.TagHTTPStatusCode.Set(sp, statusCode) if res.StatusCode > 399 { - zipkin.TagError.Set(sp, statusCode) + t.errHandler(sp, nil, res.StatusCode) + + if t.errResponseReader != nil { + sBody, err := ioutil.ReadAll(res.Body) + if err == nil { + res.Body.Close() + (*t.errResponseReader)(sp, ioutil.NopCloser(bytes.NewBuffer(sBody))) + res.Body = ioutil.NopCloser(bytes.NewBuffer(sBody)) + } else { + t.logger.Printf("failed to read the response body in the ErrResponseReader: %v", err) + } + } } } sp.Finish() diff --git a/middleware/http/transport_test.go b/middleware/http/transport_test.go new file mode 100644 index 0000000..e2c8a77 --- /dev/null +++ b/middleware/http/transport_test.go @@ -0,0 +1,212 @@ +package http + +import ( + "context" + "errors" + "io" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + + zipkin "github.com/openzipkin/zipkin-go" + "github.com/openzipkin/zipkin-go/reporter/recorder" +) + +type errRoundTripper struct { + err error +} + +func (r errRoundTripper) RoundTrip(_ *http.Request) (*http.Response, error) { + return nil, r.err +} + +func TestRoundTripErrHandlingForRoundTripError(t *testing.T) { + expectedErr := errors.New("error message") + tracer, err := zipkin.NewTracer(nil) + if err != nil { + t.Fatalf("unexpected error when creating tracer: %v", err) + } + req, _ := http.NewRequest("GET", "localhost", nil) + transport, _ := NewTransport( + tracer, + TransportErrHandler(func(_ zipkin.Span, err error, statusCode int) { + if want, have := expectedErr, err; want != have { + t.Errorf("unexpected error, want %q, have %q", want, have) + } + }), + RoundTripper(&errRoundTripper{err: expectedErr}), + ) + + _, err = transport.RoundTrip(req) + if err == nil { + t.Fatalf("expected error: %v", expectedErr) + } +} + +func TestRoundTripErrHandlingForStatusCode(t *testing.T) { + tcs := []struct { + actualStatusCode int + expectedError int + }{ + // we start on 200, if we pass 100 it will wait until timeout. + { + actualStatusCode: 200, + }, + { + actualStatusCode: 301, + }, + { + actualStatusCode: 403, + expectedError: 403, + }, + { + actualStatusCode: 504, + expectedError: 504, + }, + } + + for _, tc := range tcs { + srv := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + rw.WriteHeader(tc.actualStatusCode) + })) + + tracer, err := zipkin.NewTracer(nil) + if err != nil { + t.Fatalf("unexpected error when creating tracer: %v", err) + } + req, _ := http.NewRequest("GET", srv.URL, nil) + transport, _ := NewTransport( + tracer, + TransportErrHandler(func(_ zipkin.Span, err error, statusCode int) { + if want, have := tc.expectedError, statusCode; want != 0 && want != have { + t.Errorf("unexpected status code, want %d, have %d", want, have) + } + }), + ) + + _, err = transport.RoundTrip(req) + if err != nil { + t.Fatalf("unexpected error in the round trip: %v", err) + } + + srv.Close() + } +} + +func TestRoundTripErrResponseReadingSuccess(t *testing.T) { + expectedBody := []byte("message") + srv := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { + rw.WriteHeader(500) + rw.Write(expectedBody) + })) + defer srv.Close() + + tracer, err := zipkin.NewTracer(nil) + if err != nil { + t.Fatalf("unexpected error when creating tracer: %v", err) + } + req, _ := http.NewRequest("GET", srv.URL, nil) + transport, _ := NewTransport( + tracer, + TransportErrResponseReader(func(_ zipkin.Span, br io.Reader) { + body, _ := ioutil.ReadAll(br) + if want, have := expectedBody, body; string(want) != string(have) { + t.Errorf("unexpected body, want %q, have %q", want, have) + } + }), + ) + + res, err := transport.RoundTrip(req) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + actualBody, _ := ioutil.ReadAll(res.Body) + if want, have := expectedBody, actualBody; string(expectedBody) != string(actualBody) { + t.Errorf("unexpected body: want %s, have %s", want, have) + } +} + +func TestTransportRequestSamplerOverridesSamplingFromContext(t *testing.T) { + cases := []struct { + Sampler func(uint64) bool + RequestSampler RequestSamplerFunc + ExpectedSampling string + }{ + // Test proper handling when there is no RequestSampler + { + Sampler: zipkin.AlwaysSample, + RequestSampler: nil, + ExpectedSampling: "1", + }, + // Test proper handling when there is no RequestSampler + { + Sampler: zipkin.NeverSample, + RequestSampler: nil, + ExpectedSampling: "0", + }, + // Test RequestSampler override sample -> no sample + { + Sampler: zipkin.AlwaysSample, + RequestSampler: func(_ *http.Request) *bool { return Discard() }, + ExpectedSampling: "0", + }, + // Test RequestSampler override no sample -> sample + { + Sampler: zipkin.NeverSample, + RequestSampler: func(_ *http.Request) *bool { return Sample() }, + ExpectedSampling: "1", + }, + // Test RequestSampler pass through of sampled decision + { + Sampler: zipkin.AlwaysSample, + RequestSampler: func(r *http.Request) *bool { + return nil + }, + ExpectedSampling: "1", + }, + // Test RequestSampler pass through of not sampled decision + { + Sampler: zipkin.NeverSample, + RequestSampler: func(r *http.Request) *bool { + return nil + }, + ExpectedSampling: "0", + }, + } + + for i, c := range cases { + srv := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { + if want, have := c.ExpectedSampling, r.Header.Get("x-b3-sampled"); want != have { + t.Errorf("unexpected sampling decision in case #%d, want %q, have %q", i, want, have) + } + })) + + // we need to use a valid reporter or Tracer will implement noop mode which makes this test invalid + rep := recorder.NewReporter() + + tracer, err := zipkin.NewTracer(rep, zipkin.WithSampler(c.Sampler)) + if err != nil { + t.Fatalf("unexpected error when creating tracer: %v", err) + } + + sp := tracer.StartSpan("op1") + defer sp.Finish() + ctx := zipkin.NewContext(context.Background(), sp) + + req, _ := http.NewRequest("GET", srv.URL, nil) + transport, _ := NewTransport( + tracer, + TransportRequestSampler(c.RequestSampler), + ) + + _, err = transport.RoundTrip(req.WithContext(ctx)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + rep.Close() + srv.Close() + } +} diff --git a/model/annotation.go b/model/annotation.go index 751f81f..795bc41 100644 --- a/model/annotation.go +++ b/model/annotation.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package model import ( diff --git a/model/annotation_test.go b/model/annotation_test.go index 2a80b8c..f90d9ed 100644 --- a/model/annotation_test.go +++ b/model/annotation_test.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package model import ( diff --git a/model/doc.go b/model/doc.go index 99c2ec3..1b11b4d 100644 --- a/model/doc.go +++ b/model/doc.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + /* Package model contains the Zipkin V2 model which is used by the Zipkin Go tracer implementation. diff --git a/model/endpoint.go b/model/endpoint.go index 9a2aa0c..58880bd 100644 --- a/model/endpoint.go +++ b/model/endpoint.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package model import "net" diff --git a/model/endpoint_test.go b/model/endpoint_test.go index 76b42d7..976248b 100644 --- a/model/endpoint_test.go +++ b/model/endpoint_test.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package model_test import ( diff --git a/model/kind.go b/model/kind.go index 8da3c9b..5d512ad 100644 --- a/model/kind.go +++ b/model/kind.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package model // Kind clarifies context of timestamp, duration and remoteEndpoint in a span. diff --git a/model/span.go b/model/span.go index fbdb7c7..f428413 100644 --- a/model/span.go +++ b/model/span.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package model import ( diff --git a/model/span_id.go b/model/span_id.go index 1b5486a..452dc87 100644 --- a/model/span_id.go +++ b/model/span_id.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package model import ( diff --git a/model/span_test.go b/model/span_test.go index 4a071b5..00d5189 100644 --- a/model/span_test.go +++ b/model/span_test.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package model import ( diff --git a/model/traceid.go b/model/traceid.go index fcbbcf7..68d12d3 100644 --- a/model/traceid.go +++ b/model/traceid.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package model import ( diff --git a/model/traceid_test.go b/model/traceid_test.go index 66ff3ab..4376b55 100644 --- a/model/traceid_test.go +++ b/model/traceid_test.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package model import ( diff --git a/noop.go b/noop.go index d1e3f75..1368b9e 100644 --- a/noop.go +++ b/noop.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zipkin import ( @@ -22,4 +36,6 @@ func (*noopSpan) Tag(string, string) {} func (*noopSpan) Finish() {} +func (*noopSpan) FinishedWithDuration(duration time.Duration) {} + func (*noopSpan) Flush() {} diff --git a/noop_test.go b/noop_test.go index e6f1663..e7c1803 100644 --- a/noop_test.go +++ b/noop_test.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zipkin import ( diff --git a/propagation/b3/doc.go b/propagation/b3/doc.go index ffe2c0c..27ce5e0 100644 --- a/propagation/b3/doc.go +++ b/propagation/b3/doc.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + /* Package b3 implements serialization and deserialization logic for Zipkin B3 Headers. diff --git a/propagation/b3/grpc.go b/propagation/b3/grpc.go index 3deed59..a1b30fa 100644 --- a/propagation/b3/grpc.go +++ b/propagation/b3/grpc.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package b3 import ( diff --git a/propagation/b3/grpc_test.go b/propagation/b3/grpc_test.go index be632f2..39997a7 100644 --- a/propagation/b3/grpc_test.go +++ b/propagation/b3/grpc_test.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package b3_test import ( diff --git a/propagation/b3/http.go b/propagation/b3/http.go index 40c72ed..d987f94 100644 --- a/propagation/b3/http.go +++ b/propagation/b3/http.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package b3 import ( @@ -7,6 +21,31 @@ import ( "github.com/openzipkin/zipkin-go/propagation" ) +type InjectOption func(opts *InjectOptions) + +type InjectOptions struct { + shouldInjectSingleHeader bool + shouldInjectMultiHeader bool +} + +// WithSingleAndMultiHeader allows to include both single and multiple +// headers in the context injection +func WithSingleAndMultiHeader() InjectOption { + return func(opts *InjectOptions) { + opts.shouldInjectSingleHeader = true + opts.shouldInjectMultiHeader = true + } +} + +// WithSingleHeaderOnly allows to include only single header in the context +// injection +func WithSingleHeaderOnly() InjectOption { + return func(opts *InjectOptions) { + opts.shouldInjectSingleHeader = true + opts.shouldInjectMultiHeader = false + } +} + // ExtractHTTP will extract a span.Context from the HTTP Request if found in // B3 header format. func ExtractHTTP(r *http.Request) propagation.Extractor { @@ -17,42 +56,72 @@ func ExtractHTTP(r *http.Request) propagation.Extractor { parentSpanIDHeader = r.Header.Get(ParentSpanID) sampledHeader = r.Header.Get(Sampled) flagsHeader = r.Header.Get(Flags) + singleHeader = r.Header.Get(Context) ) - return ParseHeaders( - traceIDHeader, spanIDHeader, parentSpanIDHeader, sampledHeader, - flagsHeader, + var ( + sc *model.SpanContext + sErr error + mErr error ) + if singleHeader != "" { + sc, sErr = ParseSingleHeader(singleHeader) + if sErr == nil { + return sc, nil + } + } + + sc, mErr = ParseHeaders( + traceIDHeader, spanIDHeader, parentSpanIDHeader, + sampledHeader, flagsHeader, + ) + + if mErr != nil && sErr != nil { + return nil, sErr + } + + return sc, mErr } } // InjectHTTP will inject a span.Context into a HTTP Request -func InjectHTTP(r *http.Request) propagation.Injector { +func InjectHTTP(r *http.Request, opts ...InjectOption) propagation.Injector { + options := InjectOptions{shouldInjectMultiHeader: true} + for _, opt := range opts { + opt(&options) + } + return func(sc model.SpanContext) error { if (model.SpanContext{}) == sc { return ErrEmptyContext } - if sc.Debug { - r.Header.Set(Flags, "1") - } else if sc.Sampled != nil { - // Debug is encoded as X-B3-Flags: 1. Since Debug implies Sampled, - // so don't also send "X-B3-Sampled: 1". - if *sc.Sampled { - r.Header.Set(Sampled, "1") - } else { - r.Header.Set(Sampled, "0") + if options.shouldInjectMultiHeader { + if sc.Debug { + r.Header.Set(Flags, "1") + } else if sc.Sampled != nil { + // Debug is encoded as X-B3-Flags: 1. Since Debug implies Sampled, + // so don't also send "X-B3-Sampled: 1". + if *sc.Sampled { + r.Header.Set(Sampled, "1") + } else { + r.Header.Set(Sampled, "0") + } } - } - if !sc.TraceID.Empty() && sc.ID > 0 { - r.Header.Set(TraceID, sc.TraceID.String()) - r.Header.Set(SpanID, sc.ID.String()) - if sc.ParentID != nil { - r.Header.Set(ParentSpanID, sc.ParentID.String()) + if !sc.TraceID.Empty() && sc.ID > 0 { + r.Header.Set(TraceID, sc.TraceID.String()) + r.Header.Set(SpanID, sc.ID.String()) + if sc.ParentID != nil { + r.Header.Set(ParentSpanID, sc.ParentID.String()) + } } } + if options.shouldInjectSingleHeader { + r.Header.Set(Context, BuildSingleHeader(sc)) + } + return nil } } diff --git a/propagation/b3/http_test.go b/propagation/b3/http_test.go index 02dbed0..c3f03b9 100644 --- a/propagation/b3/http_test.go +++ b/propagation/b3/http_test.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package b3_test import ( @@ -243,7 +257,35 @@ func TestHTTPExtractInvalidParentIDError(t *testing.T) { if want, have := b3.ErrInvalidParentSpanIDHeader, err; want != have { t.Errorf("ExtractHTTP Error want %+v, have %+v", want, have) } +} + +func TestHTTPExtractSingleFailsAndMultipleFallsbackSuccessfully(t *testing.T) { + r := newHTTPRequest(t) + + r.Header.Set(b3.Context, "invalid") + r.Header.Set(b3.TraceID, "1") + r.Header.Set(b3.SpanID, "2") + _, err := b3.ExtractHTTP(r)() + + if err != nil { + t.Errorf("ExtractHTTP Unexpected error %+v", err) + } +} + +func TestHTTPExtractSingleFailsAndMultipleFallsbackFailing(t *testing.T) { + r := newHTTPRequest(t) + + r.Header.Set(b3.Context, "0000000000000001-0000000000000002-x") + r.Header.Set(b3.TraceID, "1") + r.Header.Set(b3.SpanID, "2") + r.Header.Set(b3.ParentSpanID, "invalid_data") + + _, err := b3.ExtractHTTP(r)() + + if want, have := b3.ErrInvalidSampledByte, err; want != have { + t.Errorf("HTTPExtract Error want %+v, have %+v", want, have) + } } func TestHTTPInjectEmptyContextError(t *testing.T) { @@ -322,6 +364,49 @@ func TestHTTPInjectSampledAndDebugTrace(t *testing.T) { } } +func TestHTTPInjectWithSingleOnlyHeaders(t *testing.T) { + r := newHTTPRequest(t) + + sampled := true + sc := model.SpanContext{ + TraceID: model.TraceID{Low: 1}, + ID: model.ID(2), + Debug: true, + Sampled: &sampled, + } + + b3.InjectHTTP(r, b3.WithSingleHeaderOnly())(sc) + + if want, have := "", r.Header.Get(b3.TraceID); want != have { + t.Errorf("TraceID want empty, have %s", have) + } + + if want, have := "0000000000000001-0000000000000002-d", r.Header.Get(b3.Context); want != have { + t.Errorf("Context want %s, have %s", want, have) + } +} +func TestHTTPInjectWithBothSingleAndMultipleHeaders(t *testing.T) { + r := newHTTPRequest(t) + + sampled := true + sc := model.SpanContext{ + TraceID: model.TraceID{Low: 1}, + ID: model.ID(2), + Debug: true, + Sampled: &sampled, + } + + b3.InjectHTTP(r, b3.WithSingleAndMultiHeader())(sc) + + if want, have := "0000000000000001", r.Header.Get(b3.TraceID); want != have { + t.Errorf("Trace ID want %s, have %s", want, have) + } + + if want, have := "0000000000000001-0000000000000002-d", r.Header.Get(b3.Context); want != have { + t.Errorf("Context want %s, have %s", want, have) + } +} + func newHTTPRequest(t *testing.T) *http.Request { r, err := http.NewRequest("test", "", nil) if err != nil { diff --git a/propagation/b3/shared.go b/propagation/b3/shared.go index d566c0c..04bcae8 100644 --- a/propagation/b3/shared.go +++ b/propagation/b3/shared.go @@ -1,9 +1,24 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package b3 import "errors" // Common Header Extraction / Injection errors var ( + ErrInvalidSampledByte = errors.New("invalid B3 Sampled found") ErrInvalidSampledHeader = errors.New("invalid B3 Sampled header found") ErrInvalidFlagsHeader = errors.New("invalid B3 Flags header found") ErrInvalidTraceIDHeader = errors.New("invalid B3 TraceID header found") @@ -11,7 +26,11 @@ var ( ErrInvalidParentSpanIDHeader = errors.New("invalid B3 ParentSpanID header found") ErrInvalidScope = errors.New("require either both TraceID and SpanID or none") ErrInvalidScopeParent = errors.New("ParentSpanID requires both TraceID and SpanID to be available") + ErrInvalidScopeParentSingle = errors.New("ParentSpanID requires TraceID, SpanID and Sampled to be available") ErrEmptyContext = errors.New("empty request context") + ErrInvalidTraceIDValue = errors.New("invalid B3 TraceID value found") + ErrInvalidSpanIDValue = errors.New("invalid B3 SpanID value found") + ErrInvalidParentSpanIDValue = errors.New("invalid B3 ParentSpanID value found") ) // Default B3 Header keys @@ -21,4 +40,5 @@ const ( ParentSpanID = "x-b3-parentspanid" Sampled = "x-b3-sampled" Flags = "x-b3-flags" + Context = "b3" ) diff --git a/propagation/b3/spancontext.go b/propagation/b3/spancontext.go index 0537f83..e3569e0 100644 --- a/propagation/b3/spancontext.go +++ b/propagation/b3/spancontext.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package b3 import ( @@ -74,3 +88,115 @@ func ParseHeaders( return sc, nil } + +// ParseSingleHeader takes values found from B3 Single Header and tries to reconstruct a +// SpanContext. +func ParseSingleHeader(contextHeader string) (*model.SpanContext, error) { + if contextHeader == "" { + return nil, ErrEmptyContext + } + + var ( + sc = model.SpanContext{} + sampling string + ) + + headerLen := len(contextHeader) + + if headerLen == 1 { + sampling = contextHeader + } else if headerLen == 16 || headerLen == 32 { + return nil, ErrInvalidScope + } else if headerLen >= 16+16+1 { + var high, low uint64 + pos := 0 + if string(contextHeader[16]) != "-" { + // traceID must be 128 bits + var err error + high, err = strconv.ParseUint(contextHeader[0:16], 16, 64) + if err != nil { + return nil, ErrInvalidTraceIDValue + } + pos = 16 + } + + low, err := strconv.ParseUint(contextHeader[pos+1:pos+16], 16, 64) + if err != nil { + return nil, ErrInvalidTraceIDValue + } + + sc.TraceID = model.TraceID{High: high, Low: low} + + rawID, err := strconv.ParseUint(contextHeader[pos+16+1:pos+16+1+16], 16, 64) + if err != nil { + return nil, ErrInvalidSpanIDValue + } + + sc.ID = model.ID(rawID) + + if headerLen > pos+16+1+16 { + if headerLen == pos+16+1+16+1 { + return nil, ErrInvalidSampledByte + } + + if headerLen == pos+16+1+16+1+1 { + sampling = string(contextHeader[pos+16+1+16+1]) + } else if headerLen == pos+16+1+16+1+16 { + return nil, ErrInvalidScopeParentSingle + } else if headerLen == pos+16+1+16+1+1+1+16 { + sampling = string(contextHeader[pos+16+1+16+1]) + + rawParentID, err := strconv.ParseUint(contextHeader[pos+16+1+16+1+1+1:], 16, 64) + if err != nil { + return nil, ErrInvalidParentSpanIDValue + } + + parentID := model.ID(rawParentID) + sc.ParentID = &parentID + } else { + return nil, ErrInvalidParentSpanIDValue + } + } + } else { + return nil, ErrInvalidTraceIDValue + } + switch sampling { + case "d": + sc.Debug = true + case "1": + trueVal := true + sc.Sampled = &trueVal + case "0": + falseVal := false + sc.Sampled = &falseVal + case "": + default: + return nil, ErrInvalidSampledByte + } + + return &sc, nil +} + +// BuildSingleHeader takes the values from the SpanContext and builds the B3 header +func BuildSingleHeader(sc model.SpanContext) string { + header := []string{} + if !sc.TraceID.Empty() && sc.ID > 0 { + header = append(header, sc.TraceID.String(), sc.ID.String()) + } + + if sc.Debug { + header = append(header, "d") + } else if sc.Sampled != nil { + if *sc.Sampled { + header = append(header, "1") + } else { + header = append(header, "0") + } + } + + if sc.ParentID != nil { + header = append(header, sc.ParentID.String()) + } + + return strings.Join(header, "-") +} diff --git a/propagation/b3/spancontext_test.go b/propagation/b3/spancontext_test.go new file mode 100644 index 0000000..5425a55 --- /dev/null +++ b/propagation/b3/spancontext_test.go @@ -0,0 +1,125 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package b3 + +import ( + "testing" + + "github.com/openzipkin/zipkin-go/model" +) + +func TestParseHeaderSuccess(t *testing.T) { + trueVal := true + falseVal := false + ParentIDVal := model.ID(456) + + testCases := []struct { + header string + expectedContext *model.SpanContext + expectedErr error + }{ + {"d", &model.SpanContext{Debug: true}, nil}, + {"1", &model.SpanContext{Sampled: &trueVal}, nil}, + {"000000000000007b00000000000001c8-000000000000007b", &model.SpanContext{TraceID: model.TraceID{High: 123, Low: 456}, ID: model.ID(123)}, nil}, + {"000000000000007b00000000000001c8-000000000000007b-0", &model.SpanContext{TraceID: model.TraceID{High: 123, Low: 456}, ID: model.ID(123), Sampled: &falseVal}, nil}, + { + "000000000000007b00000000000001c8-000000000000007b-1-00000000000001c8", + &model.SpanContext{ + TraceID: model.TraceID{High: 123, Low: 456}, + ID: model.ID(123), + ParentID: &ParentIDVal, + Sampled: &trueVal, + }, + nil, + }, + {"", nil, ErrEmptyContext}, + } + + for _, testCase := range testCases { + actualContext, actualErr := ParseSingleHeader(testCase.header) + if testCase.expectedContext != nil { + if actualErr != nil { + t.Fatalf("unexpected error for header %q: %s", testCase.header, actualErr.Error()) + } + if !(actualContext.TraceID == testCase.expectedContext.TraceID && + actualContext.ID == testCase.expectedContext.ID && + ((actualContext.ParentID == nil && testCase.expectedContext.ParentID == nil) || + *actualContext.ParentID == *testCase.expectedContext.ParentID) && + ((actualContext.Sampled == nil && testCase.expectedContext.Sampled == nil) || + *actualContext.Sampled == *testCase.expectedContext.Sampled) && + actualContext.Debug == testCase.expectedContext.Debug) { + t.Fatalf("unexpected context for header %q, want: %v, have %v", testCase.header, *testCase.expectedContext, *actualContext) + } + } + + if want, have := actualErr, testCase.expectedErr; want != have { + t.Fatalf("unexpected error for header %q, want: %v, have %v", testCase.header, want, have) + } + } +} + +func TestParseHeaderFails(t *testing.T) { + testCases := []struct { + header string + expectedErr error + }{ + {"a", ErrInvalidSampledByte}, + {"3", ErrInvalidSampledByte}, + {"000000000000007b", ErrInvalidScope}, + {"000000000000007b00000000000001c8", ErrInvalidScope}, + {"000000000000007b00000000000001c8-000000000000007b-", ErrInvalidSampledByte}, + {"000000000000007b00000000000001c8-000000000000007b-3", ErrInvalidSampledByte}, + {"000000000000007b00000000000001c8-000000000000007b-00000000000001c8", ErrInvalidScopeParentSingle}, + {"000000000000007b00000000000001c8-000000000000007b-1-00000000000001c", ErrInvalidParentSpanIDValue}, + {"", ErrEmptyContext}, + } + + for _, testCase := range testCases { + _, actualErr := ParseSingleHeader(testCase.header) + if want, have := testCase.expectedErr, actualErr; want != have { + t.Fatalf("unexpected error for header %q, want: %q, have: %q", testCase.header, want, have) + } + } +} + +func TestBuildHeader(t *testing.T) { + trueVal := true + falseVal := false + ParentIDVal := model.ID(456) + + testCases := []struct { + context model.SpanContext + expectedHeader string + }{ + {model.SpanContext{ID: model.ID(123)}, ""}, + {model.SpanContext{Debug: true}, "d"}, + {model.SpanContext{Sampled: &trueVal}, "1"}, + {model.SpanContext{TraceID: model.TraceID{High: 123, Low: 456}, ID: model.ID(123)}, "000000000000007b00000000000001c8-000000000000007b"}, + {model.SpanContext{TraceID: model.TraceID{High: 123, Low: 456}, ID: model.ID(123), Sampled: &falseVal}, "000000000000007b00000000000001c8-000000000000007b-0"}, + {model.SpanContext{ + TraceID: model.TraceID{High: 123, Low: 456}, + ID: model.ID(123), + ParentID: &ParentIDVal, + Sampled: &falseVal, + }, "000000000000007b00000000000001c8-000000000000007b-0-00000000000001c8"}, + } + + for _, testCase := range testCases { + actualHeader := BuildSingleHeader(testCase.context) + if want, have := actualHeader, testCase.expectedHeader; want != have { + t.Fatalf("unexpected header value, want: %s, have %s", want, have) + } + } +} diff --git a/propagation/propagation.go b/propagation/propagation.go index 4168508..067b28e 100644 --- a/propagation/propagation.go +++ b/propagation/propagation.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + /* Package propagation holds the required function signatures for Injection and Extraction as used by the Zipkin Tracer. diff --git a/proto/testing/service.pb.go b/proto/testing/service.pb.go index ec611e8..18dbbbe 100644 --- a/proto/testing/service.pb.go +++ b/proto/testing/service.pb.go @@ -34,7 +34,7 @@ func (m *HelloRequest) Reset() { *m = HelloRequest{} } func (m *HelloRequest) String() string { return proto.CompactTextString(m) } func (*HelloRequest) ProtoMessage() {} func (*HelloRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_service_e09c5aeb723c4429, []int{0} + return fileDescriptor_service_eae06f0e4375fa1a, []int{0} } func (m *HelloRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_HelloRequest.Unmarshal(m, b) @@ -64,6 +64,7 @@ func (m *HelloRequest) GetPayload() string { type HelloResponse struct { Payload string `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` Metadata map[string]string `protobuf:"bytes,2,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + SpanContext map[string]string `protobuf:"bytes,3,rep,name=span_context,json=spanContext,proto3" json:"span_context,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -73,7 +74,7 @@ func (m *HelloResponse) Reset() { *m = HelloResponse{} } func (m *HelloResponse) String() string { return proto.CompactTextString(m) } func (*HelloResponse) ProtoMessage() {} func (*HelloResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_service_e09c5aeb723c4429, []int{1} + return fileDescriptor_service_eae06f0e4375fa1a, []int{1} } func (m *HelloResponse) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_HelloResponse.Unmarshal(m, b) @@ -107,10 +108,18 @@ func (m *HelloResponse) GetMetadata() map[string]string { return nil } +func (m *HelloResponse) GetSpanContext() map[string]string { + if m != nil { + return m.SpanContext + } + return nil +} + func init() { proto.RegisterType((*HelloRequest)(nil), "zipkin.testing.HelloRequest") proto.RegisterType((*HelloResponse)(nil), "zipkin.testing.HelloResponse") proto.RegisterMapType((map[string]string)(nil), "zipkin.testing.HelloResponse.MetadataEntry") + proto.RegisterMapType((map[string]string)(nil), "zipkin.testing.HelloResponse.SpanContextEntry") } // Reference imports to suppress errors if they are not otherwise used. @@ -186,23 +195,26 @@ var _HelloService_serviceDesc = grpc.ServiceDesc{ } func init() { - proto.RegisterFile("proto/testing/service.proto", fileDescriptor_service_e09c5aeb723c4429) + proto.RegisterFile("proto/testing/service.proto", fileDescriptor_service_eae06f0e4375fa1a) } -var fileDescriptor_service_e09c5aeb723c4429 = []byte{ - // 216 bytes of a gzipped FileDescriptorProto +var fileDescriptor_service_eae06f0e4375fa1a = []byte{ + // 262 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x2e, 0x28, 0xca, 0x2f, 0xc9, 0xd7, 0x2f, 0x49, 0x2d, 0x2e, 0xc9, 0xcc, 0x4b, 0xd7, 0x2f, 0x4e, 0x2d, 0x2a, 0xcb, 0x4c, 0x4e, 0xd5, 0x03, 0x8b, 0x0a, 0xf1, 0x55, 0x65, 0x16, 0x64, 0x67, 0xe6, 0xe9, 0x41, 0x65, 0x95, 0x34, 0xb8, 0x78, 0x3c, 0x52, 0x73, 0x72, 0xf2, 0x83, 0x52, 0x0b, 0x4b, 0x53, 0x8b, 0x4b, 0x84, 0x24, 0xb8, 0xd8, 0x0b, 0x12, 0x2b, 0x73, 0xf2, 0x13, 0x53, 0x24, 0x18, 0x15, 0x18, 0x35, 0x38, - 0x83, 0x60, 0x5c, 0xa5, 0xf5, 0x8c, 0x5c, 0xbc, 0x50, 0xa5, 0xc5, 0x05, 0xf9, 0x79, 0xc5, 0xa9, + 0x83, 0x60, 0x5c, 0xa5, 0x43, 0x4c, 0x5c, 0xbc, 0x50, 0xa5, 0xc5, 0x05, 0xf9, 0x79, 0xc5, 0xa9, 0xb8, 0xd5, 0x0a, 0xb9, 0x73, 0x71, 0xe4, 0xa6, 0x96, 0x24, 0xa6, 0x24, 0x96, 0x24, 0x4a, 0x30, 0x29, 0x30, 0x6b, 0x70, 0x1b, 0x69, 0xeb, 0xa1, 0x5a, 0xac, 0x87, 0x62, 0x94, 0x9e, 0x2f, 0x54, - 0xb5, 0x6b, 0x5e, 0x49, 0x51, 0x65, 0x10, 0x5c, 0xb3, 0x94, 0x35, 0x17, 0x2f, 0x8a, 0x94, 0x90, - 0x00, 0x17, 0x73, 0x76, 0x6a, 0x25, 0xd4, 0x3e, 0x10, 0x53, 0x48, 0x84, 0x8b, 0xb5, 0x2c, 0x31, - 0xa7, 0x34, 0x55, 0x82, 0x09, 0x2c, 0x06, 0xe1, 0x58, 0x31, 0x59, 0x30, 0x1a, 0x85, 0x40, 0xfd, - 0x16, 0x0c, 0x09, 0x01, 0x21, 0x17, 0x2e, 0x56, 0x30, 0x5f, 0x48, 0x06, 0x87, 0x63, 0xc0, 0x41, - 0x20, 0x25, 0x8b, 0xd7, 0xa9, 0x49, 0x6c, 0xe0, 0x80, 0x34, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, - 0xcf, 0xab, 0xc2, 0x0d, 0x67, 0x01, 0x00, 0x00, + 0xb5, 0x6b, 0x5e, 0x49, 0x51, 0x65, 0x10, 0x5c, 0xb3, 0x50, 0x20, 0x17, 0x4f, 0x71, 0x41, 0x62, + 0x5e, 0x7c, 0x72, 0x7e, 0x5e, 0x49, 0x6a, 0x45, 0x89, 0x04, 0x33, 0xd8, 0x30, 0x3d, 0xfc, 0x86, + 0x05, 0x17, 0x24, 0xe6, 0x39, 0x43, 0x34, 0x40, 0xcc, 0xe3, 0x2e, 0x46, 0x88, 0x48, 0x59, 0x73, + 0xf1, 0xa2, 0xd8, 0x26, 0x24, 0xc0, 0xc5, 0x9c, 0x9d, 0x5a, 0x09, 0xf5, 0x02, 0x88, 0x29, 0x24, + 0xc2, 0xc5, 0x5a, 0x96, 0x98, 0x53, 0x9a, 0x2a, 0xc1, 0x04, 0x16, 0x83, 0x70, 0xac, 0x98, 0x2c, + 0x18, 0xa5, 0xec, 0xb8, 0x04, 0xd0, 0x4d, 0x27, 0x45, 0xbf, 0x51, 0x08, 0x34, 0xb8, 0x83, 0x21, + 0x91, 0x22, 0xe4, 0xc2, 0xc5, 0x0a, 0xe6, 0x0b, 0xc9, 0xe0, 0xf0, 0x12, 0x38, 0x56, 0xa4, 0x64, + 0xf1, 0x7a, 0x38, 0x89, 0x0d, 0x1c, 0xb7, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x9f, 0x24, + 0x1b, 0x5e, 0xfa, 0x01, 0x00, 0x00, } diff --git a/proto/testing/service.proto b/proto/testing/service.proto index 588dd98..93cc502 100644 --- a/proto/testing/service.proto +++ b/proto/testing/service.proto @@ -1,4 +1,4 @@ -// Copyright 2018 The OpenZipkin Authors +// Copyright 2019 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -24,6 +24,8 @@ message HelloResponse { string payload = 1; map metadata = 2; + + map span_context = 3; } service HelloService { diff --git a/proto/v2/convert_proto.go b/proto/v2/decode_proto.go similarity index 94% rename from proto/v2/convert_proto.go rename to proto/v2/decode_proto.go index 4a821f9..eb733c3 100644 --- a/proto/v2/convert_proto.go +++ b/proto/v2/decode_proto.go @@ -1,4 +1,4 @@ -// Copyright 2018 The OpenZipkin Authors +// Copyright 2019 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -90,7 +90,7 @@ func protoSpanToModelSpan(s *Span, debugWasSet bool) (*zipkinmodel.SpanModel, er LocalEndpoint: protoEndpointToModelEndpoint(s.LocalEndpoint), RemoteEndpoint: protoEndpointToModelEndpoint(s.RemoteEndpoint), Shared: s.Shared, - Annotations: protoAnnotationToModelAnnotations(s.Annotations), + Annotations: protoAnnotationsToModelAnnotations(s.Annotations), } return zms, nil @@ -135,7 +135,7 @@ func protoSpanIDToModelSpanID(spanId []byte) (zid *zipkinmodel.ID, blank bool, e return &zid_, false, nil } -func protoAnnotationToModelAnnotations(zpa []*Annotation) (zma []zipkinmodel.Annotation) { +func protoAnnotationsToModelAnnotations(zpa []*Annotation) (zma []zipkinmodel.Annotation) { for _, za := range zpa { if za != nil { zma = append(zma, zipkinmodel.Annotation{ @@ -152,5 +152,5 @@ func protoAnnotationToModelAnnotations(zpa []*Annotation) (zma []zipkinmodel.Ann } func microsToTime(us uint64) time.Time { - return time.Unix(0, int64(us*1e3)) + return time.Unix(0, int64(us*1e3)).UTC() } diff --git a/proto/v2/convert_proto_test.go b/proto/v2/decode_proto_test.go similarity index 99% rename from proto/v2/convert_proto_test.go rename to proto/v2/decode_proto_test.go index c39d408..73d416e 100644 --- a/proto/v2/convert_proto_test.go +++ b/proto/v2/decode_proto_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 The OpenZipkin Authors +// Copyright 2019 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/proto/v2/encode_proto.go b/proto/v2/encode_proto.go new file mode 100644 index 0000000..fc5b925 --- /dev/null +++ b/proto/v2/encode_proto.go @@ -0,0 +1,130 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zipkin_proto3 + +import ( + "encoding/binary" + "errors" + "time" + + "github.com/gogo/protobuf/proto" + zipkinmodel "github.com/openzipkin/zipkin-go/model" +) + +var errNilProtoSpan = errors.New("expecting a non-nil Span") + +// SpanSerializer implements http.SpanSerializer +type SpanSerializer struct{} + +// Serialize takes an array of zipkin SpanModel objects and serializes it to a protobuf blob. +func (SpanSerializer) Serialize(sms []*zipkinmodel.SpanModel) (protoBlob []byte, err error) { + var listOfSpans ListOfSpans + + for _, sm := range sms { + sp, err := modelSpanToProtoSpan(sm) + if err != nil { + return nil, err + } + listOfSpans.Spans = append(listOfSpans.Spans, sp) + } + + return proto.Marshal(&listOfSpans) +} + +// ContentType returns the ContentType needed for this encoding. +func (SpanSerializer) ContentType() string { + return "application/x-protobuf" +} + +func modelSpanToProtoSpan(sm *zipkinmodel.SpanModel) (*Span, error) { + if sm == nil { + return nil, errNilProtoSpan + } + + traceID := make([]byte, 16) + binary.BigEndian.PutUint64(traceID[0:8], uint64(sm.TraceID.High)) + binary.BigEndian.PutUint64(traceID[8:16], uint64(sm.TraceID.Low)) + + parentID := make([]byte, 8) + if sm.ParentID != nil { + binary.BigEndian.PutUint64(parentID, uint64(*sm.ParentID)) + } + + id := make([]byte, 8) + binary.BigEndian.PutUint64(id, uint64(sm.ID)) + + var timeStamp uint64 + if !sm.Timestamp.IsZero() { + timeStamp = uint64(sm.Timestamp.Round(time.Microsecond).UnixNano() / 1e3) + } + + return &Span{ + TraceId: traceID, + ParentId: parentID, + Id: id, + Debug: sm.Debug, + Kind: Span_Kind(Span_Kind_value[string(sm.Kind)]), + Name: sm.Name, + Timestamp: timeStamp, + Tags: sm.Tags, + Duration: uint64(sm.Duration.Nanoseconds() / 1e3), + LocalEndpoint: modelEndpointToProtoEndpoint(sm.LocalEndpoint), + RemoteEndpoint: modelEndpointToProtoEndpoint(sm.RemoteEndpoint), + Shared: sm.Shared, + Annotations: modelAnnotationsToProtoAnnotations(sm.Annotations), + }, nil +} + +func durationToMicros(d time.Duration) (uint64, error) { + if d < time.Microsecond { + if d < 0 { + return 0, zipkinmodel.ErrValidDurationRequired + } else if d > 0 { + d = 1 * time.Microsecond + } + } else { + d += 500 * time.Nanosecond + } + return uint64(d.Nanoseconds() / 1e3), nil +} + +func modelEndpointToProtoEndpoint(ep *zipkinmodel.Endpoint) *Endpoint { + if ep == nil { + return nil + } + return &Endpoint{ + ServiceName: ep.ServiceName, + Ipv4: []byte(ep.IPv4), + Ipv6: []byte(ep.IPv6), + Port: int32(ep.Port), + } +} + +func modelAnnotationsToProtoAnnotations(mas []zipkinmodel.Annotation) (pas []*Annotation) { + for _, ma := range mas { + pas = append(pas, &Annotation{ + Timestamp: timeToMicros(ma.Timestamp), + Value: ma.Value, + }) + } + return +} + +func timeToMicros(t time.Time) uint64 { + if t.IsZero() { + return 0 + } + return uint64(t.UnixNano()) / 1e3 +} diff --git a/proto/v2/encode_proto_test.go b/proto/v2/encode_proto_test.go new file mode 100644 index 0000000..33ec74b --- /dev/null +++ b/proto/v2/encode_proto_test.go @@ -0,0 +1,103 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zipkin_proto3_test + +import ( + "encoding/json" + "net" + "reflect" + "testing" + "time" + + zipkinmodel "github.com/openzipkin/zipkin-go/model" + zipkin_proto3 "github.com/openzipkin/zipkin-go/proto/v2" +) + +func TestExportSpans(t *testing.T) { + want := []*zipkinmodel.SpanModel{ + { + SpanContext: zipkinmodel.SpanContext{ + TraceID: zipkinmodel.TraceID{ + High: 0x7F6F5F4F3F2F1F0F, + Low: 0xF7F6F5F4F3F2F1F0, + }, + ID: 0xF7F6F5F4F3F2F1F0, + ParentID: idPtr(0xF7F6F5F4F3F2F1F0), + Debug: true, + }, + Name: "ProtoSpan1", + Timestamp: now, + Duration: 12 * time.Second, + Shared: false, + Kind: zipkinmodel.Consumer, + LocalEndpoint: &zipkinmodel.Endpoint{ + ServiceName: "svc-1", + IPv4: net.IP{0xC0, 0xA8, 0x00, 0x01}, + Port: 8009, + }, + RemoteEndpoint: &zipkinmodel.Endpoint{ + ServiceName: "memcached", + IPv6: net.IP{0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x53, 0xa7, 0x7c, 0xda, 0x4d, 0xd2, 0x1b}, + Port: 11211, + }, + }, + { + SpanContext: zipkinmodel.SpanContext{ + TraceID: zipkinmodel.TraceID{ + High: 0x7A6A5A4A3A2A1A0A, + Low: 0xC7C6C5C4C3C2C1C0, + }, + ID: 0x6766656463626160, + ParentID: idPtr(0x1716151413121110), + Debug: true, + }, + Name: "CacheWarmUp", + Timestamp: minus10hr5ms, + Kind: zipkinmodel.Producer, + Duration: 7 * time.Second, + LocalEndpoint: &zipkinmodel.Endpoint{ + ServiceName: "search", + IPv4: net.IP{0x0A, 0x00, 0x00, 0x0D}, + Port: 8009, + }, + RemoteEndpoint: &zipkinmodel.Endpoint{ + ServiceName: "redis", + IPv6: net.IP{0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x53, 0xa7, 0x7c, 0xda, 0x4d, 0xd2, 0x1b}, + Port: 6379, + }, + Annotations: []zipkinmodel.Annotation{ + { + Timestamp: minus10hr5ms, + Value: "DB reset", + }, + { + Timestamp: minus10hr5ms, + Value: "GC Cycle 39", + }, + }, + }, + } + + protoBlob, err := zipkin_proto3.SpanSerializer{}.Serialize(want) + if err != nil { + t.Fatalf("Failed to parse spans from protobuf blob: %v", err) + } + + if got, _ := zipkin_proto3.ParseSpans(protoBlob, true); !reflect.DeepEqual(want, got) { + w, _ := json.Marshal(want) + g, _ := json.Marshal(got) + t.Errorf("conversion error!\nWANT:\n%s\n\nGOT:\n%s\n", w, g) + } +} diff --git a/proto/v2/zipkin.proto b/proto/v2/zipkin.proto index 9a4b336..14cdb35 100644 --- a/proto/v2/zipkin.proto +++ b/proto/v2/zipkin.proto @@ -1,4 +1,4 @@ -// Copyright 2018 The OpenZipkin Authors +// Copyright 2019 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/reporter/amqp/amqp.go b/reporter/amqp/amqp.go new file mode 100644 index 0000000..5aca7f1 --- /dev/null +++ b/reporter/amqp/amqp.go @@ -0,0 +1,201 @@ +/* +Package amqp implements a RabbitMq reporter to send spans to a Rabbit server/cluster. +*/ +package amqp + +import ( + "encoding/json" + "fmt" + "log" + "os" + + "github.com/streadway/amqp" + + "github.com/openzipkin/zipkin-go/model" + "github.com/openzipkin/zipkin-go/reporter" +) + +// defaultRmqRoutingKey/Exchange/Kind sets the standard RabbitMQ queue our Reporter will publish on. +const ( + defaultRmqRoutingKey = "zipkin" + defaultRmqExchange = "zipkin" + defaultExchangeKind = "direct" +) + +// rmqReporter implements Reporter by publishing spans to a RabbitMQ exchange +type rmqReporter struct { + e chan error + channel *amqp.Channel + conn *amqp.Connection + exchange string + queue string + logger *log.Logger +} + +// ReporterOption sets a parameter for the rmqReporter +type ReporterOption func(c *rmqReporter) + +// Logger sets the logger used to report errors in the collection +// process. +func Logger(logger *log.Logger) ReporterOption { + return func(c *rmqReporter) { + c.logger = logger + } +} + +// Exchange sets the Exchange used to send messages ( +// see https://github.com/openzipkin/zipkin/tree/master/zipkin-collector/rabbitmq +// if want to change default routing key or exchange +func Exchange(exchange string) ReporterOption { + return func(c *rmqReporter) { + c.exchange = exchange + } +} + +// Queue sets the Queue used to send messages +func Queue(queue string) ReporterOption { + return func(c *rmqReporter) { + c.queue = queue + } +} + +// Channel sets the Channel used to send messages +func Channel(ch *amqp.Channel) ReporterOption { + return func(c *rmqReporter) { + c.channel = ch + } +} + +// Connection sets the Connection used to send messages +func Connection(conn *amqp.Connection) ReporterOption { + return func(c *rmqReporter) { + c.conn = conn + } +} + +// NewReporter returns a new RabbitMq-backed Reporter. address should be as described here: https://www.rabbitmq.com/uri-spec.html +func NewReporter(address string, options ...ReporterOption) (reporter.Reporter, error) { + r := &rmqReporter{ + logger: log.New(os.Stderr, "", log.LstdFlags), + queue: defaultRmqRoutingKey, + exchange: defaultRmqExchange, + e: make(chan error), + } + + for _, option := range options { + option(r) + } + + checks := []func() error{ + r.queueVerify, + r.exchangeVerify, + r.queueBindVerify, + } + + var err error + + if r.conn == nil { + r.conn, err = amqp.Dial(address) + if err != nil { + return nil, err + } + } + + if r.channel == nil { + r.channel, err = r.conn.Channel() + if err != nil { + return nil, err + } + } + + for i := 0; i < len(checks); i++ { + if err := checks[i](); err != nil { + return nil, err + } + } + + go r.logErrors() + + return r, nil +} + +func (r *rmqReporter) logErrors() { + for err := range r.e { + r.logger.Print("msg", err.Error()) + } +} + +func (r *rmqReporter) Send(s model.SpanModel) { + // Zipkin expects the message to be wrapped in an array + ss := []model.SpanModel{s} + m, err := json.Marshal(ss) + if err != nil { + r.e <- fmt.Errorf("failed when marshalling the span: %s\n", err.Error()) + return + } + + msg := amqp.Publishing{ + Body: m, + } + + err = r.channel.Publish(defaultRmqExchange, defaultRmqRoutingKey, false, false, msg) + if err != nil { + r.e <- fmt.Errorf("failed when publishing the span: %s\n", err.Error()) + } +} + +func (r *rmqReporter) queueBindVerify() error { + return r.channel.QueueBind( + defaultRmqRoutingKey, + defaultRmqRoutingKey, + defaultRmqExchange, + false, + nil) +} + +func (r *rmqReporter) exchangeVerify() error { + err := r.channel.ExchangeDeclare( + defaultRmqExchange, + defaultExchangeKind, + true, + false, + false, + false, + nil, + ) + + if err != nil { + return err + } + + return nil +} + +func (r *rmqReporter) queueVerify() error { + _, err := r.channel.QueueDeclare( + defaultRmqExchange, + true, + false, + false, + false, + nil, + ) + if err != nil { + return err + } + + return nil +} + +func (r *rmqReporter) Close() error { + err := r.channel.Close() + if err != nil { + return err + } + + err = r.conn.Close() + if err != nil { + return err + } + return nil +} diff --git a/reporter/amqp/amqp_test.go b/reporter/amqp/amqp_test.go new file mode 100644 index 0000000..6a1b84c --- /dev/null +++ b/reporter/amqp/amqp_test.go @@ -0,0 +1,135 @@ +// +build !windows + +package amqp_test + +import ( + "encoding/json" + "testing" + "time" + + "github.com/openzipkin/zipkin-go/model" + zipkinamqp "github.com/openzipkin/zipkin-go/reporter/amqp" + "github.com/streadway/amqp" +) + +var spans = []*model.SpanModel{ + makeNewSpan("avg", 123, 456, 0, true), + makeNewSpan("sum", 123, 789, 456, true), + makeNewSpan("div", 123, 101112, 456, true), +} + +func TestRabbitProduce(t *testing.T) { + address := "amqp://guest:guest@localhost:5672/" + _, ch, closeFunc := setupRabbit(t, address) + defer closeFunc() + + c, err := zipkinamqp.NewReporter(address, zipkinamqp.Channel(ch)) + if err != nil { + t.Fatal(err) + } + + msgs := setupConsume(t, ch) + + for _, s := range spans { + c.Send(*s) + } + + for _, s := range spans { + msg := <-msgs + ds := deserializeSpan(t, msg.Body) + testEqual(t, s, ds) + } +} + +func TestRabbitClose(t *testing.T) { + address := "amqp://guest:guest@127.0.0.1:5672/" + conn, ch, closeFunc := setupRabbit(t, address) + defer closeFunc() + + r, err := zipkinamqp.NewReporter(address, zipkinamqp.Channel(ch), zipkinamqp.Connection(conn)) + if err != nil { + t.Fatal(err) + } + if err = r.Close(); err != nil { + t.Fatal(err) + } +} + +func setupRabbit(t *testing.T, address string) (conn *amqp.Connection, ch *amqp.Channel, close func()) { + var err error + conn, err = amqp.Dial(address) + failOnError(t, err, "Failed to connect to RabbitMQ") + + ch, err = conn.Channel() + failOnError(t, err, "Failed to open a channel") + + close = func() { + conn.Close() + ch.Close() + } + return +} + +func setupConsume(t *testing.T, ch *amqp.Channel) <-chan amqp.Delivery { + csm, err := ch.Consume( + "zipkin", + "", + true, + false, + false, + false, + nil, + ) + failOnError(t, err, "Failed to register a consumer") + return csm +} + +func deserializeSpan(t *testing.T, data []byte) *model.SpanModel { + var receivedSpans []model.SpanModel + err := json.Unmarshal(data, &receivedSpans) + if err != nil { + t.Fatal(err) + } + return &receivedSpans[0] +} + +func failOnError(t *testing.T, err error, msg string) { + if err != nil { + t.Fatalf("%s: %s", msg, err) + } +} + +func testEqual(t *testing.T, want *model.SpanModel, have *model.SpanModel) { + if have.TraceID != want.TraceID { + t.Errorf("incorrect trace_id. have %d, want %d", have.TraceID, want.TraceID) + } + if have.ID != want.ID { + t.Errorf("incorrect id. have %d, want %d", have.ID, want.ID) + } + if have.ParentID == nil { + if want.ParentID != nil { + t.Errorf("incorrect parent_id. have %d, want %d", have.ParentID, want.ParentID) + } + } else if *have.ParentID != *want.ParentID { + t.Errorf("incorrect parent_id. have %d, want %d", have.ParentID, want.ParentID) + } +} + +func makeNewSpan(methodName string, traceID, spanID, parentSpanID uint64, debug bool) *model.SpanModel { + timestamp := time.Now() + var parentID = new(model.ID) + if parentSpanID != 0 { + *parentID = model.ID(parentSpanID) + } + + return &model.SpanModel{ + SpanContext: model.SpanContext{ + TraceID: model.TraceID{Low: traceID}, + ID: model.ID(spanID), + ParentID: parentID, + Debug: debug, + }, + Name: methodName, + Timestamp: timestamp, + } +} diff --git a/reporter/http/http.go b/reporter/http/http.go index af3b8ee..3a48ce5 100644 --- a/reporter/http/http.go +++ b/reporter/http/http.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + /* Package http implements a HTTP reporter to send spans to Zipkin V2 collectors. */ @@ -5,7 +19,6 @@ package http import ( "bytes" - "encoding/json" "log" "net/http" "os" @@ -39,6 +52,7 @@ type httpReporter struct { quit chan struct{} shutdown chan error reqCallback RequestCallbackFn + serializer reporter.SpanSerializer } // Send implements reporter @@ -113,7 +127,7 @@ func (r *httpReporter) sendBatch() error { return nil } - body, err := json.Marshal(sendBatch) + body, err := r.serializer.Serialize(sendBatch) if err != nil { r.logger.Printf("failed when marshalling the spans batch: %s\n", err.Error()) return err @@ -124,7 +138,7 @@ func (r *httpReporter) sendBatch() error { r.logger.Printf("failed when creating the request: %s\n", err.Error()) return err } - req.Header.Set("Content-Type", "application/json") + req.Header.Set("Content-Type", r.serializer.ContentType()) if r.reqCallback != nil { r.reqCallback(req) } @@ -189,6 +203,22 @@ func RequestCallback(rc RequestCallbackFn) ReporterOption { return func(r *httpReporter) { r.reqCallback = rc } } +// Logger sets the logger used to report errors in the collection +// process. +func Logger(l *log.Logger) ReporterOption { + return func(r *httpReporter) { r.logger = l } +} + +// Serializer sets the serialization function to use for sending span data to +// Zipkin. +func Serializer(serializer reporter.SpanSerializer) ReporterOption { + return func(r *httpReporter) { + if serializer != nil { + r.serializer = serializer + } + } +} + // NewReporter returns a new HTTP Reporter. // url should be the endpoint to send the spans to, e.g. // http://localhost:9411/api/v2/spans @@ -206,6 +236,7 @@ func NewReporter(url string, opts ...ReporterOption) reporter.Reporter { shutdown: make(chan error, 1), sendMtx: &sync.Mutex{}, batchMtx: &sync.Mutex{}, + serializer: reporter.JSONSerializer{}, } for _, opt := range opts { diff --git a/reporter/http/http_test.go b/reporter/http/http_test.go index 04c4d32..84aacf8 100644 --- a/reporter/http/http_test.go +++ b/reporter/http/http_test.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package http_test import ( diff --git a/reporter/kafka/kafka.go b/reporter/kafka/kafka.go index a88ebb4..a50986f 100644 --- a/reporter/kafka/kafka.go +++ b/reporter/kafka/kafka.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + /* Package kafka implements a Kafka reporter to send spans to a Kafka server/cluster. */ @@ -21,9 +35,10 @@ const defaultKafkaTopic = "zipkin" // kafkaReporter implements Reporter by publishing spans to a Kafka // broker. type kafkaReporter struct { - producer sarama.AsyncProducer - logger *log.Logger - topic string + producer sarama.AsyncProducer + logger *log.Logger + topic string + serializer reporter.SpanSerializer } // ReporterOption sets a parameter for the kafkaReporter @@ -51,12 +66,23 @@ func Topic(t string) ReporterOption { } } +// Serializer sets the serialization function to use for sending span data to +// Zipkin. +func Serializer(serializer reporter.SpanSerializer) ReporterOption { + return func(c *kafkaReporter) { + if serializer != nil { + c.serializer = serializer + } + } +} + // NewReporter returns a new Kafka-backed Reporter. address should be a slice of // TCP endpoints of the form "host:port". func NewReporter(address []string, options ...ReporterOption) (reporter.Reporter, error) { r := &kafkaReporter{ - logger: log.New(os.Stderr, "", log.LstdFlags), - topic: defaultKafkaTopic, + logger: log.New(os.Stderr, "", log.LstdFlags), + topic: defaultKafkaTopic, + serializer: reporter.JSONSerializer{}, } for _, option := range options { diff --git a/reporter/kafka/kafka_test.go b/reporter/kafka/kafka_test.go index 09e9ccc..fcc5e21 100644 --- a/reporter/kafka/kafka_test.go +++ b/reporter/kafka/kafka_test.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package kafka_test import ( @@ -10,6 +24,7 @@ import ( "github.com/Shopify/sarama" "github.com/openzipkin/zipkin-go/model" + zipkin_proto3 "github.com/openzipkin/zipkin-go/proto/v2" "github.com/openzipkin/zipkin-go/reporter" "github.com/openzipkin/zipkin-go/reporter/kafka" ) @@ -66,6 +81,25 @@ func TestKafkaProduce(t *testing.T) { } } +func TestKafkaProduceProto(t *testing.T) { + p := newStubProducer(false) + c, err := kafka.NewReporter( + []string{"192.0.2.10:9092"}, + kafka.Producer(p), + kafka.Serializer(zipkin_proto3.SpanSerializer{}), + ) + if err != nil { + t.Fatal(err) + } + + for _, want := range spans { + m := sendSpan(t, c, p, *want) + testMetadata(t, m) + have := deserializeSpan(t, m.Value) + testEqual(t, want, have) + } +} + func TestKafkaClose(t *testing.T) { p := newStubProducer(false) r, err := kafka.NewReporter( diff --git a/reporter/log/log.go b/reporter/log/log.go index cddcfd2..dc9758f 100644 --- a/reporter/log/log.go +++ b/reporter/log/log.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + /* Package log implements a reporter to send spans in V2 JSON format to the Go standard Logger. diff --git a/reporter/recorder/recorder.go b/reporter/recorder/recorder.go index df15a81..acfa5ee 100644 --- a/reporter/recorder/recorder.go +++ b/reporter/recorder/recorder.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + /* Package recorder implements a reporter to record spans in v2 format. */ diff --git a/reporter/recorder/recorder_test.go b/reporter/recorder/recorder_test.go index 5ab00d7..fed02c0 100644 --- a/reporter/recorder/recorder_test.go +++ b/reporter/recorder/recorder_test.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package recorder import ( diff --git a/reporter/reporter.go b/reporter/reporter.go index 09b877e..921aff5 100644 --- a/reporter/reporter.go +++ b/reporter/reporter.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + /* Package reporter holds the Reporter interface which is used by the Zipkin Tracer to send finished spans. diff --git a/reporter/serializer.go b/reporter/serializer.go new file mode 100644 index 0000000..6647e2b --- /dev/null +++ b/reporter/serializer.go @@ -0,0 +1,42 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package reporter + +import ( + "encoding/json" + + "github.com/openzipkin/zipkin-go/model" +) + +// SpanSerializer describes the methods needed for allowing to set Span encoding +// type for the various Zipkin transports. +type SpanSerializer interface { + Serialize([]*model.SpanModel) ([]byte, error) + ContentType() string +} + +// JSONSerializer implements the default JSON encoding SpanSerializer. +type JSONSerializer struct{} + +// Serialize takes an array of Zipkin SpanModel objects and returns a JSON +// encoding of it. +func (JSONSerializer) Serialize(spans []*model.SpanModel) ([]byte, error) { + return json.Marshal(spans) +} + +// ContentType returns the ContentType needed for this encoding. +func (JSONSerializer) ContentType() string { + return "application/json" +} diff --git a/sample.go b/sample.go index 248b8f5..6103c13 100644 --- a/sample.go +++ b/sample.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zipkin import ( @@ -13,12 +27,12 @@ import ( type Sampler func(id uint64) bool // NeverSample will always return false. If used by a service it will not allow -// the service to start traces but will still allow the service to participate +// the service to start traces but will still allow the service to participate // in traces started upstream. func NeverSample(_ uint64) bool { return false } // AlwaysSample will always return true. If used by a service it will always start -// traces if no upstream trace has been propagated. If an incoming upstream trace +// traces if no upstream trace has been propagated. If an incoming upstream trace // is not sampled the service will adhere to this and only propagate the context. func AlwaysSample(_ uint64) bool { return true } diff --git a/sample_test.go b/sample_test.go index 56856f6..067bc59 100644 --- a/sample_test.go +++ b/sample_test.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zipkin_test import ( diff --git a/span.go b/span.go index 7d03924..cc91568 100644 --- a/span.go +++ b/span.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zipkin import ( @@ -31,6 +45,12 @@ type Span interface { // span.Flush). Finish() + // Finish the Span with duration and send to Reporter. If DelaySend option was used at + // Span creation time, FinishedWithDuration will not send the Span to the Reporter. It then + // becomes the user's responsibility to get the Span reported (by using + // span.Flush). + FinishedWithDuration(duration time.Duration) + // Flush the Span to the Reporter (regardless of being finished or not). // This can be used if the DelaySend SpanOption was set or when dealing with // one-way RPC tracing where duration might not be measured. diff --git a/span_implementation.go b/span_implementation.go index 5872308..72904a8 100644 --- a/span_implementation.go +++ b/span_implementation.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zipkin import ( @@ -71,6 +85,15 @@ func (s *spanImpl) Finish() { } } +func (s *spanImpl) FinishedWithDuration(d time.Duration) { + if atomic.CompareAndSwapInt32(&s.mustCollect, 1, 0) { + s.Duration = d + if s.flushOnFinish { + s.tracer.reporter.Send(s.SpanModel) + } + } +} + func (s *spanImpl) Flush() { if s.SpanModel.Debug || (s.SpanModel.Sampled != nil && *s.SpanModel.Sampled) { s.tracer.reporter.Send(s.SpanModel) diff --git a/span_options.go b/span_options.go index e6fbcf0..5ac60bf 100644 --- a/span_options.go +++ b/span_options.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zipkin import ( diff --git a/span_test.go b/span_test.go index d526d40..a839319 100644 --- a/span_test.go +++ b/span_test.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zipkin import ( diff --git a/tags.go b/tags.go index 28be6dd..650913c 100644 --- a/tags.go +++ b/tags.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zipkin // Tag holds available types diff --git a/tracer.go b/tracer.go index a050e30..0f294cf 100644 --- a/tracer.go +++ b/tracer.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zipkin import ( diff --git a/tracer_options.go b/tracer_options.go index 7a3608f..533c5e4 100644 --- a/tracer_options.go +++ b/tracer_options.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zipkin import ( diff --git a/tracer_test.go b/tracer_test.go index 11dc7b1..d90d217 100644 --- a/tracer_test.go +++ b/tracer_test.go @@ -1,3 +1,17 @@ +// Copyright 2019 The OpenZipkin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zipkin import ( @@ -747,6 +761,20 @@ func TestStartSpanFromContext(t *testing.T) { } } +func TestStartSpanFromContextForEmptyContext(t *testing.T) { + ctx := context.Background() + rep := reporter.NewNoopReporter() + defer rep.Close() + + tr, err := NewTracer(rep, WithSampler(NeverSample)) + if err != nil { + t.Fatalf("unable to create tracer instance: %+v", err) + } + + span, _ := tr.StartSpanFromContext(ctx, "my span") + span.Finish() +} + func TestLocalEndpoint(t *testing.T) { rep := reporter.NewNoopReporter() defer rep.Close() From 624e68963d7038f2857c639d2d928476b6db83da Mon Sep 17 00:00:00 2001 From: "javier.viera" Date: Tue, 6 Aug 2019 09:07:57 +0200 Subject: [PATCH 03/18] Add Pubsub dependencies --- .gitignore | 1 + go.mod | 14 +++++--- go.sum | 71 +++++++++++++++++++++++++++++++++++++++ reporter/pubsub/pubsub.go | 26 +++++++------- 4 files changed, 96 insertions(+), 16 deletions(-) diff --git a/.gitignore b/.gitignore index 11b90db..9f86f2a 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ _testmain.go *.prof .idea +vendor \ No newline at end of file diff --git a/go.mod b/go.mod index ed37c1c..0788229 100644 --- a/go.mod +++ b/go.mod @@ -1,25 +1,31 @@ module github.com/openzipkin/zipkin-go require ( + cloud.google.com/go v0.38.0 github.com/Shopify/sarama v1.19.0 github.com/Shopify/toxiproxy v2.1.4+incompatible // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/eapache/go-resiliency v1.1.0 // indirect github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 // indirect github.com/eapache/queue v1.1.0 // indirect + github.com/envoyproxy/go-control-plane v0.6.9 // indirect + github.com/gogo/googleapis v1.1.0 // indirect github.com/gogo/protobuf v1.2.0 - github.com/golang/protobuf v1.2.0 + github.com/golang/protobuf v1.3.1 github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect + github.com/googleapis/gax-go v2.0.2+incompatible // indirect github.com/gorilla/context v1.1.1 // indirect github.com/gorilla/mux v1.6.2 + github.com/lyft/protoc-gen-validate v0.0.13 // indirect github.com/onsi/ginkgo v1.7.0 github.com/onsi/gomega v1.4.3 github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1 // indirect github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a // indirect github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94 - golang.org/x/net v0.0.0-20190311183353-d8887717615a - golang.org/x/sync v0.0.0-20181108010431-42b317875d0f // indirect - google.golang.org/grpc v1.20.0 + go.opencensus.io v0.22.0 // indirect + golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c + google.golang.org/api v0.7.0 // indirect + google.golang.org/grpc v1.20.1 ) go 1.12 diff --git a/go.sum b/go.sum index 5274bc9..5a7f514 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,8 @@ +cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Shopify/sarama v1.19.0 h1:9oksLxC6uxVPHPVYUmq6xhr1BOF/hHobWH2UzO67z1s= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= @@ -22,16 +26,32 @@ github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= 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/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/googleapis/gax-go v2.0.2+incompatible h1:silFMLAnr330+NRuag/VjIGF7TLp/LBrV2CJKFLWEww= +github.com/googleapis/gax-go v2.0.2+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= +github.com/googleapis/gax-go/v2 v2.0.4 h1:hU4mGcQI4DaAYW+IbTun+2qEZVFxK0ySjQLTbS0VQKc= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 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 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= @@ -45,26 +65,75 @@ github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhD github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94 h1:0ngsPmuP6XIjiFRNFYlvKwSr5zff2v+uPHaffZ6/M4k= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/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-20190108225652-1e06a53dbb7e/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 h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= +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 h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +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-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0 h1:9sdfJOzWlkqPltHAuzT2Cp+yrBeY1KRVYgms8soxMwM= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 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/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873 h1:nfPFGzJkUDX6uBmpN/pSw7MbOAWegH5QDQuoXFHedLg= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0 h1:DlsSIrgEBuZAUFJcta2B5i/lzeHHbnfkNFAfFXLVFYQ= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= +google.golang.org/grpc v1.20.1 h1:Hz2g2wirWK7H0qIIhGIqRGTuMwTE8HEKFnDZZ7lm9NU= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= @@ -74,3 +143,5 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1/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-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/reporter/pubsub/pubsub.go b/reporter/pubsub/pubsub.go index 2045fad..6fb9e5d 100644 --- a/reporter/pubsub/pubsub.go +++ b/reporter/pubsub/pubsub.go @@ -12,17 +12,18 @@ import ( const defaultPubSubTopic = "pubsub" -// PubSubReporter implements Reporter by publishing spans to a GCP pubsub. -type PubSubReporter struct { - logger *log.Logger - topic string +// Reporter implements Reporter by publishing spans to a GCP pubsub. +type Reporter struct { + logger *log.Logger + topic string client *pubsub.Client } // ReporterOption sets a parameter for the PubSubReporter -type ReporterOption func(c *PubSubReporter) +type ReporterOption func(c *Reporter) -func (r *PubSubReporter) Send(s model.SpanModel) { +// Send send span to topic +func (r *Reporter) Send(s model.SpanModel) { // Zipkin expects the message to be wrapped in an array ss := []model.SpanModel{s} m, err := json.Marshal(ss) @@ -36,28 +37,29 @@ func (r *PubSubReporter) Send(s model.SpanModel) { } } -func (r *PubSubReporter) Close() error { +// Close close span +func (r *Reporter) Close() error { return r.client.Close() } // Logger sets the logger used to report errors in the collection // process. func Logger(logger *log.Logger) ReporterOption { - return func(c *PubSubReporter) { + return func(c *Reporter) { c.logger = logger } } // Client sets the client used to produce to pubsub. func Client(client *pubsub.Client) ReporterOption { - return func(c *PubSubReporter) { + return func(c *Reporter) { c.client = client } } // Topic sets the kafka topic to attach the reporter producer on. func Topic(t string) ReporterOption { - return func(c *PubSubReporter) { + return func(c *Reporter) { c.topic = t } } @@ -65,7 +67,7 @@ func Topic(t string) ReporterOption { // NewReporter returns a new Kafka-backed Reporter. address should be a slice of // TCP endpoints of the form "host:port". func NewReporter(options ...ReporterOption) (reporter.Reporter, error) { - r := &PubSubReporter{ + r := &Reporter{ logger: log.New(os.Stderr, "", log.LstdFlags), topic: defaultPubSubTopic, } @@ -89,7 +91,7 @@ func NewReporter(options ...ReporterOption) (reporter.Reporter, error) { return r, nil } -func (r *PubSubReporter) publish(msg []byte) error { +func (r *Reporter) publish(msg []byte) error { ctx := context.Background() t := r.client.Topic(r.topic) From f9914a562f0caad5a0c4a2bd95691a65f00e06d4 Mon Sep 17 00:00:00 2001 From: "javier.viera" Date: Tue, 6 Aug 2019 10:37:35 +0200 Subject: [PATCH 04/18] Configure topic with env name --- reporter/pubsub/pubsub.go | 11 ++++++++--- reporter/pubsub/pubsub_test.go | 6 +++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/reporter/pubsub/pubsub.go b/reporter/pubsub/pubsub.go index 6fb9e5d..10f1716 100644 --- a/reporter/pubsub/pubsub.go +++ b/reporter/pubsub/pubsub.go @@ -10,7 +10,7 @@ import ( "os" ) -const defaultPubSubTopic = "pubsub" +const defaultPubSubTopic = "opentracing" // Reporter implements Reporter by publishing spans to a GCP pubsub. type Reporter struct { @@ -57,16 +57,21 @@ func Client(client *pubsub.Client) ReporterOption { } } -// Topic sets the kafka topic to attach the reporter producer on. +// Topic sets the pubsub topic to attach the reporter producer on. func Topic(t string) ReporterOption { return func(c *Reporter) { c.topic = t } } -// NewReporter returns a new Kafka-backed Reporter. address should be a slice of +// NewReporter returns a new pubsub-backed Reporter. address should be a slice of // TCP endpoints of the form "host:port". func NewReporter(options ...ReporterOption) (reporter.Reporter, error) { + topic := os.Getenv("OPENTRACING_PUBSUB_TOPIC") + if topic == "" { + topic = defaultPubSubTopic + } + r := &Reporter{ logger: log.New(os.Stderr, "", log.LstdFlags), topic: defaultPubSubTopic, diff --git a/reporter/pubsub/pubsub_test.go b/reporter/pubsub/pubsub_test.go index a4fbf8d..34cee6b 100644 --- a/reporter/pubsub/pubsub_test.go +++ b/reporter/pubsub/pubsub_test.go @@ -19,6 +19,7 @@ var once sync.Once // guards cleanup related operations in setup. func setup(t *testing.T) *pubsub.Client { ctx := context.Background() proj := os.Getenv("GOOGLE_CLOUD_PROJECT") + fmt.Printf("GCP Project: %s\n", proj) topicID = "test-topic" client, err := pubsub.NewClient(ctx, proj) @@ -30,7 +31,7 @@ func setup(t *testing.T) *pubsub.Client { if err != nil { t.Fatalf("failed to create topic: %v", err) } - fmt.Printf("Topic created: %s\n", t.Name()) + fmt.Printf("Topic created: %s\n", topicID) return client } @@ -40,7 +41,7 @@ func TestPublish(t *testing.T) { if err != nil { t.Errorf("failed creating reporter: %v", err) } - span := makeNewSpan("avg", 123, 456, 0, true) + span := makeNewSpan("avg1", 124, 457, 0, true) reporter.Send(*span) // Cleanup resources from the previous failed tests. @@ -78,4 +79,3 @@ func makeNewSpan(methodName string, traceID, spanID, parentSpanID uint64, debug Timestamp: timestamp, } } - From c0594a6a6e4de0da8c5a2d8bc9ba09151edb849b Mon Sep 17 00:00:00 2001 From: "javier.viera" Date: Tue, 6 Aug 2019 11:40:33 +0200 Subject: [PATCH 05/18] Update package and rename default topic --- reporter/{pubsub => gcppubsub}/pubsub.go | 18 +++++++++--------- reporter/{pubsub => gcppubsub}/pubsub_test.go | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) rename reporter/{pubsub => gcppubsub}/pubsub.go (79%) rename reporter/{pubsub => gcppubsub}/pubsub_test.go (98%) diff --git a/reporter/pubsub/pubsub.go b/reporter/gcppubsub/pubsub.go similarity index 79% rename from reporter/pubsub/pubsub.go rename to reporter/gcppubsub/pubsub.go index 10f1716..0b832a3 100644 --- a/reporter/pubsub/pubsub.go +++ b/reporter/gcppubsub/pubsub.go @@ -1,4 +1,4 @@ -package pubsub +package gcppubsub import ( "cloud.google.com/go/pubsub" @@ -10,9 +10,9 @@ import ( "os" ) -const defaultPubSubTopic = "opentracing" +const defaultPubSubTopic = "defaultTopic" -// Reporter implements Reporter by publishing spans to a GCP pubsub. +// Reporter implements Reporter by publishing spans to a GCP gcppubsub. type Reporter struct { logger *log.Logger topic string @@ -33,7 +33,7 @@ func (r *Reporter) Send(s model.SpanModel) { } err = r.publish(m) if err != nil { - r.logger.Printf("Error publishing message to pubsub: %s msg: %s", err.Error(), string(m)) + r.logger.Printf("Error publishing message to gcppubsub: %s msg: %s", err.Error(), string(m)) } } @@ -50,21 +50,21 @@ func Logger(logger *log.Logger) ReporterOption { } } -// Client sets the client used to produce to pubsub. +// Client sets the client used to produce to gcppubsub. func Client(client *pubsub.Client) ReporterOption { return func(c *Reporter) { c.client = client } } -// Topic sets the pubsub topic to attach the reporter producer on. +// Topic sets the gcppubsub topic to attach the reporter producer on. func Topic(t string) ReporterOption { return func(c *Reporter) { c.topic = t } } -// NewReporter returns a new pubsub-backed Reporter. address should be a slice of +// NewReporter returns a new gcppubsub-backed Reporter. address should be a slice of // TCP endpoints of the form "host:port". func NewReporter(options ...ReporterOption) (reporter.Reporter, error) { topic := os.Getenv("OPENTRACING_PUBSUB_TOPIC") @@ -84,11 +84,11 @@ func NewReporter(options ...ReporterOption) (reporter.Reporter, error) { ctx := context.Background() proj := os.Getenv("GOOGLE_CLOUD_PROJECT") if proj == "" { - log.Fatal("GOOGLE_CLOUD_PROJECT environment variable must be set. Traces wont be sent to pubsub") + log.Fatal("GOOGLE_CLOUD_PROJECT environment variable must be set. Traces wont be sent to gcppubsub") } client, err := pubsub.NewClient(ctx, proj) if err != nil { - log.Fatalf("Could not create pubsub Client: %v", err) + log.Fatalf("Could not create gcppubsub Client: %v", err) } r.client = client } diff --git a/reporter/pubsub/pubsub_test.go b/reporter/gcppubsub/pubsub_test.go similarity index 98% rename from reporter/pubsub/pubsub_test.go rename to reporter/gcppubsub/pubsub_test.go index 34cee6b..8f72a40 100644 --- a/reporter/pubsub/pubsub_test.go +++ b/reporter/gcppubsub/pubsub_test.go @@ -1,4 +1,4 @@ -package pubsub +package gcppubsub import ( "context" From 692e01bbba09eaf5ccf0f360a6309a4e785ebf4b Mon Sep 17 00:00:00 2001 From: "javier.viera" Date: Tue, 6 Aug 2019 11:43:14 +0200 Subject: [PATCH 06/18] Rename default topic env var --- reporter/gcppubsub/pubsub.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reporter/gcppubsub/pubsub.go b/reporter/gcppubsub/pubsub.go index 0b832a3..d98f7e0 100644 --- a/reporter/gcppubsub/pubsub.go +++ b/reporter/gcppubsub/pubsub.go @@ -67,7 +67,7 @@ func Topic(t string) ReporterOption { // NewReporter returns a new gcppubsub-backed Reporter. address should be a slice of // TCP endpoints of the form "host:port". func NewReporter(options ...ReporterOption) (reporter.Reporter, error) { - topic := os.Getenv("OPENTRACING_PUBSUB_TOPIC") + topic := os.Getenv("ZIPKIN_PUBSUB_TOPIC") if topic == "" { topic = defaultPubSubTopic } From c67a9490c353ae976954f048d22ae43b372ef1df Mon Sep 17 00:00:00 2001 From: "javier.viera" Date: Tue, 6 Aug 2019 12:37:21 +0200 Subject: [PATCH 07/18] Improve tests --- reporter/gcppubsub/pubsub.go | 10 ++++------ reporter/gcppubsub/pubsub_test.go | 31 +++++++++++++++++++++++++++---- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/reporter/gcppubsub/pubsub.go b/reporter/gcppubsub/pubsub.go index d98f7e0..120c277 100644 --- a/reporter/gcppubsub/pubsub.go +++ b/reporter/gcppubsub/pubsub.go @@ -4,6 +4,7 @@ import ( "cloud.google.com/go/pubsub" "context" "encoding/json" + "errors" "github.com/openzipkin/zipkin-go/model" "github.com/openzipkin/zipkin-go/reporter" "log" @@ -67,11 +68,6 @@ func Topic(t string) ReporterOption { // NewReporter returns a new gcppubsub-backed Reporter. address should be a slice of // TCP endpoints of the form "host:port". func NewReporter(options ...ReporterOption) (reporter.Reporter, error) { - topic := os.Getenv("ZIPKIN_PUBSUB_TOPIC") - if topic == "" { - topic = defaultPubSubTopic - } - r := &Reporter{ logger: log.New(os.Stderr, "", log.LstdFlags), topic: defaultPubSubTopic, @@ -84,11 +80,13 @@ func NewReporter(options ...ReporterOption) (reporter.Reporter, error) { ctx := context.Background() proj := os.Getenv("GOOGLE_CLOUD_PROJECT") if proj == "" { - log.Fatal("GOOGLE_CLOUD_PROJECT environment variable must be set. Traces wont be sent to gcppubsub") + err := errors.New("GOOGLE_CLOUD_PROJECT environment variable must be set. Traces wont be sent to gcppubsub") + return nil, err } client, err := pubsub.NewClient(ctx, proj) if err != nil { log.Fatalf("Could not create gcppubsub Client: %v", err) + return nil, err } r.client = client } diff --git a/reporter/gcppubsub/pubsub_test.go b/reporter/gcppubsub/pubsub_test.go index 8f72a40..0f7401e 100644 --- a/reporter/gcppubsub/pubsub_test.go +++ b/reporter/gcppubsub/pubsub_test.go @@ -29,7 +29,7 @@ func setup(t *testing.T) *pubsub.Client { _, err = client.CreateTopic(ctx, topicID) if err != nil { - t.Fatalf("failed to create topic: %v", err) + fmt.Printf("failed to create topic: %v", err) } fmt.Printf("Topic created: %s\n", topicID) return client @@ -39,7 +39,7 @@ func TestPublish(t *testing.T) { c := setup(t) reporter, err := NewReporter(Client(c), Topic(topicID)) if err != nil { - t.Errorf("failed creating reporter: %v", err) + t.Fatalf("failed creating reporter: %v", err) } span := makeNewSpan("avg1", 124, 457, 0, true) reporter.Send(*span) @@ -50,17 +50,40 @@ func TestPublish(t *testing.T) { topic := c.Topic(topicID) ok, err := topic.Exists(ctx) if err != nil { - t.Fatalf("failed to check if topic exists: %v", err) + fmt.Printf("failed to check if topic exists: %v", err) } if !ok { return } if err := topic.Delete(ctx); err != nil { - t.Fatalf("failed to cleanup the topic (%q): %v", topicID, err) + fmt.Printf("failed to cleanup the topic (%q): %v", topicID, err) } }) } +func TestErrorNotProjEnv(t *testing.T) { + reporter, err := NewReporter(Topic(topicID)) + if reporter != nil { + t.Fatal("Reporter should be null when initiated without client") + } + if err == nil { + t.Fatal("NewReporter should return an error when initiated without client") + } + if err.Error() != "GOOGLE_CLOUD_PROJECT environment variable must be set. Traces wont be sent to gcppubsub" { + t.Fatal("NewReporter should return GOOGLE_CLOUD_PROJECT environment variable must be set error when initiated without client") + } +} + +func TestErrorNotCredentials(t *testing.T) { + os.Setenv("GOOGLE_CLOUD_PROJECT", "anything") + reporter, err := NewReporter(Topic(topicID)) + if err != nil { + t.Fatalf("failed creating reporter: %v", err) + } + span := makeNewSpan("avg1", 124, 457, 0, true) + reporter.Send(*span) +} + func makeNewSpan(methodName string, traceID, spanID, parentSpanID uint64, debug bool) *model.SpanModel { timestamp := time.Now() var parentID = new(model.ID) From 88ecaf9ec8c1041631f84df04962578c7a2c7ff9 Mon Sep 17 00:00:00 2001 From: "javier.viera" Date: Tue, 6 Aug 2019 13:00:03 +0200 Subject: [PATCH 08/18] Improve tests --- reporter/gcppubsub/pubsub_test.go | 56 ++++++++++++++----------------- 1 file changed, 25 insertions(+), 31 deletions(-) diff --git a/reporter/gcppubsub/pubsub_test.go b/reporter/gcppubsub/pubsub_test.go index 0f7401e..b75cc19 100644 --- a/reporter/gcppubsub/pubsub_test.go +++ b/reporter/gcppubsub/pubsub_test.go @@ -24,12 +24,14 @@ func setup(t *testing.T) *pubsub.Client { client, err := pubsub.NewClient(ctx, proj) if err != nil { - t.Fatalf("failed to create client: %v", err) + fmt.Printf("failed to create client: %s\n", topicID) + return nil } _, err = client.CreateTopic(ctx, topicID) if err != nil { fmt.Printf("failed to create topic: %v", err) + return nil } fmt.Printf("Topic created: %s\n", topicID) return client @@ -37,28 +39,30 @@ func setup(t *testing.T) *pubsub.Client { func TestPublish(t *testing.T) { c := setup(t) - reporter, err := NewReporter(Client(c), Topic(topicID)) - if err != nil { - t.Fatalf("failed creating reporter: %v", err) - } - span := makeNewSpan("avg1", 124, 457, 0, true) - reporter.Send(*span) - - // Cleanup resources from the previous failed tests. - once.Do(func() { - ctx := context.Background() - topic := c.Topic(topicID) - ok, err := topic.Exists(ctx) + if c != nil { + reporter, err := NewReporter(Client(c), Topic(topicID)) if err != nil { - fmt.Printf("failed to check if topic exists: %v", err) + t.Fatalf("failed creating reporter: %v", err) } - if !ok { - return - } - if err := topic.Delete(ctx); err != nil { - fmt.Printf("failed to cleanup the topic (%q): %v", topicID, err) - } - }) + span := makeNewSpan("avg1", 124, 457, 0, true) + reporter.Send(*span) + + // Cleanup resources from the previous failed tests. + once.Do(func() { + ctx := context.Background() + topic := c.Topic(topicID) + ok, err := topic.Exists(ctx) + if err != nil { + fmt.Printf("failed to check if topic exists: %v", err) + } + if !ok { + return + } + if err := topic.Delete(ctx); err != nil { + fmt.Printf("failed to cleanup the topic (%q): %v", topicID, err) + } + }) + } } func TestErrorNotProjEnv(t *testing.T) { @@ -74,16 +78,6 @@ func TestErrorNotProjEnv(t *testing.T) { } } -func TestErrorNotCredentials(t *testing.T) { - os.Setenv("GOOGLE_CLOUD_PROJECT", "anything") - reporter, err := NewReporter(Topic(topicID)) - if err != nil { - t.Fatalf("failed creating reporter: %v", err) - } - span := makeNewSpan("avg1", 124, 457, 0, true) - reporter.Send(*span) -} - func makeNewSpan(methodName string, traceID, spanID, parentSpanID uint64, debug bool) *model.SpanModel { timestamp := time.Now() var parentID = new(model.ID) From d38ff1ece0c029b098ac2d27310c1548e61e25d7 Mon Sep 17 00:00:00 2001 From: "javier.viera" Date: Tue, 6 Aug 2019 13:45:34 +0200 Subject: [PATCH 09/18] Improve comments --- reporter/gcppubsub/pubsub.go | 4 ++-- reporter/gcppubsub/pubsub_test.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/reporter/gcppubsub/pubsub.go b/reporter/gcppubsub/pubsub.go index 120c277..f9316e2 100644 --- a/reporter/gcppubsub/pubsub.go +++ b/reporter/gcppubsub/pubsub.go @@ -20,7 +20,7 @@ type Reporter struct { client *pubsub.Client } -// ReporterOption sets a parameter for the PubSubReporter +// ReporterOption sets a parameter for the reporter type ReporterOption func(c *Reporter) // Send send span to topic @@ -38,7 +38,7 @@ func (r *Reporter) Send(s model.SpanModel) { } } -// Close close span +// Close releases any resources held by the client (pubsub client publisher and subscriber connections). func (r *Reporter) Close() error { return r.client.Close() } diff --git a/reporter/gcppubsub/pubsub_test.go b/reporter/gcppubsub/pubsub_test.go index b75cc19..ed954a9 100644 --- a/reporter/gcppubsub/pubsub_test.go +++ b/reporter/gcppubsub/pubsub_test.go @@ -16,7 +16,7 @@ var topicID string var once sync.Once // guards cleanup related operations in setup. -func setup(t *testing.T) *pubsub.Client { +func setup() *pubsub.Client { ctx := context.Background() proj := os.Getenv("GOOGLE_CLOUD_PROJECT") fmt.Printf("GCP Project: %s\n", proj) @@ -38,7 +38,7 @@ func setup(t *testing.T) *pubsub.Client { } func TestPublish(t *testing.T) { - c := setup(t) + c := setup() if c != nil { reporter, err := NewReporter(Client(c), Topic(topicID)) if err != nil { From bb3910366b47b7ad581310ca4de56e3a5ab920fe Mon Sep 17 00:00:00 2001 From: "javier.viera" Date: Tue, 6 Aug 2019 15:48:34 +0200 Subject: [PATCH 10/18] Check send result non blocking --- reporter/gcppubsub/pubsub.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/reporter/gcppubsub/pubsub.go b/reporter/gcppubsub/pubsub.go index f9316e2..66853d6 100644 --- a/reporter/gcppubsub/pubsub.go +++ b/reporter/gcppubsub/pubsub.go @@ -102,6 +102,12 @@ func (r *Reporter) publish(msg []byte) error { // data must be a ByteString Data: msg, }) - _, err := result.Get(ctx) - return err + go func() { + _, err := result.Get(ctx) + if err != nil { + r.logger.Printf("Error sending message: %s\n", err.Error()) + } + }() + + return nil } From 537d8e6878562aaaa72586589f07476910168809 Mon Sep 17 00:00:00 2001 From: "javier.viera" Date: Tue, 6 Aug 2019 15:51:47 +0200 Subject: [PATCH 11/18] Check send result non blocking --- reporter/gcppubsub/pubsub.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/reporter/gcppubsub/pubsub.go b/reporter/gcppubsub/pubsub.go index 66853d6..2cdb1f7 100644 --- a/reporter/gcppubsub/pubsub.go +++ b/reporter/gcppubsub/pubsub.go @@ -32,10 +32,7 @@ func (r *Reporter) Send(s model.SpanModel) { r.logger.Printf("failed when marshalling the span: %s\n", err.Error()) return } - err = r.publish(m) - if err != nil { - r.logger.Printf("Error publishing message to gcppubsub: %s msg: %s", err.Error(), string(m)) - } + r.publish(m) } // Close releases any resources held by the client (pubsub client publisher and subscriber connections). @@ -94,7 +91,7 @@ func NewReporter(options ...ReporterOption) (reporter.Reporter, error) { return r, nil } -func (r *Reporter) publish(msg []byte) error { +func (r *Reporter) publish(msg []byte) { ctx := context.Background() t := r.client.Topic(r.topic) @@ -108,6 +105,4 @@ func (r *Reporter) publish(msg []byte) error { r.logger.Printf("Error sending message: %s\n", err.Error()) } }() - - return nil } From e6cd07427db8eb4cce42b3b639b6114d43b11e65 Mon Sep 17 00:00:00 2001 From: fjviera Date: Sat, 10 Aug 2019 21:11:41 +0200 Subject: [PATCH 12/18] Only accept collector creation with a valid pubsub client --- reporter/gcppubsub/pubsub.go | 26 +++++++++----------------- reporter/gcppubsub/pubsub_test.go | 9 +++++---- 2 files changed, 14 insertions(+), 21 deletions(-) diff --git a/reporter/gcppubsub/pubsub.go b/reporter/gcppubsub/pubsub.go index 2cdb1f7..4dff021 100644 --- a/reporter/gcppubsub/pubsub.go +++ b/reporter/gcppubsub/pubsub.go @@ -16,7 +16,7 @@ const defaultPubSubTopic = "defaultTopic" // Reporter implements Reporter by publishing spans to a GCP gcppubsub. type Reporter struct { logger *log.Logger - topic string + topic *pubsub.Topic client *pubsub.Client } @@ -56,7 +56,7 @@ func Client(client *pubsub.Client) ReporterOption { } // Topic sets the gcppubsub topic to attach the reporter producer on. -func Topic(t string) ReporterOption { +func Topic(t *pubsub.Topic) ReporterOption { return func(c *Reporter) { c.topic = t } @@ -67,35 +67,27 @@ func Topic(t string) ReporterOption { func NewReporter(options ...ReporterOption) (reporter.Reporter, error) { r := &Reporter{ logger: log.New(os.Stderr, "", log.LstdFlags), - topic: defaultPubSubTopic, } for _, option := range options { option(r) } + if r.client == nil { - ctx := context.Background() - proj := os.Getenv("GOOGLE_CLOUD_PROJECT") - if proj == "" { - err := errors.New("GOOGLE_CLOUD_PROJECT environment variable must be set. Traces wont be sent to gcppubsub") - return nil, err - } - client, err := pubsub.NewClient(ctx, proj) - if err != nil { - log.Fatalf("Could not create gcppubsub Client: %v", err) - return nil, err - } - r.client = client + err := errors.New("cannot create pubsub reporter without valid client") + return nil, err } + t := r.client.Topic(defaultPubSubTopic) + r.topic = t + return r, nil } func (r *Reporter) publish(msg []byte) { ctx := context.Background() - t := r.client.Topic(r.topic) - result := t.Publish(ctx, &pubsub.Message{ + result := r.topic.Publish(ctx, &pubsub.Message{ // data must be a ByteString Data: msg, }) diff --git a/reporter/gcppubsub/pubsub_test.go b/reporter/gcppubsub/pubsub_test.go index ed954a9..8efac3e 100644 --- a/reporter/gcppubsub/pubsub_test.go +++ b/reporter/gcppubsub/pubsub_test.go @@ -40,7 +40,8 @@ func setup() *pubsub.Client { func TestPublish(t *testing.T) { c := setup() if c != nil { - reporter, err := NewReporter(Client(c), Topic(topicID)) + top := c.Topic(topicID) + reporter, err := NewReporter(Client(c), Topic(top)) if err != nil { t.Fatalf("failed creating reporter: %v", err) } @@ -66,15 +67,15 @@ func TestPublish(t *testing.T) { } func TestErrorNotProjEnv(t *testing.T) { - reporter, err := NewReporter(Topic(topicID)) + reporter, err := NewReporter() if reporter != nil { t.Fatal("Reporter should be null when initiated without client") } if err == nil { t.Fatal("NewReporter should return an error when initiated without client") } - if err.Error() != "GOOGLE_CLOUD_PROJECT environment variable must be set. Traces wont be sent to gcppubsub" { - t.Fatal("NewReporter should return GOOGLE_CLOUD_PROJECT environment variable must be set error when initiated without client") + if err.Error() != "cannot create pubsub reporter without valid client" { + t.Fatal("NewReporter should return cannot create pubsub reporter without valid client error") } } From 19469f30cb0e46cf172fa42fef3cd66367374f42 Mon Sep 17 00:00:00 2001 From: fjviera Date: Sat, 10 Aug 2019 22:32:31 +0200 Subject: [PATCH 13/18] Goroutine in function --- reporter/gcppubsub/pubsub.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/reporter/gcppubsub/pubsub.go b/reporter/gcppubsub/pubsub.go index 4dff021..624dde6 100644 --- a/reporter/gcppubsub/pubsub.go +++ b/reporter/gcppubsub/pubsub.go @@ -88,13 +88,14 @@ func (r *Reporter) publish(msg []byte) { ctx := context.Background() result := r.topic.Publish(ctx, &pubsub.Message{ - // data must be a ByteString Data: msg, }) - go func() { - _, err := result.Get(ctx) - if err != nil { - r.logger.Printf("Error sending message: %s\n", err.Error()) - } - }() + go r.checkResult(*result, ctx) +} + +func (r *Reporter) checkResult(result pubsub.PublishResult, ctx context.Context) { + _, err := result.Get(ctx) + if err != nil { + r.logger.Printf("Error sending message: %s\n", err.Error()) + } } From a3fb7f9bb04d193558c35867ba525bf6b6405a3b Mon Sep 17 00:00:00 2001 From: fjviera Date: Sat, 10 Aug 2019 22:45:31 +0200 Subject: [PATCH 14/18] Fix lint issue --- reporter/gcppubsub/pubsub.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reporter/gcppubsub/pubsub.go b/reporter/gcppubsub/pubsub.go index 624dde6..bae84ee 100644 --- a/reporter/gcppubsub/pubsub.go +++ b/reporter/gcppubsub/pubsub.go @@ -90,10 +90,10 @@ func (r *Reporter) publish(msg []byte) { result := r.topic.Publish(ctx, &pubsub.Message{ Data: msg, }) - go r.checkResult(*result, ctx) + go r.checkResult(ctx, *result) } -func (r *Reporter) checkResult(result pubsub.PublishResult, ctx context.Context) { +func (r *Reporter) checkResult(ctx context.Context, result pubsub.PublishResult) { _, err := result.Get(ctx) if err != nil { r.logger.Printf("Error sending message: %s\n", err.Error()) From a85b23e4a0b5560b33c8f99454178a9512a23348 Mon Sep 17 00:00:00 2001 From: fjviera Date: Thu, 15 Aug 2019 22:49:12 +0200 Subject: [PATCH 15/18] Report send status in a single goroutine --- reporter/gcppubsub/pubsub.go | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/reporter/gcppubsub/pubsub.go b/reporter/gcppubsub/pubsub.go index bae84ee..9371b83 100644 --- a/reporter/gcppubsub/pubsub.go +++ b/reporter/gcppubsub/pubsub.go @@ -13,6 +13,8 @@ import ( const defaultPubSubTopic = "defaultTopic" +var resultMsg = make(chan reporterResult) + // Reporter implements Reporter by publishing spans to a GCP gcppubsub. type Reporter struct { logger *log.Logger @@ -20,6 +22,11 @@ type Reporter struct { client *pubsub.Client } +type reporterResult struct { + ctx context.Context + result pubsub.PublishResult +} + // ReporterOption sets a parameter for the reporter type ReporterOption func(c *Reporter) @@ -37,6 +44,7 @@ func (r *Reporter) Send(s model.SpanModel) { // Close releases any resources held by the client (pubsub client publisher and subscriber connections). func (r *Reporter) Close() error { + close(resultMsg) return r.client.Close() } @@ -80,7 +88,7 @@ func NewReporter(options ...ReporterOption) (reporter.Reporter, error) { t := r.client.Topic(defaultPubSubTopic) r.topic = t - + r.checkResult() return r, nil } @@ -90,12 +98,14 @@ func (r *Reporter) publish(msg []byte) { result := r.topic.Publish(ctx, &pubsub.Message{ Data: msg, }) - go r.checkResult(ctx, *result) + resultMsg <- reporterResult{ctx, *result} } -func (r *Reporter) checkResult(ctx context.Context, result pubsub.PublishResult) { - _, err := result.Get(ctx) - if err != nil { - r.logger.Printf("Error sending message: %s\n", err.Error()) +func (r *Reporter) checkResult() { + for n := range resultMsg { + _, err := n.result.Get(n.ctx) + if err != nil { + r.logger.Printf("Error sending message: %s\n", err.Error()) + } } } From 0cf668467fd2e7cad8502bf6e92746e8995ca8c9 Mon Sep 17 00:00:00 2001 From: fjviera Date: Thu, 15 Aug 2019 23:09:40 +0200 Subject: [PATCH 16/18] Report send status in a single goroutine --- reporter/gcppubsub/pubsub.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reporter/gcppubsub/pubsub.go b/reporter/gcppubsub/pubsub.go index 9371b83..6b6f750 100644 --- a/reporter/gcppubsub/pubsub.go +++ b/reporter/gcppubsub/pubsub.go @@ -88,7 +88,7 @@ func NewReporter(options ...ReporterOption) (reporter.Reporter, error) { t := r.client.Topic(defaultPubSubTopic) r.topic = t - r.checkResult() + go r.checkResult() return r, nil } From 5744523e47320de7584a82489ffec87a49ee2f51 Mon Sep 17 00:00:00 2001 From: "javier.viera" Date: Mon, 9 Sep 2019 12:51:19 +0200 Subject: [PATCH 17/18] Set default topic only when not configured as option --- reporter/gcppubsub/pubsub.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/reporter/gcppubsub/pubsub.go b/reporter/gcppubsub/pubsub.go index 6b6f750..96c6168 100644 --- a/reporter/gcppubsub/pubsub.go +++ b/reporter/gcppubsub/pubsub.go @@ -85,9 +85,10 @@ func NewReporter(options ...ReporterOption) (reporter.Reporter, error) { err := errors.New("cannot create pubsub reporter without valid client") return nil, err } - - t := r.client.Topic(defaultPubSubTopic) - r.topic = t + if r.topic == nil { + t := r.client.Topic(defaultPubSubTopic) + r.topic = t + } go r.checkResult() return r, nil } From 53eecf392947469335b986a2a14182a780a53f6b Mon Sep 17 00:00:00 2001 From: "javier.viera" Date: Wed, 27 Nov 2019 11:17:40 +0100 Subject: [PATCH 18/18] Improve unit tests --- reporter/gcppubsub/pubsub.go | 4 +- reporter/gcppubsub/pubsub_test.go | 61 ++++++++++++++++++------------- 2 files changed, 38 insertions(+), 27 deletions(-) diff --git a/reporter/gcppubsub/pubsub.go b/reporter/gcppubsub/pubsub.go index 96c6168..5303d25 100644 --- a/reporter/gcppubsub/pubsub.go +++ b/reporter/gcppubsub/pubsub.go @@ -11,7 +11,7 @@ import ( "os" ) -const defaultPubSubTopic = "defaultTopic" +const DefaultPubSubTopic = "defaultTopic" var resultMsg = make(chan reporterResult) @@ -86,7 +86,7 @@ func NewReporter(options ...ReporterOption) (reporter.Reporter, error) { return nil, err } if r.topic == nil { - t := r.client.Topic(defaultPubSubTopic) + t := r.client.Topic(DefaultPubSubTopic) r.topic = t } go r.checkResult() diff --git a/reporter/gcppubsub/pubsub_test.go b/reporter/gcppubsub/pubsub_test.go index 8efac3e..4fd03e6 100644 --- a/reporter/gcppubsub/pubsub_test.go +++ b/reporter/gcppubsub/pubsub_test.go @@ -20,7 +20,9 @@ func setup() *pubsub.Client { ctx := context.Background() proj := os.Getenv("GOOGLE_CLOUD_PROJECT") fmt.Printf("GCP Project: %s\n", proj) - topicID = "test-topic" + if topicID == "" { + topicID = DefaultPubSubTopic + } client, err := pubsub.NewClient(ctx, proj) if err != nil { @@ -28,7 +30,7 @@ func setup() *pubsub.Client { return nil } - _, err = client.CreateTopic(ctx, topicID) + //_, err = client.CreateTopic(ctx, topicID) if err != nil { fmt.Printf("failed to create topic: %v", err) return nil @@ -38,31 +40,40 @@ func setup() *pubsub.Client { } func TestPublish(t *testing.T) { - c := setup() - if c != nil { - top := c.Topic(topicID) - reporter, err := NewReporter(Client(c), Topic(top)) - if err != nil { - t.Fatalf("failed creating reporter: %v", err) - } - span := makeNewSpan("avg1", 124, 457, 0, true) - reporter.Send(*span) - - // Cleanup resources from the previous failed tests. - once.Do(func() { - ctx := context.Background() - topic := c.Topic(topicID) - ok, err := topic.Exists(ctx) + data := []struct { + topic string + }{ + {"test-topic"}, + {""}, + } + for _, table := range data { + topicID = table.topic + c := setup() + if c != nil { + top := c.Topic(topicID) + reporter, err := NewReporter(Client(c), Topic(top)) if err != nil { - fmt.Printf("failed to check if topic exists: %v", err) + t.Fatalf("failed creating reporter: %v", err) } - if !ok { - return - } - if err := topic.Delete(ctx); err != nil { - fmt.Printf("failed to cleanup the topic (%q): %v", topicID, err) - } - }) + span := makeNewSpan("avg1", 124, 457, 0, true) + reporter.Send(*span) + + // Cleanup resources from the previous failed tests. + once.Do(func() { + ctx := context.Background() + topic := c.Topic(topicID) + ok, err := topic.Exists(ctx) + if err != nil { + t.Fatal("failed to check if topic exists") + } + if !ok { + return + } + if err := topic.Delete(ctx); err != nil { + t.Fatal(fmt.Sprintf("failed to cleanup the topic (%q): %v", topicID, err)) + } + }) + } } }