1
0
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:
Anthony Mirabella 2020-08-05 02:30:23 -04:00 committed by GitHub
parent ea0720c05e
commit ae278e9186
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 0 additions and 2819 deletions

View File

@ -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=

View File

@ -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=

View File

@ -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"]

View File

@ -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
```

View File

@ -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")
}

View File

@ -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:

View File

@ -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
)

View File

@ -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=

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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=

View File

@ -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=

View File

@ -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=

View File

@ -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=

View File

@ -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=

View File

@ -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=

View File

@ -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=

View File

@ -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=

View File

@ -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=

View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -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
}

View File

@ -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()
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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())
}
}

View File

@ -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

View File

@ -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
})
}

View File

@ -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)
}
})
}
}

View File

@ -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

View File

@ -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
}
}

View File

@ -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)
}
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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)
})
}

View File

@ -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)
}
}

View File

@ -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")
}
}

View File

@ -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()
}

View File

@ -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),
}
}

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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=