mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-30 21:02:26 -05:00 
			
		
		
		
	
		
			
	
	
		
			176 lines
		
	
	
	
		
			5.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			176 lines
		
	
	
	
		
			5.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
|  | // GoToSocial | ||
|  | // Copyright (C) GoToSocial Authors admin@gotosocial.org | ||
|  | // SPDX-License-Identifier: AGPL-3.0-or-later | ||
|  | // | ||
|  | // This program is free software: you can redistribute it and/or modify | ||
|  | // it under the terms of the GNU Affero General Public License as published by | ||
|  | // the Free Software Foundation, either version 3 of the License, or | ||
|  | // (at your option) any later version. | ||
|  | // | ||
|  | // This program is distributed in the hope that it will be useful, | ||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||
|  | // GNU Affero General Public License for more details. | ||
|  | // | ||
|  | // You should have received a copy of the GNU Affero General Public License | ||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||
|  | 
 | ||
|  | //go:build !notracing | ||
|  | 
 | ||
|  | package tracing | ||
|  | 
 | ||
|  | import ( | ||
|  | 	"context" | ||
|  | 	"fmt" | ||
|  | 
 | ||
|  | 	"codeberg.org/gruf/go-kv" | ||
|  | 	"github.com/gin-gonic/gin" | ||
|  | 	"github.com/uptrace/bun" | ||
|  | 	"github.com/uptrace/bun/extra/bunotel" | ||
|  | 	"go.opentelemetry.io/otel" | ||
|  | 	"go.opentelemetry.io/otel/attribute" | ||
|  | 	"go.opentelemetry.io/otel/exporters/jaeger" | ||
|  | 	"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" | ||
|  | 	"go.opentelemetry.io/otel/propagation" | ||
|  | 	"go.opentelemetry.io/otel/sdk/resource" | ||
|  | 	"go.opentelemetry.io/otel/sdk/trace" | ||
|  | 	semconv "go.opentelemetry.io/otel/semconv/v1.17.0" | ||
|  | 	"go.opentelemetry.io/otel/semconv/v1.17.0/httpconv" | ||
|  | 	oteltrace "go.opentelemetry.io/otel/trace" | ||
|  | 
 | ||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/config" | ||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/gtscontext" | ||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/log" | ||
|  | ) | ||
|  | 
 | ||
|  | const ( | ||
|  | 	tracerKey  = "gotosocial-server-tracer" | ||
|  | 	tracerName = "github.com/superseriousbusiness/gotosocial/internal/tracing" | ||
|  | ) | ||
|  | 
 | ||
|  | func Initialize() error { | ||
|  | 	if !config.GetTracingEnabled() { | ||
|  | 		return nil | ||
|  | 	} | ||
|  | 
 | ||
|  | 	insecure := config.GetTracingInsecureTransport() | ||
|  | 
 | ||
|  | 	var tpo trace.TracerProviderOption | ||
|  | 	switch config.GetTracingTransport() { | ||
|  | 	case "grpc": | ||
|  | 		opts := []otlptracegrpc.Option{ | ||
|  | 			otlptracegrpc.WithEndpoint(config.GetTracingEndpoint()), | ||
|  | 		} | ||
|  | 		if insecure { | ||
|  | 			opts = append(opts, otlptracegrpc.WithInsecure()) | ||
|  | 		} | ||
|  | 		exp, err := otlptracegrpc.New(context.Background(), opts...) | ||
|  | 		if err != nil { | ||
|  | 			return fmt.Errorf("building tracing exporter: %w", err) | ||
|  | 		} | ||
|  | 		tpo = trace.WithBatcher(exp) | ||
|  | 	case "jaeger": | ||
|  | 		exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(config.GetTracingEndpoint()))) | ||
|  | 		if err != nil { | ||
|  | 			return fmt.Errorf("building tracing exporter: %w", err) | ||
|  | 		} | ||
|  | 		tpo = trace.WithBatcher(exp) | ||
|  | 	default: | ||
|  | 		return fmt.Errorf("invalid tracing transport: %s", config.GetTracingTransport()) | ||
|  | 	} | ||
|  | 	r, _ := resource.Merge( | ||
|  | 		resource.Default(), | ||
|  | 		resource.NewWithAttributes( | ||
|  | 			semconv.SchemaURL, | ||
|  | 			semconv.ServiceName("GoToSocial"), | ||
|  | 		), | ||
|  | 	) | ||
|  | 
 | ||
