Files
fission-src/pkg/utils/otel/provider.go
T
Sanket SudakeandGitHub 59e876062f Capture os signals to gracefully shutdown fission components (#2261)
- Currently, fission components don't handle shutdown signals.
So we don't get any to do the required cleanup before the fission process
exits. Adding signal capture process with cancelling context so
that all dependent processes stop working when the process gets term
signal.
- Set log level to error in otel shutdown function

Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>
2021-11-11 17:02:11 +05:30

245 lines
7.6 KiB
Go

package otel
import (
"context"
"fmt"
"os"
"strconv"
"strings"
"go.opentelemetry.io/contrib/propagators/aws/xray"
"go.opentelemetry.io/contrib/propagators/b3"
"go.opentelemetry.io/contrib/propagators/jaeger"
"go.opentelemetry.io/contrib/propagators/ot"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
"go.uber.org/zap"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
apiv1 "k8s.io/api/core/v1"
)
const (
OtelEnvPrefix = "OTEL_"
OtelEndpointEnvVar = "OTEL_EXPORTER_OTLP_ENDPOINT"
OtelInsecureEnvVar = "OTEL_EXPORTER_OTLP_INSECURE"
OtelTracesSampler = "OTEL_TRACES_SAMPLER"
OtelTracesSamplerArg = "OTEL_TRACES_SAMPLER_ARG"
OtelPropogaters = "OTEL_PROPOGATORS"
)
type OtelConfig struct {
endpoint string
insecure bool
}
/*
Each Sampler type defines its own expected input, if any.
Currently we get trace ratio for the case of,
1. traceidratio
2. parentbased_traceidratio
*/
func getSamplerArg() (float64, error) {
arg := os.Getenv(OtelTracesSamplerArg)
return strconv.ParseFloat(arg, 64)
}
/* GetPropogater returns a slice of propagators to be used by the OpenTelemetry
provider.
Supported providers:
tracecontext - W3C Trace Context
baggage - W3C Baggage
b3 - B3 Single
b3multi - B3 Multi
jaeger - Jaeger uber-trace-id header
xray - AWS X-Ray (third party)
ottrace - OpenTracing Trace (third party)
*/
func GetPropogater(logger *zap.Logger) []propagation.TextMapPropagator {
propogatersEnv := os.Getenv(OtelPropogaters)
if propogatersEnv == "" {
return []propagation.TextMapPropagator{
propagation.TraceContext{}, propagation.Baggage{},
}
}
propogators := []propagation.TextMapPropagator{}
for _, prop := range strings.Split(propogatersEnv, ",") {
switch prop {
case "tracecontext":
propogators = append(propogators, propagation.TraceContext{})
case "baggage":
propogators = append(propogators, propagation.Baggage{})
case "b3multi":
propogators = append(propogators, b3.New(b3.WithInjectEncoding(b3.B3MultipleHeader)))
case "b3":
propogators = append(propogators, b3.New(b3.WithInjectEncoding(b3.B3SingleHeader)))
case "jaeger":
propogators = append(propogators, jaeger.Jaeger{})
case "xray":
propogators = append(propogators, xray.Propagator{})
case "ottrace":
propogators = append(propogators, ot.OT{})
default:
logger.Error("Unsupported propagation type", zap.String("propagation", prop))
}
}
if len(propogators) == 0 {
return []propagation.TextMapPropagator{
propagation.TraceContext{}, propagation.Baggage{},
}
}
return propogators
}
/*
GetSampler returns a sampler that can be used to sample traces.
This is based on https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/sdk-environment-variables.md#general-sdk-configuration
We have to implement as open-telemetry Go sdk doesn't support configuration of different samplers.
Once its added we may remove this code.
Supported samplers:
always_on - Sampler that always samples spans, regardless of the parent span's sampling decision.
always_off - Sampler that never samples spans, regardless of the parent span's sampling decision.
traceidratio - Sampler that samples probabalistically based on rate.
parentbased_always_on - (default) Sampler that respects its parent span's sampling decision, but otherwise always samples.
parentbased_always_off - Sampler that respects its parent span's sampling decision, but otherwise never samples.
parentbased_traceidratio - Sampler that respects its parent span's sampling decision, but otherwise samples probabalistically based on rate.
Environment variables:
OTEL_TRACES_SAMPLER - Sampler to use(one of the above samplers)
OTEL_TRACES_SAMPLER_ARG - Argument to pass to the sampler(float value)
*/
func GetSampler() (sdktrace.Sampler, error) {
samplerType := os.Getenv(OtelTracesSampler)
switch samplerType {
case "always_on":
return sdktrace.AlwaysSample(), nil
case "always_off":
return sdktrace.NeverSample(), nil
case "parentbased_always_on":
return sdktrace.ParentBased(sdktrace.AlwaysSample()), nil
case "parentbased_always_off":
return sdktrace.ParentBased(sdktrace.NeverSample()), nil
case "traceidratio":
arg, err := getSamplerArg()
if err != nil {
return nil, fmt.Errorf("invalid sampler arg: %w", err)
}
return sdktrace.TraceIDRatioBased(arg), nil
case "parentbased_traceidratio":
arg, err := getSamplerArg()
if err != nil {
return nil, fmt.Errorf("invalid sampler arg: %w", err)
}
return sdktrace.ParentBased(sdktrace.TraceIDRatioBased(arg)), nil
default:
return sdktrace.ParentBased(sdktrace.AlwaysSample()), nil
}
}
// parseOtelConfig parses the environment variables OTEL_EXPORTER_OTLP_ENDPOINT and
func parseOtelConfig() OtelConfig {
config := OtelConfig{}
config.endpoint = os.Getenv(OtelEndpointEnvVar)
insecure, err := strconv.ParseBool(os.Getenv(OtelInsecureEnvVar))
if err != nil {
insecure = true
}
config.insecure = insecure
return config
}
func getTraceExporter(ctx context.Context, logger *zap.Logger) (*otlptrace.Exporter, error) {
otelConfig := parseOtelConfig()
if otelConfig.endpoint == "" {
if logger != nil {
logger.Info("OTEL_EXPORTER_OTLP_ENDPOINT not set, skipping Opentelemtry tracing")
}
return nil, nil
}
grpcOpts := []otlptracegrpc.Option{
otlptracegrpc.WithEndpoint(otelConfig.endpoint),
otlptracegrpc.WithDialOption(grpc.WithBlock()),
}
if otelConfig.insecure {
grpcOpts = append(grpcOpts, otlptracegrpc.WithInsecure())
} else {
grpcOpts = append(grpcOpts, otlptracegrpc.WithTLSCredentials(credentials.NewClientTLSFromCert(nil, "")))
}
exporter, err := otlptracegrpc.New(ctx, grpcOpts...)
if err != nil {
return nil, err
}
return exporter, nil
}
// Initializes an OTLP exporter, and configures the corresponding trace and metric providers.
func InitProvider(ctx context.Context, logger *zap.Logger, serviceName string) (func(context.Context), error) {
res, err := resource.New(ctx,
resource.WithAttributes(
semconv.ServiceNameKey.String(serviceName),
),
)
if err != nil {
return nil, err
}
sampler, err := GetSampler()
if err != nil {
return nil, err
}
tracerProvider := sdktrace.NewTracerProvider(
sdktrace.WithResource(res),
sdktrace.WithSampler(sampler),
)
traceExporter, err := getTraceExporter(ctx, logger)
if err != nil {
return nil, err
}
if traceExporter != nil {
bsp := sdktrace.NewBatchSpanProcessor(traceExporter)
tracerProvider.RegisterSpanProcessor(bsp)
}
otel.SetTracerProvider(tracerProvider)
propogaters := GetPropogater(logger)
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propogaters...))
// Shutdown will flush any remaining spans and shut down the exporter.
return func(ctx context.Context) {
err := tracerProvider.Shutdown(ctx)
if err != nil && logger != nil {
logger.Error("error shutting down trace provider", zap.Error(err))
}
if traceExporter != nil {
if err = traceExporter.Shutdown(ctx); err != nil && logger != nil {
logger.Error("error shutting down trace exporter", zap.Error(err))
}
}
}, nil
}
// OtelEnvForContainer returns a list of environment variables
// for the container, which start with prefix OTEL_
func OtelEnvForContainer() []apiv1.EnvVar {
otelEnvs := []apiv1.EnvVar{}
for _, e := range os.Environ() {
if strings.HasPrefix(e, OtelEnvPrefix) {
pair := strings.SplitN(e, "=", 2)
otelEnvs = append(otelEnvs, apiv1.EnvVar{
Name: pair[0],
Value: pair[1],
})
}
}
return otelEnvs
}