// 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. // Based on https://github.com/DataDog/dd-trace-go/blob/8fb554ff7cf694267f9077ae35e27ce4689ed8b6/contrib/gin-gonic/gin/gintrace.go package otelgin // import "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin" import ( "fmt" "github.com/gin-gonic/gin" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/propagation" semconv "go.opentelemetry.io/otel/semconv/v1.17.0" "go.opentelemetry.io/otel/semconv/v1.17.0/httpconv" oteltrace "go.opentelemetry.io/otel/trace" ) const ( tracerKey = "otel-go-contrib-tracer" tracerName = "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin" ) // Middleware returns middleware that will trace incoming requests. // The service parameter should describe the name of the (virtual) // server handling the request. func Middleware(service string, opts ...Option) gin.HandlerFunc { cfg := config{} for _, opt := range opts { opt.apply(&cfg) } if cfg.TracerProvider == nil { cfg.TracerProvider = otel.GetTracerProvider() } tracer := cfg.TracerProvider.Tracer( tracerName, oteltrace.WithInstrumentationVersion(SemVersion()), ) if cfg.Propagators == nil { cfg.Propagators = otel.GetTextMapPropagator() } return func(c *gin.Context) { for _, f := range cfg.Filters { if !f(c.Request) { // Serve the request to the next middleware // if a filter rejects the request. c.Next() return } } c.Set(tracerKey, tracer) savedCtx := c.Request.Context() defer func() { c.Request = c.Request.WithContext(savedCtx) }() ctx := cfg.Propagators.Extract(savedCtx, propagation.HeaderCarrier(c.Request.Header)) opts := []oteltrace.SpanStartOption{ oteltrace.WithAttributes(httpconv.ServerRequest(service, c.Request)...), oteltrace.WithSpanKind(oteltrace.SpanKindServer), } var spanName string if cfg.SpanNameFormatter == nil { spanName = c.FullPath() } else { spanName = cfg.SpanNameFormatter(c.Request) } if spanName == "" { spanName = fmt.Sprintf("HTTP %s route not found", c.Request.Method) } else { rAttr := semconv.HTTPRoute(spanName) opts = append(opts, oteltrace.WithAttributes(rAttr)) } ctx, span := tracer.Start(ctx, spanName, opts...) defer span.End() // pass the span through the request context c.Request = c.Request.WithContext(ctx) // serve the request to the next middleware c.Next() status := c.Writer.Status() span.SetStatus(httpconv.ServerStatus(status)) if status > 0 { span.SetAttributes(semconv.HTTPStatusCode(status)) } if len(c.Errors) > 0 { span.SetAttributes(attribute.String("gin.errors", c.Errors.String())) } } } // HTML will trace the rendering of the template as a child of the // span in the given context. This is a replacement for // gin.Context.HTML function - it invokes the original function after // setting up the span. func HTML(c *gin.Context, code int, name string, obj interface{}) { var tracer oteltrace.Tracer tracerInterface, ok := c.Get(tracerKey) if ok { tracer, ok = tracerInterface.(oteltrace.Tracer) } if !ok { tracer = otel.GetTracerProvider().Tracer( tracerName, oteltrace.WithInstrumentationVersion(SemVersion()), ) } savedContext := c.Request.Context() defer func() { c.Request = c.Request.WithContext(savedContext) }() opt := oteltrace.WithAttributes(attribute.String("go.template", name)) _, span := tracer.Start(savedContext, "gin.renderer.html", opt) defer func() { if r := recover(); r != nil { err := fmt.Errorf("error rendering template:%s: %s", name, r) span.RecordError(err) span.SetStatus(codes.Error, "template failure") span.End() panic(r) } else { span.End() } }() c.HTML(code, name, obj) }