Repository: go-kit/kit
Pull request #1022 (2020-10-09 21:52:11)

add support for JSONRPC opencensus tracing
New support for json-rpc opencensus tracing.

To accomplish this without breaking changes, I created a new `ServerBeforeCodec` JSONRPC server option.  Middleware should be able to inspect the details of the rpc request before the codec is invoked, but the existing `ServerBefore` hook is fired too early -- prior to JSON decode.  The new `ServerBeforeCodec` func is invoked after JSON decode, but prior to codec decode.


Patch:
diff --git a/go.mod b/go.mod
index 28c34c0..1d66e82 100644
--- a/go.mod
+++ b/go.mod
@@ -25,9 +25,10 @@ require (
 	github.com/gorilla/mux v1.7.3
 	github.com/hashicorp/consul/api v1.3.0
 	github.com/hashicorp/go-version v1.2.0 // indirect
+	github.com/hashicorp/golang-lru v0.5.1 // indirect
 	github.com/hudl/fargo v1.3.0
 	github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d
-	github.com/lightstep/lightstep-tracer-go v0.18.1
+	github.com/lightstep/lightstep-tracer-go v0.22.0
 	github.com/nats-io/nats-server/v2 v2.1.2
 	github.com/nats-io/nats.go v1.9.1
 	github.com/oklog/oklog v0.3.2
@@ -49,7 +50,7 @@ require (
 	github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271
 	github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a
 	go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738
-	go.opencensus.io v0.22.2
+	go.opencensus.io v0.22.3
 	go.uber.org/zap v1.13.0
 	golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
 	golang.org/x/time v0.0.0-20191024005414-555d28b269f0
diff --git a/go.sum b/go.sum
index 68e9d17..9a80e3e 100644
--- a/go.sum
+++ b/go.sum
@@ -1,11 +1,12 @@
 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=
 github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw=
 github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
 github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
 github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
+github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
+github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
 github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE=
 github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
 github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 h1:rFw4nCn9iMW+Vajsk51NtYIcwSTkXr+JGrMd36kTDJw=
@@ -14,7 +15,6 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
 github.com/apache/thrift v0.13.0 h1:5hryIiq9gtn+MiLVn0wP37kb/uTeRZgN08WoCsAhIhI=
 github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
@@ -85,6 +85,8 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
 github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4=
 github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
+github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
+github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
 github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
@@ -193,10 +195,10 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743 h1:143Bb8f8DuGWck/xpNUOckBVYfFbBTnLevfRZ1aVVqo=
-github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
-github.com/lightstep/lightstep-tracer-go v0.18.1 h1:vi1F1IQ8N7hNWytK9DpJsUfQhGuNSc19z330K6vl4zk=
-github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
+github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20200305213919-a88bf8de3718 h1:lrdADj7ifyBpqGJ+cT4vE5ztUoAF87uUf76+epwPViY=
+github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20200305213919-a88bf8de3718/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
+github.com/lightstep/lightstep-tracer-go v0.22.0 h1:Yy1G8UDT1Ayx010fji4IaTIsmijil5xijvwJ1OoypDk=
+github.com/lightstep/lightstep-tracer-go v0.22.0/go.mod h1:RnONwHKg89zYPmF+Uig5PpHMUcYCFgml8+r4SS53y7A=
 github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
@@ -256,7 +258,6 @@ github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsq
 github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
 github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5 h1:ZCnq+JUrvXcDVhX/xRolRBZifmabN1HcS1wrPSvxhrU=
 github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
-github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
 github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
 github.com/openzipkin/zipkin-go v0.2.2 h1:nY8Hti+WKaP0cRsSeQ026wU03QsM762XBeCXBb9NAWI=
 github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
@@ -269,7 +270,6 @@ github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtP
 github.com/performancecopilot/speed v3.0.0+incompatible h1:2WnRzIquHa5QxaJKShDkLM+sc0JPuwhXzK8OYOyt3Vg=
 github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
 github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
-github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -278,22 +278,18 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
 github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
-github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
 github.com/prometheus/client_golang v1.3.0 h1:miYCvYqFXtl/J9FIy8eNpBfYthAEFg+Ys0XyUVEcDsc=
 github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
-github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.1.0 h1:ElTg5tNp4DqfV7UQjDqv2+RJlNzsDtvNAWccbItceIE=
 github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY=
 github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
-github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8=
 github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
@@ -306,6 +302,8 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da h1:p3Vo3i64TCL
 github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
+github.com/shirou/gopsutil v2.20.1+incompatible h1:oIq9Cq4i84Hk8uQAUOG3eNdI/29hBawGrD5YRl6JRDY=
+github.com/shirou/gopsutil v2.20.1+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
@@ -342,10 +340,8 @@ go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
 go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738 h1:VcrIfasaLFkyjk6KNlXQSzO+B0fZcnECiDrKJsfxka0=
 go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
-go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
-go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
-go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs=
-go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8=
+go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY=
 go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
@@ -367,7 +363,6 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
 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-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
 golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
@@ -380,8 +375,6 @@ golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73r
 golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181220203305-927f97764cc3/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-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -392,7 +385,6 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA=
 golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 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/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -407,7 +399,6 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h
 golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -423,12 +414,10 @@ golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxb
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 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-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
@@ -438,17 +427,14 @@ golang.org/x/tools v0.0.0-20200103221440-774c71fcf114 h1:DnSr2mCsxyCE6ZgIkmcWUQY
 golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
-google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/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-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
@@ -477,7 +463,6 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
 gopkg.in/yaml.v2 v2.2.2/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=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
diff --git a/log/level/level.go b/log/level/level.go
index fceafc4..c0c050f 100644
--- a/log/level/level.go
+++ b/log/level/level.go
@@ -172,7 +172,7 @@ func WarnValue() Value { return warnValue }
 // InfoValue returns the unique value added to log events by Info.
 func InfoValue() Value { return infoValue }
 
-// DebugValue returns the unique value added to log events by Warn.
+// DebugValue returns the unique value added to log events by Debug.
 func DebugValue() Value { return debugValue }
 
 var (
diff --git a/tracing/opencensus/jsonrpc.go b/tracing/opencensus/jsonrpc.go
new file mode 100644
index 0000000..ea2b018
--- /dev/null
+++ b/tracing/opencensus/jsonrpc.go
@@ -0,0 +1,180 @@
+package opencensus
+
+import (
+	"context"
+	"net/http"
+
+	"go.opencensus.io/plugin/ochttp"
+	"go.opencensus.io/plugin/ochttp/propagation/b3"
+	"go.opencensus.io/trace"
+
+	kithttp "github.com/go-kit/kit/transport/http"
+	jsonrpc "github.com/go-kit/kit/transport/http/jsonrpc"
+)
+
+// JSONRPCClientTrace enables OpenCensus tracing of a Go kit JSONRPC transport client.
+func JSONRPCClientTrace(options ...TracerOption) jsonrpc.ClientOption {
+	cfg := TracerOptions{}
+
+	for _, option := range options {
+		option(&cfg)
+	}
+
+	if !cfg.Public && cfg.HTTPPropagate == nil {
+		cfg.HTTPPropagate = &b3.HTTPFormat{}
+	}
+
+	clientBefore := jsonrpc.ClientBefore(
+		func(ctx context.Context, req *http.Request) context.Context {
+			var name string
+
+			if cfg.Name != "" {
+				name = cfg.Name
+			} else {
+				// OpenCensus states Path being default naming for a client span
+				name = ctx.Value(jsonrpc.ContextKeyRequestMethod).(string)
+			}
+
+			ctx, span := trace.StartSpan(
+				ctx,
+				name,
+				trace.WithSampler(cfg.Sampler),
+				trace.WithSpanKind(trace.SpanKindClient),
+			)
+
+			span.AddAttributes(
+				trace.StringAttribute(ochttp.HostAttribute, req.URL.Host),
+				trace.StringAttribute(ochttp.MethodAttribute, req.Method),
+				trace.StringAttribute(ochttp.PathAttribute, req.URL.Path),
+				trace.StringAttribute(ochttp.UserAgentAttribute, req.UserAgent()),
+			)
+
+			if !cfg.Public {
+				cfg.HTTPPropagate.SpanContextToRequest(span.SpanContext(), req)
+			}
+
+			return ctx
+		},
+	)
+
+	clientAfter := jsonrpc.ClientAfter(
+		func(ctx context.Context, res *http.Response) context.Context {
+			if span := trace.FromContext(ctx); span != nil {
+				span.SetStatus(ochttp.TraceStatus(res.StatusCode, http.StatusText(res.StatusCode)))
+				span.AddAttributes(
+					trace.Int64Attribute(ochttp.StatusCodeAttribute, int64(res.StatusCode)),
+				)
+			}
+			return ctx
+		},
+	)
+
+	clientFinalizer := jsonrpc.ClientFinalizer(
+		func(ctx context.Context, err error) {
+			if span := trace.FromContext(ctx); span != nil {
+				if err != nil {
+					span.SetStatus(trace.Status{
+						Code:    trace.StatusCodeUnknown,
+						Message: err.Error(),
+					})
+				}
+				span.End()
+			}
+		},
+	)
+
+	return func(c *jsonrpc.Client) {
+		clientBefore(c)
+		clientAfter(c)
+		clientFinalizer(c)
+	}
+}
+
+// JSONRPCServerTrace enables OpenCensus tracing of a Go kit JSONRPC transport server.
+func JSONRPCServerTrace(options ...TracerOption) jsonrpc.ServerOption {
+	cfg := TracerOptions{}
+
+	for _, option := range options {
+		option(&cfg)
+	}
+
+	if !cfg.Public && cfg.HTTPPropagate == nil {
+		cfg.HTTPPropagate = &b3.HTTPFormat{}
+	}
+
+	serverBeforeCodec := jsonrpc.ServerBeforeCodec(
+		func(ctx context.Context, httpReq *http.Request, req jsonrpc.Request) context.Context {
+			var (
+				spanContext trace.SpanContext
+				span        *trace.Span
+				name        string
+				ok          bool
+			)
+
+			if cfg.Name != "" {
+				name = cfg.Name
+			} else {
+				name = ctx.Value(jsonrpc.ContextKeyRequestMethod).(string)
+				if name == "" {
+					// we can't find the rpc method. probably the
+					// unaryInterceptor was not wired up.
+					name = "unknown jsonrpc method"
+				}
+			}
+
+			spanContext, ok = cfg.HTTPPropagate.SpanContextFromRequest(httpReq)
+			if ok && !cfg.Public {
+				ctx, span = trace.StartSpanWithRemoteParent(
+					ctx,
+					name,
+					spanContext,
+					trace.WithSpanKind(trace.SpanKindServer),
+					trace.WithSampler(cfg.Sampler),
+				)
+			} else {
+				ctx, span = trace.StartSpan(
+					ctx,
+					name,
+					trace.WithSpanKind(trace.SpanKindServer),
+					trace.WithSampler(cfg.Sampler),
+				)
+				if ok {
+					span.AddLink(trace.Link{
+						TraceID:    spanContext.TraceID,
+						SpanID:     spanContext.SpanID,
+						Type:       trace.LinkTypeChild,
+						Attributes: nil,
+					})
+				}
+			}
+
+			span.AddAttributes(
+				trace.StringAttribute(ochttp.MethodAttribute, httpReq.Method),
+				trace.StringAttribute(ochttp.PathAttribute, httpReq.URL.Path),
+			)
+
+			return ctx
+		},
+	)
+
+	serverFinalizer := jsonrpc.ServerFinalizer(
+		func(ctx context.Context, code int, r *http.Request) {
+			if span := trace.FromContext(ctx); span != nil {
+				span.SetStatus(ochttp.TraceStatus(code, http.StatusText(code)))
+
+				if rs, ok := ctx.Value(kithttp.ContextKeyResponseSize).(int64); ok {
+					span.AddAttributes(
+						trace.Int64Attribute("http.response_size", rs),
+					)
+				}
+
+				span.End()
+			}
+		},
+	)
+
+	return func(s *jsonrpc.Server) {
+		serverBeforeCodec(s)
+		serverFinalizer(s)
+	}
+}
diff --git a/transport/http/jsonrpc/client.go b/transport/http/jsonrpc/client.go
index 86e4123..14105aa 100644
--- a/transport/http/jsonrpc/client.go
+++ b/transport/http/jsonrpc/client.go
@@ -159,6 +159,8 @@ func (c Client) Endpoint() endpoint.Endpoint {
 			}()
 		}
 
+		ctx = context.WithValue(ctx, ContextKeyRequestMethod, c.method)
+
 		var params json.RawMessage
 		if params, err = c.enc(ctx, request); err != nil {
 			return nil, err
@@ -207,7 +209,12 @@ func (c Client) Endpoint() endpoint.Endpoint {
 			return nil, err
 		}
 
-		return c.dec(ctx, rpcRes)
+		response, err := c.dec(ctx, rpcRes)
+		if err != nil {
+			return nil, err
+		}
+
+		return response, nil
 	}
 }
 
diff --git a/transport/http/jsonrpc/request_response_types.go b/transport/http/jsonrpc/request_response_types.go
index 1c6630b..ffae404 100644
--- a/transport/http/jsonrpc/request_response_types.go
+++ b/transport/http/jsonrpc/request_response_types.go
@@ -1,6 +1,10 @@
 package jsonrpc
 
-import "encoding/json"
+import (
+	"context"
+	"encoding/json"
+	"net/http"
+)
 
 // Request defines a JSON RPC request from the spec
 // http://www.jsonrpc.org/specification#request_object
@@ -26,6 +30,11 @@ type RequestID struct {
 	stringError error
 }
 
+// RequestFunc may take information from decoded json body and place in
+// request context. In Servers, RequestFuncs are executed after json is parsed
+// but prior to invoking the codec
+type RequestFunc func(context.Context, *http.Request, Request) context.Context
+
 // UnmarshalJSON satisfies json.Unmarshaler
 func (id *RequestID) UnmarshalJSON(b []byte) error {
 	id.intError = json.Unmarshal(b, &id.intValue)
@@ -79,3 +88,9 @@ const (
 	// ContentType defines the content type to be served.
 	ContentType string = "application/json; charset=utf-8"
 )
+
+type contextKey int
+
+const (
+	ContextKeyRequestMethod contextKey = iota
+)
diff --git a/transport/http/jsonrpc/server.go b/transport/http/jsonrpc/server.go
index 75666e4..3250275 100644
--- a/transport/http/jsonrpc/server.go
+++ b/transport/http/jsonrpc/server.go
@@ -19,6 +19,7 @@ var requestIDKey requestIDKeyType
 type Server struct {
 	ecm          EndpointCodecMap
 	before       []httptransport.RequestFunc
+	beforeCodec  []RequestFunc
 	after        []httptransport.ServerResponseFunc
 	errorEncoder httptransport.ErrorEncoder
 	finalizer    httptransport.ServerFinalizerFunc
@@ -50,6 +51,14 @@ func ServerBefore(before ...httptransport.RequestFunc) ServerOption {
 	return func(s *Server) { s.before = append(s.before, before...) }
 }
 
+// ServerBeforeCodec functions are executed after the JSON request body has been
+// decoded, but before the method's decoder is called. This provides an opportunity
+// for middleware to inspect the contents of the rpc request before being passed
+// to the codec.
+func ServerBeforeCodec(beforeCodec ...RequestFunc) ServerOption {
+	return func(s *Server) { s.beforeCodec = append(s.beforeCodec, beforeCodec...) }
+}
+
 // ServerAfter functions are executed on the HTTP response writer after the
 // endpoint is invoked, but before anything is written to the client.
 func ServerAfter(after ...httptransport.ServerResponseFunc) ServerOption {
@@ -110,6 +119,11 @@ func (s Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	}
 
 	ctx = context.WithValue(ctx, requestIDKey, req.ID)
+	ctx = context.WithValue(ctx, ContextKeyRequestMethod, req.Method)
+
+	for _, f := range s.beforeCodec {
+		ctx = f(ctx, r, req)
+	}
 
 	// Get the endpoint and codecs from the map using the method
 	// defined in the JSON  object


Test patch:
diff --git a/tracing/opencensus/grpc_test.go b/tracing/opencensus/grpc_test.go
index cdb12ae..be396d1 100644
--- a/tracing/opencensus/grpc_test.go
+++ b/tracing/opencensus/grpc_test.go
@@ -51,7 +51,10 @@ func TestGRPCClientTrace(t *testing.T) {
 	}
 
 	for _, tr := range traces {
-		clientTracer := ockit.GRPCClientTrace(ockit.WithName(tr.name))
+		clientTracer := ockit.GRPCClientTrace(
+			ockit.WithName(tr.name),
+			ockit.WithSampler(trace.AlwaysSample()),
+		)
 
 		ep := grpctransport.NewClient(
 			cc,
@@ -136,7 +139,10 @@ func TestGRPCServerTrace(t *testing.T) {
 			func(context.Context, interface{}) (interface{}, error) {
 				return nil, tr.err
 			},
-			ockit.GRPCServerTrace(ockit.WithName(tr.name)),
+			ockit.GRPCServerTrace(
+				ockit.WithName(tr.name),
+				ockit.WithSampler(trace.AlwaysSample()),
+			),
 		)
 
 		if tr.useParent {
diff --git a/tracing/opencensus/http_test.go b/tracing/opencensus/http_test.go
index 78ff7d5..7f42c54 100644
--- a/tracing/opencensus/http_test.go
+++ b/tracing/opencensus/http_test.go
@@ -38,7 +38,10 @@ func TestHTTPClientTrace(t *testing.T) {
 	}
 
 	for _, tr := range traces {
-		clientTracer := ockit.HTTPClientTrace(ockit.WithName(tr.name))
+		clientTracer := ockit.HTTPClientTrace(
+			ockit.WithName(tr.name),
+			ockit.WithSampler(trace.AlwaysSample()),
+		)
 		ep := kithttp.NewClient(
 			"GET",
 			rURL,
@@ -117,6 +120,7 @@ func TestHTTPServerTrace(t *testing.T) {
 			func(context.Context, http.ResponseWriter, interface{}) error { return errors.New("dummy") },
 			ockit.HTTPServerTrace(
 				ockit.WithName(tr.name),
+				ockit.WithSampler(trace.AlwaysSample()),
 				ockit.WithHTTPPropagation(tr.propagation),
 			),
 		)
@@ -134,6 +138,9 @@ func TestHTTPServerTrace(t *testing.T) {
 		if tr.useParent {
 			client = http.Client{
 				Transport: &ochttp.Transport{
+					StartOptions: trace.StartOptions{
+						Sampler: trace.AlwaysSample(),
+					},
 					Propagation: tr.propagation,
 				},
 			}
diff --git a/tracing/opencensus/jsonrpc_test.go b/tracing/opencensus/jsonrpc_test.go
new file mode 100644
index 0000000..c9c47ff
--- /dev/null
+++ b/tracing/opencensus/jsonrpc_test.go
@@ -0,0 +1,194 @@
+package opencensus_test
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"net/http"
+	"net/http/httptest"
+	"net/url"
+	"testing"
+
+	"go.opencensus.io/plugin/ochttp"
+	"go.opencensus.io/plugin/ochttp/propagation/b3"
+	"go.opencensus.io/plugin/ochttp/propagation/tracecontext"
+	"go.opencensus.io/trace"
+	"go.opencensus.io/trace/propagation"
+
+	"github.com/go-kit/kit/endpoint"
+	ockit "github.com/go-kit/kit/tracing/opencensus"
+	jsonrpc "github.com/go-kit/kit/transport/http/jsonrpc"
+)
+
+func TestJSONRPCClientTrace(t *testing.T) {
+	var (
+		err          error
+		rec          = &recordingExporter{}
+		rURL, _      = url.Parse("https://httpbin.org/anything")
+		endpointName = "DummyEndpoint"
+	)
+
+	trace.RegisterExporter(rec)
+
+	traces := []struct {
+		name string
+		err  error
+	}{
+		{"", nil},
+		{"CustomName", nil},
+		{"", errors.New("dummy-error")},
+	}
+
+	for _, tr := range traces {
+		clientTracer := ockit.JSONRPCClientTrace(
+			ockit.WithName(tr.name),
+			ockit.WithSampler(trace.AlwaysSample()),
+		)
+		ep := jsonrpc.NewClient(
+			rURL,
+			endpointName,
+			jsonrpc.ClientRequestEncoder(func(ctx context.Context, i interface{}) (json.RawMessage, error) {
+				return json.RawMessage(`{}`), nil
+			}),
+			jsonrpc.ClientResponseDecoder(func(ctx context.Context, r jsonrpc.Response) (response interface{}, err error) {
+				return nil, tr.err
+			}),
+			clientTracer,
+		).Endpoint()
+
+		ctx, parentSpan := trace.StartSpan(context.Background(), "test")
+
+		_, err = ep(ctx, nil)
+		if want, have := tr.err, err; want != have {
+			t.Fatalf("unexpected error, want %s, have %s", tr.err.Error(), err.Error())
+		}
+
+		spans := rec.Flush()
+		if want, have := 1, len(spans); want != have {
+			t.Fatalf("incorrect number of spans, want %d, have %d", want, have)
+		}
+
+		span := spans[0]
+		if want, have := parentSpan.SpanContext().SpanID, span.ParentSpanID; want != have {
+			t.Errorf("incorrect parent ID, want %s, have %s", want, have)
+		}
+
+		if want, have := tr.name, span.Name; want != have && want != "" {
+			t.Errorf("incorrect span name, want %s, have %s", want, have)
+		}
+
+		if want, have := endpointName, span.Name; want != have && tr.name == "" {
+			t.Errorf("incorrect span name, want %s, have %s", want, have)
+		}
+
+		code := trace.StatusCodeOK
+		if tr.err != nil {
+			code = trace.StatusCodeUnknown
+
+			if want, have := err.Error(), span.Status.Message; want != have {
+				t.Errorf("incorrect span status msg, want %s, have %s", want, have)
+			}
+		}
+
+		if want, have := int32(code), span.Status.Code; want != have {
+			t.Errorf("incorrect span status code, want %d, have %d", want, have)
+		}
+	}
+}
+
+func TestJSONRPCServerTrace(t *testing.T) {
+	var (
+		endpointName = "DummyEndpoint"
+		rec          = &recordingExporter{}
+	)
+
+	trace.RegisterExporter(rec)
+
+	traces := []struct {
+		useParent   bool
+		name        string
+		err         error
+		propagation propagation.HTTPFormat
+	}{
+		{false, "", nil, nil},
+		{true, "", nil, nil},
+		{true, "CustomName", nil, &b3.HTTPFormat{}},
+		{true, "", errors.New("dummy-error"), &tracecontext.HTTPFormat{}},
+	}
+
+	for _, tr := range traces {
+		var client http.Client
+
+		handler := jsonrpc.NewServer(
+			jsonrpc.EndpointCodecMap{
+				endpointName: jsonrpc.EndpointCodec{
+					Endpoint: endpoint.Nop,
+					Decode:   func(context.Context, json.RawMessage) (interface{}, error) { return nil, nil },
+					Encode:   func(context.Context, interface{}) (json.RawMessage, error) { return nil, tr.err },
+				},
+			},
+			ockit.JSONRPCServerTrace(
+				ockit.WithName(tr.name),
+				ockit.WithSampler(trace.AlwaysSample()),
+				ockit.WithHTTPPropagation(tr.propagation),
+			),
+		)
+
+		server := httptest.NewServer(handler)
+		defer server.Close()
+
+		jsonStr := []byte(fmt.Sprintf(`{"method":"%s"}`, endpointName))
+		req, err := http.NewRequest("POST", server.URL, bytes.NewBuffer(jsonStr))
+		if err != nil {
+			t.Fatalf("unable to create JSONRPC request: %s", err.Error())
+		}
+
+		if tr.useParent {
+			client = http.Client{
+				Transport: &ochttp.Transport{
+					StartOptions: trace.StartOptions{
+						Sampler: trace.AlwaysSample(),
+					},
+					Propagation: tr.propagation,
+				},
+			}
+		}
+
+		resp, err := client.Do(req.WithContext(context.Background()))
+		if err != nil {
+			t.Fatalf("unable to send JSONRPC request: %s", err.Error())
+		}
+		resp.Body.Close()
+
+		spans := rec.Flush()
+
+		expectedSpans := 1
+		if tr.useParent {
+			expectedSpans++
+		}
+
+		if want, have := expectedSpans, len(spans); want != have {
+			t.Fatalf("incorrect number of spans, want %d, have %d", want, have)
+		}
+
+		if tr.useParent {
+			if want, have := spans[1].TraceID, spans[0].TraceID; want != have {
+				t.Errorf("incorrect trace ID, want %s, have %s", want, have)
+			}
+
+			if want, have := spans[1].SpanID, spans[0].ParentSpanID; want != have {
+				t.Errorf("incorrect span ID, want %s, have %s", want, have)
+			}
+		}
+
+		if want, have := tr.name, spans[0].Name; want != have && want != "" {
+			t.Errorf("incorrect span name, want %s, have %s", want, have)
+		}
+
+		if want, have := endpointName, spans[0].Name; want != have && tr.name == "" {
+			t.Errorf("incorrect span name, want %s, have %s", want, have)
+		}
+	}
+}
diff --git a/transport/http/jsonrpc/server_test.go b/transport/http/jsonrpc/server_test.go
index c279d3c..c05435d 100644
--- a/transport/http/jsonrpc/server_test.go
+++ b/transport/http/jsonrpc/server_test.go
@@ -235,6 +235,42 @@ func TestServerHappyPath(t *testing.T) {
 	}
 }
 
+func TestMultipleServerBeforeCodec(t *testing.T) {
+	var done = make(chan struct{})
+	ecm := jsonrpc.EndpointCodecMap{
+		"add": jsonrpc.EndpointCodec{
+			Endpoint: endpoint.Nop,
+			Decode:   nopDecoder,
+			Encode:   nopEncoder,
+		},
+	}
+	handler := jsonrpc.NewServer(
+		ecm,
+		jsonrpc.ServerBeforeCodec(func(ctx context.Context, r *http.Request, req jsonrpc.Request) context.Context {
+			ctx = context.WithValue(ctx, "one", 1)
+
+			return ctx
+		}),
+		jsonrpc.ServerBeforeCodec(func(ctx context.Context, r *http.Request, req jsonrpc.Request) context.Context {
+			if _, ok := ctx.Value("one").(int); !ok {
+				t.Error("Value was not set properly when multiple ServerBeforeCodecs are used")
+			}
+
+			close(done)
+			return ctx
+		}),
+	)
+	server := httptest.NewServer(handler)
+	defer server.Close()
+	http.Post(server.URL, "application/json", addBody()) // nolint
+
+	select {
+	case <-done:
+	case <-time.After(time.Second):
+		t.Fatal("timeout waiting for finalizer")
+	}
+}
+
 func TestMultipleServerBefore(t *testing.T) {
 	var done = make(chan struct{})
 	ecm := jsonrpc.EndpointCodecMap{