Quentin Gliech af5e8579b2 Better propagate request context
This properly propagates the go Context on down to all HTTP calls, which means that outgoing request have the OTLP trace context.
This also adds the Jaeger propagator to the list of OTEL propagators, so that Synapse properly gets the incoming trace context.
It also upgrades all the OTEL libraries
2023-09-13 19:41:52 +02:00

152 lines
4.3 KiB
Go

package internal
import (
"context"
"encoding/base64"
"fmt"
"net/url"
"runtime/trace"
"go.opentelemetry.io/contrib/propagators/jaeger"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/resource"
tracesdk "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
otrace "go.opentelemetry.io/otel/trace"
)
const tracerName = "sliding-sync"
type TraceKey string
var (
OTLPTagDeviceID TraceKey = "device_id"
OTLPTagUserID TraceKey = "user_id"
OTLPTagConnID TraceKey = "conn_id"
OTLPTagTxnID TraceKey = "txn_id"
)
// SetAttributeOnContext sets one of the trace tag keys on the given context, so derived spans will use said tags.
func SetAttributeOnContext(ctx context.Context, key TraceKey, val string) context.Context {
return context.WithValue(ctx, key, val)
}
// attributesFromContext sets span tags based on data in the provided ctx
func attributesFromContext(ctx context.Context) []otrace.SpanStartOption {
var attrs []attribute.KeyValue
for _, tag := range []TraceKey{OTLPTagConnID, OTLPTagDeviceID, OTLPTagUserID, OTLPTagTxnID} {
val := ctx.Value(tag)
if val == nil {
continue
}
attrs = append(attrs, attribute.String(string(tag), val.(string)))
}
if len(attrs) == 0 {
return nil
}
return []otrace.SpanStartOption{
otrace.WithAttributes(attrs...),
}
}
type Task struct {
t *trace.Task
o otrace.Span
}
func (s *Task) End() {
s.t.End()
s.o.End()
}
// combined runtime/trace and OTLP span
type RuntimeTraceOTLPSpan struct {
region *trace.Region
span otrace.Span
}
func (s *RuntimeTraceOTLPSpan) End() {
s.region.End()
s.span.End()
}
func Logf(ctx context.Context, category, format string, args ...interface{}) {
trace.Logf(ctx, category, format, args...)
s := otrace.SpanFromContext(ctx)
s.AddEvent(fmt.Sprintf(format, args...), otrace.WithAttributes(
attribute.String("category", category),
))
}
func StartSpan(ctx context.Context, name string) (newCtx context.Context, span *RuntimeTraceOTLPSpan) {
region := trace.StartRegion(ctx, name)
newCtx, ospan := otel.Tracer(tracerName).Start(ctx, name, attributesFromContext(ctx)...)
// use the same api as NewTask to allow context nesting
return newCtx, &RuntimeTraceOTLPSpan{
region: region,
span: ospan,
}
}
func StartTask(ctx context.Context, name string) (context.Context, *Task) {
ctx, task := trace.NewTask(ctx, name)
newCtx, ospan := otel.Tracer(tracerName).Start(ctx, name, attributesFromContext(ctx)...)
return newCtx, &Task{
t: task,
o: ospan,
}
}
func ConfigureOTLP(otlpURL, otlpUser, otlpPass, version string) error {
ctx := context.Background()
parsedOTLPURL, err := url.Parse(otlpURL)
if err != nil {
return err
}
isInsecure := parsedOTLPURL.Scheme == "http" // e.g testing and development
if parsedOTLPURL.Path != "" {
return fmt.Errorf("OTLP URL %s cannot contain any path segments", otlpURL)
}
opts := []otlptracehttp.Option{
otlptracehttp.WithEndpoint(parsedOTLPURL.Host),
}
if isInsecure {
opts = append(opts, otlptracehttp.WithInsecure())
}
fmt.Println("ConfigureOTLP: host=", parsedOTLPURL.Host, "insecure=", isInsecure, "basic auth=", otlpPass != "" && otlpUser != "")
if otlpPass != "" && otlpUser != "" {
opts = append(opts, otlptracehttp.WithHeaders(
map[string]string{
"Authorization": fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", otlpUser, otlpPass)))),
},
))
}
client := otlptracehttp.NewClient(opts...)
// Create the OTLP exporter
exp, err := otlptrace.New(ctx, client)
if err != nil {
return err
}
tp := tracesdk.NewTracerProvider(
// Always be sure to batch in production.
tracesdk.WithBatcher(exp),
// Record information about this application in a Resource.
tracesdk.WithResource(resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceName("sliding-sync"),
attribute.String("version", version),
)),
)
otel.SetTracerProvider(tp)
// setup traceparent (TraceContext) handling, and pass through any Baggage
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
propagation.Baggage{}, propagation.TraceContext{}, jaeger.Jaeger{},
))
return nil
}