|  | 	tp := trace.NewTracerProvider( | ||
|  | 		tpo, | ||
|  | 		trace.WithResource(r), | ||
|  | 	) | ||
|  | 	otel.SetTracerProvider(tp) | ||
|  | 	propagator := propagation.NewCompositeTextMapPropagator( | ||
|  | 		propagation.TraceContext{}, | ||
|  | 		propagation.Baggage{}, | ||
|  | 	) | ||
|  | 	otel.SetTextMapPropagator(propagator) | ||
|  | 	log.Hook(func(ctx context.Context, kvs []kv.Field) []kv.Field { | ||
|  | 		span := oteltrace.SpanFromContext(ctx) | ||
|  | 		if span != nil && span.SpanContext().HasTraceID() { | ||
|  | 			return append(kvs, kv.Field{K: "traceID", V: span.SpanContext().TraceID().String()}) | ||
|  | 		} | ||
|  | 		return kvs | ||
|  | 	}) | ||
|  | 	return nil | ||
|  | } | ||
|  | 
 | ||
|  | // InstrumentGin is a middleware injecting tracing information based on the | ||
|  | // otelgin implementation found at | ||
|  | // https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/instrumentation/github.com/gin-gonic/gin/otelgin/gintrace.go | ||
|  | func InstrumentGin() gin.HandlerFunc { | ||
|  | 	provider := otel.GetTracerProvider() | ||
|  | 	tracer := provider.Tracer( | ||
|  | 		tracerName, | ||
|  | 		oteltrace.WithInstrumentationVersion(config.GetSoftwareVersion()), | ||
|  | 	) | ||
|  | 	propagator := otel.GetTextMapPropagator() | ||
|  | 	return func(c *gin.Context) { | ||
|  | 		c.Set(tracerKey, tracer) | ||
|  | 		savedCtx := c.Request.Context() | ||
|  | 		defer func() { | ||
|  | 			c.Request = c.Request.WithContext(savedCtx) | ||
|  | 		}() | ||
|  | 		ctx := propagator.Extract(savedCtx, propagation.HeaderCarrier(c.Request.Header)) | ||
|  | 		opts := []oteltrace.SpanStartOption{ | ||
|  | 			oteltrace.WithAttributes(httpconv.ServerRequest(config.GetHost(), c.Request)...), | ||
|  | 			oteltrace.WithSpanKind(oteltrace.SpanKindServer), | ||
|  | 		} | ||
|  | 		spanName := c.FullPath() | ||
|  | 		if spanName == "" { | ||
|  | 			spanName = fmt.Sprintf("HTTP %s route not found", c.Request.Method) | ||
|  | 		} else { | ||
|  | 			rAttr := semconv.HTTPRoute(spanName) | ||
|  | 			opts = append(opts, oteltrace.WithAttributes(rAttr)) | ||
|  | 		} | ||
|  | 		id := gtscontext.RequestID(c.Request.Context()) | ||
|  | 		if id != "" { | ||
|  | 			opts = append(opts, oteltrace.WithAttributes(attribute.String("requestID", id))) | ||
|  | 		} | ||
|  | 		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())) | ||
|  | 		} | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | func InjectRequestID() gin.HandlerFunc { | ||
|  | 	return func(c *gin.Context) { | ||
|  | 		id := gtscontext.RequestID(c.Request.Context()) | ||
|  | 		if id != "" { | ||
|  | 			span := oteltrace.SpanFromContext(c.Request.Context()) | ||
|  | 			span.SetAttributes(attribute.String("requestID", id)) | ||
|  | 		} | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | func InstrumentBun() bun.QueryHook { | ||
|  | 	return bunotel.NewQueryHook( | ||
|  | 		bunotel.WithFormattedQueries(true), | ||
|  | 	) | ||
|  | } |