github.com/OpsMx/go-app-base@v0.0.24/tracer/tracer.go (about)

     1  // Copyright 2022 OpsMx, Inc
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  // http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package tracer
    16  
    17  import (
    18  	"context"
    19  	"io"
    20  	"log"
    21  	"os"
    22  	"time"
    23  
    24  	"go.opentelemetry.io/otel"
    25  	"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
    26  	"go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
    27  	"go.opentelemetry.io/otel/propagation"
    28  	"go.opentelemetry.io/otel/sdk/resource"
    29  	"go.opentelemetry.io/otel/sdk/trace"
    30  	tracesdk "go.opentelemetry.io/otel/sdk/trace"
    31  	semconv "go.opentelemetry.io/otel/semconv/v1.9.0"
    32  )
    33  
    34  // TracerProvider holds the state for a provider, and provides an
    35  // easy way to shut it down without exposing the specific type
    36  // of tracer returned.
    37  type TracerProvider struct {
    38  	Provider *tracesdk.TracerProvider
    39  }
    40  
    41  // NewTracerProvider returns an OpenTelemetry TracerProvider configured to use
    42  // the Jaeger exporter that will send spans to the provided url. The returned
    43  // TracerProvider will also use a Resource configured with all the information
    44  // about the application.
    45  //
    46  // If no error is returned, `defer provider.Shutdown(ctx)` should be set up
    47  // to ensure flushing occurs.
    48  //
    49  // If the otlpEndpoint URL is empty, the OpenTelemetry
    50  // TracerProvider will be configured to not report to an external tracer.
    51  //
    52  // If traceToStdout is true, traces will be sent to stdout.
    53  func NewTracerProvider(ctx context.Context, otlpEndpoint string, traceToStdout bool, githash string, appname string, traceRatio float64) (*TracerProvider, error) {
    54  	res, err := resource.New(ctx,
    55  		// add detectors here if needed
    56  		resource.WithTelemetrySDK(),
    57  		resource.WithAttributes(
    58  			semconv.ServiceNameKey.String(appname),
    59  			semconv.ServiceVersionKey.String(githash),
    60  		))
    61  	if err != nil {
    62  		log.Fatalf("resource.New: %v", err)
    63  	}
    64  
    65  	opts := []tracesdk.TracerProviderOption{
    66  		tracesdk.WithResource(res),
    67  		tracesdk.WithSampler(tracesdk.ParentBased(tracesdk.TraceIDRatioBased(traceRatio))),
    68  	}
    69  
    70  	exporterCount := 0
    71  	if otlpEndpoint != "" {
    72  		exp, err := otlptracehttp.New(ctx, otlptracehttp.WithEndpoint(otlpEndpoint))
    73  		if err != nil {
    74  			return nil, err
    75  		}
    76  		opts = append(opts, tracesdk.WithBatcher(exp))
    77  		exporterCount++
    78  	}
    79  
    80  	if traceToStdout {
    81  		exp, err := newConsoleExporter(os.Stdout)
    82  		if err != nil {
    83  			return nil, err
    84  		}
    85  		opts = append(opts, tracesdk.WithBatcher(exp))
    86  		exporterCount++
    87  	}
    88  
    89  	if exporterCount == 0 {
    90  		exp, err := newNullExporter()
    91  		if err != nil {
    92  			return nil, err
    93  		}
    94  		opts = append(opts, tracesdk.WithBatcher(exp))
    95  	}
    96  
    97  	tp := tracesdk.NewTracerProvider(opts...)
    98  
    99  	otel.SetTracerProvider(tp)
   100  
   101  	otel.SetTextMapPropagator(propagation.TraceContext{})
   102  	return &TracerProvider{Provider: tp}, nil
   103  }
   104  
   105  // Shutdown should be deferred immediately after NewTracerProvider()
   106  // when no error is returned.  This will ensure that on app termination
   107  // it will flush any buffered traces, if possible.  A maximum time
   108  // of 5 seconds will be allowed before we give up, to prevent a hang
   109  // at shutdown.
   110  func (p *TracerProvider) Shutdown(ctx context.Context) {
   111  	ctx, cancel := context.WithTimeout(ctx, time.Second*5)
   112  	defer cancel()
   113  	if err := p.Provider.Shutdown(ctx); err != nil {
   114  		log.Printf("shutting down tracing: %v", err)
   115  	}
   116  }
   117  
   118  func newConsoleExporter(w io.Writer) (trace.SpanExporter, error) {
   119  	return stdouttrace.New(
   120  		stdouttrace.WithWriter(w),
   121  		stdouttrace.WithPrettyPrint(),
   122  	)
   123  }
   124  
   125  var _ trace.SpanExporter = &nullExporter{}
   126  
   127  // dummy null exporter
   128  func newNullExporter() (trace.SpanExporter, error) {
   129  	return nullExporter{}, nil
   130  }
   131  
   132  type nullExporter struct{}
   133  
   134  func (nullExporter) ExportSpans(_ context.Context, _ []trace.ReadOnlySpan) error {
   135  	return nil
   136  }
   137  
   138  func (nullExporter) Shutdown(_ context.Context) error {
   139  	return nil
   140  }