github.com/safedep/dry@v0.0.0-20241016050132-a15651f0548b/obs/tracing.go (about)

     1  package obs
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"strconv"
     8  
     9  	"github.com/safedep/dry/log"
    10  	"github.com/safedep/dry/utils"
    11  	"go.opentelemetry.io/otel"
    12  	"go.opentelemetry.io/otel/attribute"
    13  	"go.opentelemetry.io/otel/codes"
    14  	"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
    15  	"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
    16  	"go.opentelemetry.io/otel/propagation"
    17  	"go.opentelemetry.io/otel/sdk/resource"
    18  	sdktrace "go.opentelemetry.io/otel/sdk/trace"
    19  	"go.opentelemetry.io/otel/trace"
    20  )
    21  
    22  const (
    23  	tracerControlEnvKey         = "APP_SERVICE_OBS_ENABLED"
    24  	tracerServiceNameEnvKey     = "APP_SERVICE_NAME"
    25  	tracerServiceEnvEnvKey      = "APP_SERVICE_ENV"
    26  	tracerServiceLabelEnvKey    = "APP_SERVICE_LABELS"
    27  	tracerOtelExporterUrlEnvKey = "APP_OTEL_EXPORTER_OTLP_ENDPOINT"
    28  )
    29  
    30  var (
    31  	globalTracer = otel.Tracer("NOP")
    32  )
    33  
    34  // InitTracing initializes the global tracer
    35  func InitTracing() func(context.Context) error {
    36  	if !isTracingEnabled() {
    37  		log.Debugf("Tracing is not enabled")
    38  		return func(ctx context.Context) error { return nil }
    39  	}
    40  
    41  	serviceName := os.Getenv(tracerServiceNameEnvKey)
    42  	serviceEnv := os.Getenv(tracerServiceEnvEnvKey)
    43  	otlpExporterUrl := os.Getenv(tracerOtelExporterUrlEnvKey)
    44  
    45  	if utils.IsEmptyString(serviceName) || utils.IsEmptyString(otlpExporterUrl) {
    46  		panic("tracer is enable but required environment is not defined")
    47  	}
    48  
    49  	// NOTE: We expect the collector to be a sidecar
    50  	// TODO: Revisit this for using a secure channel
    51  	exporter, err := otlptrace.New(
    52  		context.Background(),
    53  		otlptracegrpc.NewClient(
    54  			otlptracegrpc.WithInsecure(),
    55  			otlptracegrpc.WithEndpoint(otlpExporterUrl),
    56  		),
    57  	)
    58  
    59  	if err != nil {
    60  		panic(fmt.Sprintf("error creating otlp exporter: %v", err))
    61  	}
    62  
    63  	resources, err := resource.New(
    64  		context.Background(),
    65  		resource.WithAttributes(
    66  			attribute.String("service.name", serviceName),
    67  			attribute.String("service.environment", serviceEnv),
    68  			attribute.String("service.language", "go"),
    69  		),
    70  	)
    71  
    72  	if err != nil {
    73  		panic(fmt.Sprintf("error creating otlp resource: %v", err))
    74  	}
    75  
    76  	otel.SetTracerProvider(
    77  		sdktrace.NewTracerProvider(
    78  			sdktrace.WithSampler(sdktrace.AlwaysSample()),
    79  			sdktrace.WithBatcher(exporter),
    80  			sdktrace.WithResource(resources),
    81  		),
    82  	)
    83  
    84  	otel.SetTextMapPropagator(propagation.TraceContext{})
    85  	globalTracer = otel.Tracer(serviceName)
    86  
    87  	log.Debugf("Tracer initialized for service=%s env=%s",
    88  		serviceName, serviceEnv)
    89  
    90  	return exporter.Shutdown
    91  }
    92  
    93  func ShutdownTracing() {
    94  	// Explicitly flush and shutdown tracers
    95  }
    96  
    97  func Spanned(current context.Context, name string,
    98  	tracedFn func(context.Context) error) error {
    99  	newCtx, span := globalTracer.Start(current, name)
   100  	defer span.End()
   101  
   102  	err := tracedFn(newCtx)
   103  	if err != nil {
   104  		span.RecordError(err)
   105  		span.SetStatus(codes.Error, err.Error())
   106  	} else {
   107  		span.SetStatus(codes.Ok, "")
   108  	}
   109  
   110  	return err
   111  }
   112  
   113  func SpannedT[T any](current context.Context, name string, tracedFn func(context.Context) (T, error)) (T, error) {
   114  	var ret T
   115  	var err error
   116  
   117  	err = Spanned(current, name, func(ctx context.Context) error {
   118  		ret, err = tracedFn(ctx)
   119  		return err
   120  	})
   121  
   122  	return ret, err
   123  }
   124  
   125  func SetSpanAttribute(ctx context.Context, key string, value string) {
   126  	span := trace.SpanFromContext(ctx)
   127  	span.SetAttributes(attribute.KeyValue{
   128  		Key:   attribute.Key(key),
   129  		Value: attribute.StringValue(value),
   130  	})
   131  }
   132  
   133  func LoggerTags(ctx context.Context) map[string]any {
   134  	tags := map[string]any{}
   135  	span := trace.SpanFromContext(ctx)
   136  
   137  	if span.IsRecording() {
   138  		tags["span_id"] = span.SpanContext().SpanID()
   139  		tags["trace_id"] = span.SpanContext().TraceID()
   140  		tags["trace_flags"] = span.SpanContext().TraceFlags()
   141  	}
   142  
   143  	return tags
   144  }
   145  
   146  func isTracingEnabled() bool {
   147  	bRet, err := strconv.ParseBool(os.Getenv(tracerControlEnvKey))
   148  	if err != nil {
   149  		return false
   150  	}
   151  
   152  	return bRet
   153  }