2024-07-23 17:46:15 +02:00
|
|
|
package unitel
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"os"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/getsentry/sentry-go"
|
|
|
|
"github.com/rs/zerolog/log"
|
|
|
|
"go.opentelemetry.io/otel"
|
|
|
|
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
|
|
|
|
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
|
|
|
|
"go.opentelemetry.io/otel/exporters/stdout/stdoutmetric"
|
|
|
|
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
|
|
|
|
"go.opentelemetry.io/otel/metric"
|
|
|
|
"go.opentelemetry.io/otel/propagation"
|
|
|
|
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
|
|
|
|
"go.opentelemetry.io/otel/sdk/resource"
|
|
|
|
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
|
|
|
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
|
|
|
|
"go.opentelemetry.io/otel/trace"
|
|
|
|
"go.opentelemetry.io/otel/trace/noop"
|
|
|
|
)
|
|
|
|
|
2024-07-27 19:40:22 +02:00
|
|
|
type telemetryContextKey struct{}
|
2024-07-23 17:46:15 +02:00
|
|
|
|
|
|
|
type Opts struct {
|
2024-08-23 00:34:37 +02:00
|
|
|
Environment string
|
|
|
|
|
|
|
|
ServiceName string
|
|
|
|
ServiceVersion string
|
|
|
|
|
2024-07-23 17:46:15 +02:00
|
|
|
OTLPEndpoint string
|
|
|
|
SentryDSN string
|
|
|
|
}
|
|
|
|
|
|
|
|
func ParseOpts(serviceName string) Opts {
|
|
|
|
return Opts{
|
|
|
|
Environment: os.Getenv("ENVIRONMENT"),
|
|
|
|
ServiceName: serviceName,
|
|
|
|
OTLPEndpoint: os.Getenv("OTLP_ENDPOINT"),
|
|
|
|
SentryDSN: os.Getenv("SENTRY_DSN"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type Telemetry struct {
|
2024-07-31 21:58:27 +02:00
|
|
|
Opts Opts
|
2024-08-23 00:34:37 +02:00
|
|
|
Propagator propagation.TextMapPropagator
|
|
|
|
TracerProvider trace.TracerProvider
|
|
|
|
MeterProvider metric.MeterProvider
|
2024-07-23 17:46:15 +02:00
|
|
|
Tracer trace.Tracer
|
|
|
|
Meter metric.Meter
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Telemetry) Shutdown(ctx context.Context) {
|
2024-08-23 00:34:37 +02:00
|
|
|
if tp, ok := o.TracerProvider.(*sdktrace.TracerProvider); ok {
|
2024-07-23 17:46:15 +02:00
|
|
|
if err := tp.Shutdown(ctx); err != nil {
|
|
|
|
log.Error().Err(err).Msg("failed to shutdown TracerProvider")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-23 00:34:37 +02:00
|
|
|
if mp, ok := o.MeterProvider.(*sdkmetric.MeterProvider); ok {
|
2024-07-23 17:46:15 +02:00
|
|
|
if err := mp.Shutdown(ctx); err != nil {
|
|
|
|
log.Error().Err(err).Msg("failed to stop MeterProvider")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-31 21:58:27 +02:00
|
|
|
if o.Opts.SentryDSN != "" {
|
2024-07-23 17:46:15 +02:00
|
|
|
if ok := sentry.Flush(5 * time.Second); !ok {
|
|
|
|
log.Error().Msg("failed to flush Sentry")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func SetOnContext(o *Telemetry, ctx context.Context) context.Context {
|
2024-07-27 19:40:22 +02:00
|
|
|
return context.WithValue(ctx, telemetryContextKey{}, o)
|
2024-07-23 17:46:15 +02:00
|
|
|
}
|
|
|
|
func FromContext(ctx context.Context) *Telemetry {
|
2024-07-27 19:40:22 +02:00
|
|
|
return ctx.Value(telemetryContextKey{}).(*Telemetry)
|
2024-07-23 17:46:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func newSpanExporter(ctx context.Context, opts Opts) (sdktrace.SpanExporter, error) {
|
|
|
|
return otlptracegrpc.New(
|
|
|
|
ctx,
|
|
|
|
otlptracegrpc.WithInsecure(),
|
|
|
|
otlptracegrpc.WithEndpoint(opts.OTLPEndpoint),
|
|
|
|
otlptracegrpc.WithReconnectionPeriod(time.Second*5),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
func newMetricExporter(ctx context.Context, opts Opts) (*otlpmetricgrpc.Exporter, error) {
|
|
|
|
return otlpmetricgrpc.New(
|
|
|
|
ctx,
|
|
|
|
otlpmetricgrpc.WithInsecure(),
|
|
|
|
otlpmetricgrpc.WithEndpoint(opts.OTLPEndpoint),
|
|
|
|
otlpmetricgrpc.WithReconnectionPeriod(time.Second*5),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
func newTextMapPropagator() propagation.TextMapPropagator {
|
|
|
|
return propagation.NewCompositeTextMapPropagator(
|
|
|
|
propagation.TraceContext{},
|
|
|
|
propagation.Baggage{},
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
func getSampleRate(opts Opts) float64 {
|
|
|
|
switch opts.Environment {
|
|
|
|
case "development":
|
|
|
|
return 1.0
|
|
|
|
case "staging":
|
|
|
|
return 0.5
|
|
|
|
case "production":
|
|
|
|
return 0.2
|
|
|
|
default:
|
|
|
|
return 1.0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func initializeSentry(opts Opts) error {
|
|
|
|
if opts.SentryDSN == "" {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return sentry.Init(sentry.ClientOptions{
|
|
|
|
Dsn: opts.SentryDSN,
|
2024-08-23 00:34:37 +02:00
|
|
|
Release: opts.ServiceVersion,
|
2024-07-23 17:46:15 +02:00
|
|
|
AttachStacktrace: true,
|
|
|
|
SampleRate: getSampleRate(opts),
|
|
|
|
EnableTracing: true,
|
|
|
|
TracesSampleRate: 1.0,
|
|
|
|
ProfilesSampleRate: 1.0,
|
|
|
|
Debug: false,
|
|
|
|
Environment: opts.Environment,
|
|
|
|
BeforeSend: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event {
|
|
|
|
// Only send stuff to Sentry when it includes exceptions
|
|
|
|
if len(event.Exception) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return event
|
|
|
|
},
|
|
|
|
// BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event {
|
|
|
|
// // Only send stuff to Sentry when it includes exceptions
|
|
|
|
// if len(event.Exception) == 0 {
|
|
|
|
// return nil
|
|
|
|
// }
|
|
|
|
|
|
|
|
// return event
|
|
|
|
// },
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func Initialize(opts Opts) (*Telemetry, error) {
|
|
|
|
propagator := newTextMapPropagator()
|
|
|
|
otel.SetTextMapPropagator(propagator)
|
|
|
|
|
|
|
|
r, err := resource.Merge(
|
|
|
|
resource.Default(),
|
|
|
|
resource.NewWithAttributes(
|
|
|
|
semconv.SchemaURL,
|
|
|
|
semconv.ServiceName(opts.ServiceName),
|
2024-08-23 00:34:37 +02:00
|
|
|
semconv.ServiceVersion(opts.ServiceVersion),
|
2024-07-23 17:46:15 +02:00
|
|
|
semconv.DeploymentEnvironment(opts.Environment),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var tracerProvider trace.TracerProvider
|
|
|
|
if opts.OTLPEndpoint == "" {
|
|
|
|
tracerProvider = noop.NewTracerProvider()
|
|
|
|
} else {
|
|
|
|
var exp sdktrace.SpanExporter
|
|
|
|
var err error
|
|
|
|
if opts.OTLPEndpoint == "console" {
|
|
|
|
exp, err = stdouttrace.New(stdouttrace.WithPrettyPrint())
|
|
|
|
} else {
|
|
|
|
exp, err = newSpanExporter(context.Background(), opts)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
tracerProvider = sdktrace.NewTracerProvider(
|
|
|
|
sdktrace.WithBatcher(exp),
|
|
|
|
sdktrace.WithResource(r),
|
|
|
|
sdktrace.WithSampler(sdktrace.ParentBased(sdktrace.TraceIDRatioBased(getSampleRate(opts)))),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
var metricProvider metric.MeterProvider
|
|
|
|
if opts.OTLPEndpoint == "" {
|
|
|
|
// noop meter provider
|
|
|
|
metricProvider = sdkmetric.NewMeterProvider(sdkmetric.WithResource(r))
|
|
|
|
} else {
|
|
|
|
var exp sdkmetric.Exporter
|
|
|
|
if opts.OTLPEndpoint == "console" {
|
|
|
|
exp, err = stdoutmetric.New(stdoutmetric.WithPrettyPrint())
|
|
|
|
} else {
|
|
|
|
exp, err = newMetricExporter(context.Background(), opts)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
metricProvider = sdkmetric.NewMeterProvider(
|
|
|
|
sdkmetric.WithResource(r),
|
|
|
|
sdkmetric.WithReader(sdkmetric.NewPeriodicReader(exp, sdkmetric.WithInterval(15*time.Second))),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
otel.SetTracerProvider(tracerProvider)
|
|
|
|
otel.SetMeterProvider(metricProvider)
|
|
|
|
|
|
|
|
if err := initializeSentry(opts); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &Telemetry{
|
2024-07-31 21:58:27 +02:00
|
|
|
Opts: opts,
|
2024-08-23 00:34:37 +02:00
|
|
|
Propagator: propagator,
|
|
|
|
TracerProvider: tracerProvider,
|
|
|
|
MeterProvider: metricProvider,
|
2024-07-23 17:46:15 +02:00
|
|
|
Tracer: tracerProvider.Tracer(opts.ServiceName),
|
|
|
|
Meter: metricProvider.Meter(opts.ServiceName),
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Telemetry) HandleError(ctx context.Context, err error) {
|
|
|
|
if span := SpanFromContext(ctx); span != nil {
|
|
|
|
span.CaptureError(err)
|
|
|
|
} else {
|
|
|
|
if hub := sentry.CurrentHub().Clone(); hub != nil {
|
|
|
|
hub.CaptureException(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
otel.Handle(err)
|
|
|
|
}
|
|
|
|
}
|