diff --git a/.github/workflows/kind.yml b/.github/workflows/kind.yml index f12ae3b07..deb603e3a 100644 --- a/.github/workflows/kind.yml +++ b/.github/workflows/kind.yml @@ -10,10 +10,10 @@ on: jobs: kubernetes-test: - strategy: + strategy: matrix: k8s-version: ["v1.26.0"] - library: ["gorillamux", "nethttp"] + library: ["gorillamux", "nethttp", "gin"] runs-on: ubuntu-latest steps: - name: Checkout Repo diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ef39e243..3d875776f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ OpenTelemetry Go Automatic Instrumentation adheres to [Semantic Versioning](http ## [Unreleased] +### Added + +- Add [gin-gonic/gin](https://github.com/gin-gonic/gin) instrumentation. ([#100](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/100)) ### Changed - Change `OTEL_TARGET_EXE` environment variable to `OTEL_GO_AUTO_TARGET_EXE`. diff --git a/Makefile b/Makefile index ebe6de1af..75c19e356 100644 --- a/Makefile +++ b/Makefile @@ -94,9 +94,10 @@ license-header-check: exit 1; \ fi -.PHONY: fixture-nethttp fixture-gorillamux +.PHONY: fixture-nethttp fixture-gorillamux fixture-gin fixture-nethttp: fixtures/nethttp fixture-gorillamux: fixtures/gorillamux +fixture-gin: fixtures/gin fixtures/%: LIBRARY=$* fixtures/%: IMG=otel-go-instrumentation $(MAKE) docker-build diff --git a/pkg/instrumentors/bpf/github.com/gin-gonic/gin/bpf/probe.bpf.c b/pkg/instrumentors/bpf/github.com/gin-gonic/gin/bpf/probe.bpf.c new file mode 100644 index 000000000..9ee1fe7b6 --- /dev/null +++ b/pkg/instrumentors/bpf/github.com/gin-gonic/gin/bpf/probe.bpf.c @@ -0,0 +1,105 @@ +// 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. + +#include "arguments.h" +#include "span_context.h" +#include "go_context.h" + +char __license[] SEC("license") = "Dual MIT/GPL"; + +#define PATH_MAX_LEN 100 +#define METHOD_MAX_LEN 6 // Longer method: DELETE +#define MAX_CONCURRENT 50 + +struct http_request_t { + u64 start_time; + u64 end_time; + char method[METHOD_MAX_LEN]; + char path[PATH_MAX_LEN]; + struct span_context sc; +}; + +// map key: pointer to the goroutine that handles the request +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __type(key, void *); + __type(value, struct http_request_t); + __uint(max_entries, MAX_CONCURRENT); +} context_to_http_events SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); +} events SEC(".maps"); + +// Injected in init +volatile const u64 method_ptr_pos; +volatile const u64 url_ptr_pos; +volatile const u64 path_ptr_pos; + +// This instrumentation attaches uprobe to the following function: +// func (engine *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request) +SEC("uprobe/GinEngine_ServeHTTP") +int uprobe_GinEngine_ServeHTTP(struct pt_regs *ctx) { + u64 request_pos = 4; + struct http_request_t httpReq = {}; + httpReq.start_time = bpf_ktime_get_ns(); + + // Get request struct + void *req_ptr = get_argument(ctx, request_pos); + + // Get method from request + void *method_ptr = 0; + bpf_probe_read(&method_ptr, sizeof(method_ptr), (void *)(req_ptr + method_ptr_pos)); + u64 method_len = 0; + bpf_probe_read(&method_len, sizeof(method_len), (void *)(req_ptr + (method_ptr_pos + 8))); + u64 method_size = sizeof(httpReq.method); + method_size = method_size < method_len ? method_size : method_len; + bpf_probe_read(&httpReq.method, method_size, method_ptr); + + // get path from Request.URL + void *url_ptr = 0; + bpf_probe_read(&url_ptr, sizeof(url_ptr), (void *)(req_ptr + url_ptr_pos)); + void *path_ptr = 0; + bpf_probe_read(&path_ptr, sizeof(path_ptr), (void *)(url_ptr + path_ptr_pos)); + u64 path_len = 0; + bpf_probe_read(&path_len, sizeof(path_len), (void *)(url_ptr + (path_ptr_pos + 8))); + u64 path_size = sizeof(httpReq.path); + path_size = path_size < path_len ? path_size : path_len; + bpf_probe_read(&httpReq.path, path_size, path_ptr); + + // Get goroutine pointer + void *goroutine = get_goroutine_address(ctx); + + // Write event + httpReq.sc = generate_span_context(); + bpf_map_update_elem(&context_to_http_events, &goroutine, &httpReq, 0); + long res = bpf_map_update_elem(&spans_in_progress, &goroutine, &httpReq.sc, 0); + return 0; +} + +SEC("uprobe/GinEngine_ServeHTTP") +int uprobe_GinEngine_ServeHTTP_Returns(struct pt_regs *ctx) { + u64 request_pos = 4; + void *req_ptr = get_argument(ctx, request_pos); + void *goroutine = get_goroutine_address(ctx); + + void *httpReq_ptr = bpf_map_lookup_elem(&context_to_http_events, &goroutine); + struct http_request_t httpReq = {}; + bpf_probe_read(&httpReq, sizeof(httpReq), httpReq_ptr); + httpReq.end_time = bpf_ktime_get_ns(); + bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &httpReq, sizeof(httpReq)); + bpf_map_delete_elem(&context_to_http_events, &goroutine); + bpf_map_delete_elem(&spans_in_progress, &goroutine); + return 0; +} \ No newline at end of file diff --git a/pkg/instrumentors/bpf/github.com/gin-gonic/gin/probe.go b/pkg/instrumentors/bpf/github.com/gin-gonic/gin/probe.go new file mode 100644 index 000000000..347190ff9 --- /dev/null +++ b/pkg/instrumentors/bpf/github.com/gin-gonic/gin/probe.go @@ -0,0 +1,227 @@ +// 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 gin + +import ( + "bytes" + "encoding/binary" + "errors" + "os" + + "go.opentelemetry.io/auto/pkg/instrumentors/bpffs" + + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/link" + "github.com/cilium/ebpf/perf" + "go.opentelemetry.io/auto/pkg/inject" + "go.opentelemetry.io/auto/pkg/instrumentors/context" + "go.opentelemetry.io/auto/pkg/instrumentors/events" + "go.opentelemetry.io/auto/pkg/log" + "go.opentelemetry.io/otel/attribute" + semconv "go.opentelemetry.io/otel/semconv/v1.7.0" + "go.opentelemetry.io/otel/trace" + "golang.org/x/sys/unix" +) + +//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -target bpfel -cc clang -cflags $CFLAGS bpf ./bpf/probe.bpf.c + +// Event represents an event in the gin-gonic/gin server during an HTTP +// request-response. +type Event struct { + StartTime uint64 + EndTime uint64 + Method [6]byte + Path [100]byte + SpanContext context.EBPFSpanContext +} + +// Instrumentor is the gin-gonic/gin instrumentor. +type Instrumentor struct { + bpfObjects *bpfObjects + uprobes []link.Link + returnProbs []link.Link + eventsReader *perf.Reader +} + +// New returns a new [Instrumentor]. +func New() *Instrumentor { + return &Instrumentor{} +} + +// LibraryName returns the gin-gonic/gin package import path. +func (h *Instrumentor) LibraryName() string { + return "github.com/gin-gonic/gin" +} + +// FuncNames returns the function names from "github.com/gin-gonic/gin" that are +// instrumented. +func (h *Instrumentor) FuncNames() []string { + return []string{"github.com/gin-gonic/gin.(*Engine).ServeHTTP"} +} + +// Load loads all instrumentation offsets. +func (h *Instrumentor) Load(ctx *context.InstrumentorContext) error { + spec, err := ctx.Injector.Inject(loadBpf, "go", ctx.TargetDetails.GoVersion.Original(), []*inject.InjectStructField{ + { + VarName: "method_ptr_pos", + StructName: "net/http.Request", + Field: "Method", + }, + { + VarName: "url_ptr_pos", + StructName: "net/http.Request", + Field: "URL", + }, + { + VarName: "path_ptr_pos", + StructName: "net/url.URL", + Field: "Path", + }, + }, false) + + if err != nil { + return err + } + + h.bpfObjects = &bpfObjects{} + err = spec.LoadAndAssign(h.bpfObjects, &ebpf.CollectionOptions{ + Maps: ebpf.MapOptions{ + PinPath: bpffs.BPFFsPath, + }, + }) + if err != nil { + return err + } + + for _, funcName := range h.FuncNames() { + h.registerProbes(ctx, funcName) + } + + rd, err := perf.NewReader(h.bpfObjects.Events, os.Getpagesize()) + if err != nil { + return err + } + h.eventsReader = rd + + return nil +} + +func (h *Instrumentor) registerProbes(ctx *context.InstrumentorContext, funcName string) { + logger := log.Logger.WithName("gin-gonic/gin-instrumentor").WithValues("function", funcName) + offset, err := ctx.TargetDetails.GetFunctionOffset(funcName) + if err != nil { + logger.Error(err, "could not find function start offset. Skipping") + return + } + retOffsets, err := ctx.TargetDetails.GetFunctionReturns(funcName) + if err != nil { + logger.Error(err, "could not find function end offsets. Skipping") + return + } + + up, err := ctx.Executable.Uprobe("", h.bpfObjects.UprobeGinEngineServeHTTP, &link.UprobeOptions{ + Address: offset, + }) + if err != nil { + logger.V(1).Info("could not insert start uprobe. Skipping", + "error", err.Error()) + return + } + + h.uprobes = append(h.uprobes, up) + + for _, ret := range retOffsets { + retProbe, err := ctx.Executable.Uprobe("", h.bpfObjects.UprobeGinEngineServeHTTP_Returns, &link.UprobeOptions{ + Address: ret, + }) + if err != nil { + logger.Error(err, "could not insert return uprobe. Skipping") + return + } + h.returnProbs = append(h.returnProbs, retProbe) + } +} + +// Run runs the events processing loop. +func (h *Instrumentor) Run(eventsChan chan<- *events.Event) { + logger := log.Logger.WithName("gin-gonic/gin-instrumentor") + var event Event + for { + record, err := h.eventsReader.Read() + if err != nil { + if errors.Is(err, perf.ErrClosed) { + return + } + logger.Error(err, "error reading from perf reader") + continue + } + + if record.LostSamples != 0 { + logger.V(0).Info("perf event ring buffer full", "dropped", record.LostSamples) + continue + } + + if err := binary.Read(bytes.NewBuffer(record.RawSample), binary.LittleEndian, &event); err != nil { + logger.Error(err, "error parsing perf event") + continue + } + + eventsChan <- h.convertEvent(&event) + } +} + +func (h *Instrumentor) convertEvent(e *Event) *events.Event { + method := unix.ByteSliceToString(e.Method[:]) + path := unix.ByteSliceToString(e.Path[:]) + + sc := trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: e.SpanContext.TraceID, + SpanID: e.SpanContext.SpanID, + TraceFlags: trace.FlagsSampled, + }) + + return &events.Event{ + Library: h.LibraryName(), + Name: path, + Kind: trace.SpanKindServer, + StartTime: int64(e.StartTime), + EndTime: int64(e.EndTime), + SpanContext: &sc, + Attributes: []attribute.KeyValue{ + semconv.HTTPMethodKey.String(method), + semconv.HTTPTargetKey.String(path), + }, + } +} + +// Close stops the Instrumentor. +func (h *Instrumentor) Close() { + log.Logger.V(0).Info("closing gin-gonic/gin instrumentor") + if h.eventsReader != nil { + h.eventsReader.Close() + } + + for _, r := range h.uprobes { + r.Close() + } + + for _, r := range h.returnProbs { + r.Close() + } + + if h.bpfObjects != nil { + h.bpfObjects.Close() + } +} diff --git a/pkg/instrumentors/manager.go b/pkg/instrumentors/manager.go index 8ea637c41..1515df86a 100644 --- a/pkg/instrumentors/manager.go +++ b/pkg/instrumentors/manager.go @@ -18,6 +18,7 @@ import ( "fmt" "go.opentelemetry.io/auto/pkg/instrumentors/allocator" + "go.opentelemetry.io/auto/pkg/instrumentors/bpf/github.com/gin-gonic/gin" gorillaMux "go.opentelemetry.io/auto/pkg/instrumentors/bpf/github.com/gorilla/mux" "go.opentelemetry.io/auto/pkg/instrumentors/bpf/google/golang/org/grpc" grpcServer "go.opentelemetry.io/auto/pkg/instrumentors/bpf/google/golang/org/grpc/server" @@ -107,6 +108,7 @@ func registerInstrumentors(m *Manager) error { grpcServer.New(), httpServer.New(), gorillaMux.New(), + gin.New(), } for _, i := range insts { diff --git a/test/e2e/gin/Dockerfile b/test/e2e/gin/Dockerfile new file mode 100644 index 000000000..646ec7a43 --- /dev/null +++ b/test/e2e/gin/Dockerfile @@ -0,0 +1,4 @@ +FROM golang:1.20 +WORKDIR /sample-app +COPY . . +RUN go build -o main diff --git a/test/e2e/gin/go.mod b/test/e2e/gin/go.mod new file mode 100644 index 000000000..343b1da55 --- /dev/null +++ b/test/e2e/gin/go.mod @@ -0,0 +1,31 @@ +module go.opentelemetry.io/auto/test/e2e/gin + +go 1.20 + +require github.com/gin-gonic/gin v1.9.0 + +require ( + github.com/bytedance/sonic v1.8.0 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.11.2 // indirect + github.com/goccy/go-json v0.10.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.0.9 // indirect + github.com/leodido/go-urn v1.2.1 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.0.6 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.9 // indirect + golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect + golang.org/x/crypto v0.5.0 // indirect + golang.org/x/net v0.7.0 // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/text v0.7.0 // indirect + google.golang.org/protobuf v1.28.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/test/e2e/gin/go.sum b/test/e2e/gin/go.sum new file mode 100644 index 000000000..bfad1c04d --- /dev/null +++ b/test/e2e/gin/go.sum @@ -0,0 +1,81 @@ +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA= +github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +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/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8= +github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU= +github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s= +github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= +github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= +github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= +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/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU= +github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= +golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +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/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/test/e2e/gin/main.go b/test/e2e/gin/main.go new file mode 100644 index 000000000..ee4b2f6ab --- /dev/null +++ b/test/e2e/gin/main.go @@ -0,0 +1,50 @@ +// 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/ioutil" + "log" + "net/http" + "time" + + "github.com/gin-gonic/gin" +) + +func main() { + r := gin.Default() + r.GET("/hello-gin", func(c *gin.Context) { + c.String(http.StatusOK, "hello\n") + }) + go r.Run(); + + // give time for auto-instrumentation to start up + time.Sleep(5 * time.Second) + + resp, err := http.Get("http://localhost:8080/hello-gin") + if err != nil { + log.Fatal(err) + } + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Fatal(err) + } + + log.Printf("Body: %s\n", string(body)) + _ = resp.Body.Close() + + // give time for auto-instrumentation to report signal + time.Sleep(5 * time.Second) +} diff --git a/test/e2e/gin/traces.json b/test/e2e/gin/traces.json new file mode 100644 index 000000000..7d93f8b7d --- /dev/null +++ b/test/e2e/gin/traces.json @@ -0,0 +1,59 @@ +{ + "resourceSpans": [ + { + "resource": { + "attributes": [ + { + "key": "service.name", + "value": { + "stringValue": "sample-app" + } + }, + { + "key": "telemetry.auto.version", + "value": { + "stringValue": "v0.1.0-alpha" + } + }, + { + "key": "telemetry.sdk.language", + "value": { + "stringValue": "go" + } + } + ] + }, + "scopeSpans": [ + { + "scope": { + "name": "github.com/gin-gonic/gin" + }, + "spans": [ + { + "attributes": [ + { + "key": "http.method", + "value": { + "stringValue": "GET" + } + }, + { + "key": "http.target", + "value": { + "stringValue": "/hello-gin" + } + } + ], + "kind": 2, + "name": "/hello-gin", + "parentSpanId": "", + "spanId": "xxxxx", + "status": {}, + "traceId": "xxxxx" + } + ] + } + ] + } + ] +}