mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2025-03-19 21:08:07 +02:00
Remove othttp and httptrace instrumentations that have moved to contrib (#1032)
This commit is contained in:
parent
ea0720c05e
commit
ae278e9186
@ -13,7 +13,6 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
|
@ -13,7 +13,6 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
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=
|
||||
|
@ -1,24 +0,0 @@
|
||||
# Copyright The OpenTelemetry Authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
FROM golang:1.14-alpine AS base
|
||||
COPY . /go/src/github.com/open-telemetry/opentelemetry-go/
|
||||
WORKDIR /go/src/github.com/open-telemetry/opentelemetry-go/example/http/
|
||||
|
||||
FROM base AS example-http-server
|
||||
RUN go install ./server/server.go
|
||||
CMD ["/go/bin/server"]
|
||||
|
||||
FROM base AS example-http-client
|
||||
RUN go install ./client/client.go
|
||||
CMD ["/go/bin/client"]
|
@ -1,24 +0,0 @@
|
||||
# HTTP Client-Server Example
|
||||
|
||||
An HTTP client connects to an HTTP server. They both generate span information to `stdout`.
|
||||
These instructions expect you have [docker-compose](https://docs.docker.com/compose/) installed.
|
||||
|
||||
Bring up the `http-server` and `http-client` services to run the example:
|
||||
```sh
|
||||
docker-compose up --detach http-server http-client
|
||||
```
|
||||
|
||||
The `http-client` service sends just one HTTP request to `http-server` and then exits. View the span generated to `stdout` in the logs:
|
||||
```sh
|
||||
docker-compose logs http-client
|
||||
```
|
||||
|
||||
View the span generated by `http-server` in the logs:
|
||||
```sh
|
||||
docker-compose logs http-server
|
||||
```
|
||||
|
||||
Shut down the services when you are finished with the example:
|
||||
```sh
|
||||
docker-compose down
|
||||
```
|
@ -1,96 +0,0 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
|
||||
"go.opentelemetry.io/otel/api/kv"
|
||||
"go.opentelemetry.io/otel/api/trace"
|
||||
"go.opentelemetry.io/otel/semconv"
|
||||
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel/api/correlation"
|
||||
"go.opentelemetry.io/otel/api/global"
|
||||
"go.opentelemetry.io/otel/exporters/stdout"
|
||||
"go.opentelemetry.io/otel/instrumentation/httptrace"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
)
|
||||
|
||||
func initTracer() {
|
||||
// Create stdout exporter to be able to retrieve
|
||||
// the collected spans.
|
||||
exporter, err := stdout.NewExporter(stdout.WithPrettyPrint())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// For the demonstration, use sdktrace.AlwaysSample sampler to sample all traces.
|
||||
// In a production application, use sdktrace.ProbabilitySampler with a desired probability.
|
||||
tp, err := sdktrace.NewProvider(sdktrace.WithConfig(sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()}),
|
||||
sdktrace.WithSyncer(exporter))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
global.SetTraceProvider(tp)
|
||||
}
|
||||
|
||||
func main() {
|
||||
initTracer()
|
||||
url := flag.String("server", "http://localhost:7777/hello", "server url")
|
||||
flag.Parse()
|
||||
|
||||
client := http.DefaultClient
|
||||
ctx := correlation.NewContext(context.Background(),
|
||||
kv.String("username", "donuts"),
|
||||
)
|
||||
|
||||
var body []byte
|
||||
|
||||
tr := global.Tracer("example/client")
|
||||
err := tr.WithSpan(ctx, "say hello",
|
||||
func(ctx context.Context) error {
|
||||
req, _ := http.NewRequest("GET", *url, nil)
|
||||
|
||||
ctx, req = httptrace.W3C(ctx, req)
|
||||
httptrace.Inject(ctx, req)
|
||||
|
||||
fmt.Printf("Sending request...\n")
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
body, err = ioutil.ReadAll(res.Body)
|
||||
_ = res.Body.Close()
|
||||
|
||||
return err
|
||||
},
|
||||
trace.WithAttributes(semconv.PeerServiceKey.String("ExampleService")))
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Response Received: %s\n\n\n", body)
|
||||
fmt.Printf("Waiting for few seconds to export spans ...\n\n")
|
||||
time.Sleep(10 * time.Second)
|
||||
fmt.Printf("Inspect traces on stdout\n")
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
# Copyright The OpenTelemetry Authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
version: "3.7"
|
||||
services:
|
||||
http-server:
|
||||
build:
|
||||
dockerfile: $PWD/Dockerfile
|
||||
context: ../..
|
||||
target: example-http-server
|
||||
networks:
|
||||
- example
|
||||
http-client:
|
||||
build:
|
||||
dockerfile: $PWD/Dockerfile
|
||||
context: ../..
|
||||
target: example-http-client
|
||||
command: ["/go/bin/client", "-server", "http://http-server:7777/hello"]
|
||||
networks:
|
||||
- example
|
||||
depends_on:
|
||||
- http-server
|
||||
networks:
|
||||
example:
|
@ -1,15 +0,0 @@
|
||||
module go.opentelemetry.io/otel/example/http
|
||||
|
||||
go 1.14
|
||||
|
||||
replace (
|
||||
go.opentelemetry.io/otel => ../..
|
||||
go.opentelemetry.io/otel/exporters/stdout => ../../exporters/stdout
|
||||
go.opentelemetry.io/otel/sdk => ../../sdk
|
||||
)
|
||||
|
||||
require (
|
||||
go.opentelemetry.io/otel v0.10.0
|
||||
go.opentelemetry.io/otel/exporters/stdout v0.10.0
|
||||
go.opentelemetry.io/otel/sdk v0.10.0
|
||||
)
|
@ -1,100 +0,0 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/DataDog/sketches-go v0.0.1 h1:RtG+76WKgZuz6FIaGsjoPePmadDBkuD/KC6+ZWu78b8=
|
||||
github.com/DataDog/sketches-go v0.0.1/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60=
|
||||
github.com/benbjohnson/clock v1.0.3 h1:vkLuvpK4fmtSCuo60+yC63p7y0BmQ8gm5ZXGuBCJyXg=
|
||||
github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
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/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
|
||||
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-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-20190613194153-d28f0bde5980/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/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03 h1:4HYDjxeNXAOTv3o1N2tjo8UUSlhQgAD52FVkwxnWgM8=
|
||||
google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.31.0 h1:T7P4R73V3SSDPhH7WW7ATbfViLtmamH0DKrP3f9AuDI=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
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=
|
@ -1,21 +0,0 @@
|
||||
# Copyright The OpenTelemetry Authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# A basic modd.conf file for Go development.
|
||||
|
||||
# Run go test on ALL modules on startup, and subsequently only on modules
|
||||
# containing changes.
|
||||
server.go {
|
||||
daemon +sigterm: go run server.go
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"go.opentelemetry.io/otel/api/correlation"
|
||||
"go.opentelemetry.io/otel/api/global"
|
||||
"go.opentelemetry.io/otel/api/trace"
|
||||
"go.opentelemetry.io/otel/exporters/stdout"
|
||||
"go.opentelemetry.io/otel/instrumentation/httptrace"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
"go.opentelemetry.io/otel/semconv"
|
||||
)
|
||||
|
||||
func initTracer() {
|
||||
// Create stdout exporter to be able to retrieve
|
||||
// the collected spans.
|
||||
exporter, err := stdout.NewExporter(stdout.WithPrettyPrint())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// For the demonstration, use sdktrace.AlwaysSample sampler to sample all traces.
|
||||
// In a production application, use sdktrace.ProbabilitySampler with a desired probability.
|
||||
tp, err := sdktrace.NewProvider(sdktrace.WithConfig(sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()}),
|
||||
sdktrace.WithSyncer(exporter),
|
||||
sdktrace.WithResource(resource.New(semconv.ServiceNameKey.String("ExampleService"))))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
global.SetTraceProvider(tp)
|
||||
}
|
||||
|
||||
func main() {
|
||||
initTracer()
|
||||
tr := global.Tracer("example/server")
|
||||
|
||||
helloHandler := func(w http.ResponseWriter, req *http.Request) {
|
||||
attrs, entries, spanCtx := httptrace.Extract(req.Context(), req)
|
||||
|
||||
req = req.WithContext(correlation.ContextWithMap(req.Context(), correlation.NewMap(correlation.MapUpdate{
|
||||
MultiKV: entries,
|
||||
})))
|
||||
|
||||
ctx, span := tr.Start(
|
||||
trace.ContextWithRemoteSpanContext(req.Context(), spanCtx),
|
||||
"hello",
|
||||
trace.WithAttributes(attrs...),
|
||||
)
|
||||
defer span.End()
|
||||
|
||||
span.AddEvent(ctx, "handling this...")
|
||||
|
||||
_, _ = io.WriteString(w, "Hello, world!\n")
|
||||
}
|
||||
|
||||
http.HandleFunc("/hello", helloHandler)
|
||||
err := http.ListenAndServe(":7777", nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
@ -41,7 +41,6 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
|
@ -13,7 +13,6 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
|
@ -13,7 +13,6 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
|
@ -24,7 +24,6 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
|
@ -18,7 +18,6 @@ github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4s
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
|
||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
|
@ -24,7 +24,6 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
|
@ -13,7 +13,6 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
|
@ -13,7 +13,6 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
|
@ -43,7 +43,6 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
|
@ -18,7 +18,6 @@ github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4s
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
|
||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
|
1
go.mod
1
go.mod
@ -4,7 +4,6 @@ go 1.14
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.1
|
||||
github.com/golang/protobuf v1.4.2
|
||||
github.com/google/go-cmp v0.5.1
|
||||
github.com/kr/pretty v0.1.0 // indirect
|
||||
|
2
go.sum
2
go.sum
@ -11,8 +11,6 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
|
||||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
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=
|
||||
|
@ -1,28 +0,0 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package httptrace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptrace"
|
||||
)
|
||||
|
||||
// Client
|
||||
func W3C(ctx context.Context, req *http.Request) (context.Context, *http.Request) {
|
||||
ctx = httptrace.WithClientTrace(ctx, NewClientTrace(ctx))
|
||||
req = req.WithContext(ctx)
|
||||
return ctx, req
|
||||
}
|
@ -1,253 +0,0 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package httptrace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net/http/httptrace"
|
||||
"net/textproto"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"go.opentelemetry.io/otel/semconv"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
|
||||
"go.opentelemetry.io/otel/api/global"
|
||||
"go.opentelemetry.io/otel/api/kv"
|
||||
"go.opentelemetry.io/otel/api/trace"
|
||||
)
|
||||
|
||||
var (
|
||||
HTTPStatus = kv.Key("http.status")
|
||||
HTTPHeaderMIME = kv.Key("http.mime")
|
||||
HTTPRemoteAddr = kv.Key("http.remote")
|
||||
HTTPLocalAddr = kv.Key("http.local")
|
||||
)
|
||||
|
||||
var (
|
||||
hookMap = map[string]string{
|
||||
"http.dns": "http.getconn",
|
||||
"http.connect": "http.getconn",
|
||||
"http.tls": "http.getconn",
|
||||
}
|
||||
)
|
||||
|
||||
func parentHook(hook string) string {
|
||||
if strings.HasPrefix(hook, "http.connect") {
|
||||
return hookMap["http.connect"]
|
||||
}
|
||||
return hookMap[hook]
|
||||
}
|
||||
|
||||
type clientTracer struct {
|
||||
context.Context
|
||||
|
||||
tr trace.Tracer
|
||||
|
||||
activeHooks map[string]context.Context
|
||||
root trace.Span
|
||||
mtx sync.Mutex
|
||||
}
|
||||
|
||||
func NewClientTrace(ctx context.Context) *httptrace.ClientTrace {
|
||||
ct := &clientTracer{
|
||||
Context: ctx,
|
||||
activeHooks: make(map[string]context.Context),
|
||||
}
|
||||
|
||||
ct.tr = global.Tracer("go.opentelemetry.io/otel/instrumentation/httptrace")
|
||||
|
||||
return &httptrace.ClientTrace{
|
||||
GetConn: ct.getConn,
|
||||
GotConn: ct.gotConn,
|
||||
PutIdleConn: ct.putIdleConn,
|
||||
GotFirstResponseByte: ct.gotFirstResponseByte,
|
||||
Got100Continue: ct.got100Continue,
|
||||
Got1xxResponse: ct.got1xxResponse,
|
||||
DNSStart: ct.dnsStart,
|
||||
DNSDone: ct.dnsDone,
|
||||
ConnectStart: ct.connectStart,
|
||||
ConnectDone: ct.connectDone,
|
||||
TLSHandshakeStart: ct.tlsHandshakeStart,
|
||||
TLSHandshakeDone: ct.tlsHandshakeDone,
|
||||
WroteHeaderField: ct.wroteHeaderField,
|
||||
WroteHeaders: ct.wroteHeaders,
|
||||
Wait100Continue: ct.wait100Continue,
|
||||
WroteRequest: ct.wroteRequest,
|
||||
}
|
||||
}
|
||||
|
||||
func (ct *clientTracer) start(hook, spanName string, attrs ...kv.KeyValue) {
|
||||
ct.mtx.Lock()
|
||||
defer ct.mtx.Unlock()
|
||||
|
||||
if hookCtx, found := ct.activeHooks[hook]; !found {
|
||||
var sp trace.Span
|
||||
ct.activeHooks[hook], sp = ct.tr.Start(ct.getParentContext(hook), spanName, trace.WithAttributes(attrs...), trace.WithSpanKind(trace.SpanKindClient))
|
||||
if ct.root == nil {
|
||||
ct.root = sp
|
||||
}
|
||||
} else {
|
||||
// end was called before start finished, add the start attributes and end the span here
|
||||
span := trace.SpanFromContext(hookCtx)
|
||||
span.SetAttributes(attrs...)
|
||||
span.End()
|
||||
|
||||
delete(ct.activeHooks, hook)
|
||||
}
|
||||
}
|
||||
|
||||
func (ct *clientTracer) end(hook string, err error, attrs ...kv.KeyValue) {
|
||||
ct.mtx.Lock()
|
||||
defer ct.mtx.Unlock()
|
||||
if ctx, ok := ct.activeHooks[hook]; ok {
|
||||
span := trace.SpanFromContext(ctx)
|
||||
if err != nil {
|
||||
span.SetStatus(codes.Unknown, err.Error())
|
||||
}
|
||||
span.SetAttributes(attrs...)
|
||||
span.End()
|
||||
delete(ct.activeHooks, hook)
|
||||
} else {
|
||||
// start is not finished before end is called.
|
||||
// Start a span here with the ending attributes that will be finished when start finishes.
|
||||
// Yes, it's backwards. v0v
|
||||
ctx, span := ct.tr.Start(ct.getParentContext(hook), hook, trace.WithAttributes(attrs...), trace.WithSpanKind(trace.SpanKindClient))
|
||||
if err != nil {
|
||||
span.SetStatus(codes.Unknown, err.Error())
|
||||
}
|
||||
ct.activeHooks[hook] = ctx
|
||||
}
|
||||
}
|
||||
|
||||
func (ct *clientTracer) getParentContext(hook string) context.Context {
|
||||
ctx, ok := ct.activeHooks[parentHook(hook)]
|
||||
if !ok {
|
||||
return ct.Context
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (ct *clientTracer) span(hook string) trace.Span {
|
||||
ct.mtx.Lock()
|
||||
defer ct.mtx.Unlock()
|
||||
if ctx, ok := ct.activeHooks[hook]; ok {
|
||||
return trace.SpanFromContext(ctx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ct *clientTracer) getConn(host string) {
|
||||
ct.start("http.getconn", "http.getconn", semconv.HTTPHostKey.String(host))
|
||||
}
|
||||
|
||||
func (ct *clientTracer) gotConn(info httptrace.GotConnInfo) {
|
||||
ct.end("http.getconn",
|
||||
nil,
|
||||
HTTPRemoteAddr.String(info.Conn.RemoteAddr().String()),
|
||||
HTTPLocalAddr.String(info.Conn.LocalAddr().String()),
|
||||
)
|
||||
}
|
||||
|
||||
func (ct *clientTracer) putIdleConn(err error) {
|
||||
ct.end("http.receive", err)
|
||||
}
|
||||
|
||||
func (ct *clientTracer) gotFirstResponseByte() {
|
||||
ct.start("http.receive", "http.receive")
|
||||
}
|
||||
|
||||
func (ct *clientTracer) dnsStart(info httptrace.DNSStartInfo) {
|
||||
ct.start("http.dns", "http.dns", semconv.HTTPHostKey.String(info.Host))
|
||||
}
|
||||
|
||||
func (ct *clientTracer) dnsDone(info httptrace.DNSDoneInfo) {
|
||||
ct.end("http.dns", info.Err)
|
||||
}
|
||||
|
||||
func (ct *clientTracer) connectStart(network, addr string) {
|
||||
ct.start("http.connect."+addr, "http.connect", HTTPRemoteAddr.String(addr))
|
||||
}
|
||||
|
||||
func (ct *clientTracer) connectDone(network, addr string, err error) {
|
||||
ct.end("http.connect."+addr, err)
|
||||
}
|
||||
|
||||
func (ct *clientTracer) tlsHandshakeStart() {
|
||||
ct.start("http.tls", "http.tls")
|
||||
}
|
||||
|
||||
func (ct *clientTracer) tlsHandshakeDone(_ tls.ConnectionState, err error) {
|
||||
ct.end("http.tls", err)
|
||||
}
|
||||
|
||||
func (ct *clientTracer) wroteHeaderField(k string, v []string) {
|
||||
if ct.span("http.headers") == nil {
|
||||
ct.start("http.headers", "http.headers")
|
||||
}
|
||||
ct.root.SetAttributes(kv.String("http."+strings.ToLower(k), sliceToString(v)))
|
||||
}
|
||||
|
||||
func (ct *clientTracer) wroteHeaders() {
|
||||
if ct.span("http.headers") != nil {
|
||||
ct.end("http.headers", nil)
|
||||
}
|
||||
ct.start("http.send", "http.send")
|
||||
}
|
||||
|
||||
func (ct *clientTracer) wroteRequest(info httptrace.WroteRequestInfo) {
|
||||
if info.Err != nil {
|
||||
ct.root.SetStatus(codes.Unknown, info.Err.Error())
|
||||
}
|
||||
ct.end("http.send", info.Err)
|
||||
}
|
||||
|
||||
func (ct *clientTracer) got100Continue() {
|
||||
ct.span("http.receive").AddEvent(ct.Context, "GOT 100 - Continue")
|
||||
}
|
||||
|
||||
func (ct *clientTracer) wait100Continue() {
|
||||
ct.span("http.receive").AddEvent(ct.Context, "GOT 100 - Wait")
|
||||
}
|
||||
|
||||
func (ct *clientTracer) got1xxResponse(code int, header textproto.MIMEHeader) error {
|
||||
ct.span("http.receive").AddEvent(ct.Context, "GOT 1xx",
|
||||
HTTPStatus.Int(code),
|
||||
HTTPHeaderMIME.String(sm2s(header)),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
func sliceToString(value []string) string {
|
||||
if len(value) == 0 {
|
||||
return "undefined"
|
||||
}
|
||||
return strings.Join(value, ",")
|
||||
}
|
||||
|
||||
func sm2s(value map[string][]string) string {
|
||||
var buf strings.Builder
|
||||
for k, v := range value {
|
||||
if buf.Len() != 0 {
|
||||
buf.WriteString(",")
|
||||
}
|
||||
buf.WriteString(k)
|
||||
buf.WriteString("=")
|
||||
buf.WriteString(sliceToString(v))
|
||||
}
|
||||
return buf.String()
|
||||
}
|
@ -1,238 +0,0 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
package httptrace_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
nhtrace "net/http/httptrace"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"go.opentelemetry.io/otel/api/global"
|
||||
"go.opentelemetry.io/otel/api/kv"
|
||||
"go.opentelemetry.io/otel/api/trace/testtrace"
|
||||
"go.opentelemetry.io/otel/instrumentation/httptrace"
|
||||
)
|
||||
|
||||
type SpanRecorder map[string]*testtrace.Span
|
||||
|
||||
func (sr *SpanRecorder) OnStart(span *testtrace.Span) {}
|
||||
func (sr *SpanRecorder) OnEnd(span *testtrace.Span) { (*sr)[span.Name()] = span }
|
||||
|
||||
func TestHTTPRequestWithClientTrace(t *testing.T) {
|
||||
sr := SpanRecorder{}
|
||||
tp := testtrace.NewProvider(testtrace.WithSpanRecorder(&sr))
|
||||
global.SetTraceProvider(tp)
|
||||
tr := tp.Tracer("httptrace/client")
|
||||
|
||||
// Mock http server
|
||||
ts := httptest.NewServer(
|
||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
}),
|
||||
)
|
||||
defer ts.Close()
|
||||
address := ts.Listener.Addr()
|
||||
|
||||
client := ts.Client()
|
||||
err := tr.WithSpan(context.Background(), "test",
|
||||
func(ctx context.Context) error {
|
||||
req, _ := http.NewRequest("GET", ts.URL, nil)
|
||||
_, req = httptrace.W3C(ctx, req)
|
||||
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("Request failed: %s", err.Error())
|
||||
}
|
||||
_ = res.Body.Close()
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
panic("unexpected error in http request: " + err.Error())
|
||||
}
|
||||
|
||||
testLen := []struct {
|
||||
name string
|
||||
attributes map[kv.Key]kv.Value
|
||||
parent string
|
||||
}{
|
||||
{
|
||||
name: "http.connect",
|
||||
attributes: map[kv.Key]kv.Value{
|
||||
kv.Key("http.remote"): kv.StringValue(address.String()),
|
||||
},
|
||||
parent: "http.getconn",
|
||||
},
|
||||
{
|
||||
name: "http.getconn",
|
||||
attributes: map[kv.Key]kv.Value{
|
||||
kv.Key("http.remote"): kv.StringValue(address.String()),
|
||||
kv.Key("http.host"): kv.StringValue(address.String()),
|
||||
},
|
||||
parent: "test",
|
||||
},
|
||||
{
|
||||
name: "http.receive",
|
||||
parent: "test",
|
||||
},
|
||||
{
|
||||
name: "http.headers",
|
||||
parent: "test",
|
||||
},
|
||||
{
|
||||
name: "http.send",
|
||||
parent: "test",
|
||||
},
|
||||
{
|
||||
name: "test",
|
||||
},
|
||||
}
|
||||
for _, tl := range testLen {
|
||||
if !assert.Contains(t, sr, tl.name) {
|
||||
continue
|
||||
}
|
||||
span := sr[tl.name]
|
||||
if tl.parent != "" {
|
||||
if assert.Contains(t, sr, tl.parent) {
|
||||
assert.Equal(t, span.ParentSpanID(), sr[tl.parent].SpanContext().SpanID)
|
||||
}
|
||||
}
|
||||
if len(tl.attributes) > 0 {
|
||||
attrs := span.Attributes()
|
||||
if tl.name == "http.getconn" {
|
||||
// http.local attribute uses a non-deterministic port.
|
||||
local := kv.Key("http.local")
|
||||
assert.Contains(t, attrs, local)
|
||||
delete(attrs, local)
|
||||
}
|
||||
assert.Equal(t, tl.attributes, attrs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type MultiSpanRecorder map[string][]*testtrace.Span
|
||||
|
||||
func (sr *MultiSpanRecorder) Reset() { (*sr) = MultiSpanRecorder{} }
|
||||
func (sr *MultiSpanRecorder) OnStart(span *testtrace.Span) {}
|
||||
func (sr *MultiSpanRecorder) OnEnd(span *testtrace.Span) {
|
||||
(*sr)[span.Name()] = append((*sr)[span.Name()], span)
|
||||
}
|
||||
|
||||
func TestConcurrentConnectionStart(t *testing.T) {
|
||||
sr := MultiSpanRecorder{}
|
||||
global.SetTraceProvider(
|
||||
testtrace.NewProvider(testtrace.WithSpanRecorder(&sr)),
|
||||
)
|
||||
ct := httptrace.NewClientTrace(context.Background())
|
||||
tts := []struct {
|
||||
name string
|
||||
run func()
|
||||
}{
|
||||
{
|
||||
name: "Open1Close1Open2Close2",
|
||||
run: func() {
|
||||
ct.ConnectStart("tcp", "127.0.0.1:3000")
|
||||
ct.ConnectDone("tcp", "127.0.0.1:3000", nil)
|
||||
ct.ConnectStart("tcp", "[::1]:3000")
|
||||
ct.ConnectDone("tcp", "[::1]:3000", nil)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Open2Close2Open1Close1",
|
||||
run: func() {
|
||||
ct.ConnectStart("tcp", "[::1]:3000")
|
||||
ct.ConnectDone("tcp", "[::1]:3000", nil)
|
||||
ct.ConnectStart("tcp", "127.0.0.1:3000")
|
||||
ct.ConnectDone("tcp", "127.0.0.1:3000", nil)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Open1Open2Close1Close2",
|
||||
run: func() {
|
||||
ct.ConnectStart("tcp", "127.0.0.1:3000")
|
||||
ct.ConnectStart("tcp", "[::1]:3000")
|
||||
ct.ConnectDone("tcp", "127.0.0.1:3000", nil)
|
||||
ct.ConnectDone("tcp", "[::1]:3000", nil)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Open1Open2Close2Close1",
|
||||
run: func() {
|
||||
ct.ConnectStart("tcp", "127.0.0.1:3000")
|
||||
ct.ConnectStart("tcp", "[::1]:3000")
|
||||
ct.ConnectDone("tcp", "[::1]:3000", nil)
|
||||
ct.ConnectDone("tcp", "127.0.0.1:3000", nil)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Open2Open1Close1Close2",
|
||||
run: func() {
|
||||
ct.ConnectStart("tcp", "[::1]:3000")
|
||||
ct.ConnectStart("tcp", "127.0.0.1:3000")
|
||||
ct.ConnectDone("tcp", "127.0.0.1:3000", nil)
|
||||
ct.ConnectDone("tcp", "[::1]:3000", nil)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Open2Open1Close2Close1",
|
||||
run: func() {
|
||||
ct.ConnectStart("tcp", "[::1]:3000")
|
||||
ct.ConnectStart("tcp", "127.0.0.1:3000")
|
||||
ct.ConnectDone("tcp", "[::1]:3000", nil)
|
||||
ct.ConnectDone("tcp", "127.0.0.1:3000", nil)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expectedRemotes := []kv.KeyValue{
|
||||
kv.String("http.remote", "127.0.0.1:3000"),
|
||||
kv.String("http.remote", "[::1]:3000"),
|
||||
}
|
||||
for _, tt := range tts {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
sr.Reset()
|
||||
tt.run()
|
||||
spans := sr["http.connect"]
|
||||
require.Len(t, spans, 2)
|
||||
|
||||
var gotRemotes []kv.KeyValue
|
||||
for _, span := range spans {
|
||||
for k, v := range span.Attributes() {
|
||||
gotRemotes = append(gotRemotes, kv.Any(string(k), v.AsInterface()))
|
||||
}
|
||||
}
|
||||
assert.ElementsMatch(t, expectedRemotes, gotRemotes)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEndBeforeStartCreatesSpan(t *testing.T) {
|
||||
sr := MultiSpanRecorder{}
|
||||
global.SetTraceProvider(
|
||||
testtrace.NewProvider(testtrace.WithSpanRecorder(&sr)),
|
||||
)
|
||||
|
||||
ct := httptrace.NewClientTrace(context.Background())
|
||||
ct.DNSDone(nhtrace.DNSDoneInfo{})
|
||||
ct.DNSStart(nhtrace.DNSStartInfo{Host: "example.com"})
|
||||
|
||||
name := "http.dns"
|
||||
require.Contains(t, sr, name)
|
||||
spans := sr[name]
|
||||
require.Len(t, spans, 1)
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package httptrace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"go.opentelemetry.io/otel/api/correlation"
|
||||
"go.opentelemetry.io/otel/api/global"
|
||||
"go.opentelemetry.io/otel/api/kv"
|
||||
"go.opentelemetry.io/otel/api/propagation"
|
||||
"go.opentelemetry.io/otel/api/trace"
|
||||
"go.opentelemetry.io/otel/semconv"
|
||||
)
|
||||
|
||||
// Option is a function that allows configuration of the httptrace Extract()
|
||||
// and Inject() functions
|
||||
type Option func(*config)
|
||||
|
||||
type config struct {
|
||||
propagators propagation.Propagators
|
||||
}
|
||||
|
||||
func newConfig(opts []Option) *config {
|
||||
c := &config{propagators: global.Propagators()}
|
||||
for _, o := range opts {
|
||||
o(c)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// WithPropagators sets the propagators to use for Extraction and Injection
|
||||
func WithPropagators(props propagation.Propagators) Option {
|
||||
return func(c *config) {
|
||||
c.propagators = props
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the Attributes, Context Entries, and SpanContext that were encoded by Inject.
|
||||
func Extract(ctx context.Context, req *http.Request, opts ...Option) ([]kv.KeyValue, []kv.KeyValue, trace.SpanContext) {
|
||||
c := newConfig(opts)
|
||||
ctx = propagation.ExtractHTTP(ctx, c.propagators, req.Header)
|
||||
|
||||
attrs := append(
|
||||
semconv.HTTPServerAttributesFromHTTPRequest("", "", req),
|
||||
semconv.NetAttributesFromHTTPRequest("tcp", req)...,
|
||||
)
|
||||
|
||||
var correlationCtxKVs []kv.KeyValue
|
||||
correlation.MapFromContext(ctx).Foreach(func(kv kv.KeyValue) bool {
|
||||
correlationCtxKVs = append(correlationCtxKVs, kv)
|
||||
return true
|
||||
})
|
||||
|
||||
return attrs, correlationCtxKVs, trace.RemoteSpanContextFromContext(ctx)
|
||||
}
|
||||
|
||||
func Inject(ctx context.Context, req *http.Request, opts ...Option) {
|
||||
c := newConfig(opts)
|
||||
propagation.InjectHTTP(ctx, c.propagators, req.Header)
|
||||
}
|
@ -1,164 +0,0 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package httptrace_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
"go.opentelemetry.io/otel/api/correlation"
|
||||
"go.opentelemetry.io/otel/api/kv"
|
||||
"go.opentelemetry.io/otel/api/propagation"
|
||||
"go.opentelemetry.io/otel/api/trace/testtrace"
|
||||
"go.opentelemetry.io/otel/instrumentation/httptrace"
|
||||
"go.opentelemetry.io/otel/semconv"
|
||||
)
|
||||
|
||||
func TestRoundtrip(t *testing.T) {
|
||||
tr := testtrace.NewProvider().Tracer("httptrace/client")
|
||||
|
||||
var expectedAttrs map[kv.Key]string
|
||||
expectedCorrs := map[kv.Key]string{kv.Key("foo"): "bar"}
|
||||
|
||||
// Mock http server
|
||||
ts := httptest.NewServer(
|
||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
attrs, corrs, span := httptrace.Extract(r.Context(), r)
|
||||
|
||||
actualAttrs := make(map[kv.Key]string)
|
||||
for _, attr := range attrs {
|
||||
if attr.Key == semconv.NetPeerPortKey {
|
||||
// Peer port will be non-deterministic
|
||||
continue
|
||||
}
|
||||
actualAttrs[attr.Key] = attr.Value.Emit()
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(actualAttrs, expectedAttrs); diff != "" {
|
||||
t.Fatalf("[TestRoundtrip] Attributes are different: %v", diff)
|
||||
}
|
||||
|
||||
actualCorrs := make(map[kv.Key]string)
|
||||
for _, corr := range corrs {
|
||||
actualCorrs[corr.Key] = corr.Value.Emit()
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(actualCorrs, expectedCorrs); diff != "" {
|
||||
t.Fatalf("[TestRoundtrip] Correlations are different: %v", diff)
|
||||
}
|
||||
|
||||
if !span.IsValid() {
|
||||
t.Fatalf("[TestRoundtrip] Invalid span extracted: %v", span)
|
||||
}
|
||||
|
||||
_, err := w.Write([]byte("OK"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}),
|
||||
)
|
||||
defer ts.Close()
|
||||
|
||||
address := ts.Listener.Addr()
|
||||
hp := strings.Split(address.String(), ":")
|
||||
expectedAttrs = map[kv.Key]string{
|
||||
semconv.HTTPFlavorKey: "1.1",
|
||||
semconv.HTTPHostKey: address.String(),
|
||||
semconv.HTTPMethodKey: "GET",
|
||||
semconv.HTTPSchemeKey: "http",
|
||||
semconv.HTTPTargetKey: "/",
|
||||
semconv.HTTPUserAgentKey: "Go-http-client/1.1",
|
||||
semconv.HTTPRequestContentLengthKey: "3",
|
||||
semconv.NetHostIPKey: hp[0],
|
||||
semconv.NetHostPortKey: hp[1],
|
||||
semconv.NetPeerIPKey: "127.0.0.1",
|
||||
semconv.NetTransportKey: "IP.TCP",
|
||||
}
|
||||
|
||||
client := ts.Client()
|
||||
err := tr.WithSpan(context.Background(), "test",
|
||||
func(ctx context.Context) error {
|
||||
ctx = correlation.ContextWithMap(ctx, correlation.NewMap(correlation.MapUpdate{SingleKV: kv.Key("foo").String("bar")}))
|
||||
req, _ := http.NewRequest("GET", ts.URL, strings.NewReader("foo"))
|
||||
httptrace.Inject(ctx, req)
|
||||
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("Request failed: %s", err.Error())
|
||||
}
|
||||
_ = res.Body.Close()
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
panic("unexpected error in http request: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestSpecifyPropagators(t *testing.T) {
|
||||
tr := testtrace.NewProvider().Tracer("httptrace/client")
|
||||
|
||||
expectedCorrs := map[kv.Key]string{kv.Key("foo"): "bar"}
|
||||
|
||||
// Mock http server
|
||||
ts := httptest.NewServer(
|
||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, corrs, span := httptrace.Extract(r.Context(), r, httptrace.WithPropagators(propagation.New(propagation.WithExtractors(correlation.DefaultHTTPPropagator()))))
|
||||
|
||||
actualCorrs := make(map[kv.Key]string)
|
||||
for _, corr := range corrs {
|
||||
actualCorrs[corr.Key] = corr.Value.Emit()
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(actualCorrs, expectedCorrs); diff != "" {
|
||||
t.Fatalf("[TestRoundtrip] Correlations are different: %v", diff)
|
||||
}
|
||||
|
||||
if span.IsValid() {
|
||||
t.Fatalf("[TestRoundtrip] valid span extracted, expected none: %v", span)
|
||||
}
|
||||
|
||||
_, err := w.Write([]byte("OK"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}),
|
||||
)
|
||||
defer ts.Close()
|
||||
|
||||
client := ts.Client()
|
||||
err := tr.WithSpan(context.Background(), "test",
|
||||
func(ctx context.Context) error {
|
||||
ctx = correlation.ContextWithMap(ctx, correlation.NewMap(correlation.MapUpdate{SingleKV: kv.Key("foo").String("bar")}))
|
||||
req, _ := http.NewRequest("GET", ts.URL, nil)
|
||||
httptrace.Inject(ctx, req, httptrace.WithPropagators(propagation.New(propagation.WithInjectors(correlation.DefaultHTTPPropagator()))))
|
||||
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("Request failed: %s", err.Error())
|
||||
}
|
||||
_ = res.Body.Close()
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
panic("unexpected error in http request: " + err.Error())
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package othttp
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"go.opentelemetry.io/otel/api/kv"
|
||||
)
|
||||
|
||||
// Attribute keys that can be added to a span.
|
||||
const (
|
||||
ReadBytesKey = kv.Key("http.read_bytes") // if anything was read from the request body, the total number of bytes read
|
||||
ReadErrorKey = kv.Key("http.read_error") // If an error occurred while reading a request, the string of the error (io.EOF is not recorded)
|
||||
WroteBytesKey = kv.Key("http.wrote_bytes") // if anything was written to the response writer, the total number of bytes written
|
||||
WriteErrorKey = kv.Key("http.write_error") // if an error occurred while writing a reply, the string of the error (io.EOF is not recorded)
|
||||
)
|
||||
|
||||
// Server HTTP metrics
|
||||
const (
|
||||
RequestCount = "http.server.request_count" // Incoming request count total
|
||||
RequestContentLength = "http.server.request_content_length" // Incoming request bytes total
|
||||
ResponseContentLength = "http.server.response_content_length" // Incoming response bytes total
|
||||
ServerLatency = "http.server.duration" // Incoming end to end duration, microseconds
|
||||
)
|
||||
|
||||
// Filter is a predicate used to determine whether a given http.request should
|
||||
// be traced. A Filter must return true if the request should be traced.
|
||||
type Filter func(*http.Request) bool
|
@ -1,150 +0,0 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package othttp
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"go.opentelemetry.io/otel/api/metric"
|
||||
"go.opentelemetry.io/otel/api/propagation"
|
||||
"go.opentelemetry.io/otel/api/trace"
|
||||
)
|
||||
|
||||
// Config represents the configuration options available for the othttp.Handler
|
||||
// and othttp.Transport types.
|
||||
type Config struct {
|
||||
Tracer trace.Tracer
|
||||
Meter metric.Meter
|
||||
Propagators propagation.Propagators
|
||||
SpanStartOptions []trace.StartOption
|
||||
ReadEvent bool
|
||||
WriteEvent bool
|
||||
Filters []Filter
|
||||
SpanNameFormatter func(string, *http.Request) string
|
||||
}
|
||||
|
||||
// Option Interface used for setting *optional* Config properties
|
||||
type Option interface {
|
||||
Apply(*Config)
|
||||
}
|
||||
|
||||
// OptionFunc provides a convenience wrapper for simple Options
|
||||
// that can be represented as functions.
|
||||
type OptionFunc func(*Config)
|
||||
|
||||
func (o OptionFunc) Apply(c *Config) {
|
||||
o(c)
|
||||
}
|
||||
|
||||
// NewConfig creates a new Config struct and applies opts to it.
|
||||
func NewConfig(opts ...Option) *Config {
|
||||
c := &Config{}
|
||||
for _, opt := range opts {
|
||||
opt.Apply(c)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// WithTracer configures a specific tracer. If this option
|
||||
// isn't specified then the global tracer is used.
|
||||
func WithTracer(tracer trace.Tracer) Option {
|
||||
return OptionFunc(func(c *Config) {
|
||||
c.Tracer = tracer
|
||||
})
|
||||
}
|
||||
|
||||
// WithMeter configures a specific meter. If this option
|
||||
// isn't specified then the global meter is used.
|
||||
func WithMeter(meter metric.Meter) Option {
|
||||
return OptionFunc(func(c *Config) {
|
||||
c.Meter = meter
|
||||
})
|
||||
}
|
||||
|
||||
// WithPublicEndpoint configures the Handler to link the span with an incoming
|
||||
// span context. If this option is not provided, then the association is a child
|
||||
// association instead of a link.
|
||||
func WithPublicEndpoint() Option {
|
||||
return OptionFunc(func(c *Config) {
|
||||
c.SpanStartOptions = append(c.SpanStartOptions, trace.WithNewRoot())
|
||||
})
|
||||
}
|
||||
|
||||
// WithPropagators configures specific propagators. If this
|
||||
// option isn't specified then
|
||||
// go.opentelemetry.io/otel/api/global.Propagators are used.
|
||||
func WithPropagators(ps propagation.Propagators) Option {
|
||||
return OptionFunc(func(c *Config) {
|
||||
c.Propagators = ps
|
||||
})
|
||||
}
|
||||
|
||||
// WithSpanOptions configures an additional set of
|
||||
// trace.StartOptions, which are applied to each new span.
|
||||
func WithSpanOptions(opts ...trace.StartOption) Option {
|
||||
return OptionFunc(func(c *Config) {
|
||||
c.SpanStartOptions = append(c.SpanStartOptions, opts...)
|
||||
})
|
||||
}
|
||||
|
||||
// WithFilter adds a filter to the list of filters used by the handler.
|
||||
// If any filter indicates to exclude a request then the request will not be
|
||||
// traced. All filters must allow a request to be traced for a Span to be created.
|
||||
// If no filters are provided then all requests are traced.
|
||||
// Filters will be invoked for each processed request, it is advised to make them
|
||||
// simple and fast.
|
||||
func WithFilter(f Filter) Option {
|
||||
return OptionFunc(func(c *Config) {
|
||||
c.Filters = append(c.Filters, f)
|
||||
})
|
||||
}
|
||||
|
||||
type event int
|
||||
|
||||
// Different types of events that can be recorded, see WithMessageEvents
|
||||
const (
|
||||
ReadEvents event = iota
|
||||
WriteEvents
|
||||
)
|
||||
|
||||
// WithMessageEvents configures the Handler to record the specified events
|
||||
// (span.AddEvent) on spans. By default only summary attributes are added at the
|
||||
// end of the request.
|
||||
//
|
||||
// Valid events are:
|
||||
// * ReadEvents: Record the number of bytes read after every http.Request.Body.Read
|
||||
// using the ReadBytesKey
|
||||
// * WriteEvents: Record the number of bytes written after every http.ResponeWriter.Write
|
||||
// using the WriteBytesKey
|
||||
func WithMessageEvents(events ...event) Option {
|
||||
return OptionFunc(func(c *Config) {
|
||||
for _, e := range events {
|
||||
switch e {
|
||||
case ReadEvents:
|
||||
c.ReadEvent = true
|
||||
case WriteEvents:
|
||||
c.WriteEvent = true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// WithSpanNameFormatter takes a function that will be called on every
|
||||
// request and the returned string will become the Span Name
|
||||
func WithSpanNameFormatter(f func(operation string, r *http.Request) string) Option {
|
||||
return OptionFunc(func(c *Config) {
|
||||
c.SpanNameFormatter = f
|
||||
})
|
||||
}
|
@ -1,131 +0,0 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package othttp
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
mocktrace "go.opentelemetry.io/otel/internal/trace"
|
||||
)
|
||||
|
||||
func TestBasicFilter(t *testing.T) {
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
var id uint64
|
||||
tracer := mocktrace.MockTracer{StartSpanID: &id}
|
||||
|
||||
h := NewHandler(
|
||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if _, err := io.WriteString(w, "hello world"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}), "test_handler",
|
||||
WithTracer(&tracer),
|
||||
WithFilter(func(r *http.Request) bool {
|
||||
return false
|
||||
}),
|
||||
)
|
||||
|
||||
r, err := http.NewRequest(http.MethodGet, "http://localhost/", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
h.ServeHTTP(rr, r)
|
||||
if got, expected := rr.Result().StatusCode, http.StatusOK; got != expected {
|
||||
t.Fatalf("got %d, expected %d", got, expected)
|
||||
}
|
||||
if got := rr.Header().Get("Traceparent"); got != "" {
|
||||
t.Fatal("expected empty trace header")
|
||||
}
|
||||
if got, expected := id, uint64(0); got != expected {
|
||||
t.Fatalf("got %d, expected %d", got, expected)
|
||||
}
|
||||
d, err := ioutil.ReadAll(rr.Result().Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got, expected := string(d), "hello world"; got != expected {
|
||||
t.Fatalf("got %q, expected %q", got, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSpanNameFormatter(t *testing.T) {
|
||||
var testCases = []struct {
|
||||
name string
|
||||
formatter func(s string, r *http.Request) string
|
||||
operation string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "default handler formatter",
|
||||
formatter: defaultHandlerFormatter,
|
||||
operation: "test_operation",
|
||||
expected: "test_operation",
|
||||
},
|
||||
{
|
||||
name: "default transport formatter",
|
||||
formatter: defaultTransportFormatter,
|
||||
expected: http.MethodGet,
|
||||
},
|
||||
{
|
||||
name: "custom formatter",
|
||||
formatter: func(s string, r *http.Request) string {
|
||||
return r.URL.Path
|
||||
},
|
||||
operation: "",
|
||||
expected: "/hello",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
rr := httptest.NewRecorder()
|
||||
var id uint64
|
||||
var spanName string
|
||||
tracer := mocktrace.MockTracer{
|
||||
StartSpanID: &id,
|
||||
OnSpanStarted: func(span *mocktrace.MockSpan) {
|
||||
spanName = span.Name
|
||||
},
|
||||
}
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if _, err := io.WriteString(w, "hello world"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
h := NewHandler(
|
||||
handler,
|
||||
tc.operation,
|
||||
WithTracer(&tracer),
|
||||
WithSpanNameFormatter(tc.formatter),
|
||||
)
|
||||
r, err := http.NewRequest(http.MethodGet, "http://localhost/hello", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
h.ServeHTTP(rr, r)
|
||||
if got, expected := rr.Result().StatusCode, http.StatusOK; got != expected {
|
||||
t.Fatalf("got %d, expected %d", got, expected)
|
||||
}
|
||||
if got, expected := spanName, tc.expected; got != expected {
|
||||
t.Fatalf("got %q, expected %q", got, expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package othttp provides a http.Handler and functions that are
|
||||
// intended to be used to add tracing by wrapping
|
||||
// existing handlers (with Handler) and routes WithRouteTag.
|
||||
package othttp
|
@ -1,128 +0,0 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package filters provides a set of filters useful with the
|
||||
// othttp.WithFilter() option to control which inbound requests are traced.
|
||||
package filters
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"go.opentelemetry.io/otel/instrumentation/othttp"
|
||||
)
|
||||
|
||||
// Any takes a list of Filters and returns a Filter that
|
||||
// returns true if any Filter in the list returns true.
|
||||
func Any(fs ...othttp.Filter) othttp.Filter {
|
||||
return func(r *http.Request) bool {
|
||||
for _, f := range fs {
|
||||
if f(r) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// All takes a list of Filters and returns a Filter that
|
||||
// returns true only if all Filters in the list return true.
|
||||
func All(fs ...othttp.Filter) othttp.Filter {
|
||||
return func(r *http.Request) bool {
|
||||
for _, f := range fs {
|
||||
if !f(r) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// None takes a list of Filters and returns a Filter that returns
|
||||
// true only if none of the Filters in the list return true.
|
||||
func None(fs ...othttp.Filter) othttp.Filter {
|
||||
return func(r *http.Request) bool {
|
||||
for _, f := range fs {
|
||||
if f(r) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Not provides a convenience mechanism for inverting a Filter
|
||||
func Not(f othttp.Filter) othttp.Filter {
|
||||
return func(r *http.Request) bool {
|
||||
return !f(r)
|
||||
}
|
||||
}
|
||||
|
||||
// Hostname returns a Filter that returns true if the request's
|
||||
// hostname matches the provided string.
|
||||
func Hostname(h string) othttp.Filter {
|
||||
return func(r *http.Request) bool {
|
||||
return r.URL.Hostname() == h
|
||||
}
|
||||
}
|
||||
|
||||
// Path returns a Filter that returns true if the request's
|
||||
// path matches the provided string.
|
||||
func Path(p string) othttp.Filter {
|
||||
return func(r *http.Request) bool {
|
||||
return r.URL.Path == p
|
||||
}
|
||||
}
|
||||
|
||||
// PathPrefix returns a Filter that returns true if the request's
|
||||
// path starts with the provided string.
|
||||
func PathPrefix(p string) othttp.Filter {
|
||||
return func(r *http.Request) bool {
|
||||
return strings.HasPrefix(r.URL.Path, p)
|
||||
}
|
||||
}
|
||||
|
||||
// Query returns a Filter that returns true if the request
|
||||
// includes a query parameter k with a value equal to v.
|
||||
func Query(k, v string) othttp.Filter {
|
||||
return func(r *http.Request) bool {
|
||||
for _, qv := range r.URL.Query()[k] {
|
||||
if v == qv {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// QueryContains returns a Filter that returns true if the request
|
||||
// includes a query parameter k with a value that contains v.
|
||||
func QueryContains(k, v string) othttp.Filter {
|
||||
return func(r *http.Request) bool {
|
||||
for _, qv := range r.URL.Query()[k] {
|
||||
if strings.Contains(qv, v) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Method returns a Filter that returns true if the request
|
||||
// method is equal to the provided value.
|
||||
func Method(m string) othttp.Filter {
|
||||
return func(r *http.Request) bool {
|
||||
return m == r.Method
|
||||
}
|
||||
}
|
@ -1,266 +0,0 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package filters
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"go.opentelemetry.io/otel/instrumentation/othttp"
|
||||
)
|
||||
|
||||
type scenario struct {
|
||||
name string
|
||||
filter othttp.Filter
|
||||
req *http.Request
|
||||
exp bool
|
||||
}
|
||||
|
||||
func TestAny(t *testing.T) {
|
||||
for _, s := range []scenario{
|
||||
{
|
||||
name: "no matching filters",
|
||||
filter: Any(Path("/foo"), Hostname("bar.baz")),
|
||||
req: &http.Request{URL: &url.URL{Path: "/boo", Host: "baz.bar:8080"}},
|
||||
exp: false,
|
||||
},
|
||||
{
|
||||
name: "one matching filter",
|
||||
filter: Any(Path("/foo"), Hostname("bar.baz")),
|
||||
req: &http.Request{URL: &url.URL{Path: "/foo", Host: "baz.bar:8080"}},
|
||||
exp: true,
|
||||
},
|
||||
{
|
||||
name: "all matching filters",
|
||||
filter: Any(Path("/foo"), Hostname("bar.baz")),
|
||||
req: &http.Request{URL: &url.URL{Path: "/foo", Host: "bar.baz:8080"}},
|
||||
exp: true,
|
||||
},
|
||||
} {
|
||||
res := s.filter(s.req)
|
||||
if s.exp != res {
|
||||
t.Errorf("Failed testing %q. Expected %t, got %t", s.name, s.exp, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAll(t *testing.T) {
|
||||
for _, s := range []scenario{
|
||||
{
|
||||
name: "no matching filters",
|
||||
filter: All(Path("/foo"), Hostname("bar.baz")),
|
||||
req: &http.Request{URL: &url.URL{Path: "/boo", Host: "baz.bar:8080"}},
|
||||
exp: false,
|
||||
},
|
||||
{
|
||||
name: "one matching filter",
|
||||
filter: All(Path("/foo"), Hostname("bar.baz")),
|
||||
req: &http.Request{URL: &url.URL{Path: "/foo", Host: "baz.bar:8080"}},
|
||||
exp: false,
|
||||
},
|
||||
{
|
||||
name: "all matching filters",
|
||||
filter: All(Path("/foo"), Hostname("bar.baz")),
|
||||
req: &http.Request{URL: &url.URL{Path: "/foo", Host: "bar.baz:8080"}},
|
||||
exp: true,
|
||||
},
|
||||
} {
|
||||
res := s.filter(s.req)
|
||||
if s.exp != res {
|
||||
t.Errorf("Failed testing %q. Expected %t, got %t", s.name, s.exp, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNone(t *testing.T) {
|
||||
for _, s := range []scenario{
|
||||
{
|
||||
name: "no matching filters",
|
||||
filter: None(Path("/foo"), Hostname("bar.baz")),
|
||||
req: &http.Request{URL: &url.URL{Path: "/boo", Host: "baz.bar:8080"}},
|
||||
exp: true,
|
||||
},
|
||||
{
|
||||
name: "one matching filter",
|
||||
filter: None(Path("/foo"), Hostname("bar.baz")),
|
||||
req: &http.Request{URL: &url.URL{Path: "/foo", Host: "baz.bar:8080"}},
|
||||
exp: false,
|
||||
},
|
||||
{
|
||||
name: "all matching filters",
|
||||
filter: None(Path("/foo"), Hostname("bar.baz")),
|
||||
req: &http.Request{URL: &url.URL{Path: "/foo", Host: "bar.baz:8080"}},
|
||||
exp: false,
|
||||
},
|
||||
} {
|
||||
res := s.filter(s.req)
|
||||
if s.exp != res {
|
||||
t.Errorf("Failed testing %q. Expected %t, got %t", s.name, s.exp, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNot(t *testing.T) {
|
||||
req := &http.Request{URL: &url.URL{Path: "/foo", Host: "bar.baz:8080"}}
|
||||
filter := Path("/foo")
|
||||
if filter(req) == Not(filter)(req) {
|
||||
t.Error("Not filter should invert the result of the supplied filter")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPathPrefix(t *testing.T) {
|
||||
for _, s := range []scenario{
|
||||
{
|
||||
name: "non-matching prefix",
|
||||
filter: PathPrefix("/foo"),
|
||||
req: &http.Request{URL: &url.URL{Path: "/boo/far", Host: "baz.bar:8080"}},
|
||||
exp: false,
|
||||
},
|
||||
{
|
||||
name: "matching prefix",
|
||||
filter: PathPrefix("/foo"),
|
||||
req: &http.Request{URL: &url.URL{Path: "/foo/bar", Host: "bar.baz:8080"}},
|
||||
exp: true,
|
||||
},
|
||||
} {
|
||||
res := s.filter(s.req)
|
||||
if s.exp != res {
|
||||
t.Errorf("Failed testing %q. Expected %t, got %t", s.name, s.exp, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMethod(t *testing.T) {
|
||||
for _, s := range []scenario{
|
||||
{
|
||||
name: "non-matching method",
|
||||
filter: Method(http.MethodGet),
|
||||
req: &http.Request{Method: http.MethodHead, URL: &url.URL{Path: "/boo/far", Host: "baz.bar:8080"}},
|
||||
exp: false,
|
||||
},
|
||||
{
|
||||
name: "matching method",
|
||||
filter: Method(http.MethodGet),
|
||||
req: &http.Request{Method: http.MethodGet, URL: &url.URL{Path: "/boo/far", Host: "baz.bar:8080"}},
|
||||
exp: true,
|
||||
},
|
||||
} {
|
||||
res := s.filter(s.req)
|
||||
if s.exp != res {
|
||||
t.Errorf("Failed testing %q. Expected %t, got %t", s.name, s.exp, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuery(t *testing.T) {
|
||||
matching, _ := url.Parse("http://bar.baz:8080/foo/bar?key=value")
|
||||
nonMatching, _ := url.Parse("http://bar.baz:8080/foo/bar?key=other")
|
||||
for _, s := range []scenario{
|
||||
{
|
||||
name: "non-matching query parameter",
|
||||
filter: Query("key", "value"),
|
||||
req: &http.Request{Method: http.MethodHead, URL: nonMatching},
|
||||
exp: false,
|
||||
},
|
||||
{
|
||||
name: "matching query parameter",
|
||||
filter: Query("key", "value"),
|
||||
req: &http.Request{Method: http.MethodGet, URL: matching},
|
||||
exp: true,
|
||||
},
|
||||
} {
|
||||
res := s.filter(s.req)
|
||||
if s.exp != res {
|
||||
t.Errorf("Failed testing %q. Expected %t, got %t", s.name, s.exp, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueryContains(t *testing.T) {
|
||||
matching, _ := url.Parse("http://bar.baz:8080/foo/bar?key=value")
|
||||
nonMatching, _ := url.Parse("http://bar.baz:8080/foo/bar?key=other")
|
||||
for _, s := range []scenario{
|
||||
{
|
||||
name: "non-matching query parameter",
|
||||
filter: QueryContains("key", "alu"),
|
||||
req: &http.Request{Method: http.MethodHead, URL: nonMatching},
|
||||
exp: false,
|
||||
},
|
||||
{
|
||||
name: "matching query parameter",
|
||||
filter: QueryContains("key", "alu"),
|
||||
req: &http.Request{Method: http.MethodGet, URL: matching},
|
||||
exp: true,
|
||||
},
|
||||
} {
|
||||
res := s.filter(s.req)
|
||||
if s.exp != res {
|
||||
t.Errorf("Failed testing %q. Expected %t, got %t", s.name, s.exp, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHeader(t *testing.T) {
|
||||
matching := http.Header{}
|
||||
matching.Add("key", "value")
|
||||
nonMatching := http.Header{}
|
||||
nonMatching.Add("key", "other")
|
||||
for _, s := range []scenario{
|
||||
{
|
||||
name: "non-matching query parameter",
|
||||
filter: Header("key", "value"),
|
||||
req: &http.Request{Method: http.MethodHead, Header: nonMatching},
|
||||
exp: false,
|
||||
},
|
||||
{
|
||||
name: "matching query parameter",
|
||||
filter: Header("key", "value"),
|
||||
req: &http.Request{Method: http.MethodGet, Header: matching},
|
||||
exp: true,
|
||||
},
|
||||
} {
|
||||
res := s.filter(s.req)
|
||||
if s.exp != res {
|
||||
t.Errorf("Failed testing %q. Expected %t, got %t", s.name, s.exp, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHeaderContains(t *testing.T) {
|
||||
matching := http.Header{}
|
||||
matching.Add("key", "value")
|
||||
nonMatching := http.Header{}
|
||||
nonMatching.Add("key", "other")
|
||||
for _, s := range []scenario{
|
||||
{
|
||||
name: "non-matching query parameter",
|
||||
filter: HeaderContains("key", "alu"),
|
||||
req: &http.Request{Method: http.MethodHead, Header: nonMatching},
|
||||
exp: false,
|
||||
},
|
||||
{
|
||||
name: "matching query parameter",
|
||||
filter: HeaderContains("key", "alu"),
|
||||
req: &http.Request{Method: http.MethodGet, Header: matching},
|
||||
exp: true,
|
||||
},
|
||||
} {
|
||||
res := s.filter(s.req)
|
||||
if s.exp != res {
|
||||
t.Errorf("Failed testing %q. Expected %t, got %t", s.name, s.exp, res)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build go1.14
|
||||
|
||||
package filters
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"go.opentelemetry.io/otel/instrumentation/othttp"
|
||||
)
|
||||
|
||||
// Header returns a Filter that returns true if the request
|
||||
// includes a header k with a value equal to v.
|
||||
func Header(k, v string) othttp.Filter {
|
||||
return func(r *http.Request) bool {
|
||||
for _, hv := range r.Header.Values(k) {
|
||||
if v == hv {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// HeaderContains returns a Filter that returns true if the request
|
||||
// includes a header k with a value that contains v.
|
||||
func HeaderContains(k, v string) othttp.Filter {
|
||||
return func(r *http.Request) bool {
|
||||
for _, hv := range r.Header.Values(k) {
|
||||
if strings.Contains(hv, v) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build !go1.14
|
||||
|
||||
package filters
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"strings"
|
||||
|
||||
"go.opentelemetry.io/otel/instrumentation/othttp"
|
||||
)
|
||||
|
||||
// Header returns a Filter that returns true if the request
|
||||
// includes a header k with a value equal to v.
|
||||
func Header(k, v string) othttp.Filter {
|
||||
return func(r *http.Request) bool {
|
||||
for _, hv := range r.Header[textproto.CanonicalMIMEHeaderKey(k)] {
|
||||
if v == hv {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// HeaderContains returns a Filter that returns true if the request
|
||||
// includes a header k with a value that contains v.
|
||||
func HeaderContains(k, v string) othttp.Filter {
|
||||
return func(r *http.Request) bool {
|
||||
for _, hv := range r.Header[textproto.CanonicalMIMEHeaderKey(k)] {
|
||||
if strings.Contains(hv, v) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
@ -1,221 +0,0 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package othttp
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/felixge/httpsnoop"
|
||||
|
||||
"go.opentelemetry.io/otel/api/global"
|
||||
"go.opentelemetry.io/otel/api/kv"
|
||||
"go.opentelemetry.io/otel/api/metric"
|
||||
"go.opentelemetry.io/otel/api/propagation"
|
||||
"go.opentelemetry.io/otel/api/trace"
|
||||
"go.opentelemetry.io/otel/semconv"
|
||||
)
|
||||
|
||||
var _ http.Handler = &Handler{}
|
||||
|
||||
// Handler is http middleware that corresponds to the http.Handler interface and
|
||||
// is designed to wrap a http.Mux (or equivalent), while individual routes on
|
||||
// the mux are wrapped with WithRouteTag. A Handler will add various attributes
|
||||
// to the span using the kv.Keys defined in this package.
|
||||
type Handler struct {
|
||||
operation string
|
||||
handler http.Handler
|
||||
|
||||
tracer trace.Tracer
|
||||
meter metric.Meter
|
||||
propagators propagation.Propagators
|
||||
spanStartOptions []trace.StartOption
|
||||
readEvent bool
|
||||
writeEvent bool
|
||||
filters []Filter
|
||||
spanNameFormatter func(string, *http.Request) string
|
||||
counters map[string]metric.Int64Counter
|
||||
valueRecorders map[string]metric.Int64ValueRecorder
|
||||
}
|
||||
|
||||
func defaultHandlerFormatter(operation string, _ *http.Request) string {
|
||||
return operation
|
||||
}
|
||||
|
||||
// NewHandler wraps the passed handler, functioning like middleware, in a span
|
||||
// named after the operation and with any provided Options.
|
||||
func NewHandler(handler http.Handler, operation string, opts ...Option) http.Handler {
|
||||
h := Handler{
|
||||
handler: handler,
|
||||
operation: operation,
|
||||
}
|
||||
|
||||
const domain = "go.opentelemetry.io/otel/instrumentation/othttp"
|
||||
|
||||
defaultOpts := []Option{
|
||||
WithTracer(global.Tracer(domain)),
|
||||
WithMeter(global.Meter(domain)),
|
||||
WithPropagators(global.Propagators()),
|
||||
WithSpanOptions(trace.WithSpanKind(trace.SpanKindServer)),
|
||||
WithSpanNameFormatter(defaultHandlerFormatter),
|
||||
}
|
||||
|
||||
c := NewConfig(append(defaultOpts, opts...)...)
|
||||
h.configure(c)
|
||||
h.createMeasures()
|
||||
|
||||
return &h
|
||||
}
|
||||
|
||||
func (h *Handler) configure(c *Config) {
|
||||
h.tracer = c.Tracer
|
||||
h.meter = c.Meter
|
||||
h.propagators = c.Propagators
|
||||
h.spanStartOptions = c.SpanStartOptions
|
||||
h.readEvent = c.ReadEvent
|
||||
h.writeEvent = c.WriteEvent
|
||||
h.filters = c.Filters
|
||||
h.spanNameFormatter = c.SpanNameFormatter
|
||||
}
|
||||
|
||||
func handleErr(err error) {
|
||||
if err != nil {
|
||||
global.Handle(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) createMeasures() {
|
||||
h.counters = make(map[string]metric.Int64Counter)
|
||||
h.valueRecorders = make(map[string]metric.Int64ValueRecorder)
|
||||
|
||||
requestBytesCounter, err := h.meter.NewInt64Counter(RequestContentLength)
|
||||
handleErr(err)
|
||||
|
||||
responseBytesCounter, err := h.meter.NewInt64Counter(ResponseContentLength)
|
||||
handleErr(err)
|
||||
|
||||
serverLatencyMeasure, err := h.meter.NewInt64ValueRecorder(ServerLatency)
|
||||
handleErr(err)
|
||||
|
||||
h.counters[RequestContentLength] = requestBytesCounter
|
||||
h.counters[ResponseContentLength] = responseBytesCounter
|
||||
h.valueRecorders[ServerLatency] = serverLatencyMeasure
|
||||
}
|
||||
|
||||
// ServeHTTP serves HTTP requests (http.Handler)
|
||||
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
requestStartTime := time.Now()
|
||||
for _, f := range h.filters {
|
||||
if !f(r) {
|
||||
// Simply pass through to the handler if a filter rejects the request
|
||||
h.handler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
opts := append([]trace.StartOption{
|
||||
trace.WithAttributes(semconv.NetAttributesFromHTTPRequest("tcp", r)...),
|
||||
trace.WithAttributes(semconv.EndUserAttributesFromHTTPRequest(r)...),
|
||||
trace.WithAttributes(semconv.HTTPServerAttributesFromHTTPRequest(h.operation, "", r)...),
|
||||
}, h.spanStartOptions...) // start with the configured options
|
||||
|
||||
ctx := propagation.ExtractHTTP(r.Context(), h.propagators, r.Header)
|
||||
ctx, span := h.tracer.Start(ctx, h.spanNameFormatter(h.operation, r), opts...)
|
||||
defer span.End()
|
||||
|
||||
readRecordFunc := func(int64) {}
|
||||
if h.readEvent {
|
||||
readRecordFunc = func(n int64) {
|
||||
span.AddEvent(ctx, "read", ReadBytesKey.Int64(n))
|
||||
}
|
||||
}
|
||||
bw := bodyWrapper{ReadCloser: r.Body, record: readRecordFunc}
|
||||
r.Body = &bw
|
||||
|
||||
writeRecordFunc := func(int64) {}
|
||||
if h.writeEvent {
|
||||
writeRecordFunc = func(n int64) {
|
||||
span.AddEvent(ctx, "write", WroteBytesKey.Int64(n))
|
||||
}
|
||||
}
|
||||
|
||||
rww := &respWriterWrapper{ResponseWriter: w, record: writeRecordFunc, ctx: ctx, props: h.propagators}
|
||||
|
||||
// Wrap w to use our ResponseWriter methods while also exposing
|
||||
// other interfaces that w may implement (http.CloseNotifier,
|
||||
// http.Flusher, http.Hijacker, http.Pusher, io.ReaderFrom).
|
||||
|
||||
w = httpsnoop.Wrap(w, httpsnoop.Hooks{
|
||||
Header: func(httpsnoop.HeaderFunc) httpsnoop.HeaderFunc {
|
||||
return rww.Header
|
||||
},
|
||||
Write: func(httpsnoop.WriteFunc) httpsnoop.WriteFunc {
|
||||
return rww.Write
|
||||
},
|
||||
WriteHeader: func(httpsnoop.WriteHeaderFunc) httpsnoop.WriteHeaderFunc {
|
||||
return rww.WriteHeader
|
||||
},
|
||||
})
|
||||
|
||||
h.handler.ServeHTTP(w, r.WithContext(ctx))
|
||||
|
||||
setAfterServeAttributes(span, bw.read, rww.written, rww.statusCode, bw.err, rww.err)
|
||||
|
||||
// Add request metrics
|
||||
|
||||
labels := semconv.HTTPServerMetricAttributesFromHTTPRequest(h.operation, r)
|
||||
|
||||
h.counters[RequestContentLength].Add(ctx, bw.read, labels...)
|
||||
h.counters[ResponseContentLength].Add(ctx, rww.written, labels...)
|
||||
|
||||
elapsedTime := time.Since(requestStartTime).Microseconds()
|
||||
|
||||
h.valueRecorders[ServerLatency].Record(ctx, elapsedTime, labels...)
|
||||
}
|
||||
|
||||
func setAfterServeAttributes(span trace.Span, read, wrote int64, statusCode int, rerr, werr error) {
|
||||
kv := []kv.KeyValue{}
|
||||
|
||||
// TODO: Consider adding an event after each read and write, possibly as an
|
||||
// option (defaulting to off), so as to not create needlessly verbose spans.
|
||||
if read > 0 {
|
||||
kv = append(kv, ReadBytesKey.Int64(read))
|
||||
}
|
||||
if rerr != nil && rerr != io.EOF {
|
||||
kv = append(kv, ReadErrorKey.String(rerr.Error()))
|
||||
}
|
||||
if wrote > 0 {
|
||||
kv = append(kv, WroteBytesKey.Int64(wrote))
|
||||
}
|
||||
if statusCode > 0 {
|
||||
kv = append(kv, semconv.HTTPAttributesFromHTTPStatusCode(statusCode)...)
|
||||
span.SetStatus(semconv.SpanStatusFromHTTPStatusCode(statusCode))
|
||||
}
|
||||
if werr != nil && werr != io.EOF {
|
||||
kv = append(kv, WriteErrorKey.String(werr.Error()))
|
||||
}
|
||||
span.SetAttributes(kv...)
|
||||
}
|
||||
|
||||
// WithRouteTag annotates a span with the provided route name using the
|
||||
// RouteKey Tag.
|
||||
func WithRouteTag(route string, h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
span := trace.SpanFromContext(r.Context())
|
||||
span.SetAttributes(semconv.HTTPRouteKey.String(route))
|
||||
h.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
package othttp_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"go.opentelemetry.io/otel/api/kv"
|
||||
|
||||
"go.opentelemetry.io/otel/api/trace"
|
||||
"go.opentelemetry.io/otel/instrumentation/othttp"
|
||||
)
|
||||
|
||||
func ExampleNewHandler() {
|
||||
/* curl -v -d "a painting" http://localhost:7777/hello/bob/ross
|
||||
...
|
||||
* upload completely sent off: 10 out of 10 bytes
|
||||
< HTTP/1.1 200 OK
|
||||
< Traceparent: 00-76ae040ee5753f38edf1c2bd9bd128bd-dd394138cfd7a3dc-01
|
||||
< Date: Fri, 04 Oct 2019 02:33:08 GMT
|
||||
< Content-Length: 45
|
||||
< Content-Type: text/plain; charset=utf-8
|
||||
<
|
||||
Hello, bob/ross!
|
||||
You sent me this:
|
||||
a painting
|
||||
*/
|
||||
|
||||
figureOutName := func(ctx context.Context, s string) (string, error) {
|
||||
pp := strings.SplitN(s, "/", 2)
|
||||
var err error
|
||||
switch pp[1] {
|
||||
case "":
|
||||
err = fmt.Errorf("expected /hello/:name in %q", s)
|
||||
default:
|
||||
trace.SpanFromContext(ctx).SetAttributes(kv.String("name", pp[1]))
|
||||
}
|
||||
return pp[1], err
|
||||
}
|
||||
|
||||
var mux http.ServeMux
|
||||
mux.Handle("/hello/",
|
||||
othttp.WithRouteTag("/hello/:name", http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
var name string
|
||||
// Wrap another function in its own span
|
||||
if err := trace.SpanFromContext(ctx).Tracer().WithSpan(ctx, "figureOutName",
|
||||
func(ctx context.Context) error {
|
||||
var err error
|
||||
name, err = figureOutName(ctx, r.URL.Path[1:])
|
||||
return err
|
||||
}); err != nil {
|
||||
log.Println("error figuring out name: ", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
d, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
log.Println("error reading body: ", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
n, err := io.WriteString(w, "Hello, "+name+"!\nYou sent me this:\n"+string(d))
|
||||
if err != nil {
|
||||
log.Printf("error writing reply after %d bytes: %s", n, err)
|
||||
}
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
if err := http.ListenAndServe(":7777",
|
||||
othttp.NewHandler(&mux, "server",
|
||||
othttp.WithMessageEvents(othttp.ReadEvents, othttp.WriteEvents),
|
||||
),
|
||||
); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
@ -1,166 +0,0 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
package othttp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"google.golang.org/grpc/codes"
|
||||
|
||||
"go.opentelemetry.io/otel/api/kv"
|
||||
"go.opentelemetry.io/otel/api/trace"
|
||||
mockmeter "go.opentelemetry.io/otel/internal/metric"
|
||||
mocktrace "go.opentelemetry.io/otel/internal/trace"
|
||||
"go.opentelemetry.io/otel/semconv"
|
||||
)
|
||||
|
||||
func assertMetricLabels(t *testing.T, expectedLabels []kv.KeyValue, measurementBatches []mockmeter.Batch) {
|
||||
for _, batch := range measurementBatches {
|
||||
assert.ElementsMatch(t, expectedLabels, batch.Labels)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerBasics(t *testing.T) {
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
var id uint64
|
||||
tracer := mocktrace.MockTracer{StartSpanID: &id}
|
||||
meterimpl, meter := mockmeter.NewMeter()
|
||||
|
||||
operation := "test_handler"
|
||||
|
||||
h := NewHandler(
|
||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if _, err := io.WriteString(w, "hello world"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}), operation,
|
||||
WithTracer(&tracer),
|
||||
WithMeter(meter),
|
||||
)
|
||||
|
||||
r, err := http.NewRequest(http.MethodGet, "http://localhost/", strings.NewReader("foo"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
h.ServeHTTP(rr, r)
|
||||
|
||||
if len(meterimpl.MeasurementBatches) == 0 {
|
||||
t.Fatalf("got 0 recorded measurements, expected 1 or more")
|
||||
}
|
||||
|
||||
labelsToVerify := []kv.KeyValue{
|
||||
semconv.HTTPServerNameKey.String(operation),
|
||||
semconv.HTTPSchemeHTTP,
|
||||
semconv.HTTPHostKey.String(r.Host),
|
||||
semconv.HTTPFlavorKey.String(fmt.Sprintf("1.%d", r.ProtoMinor)),
|
||||
semconv.HTTPRequestContentLengthKey.Int64(3),
|
||||
}
|
||||
|
||||
assertMetricLabels(t, labelsToVerify, meterimpl.MeasurementBatches)
|
||||
|
||||
if got, expected := rr.Result().StatusCode, http.StatusOK; got != expected {
|
||||
t.Fatalf("got %d, expected %d", got, expected)
|
||||
}
|
||||
if got := rr.Header().Get("Traceparent"); got == "" {
|
||||
t.Fatal("expected non empty trace header")
|
||||
}
|
||||
if got, expected := id, uint64(1); got != expected {
|
||||
t.Fatalf("got %d, expected %d", got, expected)
|
||||
}
|
||||
d, err := ioutil.ReadAll(rr.Result().Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got, expected := string(d), "hello world"; got != expected {
|
||||
t.Fatalf("got %q, expected %q", got, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerNoWrite(t *testing.T) {
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
var id uint64
|
||||
tracer := mocktrace.MockTracer{StartSpanID: &id}
|
||||
|
||||
operation := "test_handler"
|
||||
var span trace.Span
|
||||
|
||||
h := NewHandler(
|
||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
span = trace.SpanFromContext(r.Context())
|
||||
}), operation,
|
||||
WithTracer(&tracer),
|
||||
)
|
||||
|
||||
r, err := http.NewRequest(http.MethodGet, "http://localhost/", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
h.ServeHTTP(rr, r)
|
||||
|
||||
if got, expected := rr.Result().StatusCode, http.StatusOK; got != expected {
|
||||
t.Fatalf("got %d, expected %d", got, expected)
|
||||
}
|
||||
if got := rr.Header().Get("Traceparent"); got != "" {
|
||||
t.Fatal("expected empty trace header")
|
||||
}
|
||||
if got, expected := id, uint64(1); got != expected {
|
||||
t.Fatalf("got %d, expected %d", got, expected)
|
||||
}
|
||||
if mockSpan, ok := span.(*mocktrace.MockSpan); ok {
|
||||
if got, expected := mockSpan.Status, codes.OK; got != expected {
|
||||
t.Fatalf("got %q, expected %q", got, expected)
|
||||
}
|
||||
} else {
|
||||
t.Fatalf("Expected *moctrace.MockSpan, got %T", span)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResponseWriterOptionalInterfaces(t *testing.T) {
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
var id uint64
|
||||
tracer := mocktrace.MockTracer{StartSpanID: &id}
|
||||
|
||||
// ResponseRecorder implements the Flusher interface. Make sure the
|
||||
// wrapped ResponseWriter passed to the handler still implements
|
||||
// Flusher.
|
||||
|
||||
var isFlusher bool
|
||||
h := NewHandler(
|
||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, isFlusher = w.(http.Flusher)
|
||||
if _, err := io.WriteString(w, "hello world"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}), "test_handler",
|
||||
WithTracer(&tracer))
|
||||
|
||||
r, err := http.NewRequest(http.MethodGet, "http://localhost/", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
h.ServeHTTP(rr, r)
|
||||
if !isFlusher {
|
||||
t.Fatal("http.Flusher interface not exposed")
|
||||
}
|
||||
}
|
@ -1,134 +0,0 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package othttp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"go.opentelemetry.io/otel/api/global"
|
||||
"go.opentelemetry.io/otel/api/propagation"
|
||||
"go.opentelemetry.io/otel/api/trace"
|
||||
"go.opentelemetry.io/otel/semconv"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
// Transport implements the http.RoundTripper interface and wraps
|
||||
// outbound HTTP(S) requests with a span.
|
||||
type Transport struct {
|
||||
rt http.RoundTripper
|
||||
|
||||
tracer trace.Tracer
|
||||
propagators propagation.Propagators
|
||||
spanStartOptions []trace.StartOption
|
||||
filters []Filter
|
||||
spanNameFormatter func(string, *http.Request) string
|
||||
}
|
||||
|
||||
var _ http.RoundTripper = &Transport{}
|
||||
|
||||
// NewTransport wraps the provided http.RoundTripper with one that
|
||||
// starts a span and injects the span context into the outbound request headers.
|
||||
func NewTransport(base http.RoundTripper, opts ...Option) *Transport {
|
||||
t := Transport{
|
||||
rt: base,
|
||||
}
|
||||
|
||||
defaultOpts := []Option{
|
||||
WithTracer(global.Tracer("go.opentelemetry.io/otel/instrumentation/othttp")),
|
||||
WithPropagators(global.Propagators()),
|
||||
WithSpanOptions(trace.WithSpanKind(trace.SpanKindClient)),
|
||||
WithSpanNameFormatter(defaultTransportFormatter),
|
||||
}
|
||||
|
||||
c := NewConfig(append(defaultOpts, opts...)...)
|
||||
t.configure(c)
|
||||
|
||||
return &t
|
||||
}
|
||||
|
||||
func (t *Transport) configure(c *Config) {
|
||||
t.tracer = c.Tracer
|
||||
t.propagators = c.Propagators
|
||||
t.spanStartOptions = c.SpanStartOptions
|
||||
t.filters = c.Filters
|
||||
t.spanNameFormatter = c.SpanNameFormatter
|
||||
}
|
||||
|
||||
func defaultTransportFormatter(_ string, r *http.Request) string {
|
||||
return r.Method
|
||||
}
|
||||
|
||||
// RoundTrip creates a Span and propagates its context via the provided request's headers
|
||||
// before handing the request to the configured base RoundTripper. The created span will
|
||||
// end when the response body is closed or when a read from the body returns io.EOF.
|
||||
func (t *Transport) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
for _, f := range t.filters {
|
||||
if !f(r) {
|
||||
// Simply pass through to the base RoundTripper if a filter rejects the request
|
||||
return t.rt.RoundTrip(r)
|
||||
}
|
||||
}
|
||||
|
||||
opts := append([]trace.StartOption{}, t.spanStartOptions...) // start with the configured options
|
||||
|
||||
ctx, span := t.tracer.Start(r.Context(), t.spanNameFormatter("", r), opts...)
|
||||
|
||||
r = r.WithContext(ctx)
|
||||
span.SetAttributes(semconv.HTTPClientAttributesFromHTTPRequest(r)...)
|
||||
propagation.InjectHTTP(ctx, t.propagators, r.Header)
|
||||
|
||||
res, err := t.rt.RoundTrip(r)
|
||||
if err != nil {
|
||||
span.RecordError(ctx, err, trace.WithErrorStatus(codes.Internal))
|
||||
span.End()
|
||||
return res, err
|
||||
}
|
||||
|
||||
span.SetAttributes(semconv.HTTPAttributesFromHTTPStatusCode(res.StatusCode)...)
|
||||
span.SetStatus(semconv.SpanStatusFromHTTPStatusCode(res.StatusCode))
|
||||
res.Body = &wrappedBody{ctx: ctx, span: span, body: res.Body}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
type wrappedBody struct {
|
||||
ctx context.Context
|
||||
span trace.Span
|
||||
body io.ReadCloser
|
||||
}
|
||||
|
||||
var _ io.ReadCloser = &wrappedBody{}
|
||||
|
||||
func (wb *wrappedBody) Read(b []byte) (int, error) {
|
||||
n, err := wb.body.Read(b)
|
||||
|
||||
switch err {
|
||||
case nil:
|
||||
// nothing to do here but fall through to the return
|
||||
case io.EOF:
|
||||
wb.span.End()
|
||||
default:
|
||||
wb.span.RecordError(wb.ctx, err, trace.WithErrorStatus(codes.Internal))
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (wb *wrappedBody) Close() error {
|
||||
wb.span.End()
|
||||
return wb.body.Close()
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package othttp
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func ExampleNewTransport() {
|
||||
// Create an http.Client that uses the othttp.Transport
|
||||
// wrapped around the http.DefaultTransport
|
||||
_ = http.Client{
|
||||
Transport: NewTransport(http.DefaultTransport),
|
||||
}
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package othttp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"go.opentelemetry.io/otel/api/global"
|
||||
"go.opentelemetry.io/otel/api/propagation"
|
||||
"go.opentelemetry.io/otel/api/trace"
|
||||
mocktrace "go.opentelemetry.io/otel/internal/trace"
|
||||
)
|
||||
|
||||
func TestTransportBasics(t *testing.T) {
|
||||
var id uint64
|
||||
tracer := mocktrace.MockTracer{StartSpanID: &id}
|
||||
content := []byte("Hello, world!")
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := propagation.ExtractHTTP(r.Context(), global.Propagators(), r.Header)
|
||||
span := trace.RemoteSpanContextFromContext(ctx)
|
||||
tgtID, err := trace.SpanIDFromHex(fmt.Sprintf("%016x", id))
|
||||
if err != nil {
|
||||
t.Fatalf("Error converting id to SpanID: %s", err.Error())
|
||||
}
|
||||
if span.SpanID != tgtID {
|
||||
t.Fatalf("testing remote SpanID: got %s, expected %s", span.SpanID, tgtID)
|
||||
}
|
||||
if _, err := w.Write(content); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
r, err := http.NewRequest(http.MethodGet, ts.URL, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tr := NewTransport(
|
||||
http.DefaultTransport,
|
||||
WithTracer(&tracer),
|
||||
)
|
||||
|
||||
c := http.Client{Transport: tr}
|
||||
res, err := c.Do(r)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(body, content) {
|
||||
t.Fatalf("unexpected content: got %s, expected %s", body, content)
|
||||
}
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package othttp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"go.opentelemetry.io/otel/api/propagation"
|
||||
)
|
||||
|
||||
var _ io.ReadCloser = &bodyWrapper{}
|
||||
|
||||
// bodyWrapper wraps a http.Request.Body (an io.ReadCloser) to track the number
|
||||
// of bytes read and the last error
|
||||
type bodyWrapper struct {
|
||||
io.ReadCloser
|
||||
record func(n int64) // must not be nil
|
||||
|
||||
read int64
|
||||
err error
|
||||
}
|
||||
|
||||
func (w *bodyWrapper) Read(b []byte) (int, error) {
|
||||
n, err := w.ReadCloser.Read(b)
|
||||
n1 := int64(n)
|
||||
w.read += n1
|
||||
w.err = err
|
||||
w.record(n1)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (w *bodyWrapper) Close() error {
|
||||
return w.ReadCloser.Close()
|
||||
}
|
||||
|
||||
var _ http.ResponseWriter = &respWriterWrapper{}
|
||||
|
||||
// respWriterWrapper wraps a http.ResponseWriter in order to track the number of
|
||||
// bytes written, the last error, and to catch the returned statusCode
|
||||
// TODO: The wrapped http.ResponseWriter doesn't implement any of the optional
|
||||
// types (http.Hijacker, http.Pusher, http.CloseNotifier, http.Flusher, etc)
|
||||
// that may be useful when using it in real life situations.
|
||||
type respWriterWrapper struct {
|
||||
http.ResponseWriter
|
||||
record func(n int64) // must not be nil
|
||||
|
||||
// used to inject the header
|
||||
ctx context.Context
|
||||
|
||||
props propagation.Propagators
|
||||
|
||||
written int64
|
||||
statusCode int
|
||||
err error
|
||||
wroteHeader bool
|
||||
}
|
||||
|
||||
func (w *respWriterWrapper) Header() http.Header {
|
||||
return w.ResponseWriter.Header()
|
||||
}
|
||||
|
||||
func (w *respWriterWrapper) Write(p []byte) (int, error) {
|
||||
if !w.wroteHeader {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
n, err := w.ResponseWriter.Write(p)
|
||||
n1 := int64(n)
|
||||
w.record(n1)
|
||||
w.written += n1
|
||||
w.err = err
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (w *respWriterWrapper) WriteHeader(statusCode int) {
|
||||
if w.wroteHeader {
|
||||
return
|
||||
}
|
||||
w.wroteHeader = true
|
||||
w.statusCode = statusCode
|
||||
propagation.InjectHTTP(w.ctx, w.props, w.Header())
|
||||
w.ResponseWriter.WriteHeader(statusCode)
|
||||
}
|
@ -13,7 +13,6 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
|
Loading…
x
Reference in New Issue
Block a user